From 49ae029572dcd01fa7e951066c28ae352d7653e0 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jan 2016 16:07:33 -0500 Subject: [PATCH 001/462] Don't try to process blank IPs --- lbrynet/dht/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/dht/node.py b/lbrynet/dht/node.py index 1fae2e889..a3bf56553 100644 --- a/lbrynet/dht/node.py +++ b/lbrynet/dht/node.py @@ -516,7 +516,7 @@ class Node(object): else: raise TypeError, 'No NodeID given. Therefore we can\'t store this node' - if self_store is True and self.externalIP is not None: + if self_store is True and self.externalIP: contact = Contact(self.id, self.externalIP, self.port, None, None) compact_ip = contact.compact_ip() elif '_rpcNodeContact' in kwargs: From 05189651ee635e3c0a32c79b4fb1e65d211b9e78 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Wed, 27 Jan 2016 02:55:45 -0500 Subject: [PATCH 002/462] update path for close file name on windows --- lbrynet/lbrynet_gui/StreamFrame.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_gui/StreamFrame.py b/lbrynet/lbrynet_gui/StreamFrame.py index 5f071e1a5..defcbacc9 100644 --- a/lbrynet/lbrynet_gui/StreamFrame.py +++ b/lbrynet/lbrynet_gui/StreamFrame.py @@ -33,8 +33,7 @@ class StreamFrame(object): close_file_name = "close2.gif" if os.name == "nt": - close_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), "lbrynet", - "lbrynet_downloader_gui", close_file_name) + close_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), close_file_name) else: close_file = os.path.join(os.path.dirname(__file__), close_file_name) From cb5c772233c8de56a5d081bad7f9f09d4db47650 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jan 2016 11:02:57 -0500 Subject: [PATCH 003/462] Have autofetcher use managed downloader --- lbrynet/core/LBRYcrdWallet.py | 19 ++++++++++++++----- lbrynet/lbrynet_daemon/LBRYDaemon.py | 10 +++++++++- lbrynet/lbrynet_daemon/LBRYDownloader.py | 15 ++++++++++----- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 3db07f1b3..3ac3e9f70 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -267,7 +267,7 @@ class LBRYcrdWallet(object): d.addCallback(set_address_for_peer) return d - def get_stream_info_for_name(self, name): + def get_stream_info_for_name(self, name, txid=None): def get_stream_info_from_value(result): r_dict = {} @@ -277,7 +277,8 @@ class LBRYcrdWallet(object): value_dict = json.loads(value) except ValueError: return Failure(InvalidStreamInfoError(name)) - known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail'] + known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', + 'content_license'] for field in known_fields: if field in value_dict: r_dict[field] = value_dict[field] @@ -289,7 +290,7 @@ class LBRYcrdWallet(object): return d return Failure(UnknownNameError(name)) - d = threads.deferToThread(self._get_value_for_name, name) + d = threads.deferToThread(self._get_value_for_name, name, txid) d.addCallback(get_stream_info_from_value) return d @@ -594,9 +595,17 @@ class LBRYcrdWallet(object): return rpc_conn.getnewaddress() @_catch_connection_error - def _get_value_for_name(self, name): + def _get_value_for_name(self, name, txid=None): rpc_conn = self._get_rpc_conn() - return rpc_conn.getvalueforname(name) + if not txid: + return rpc_conn.getvalueforname(name) + else: + claim = rpc_conn.getclaimsfortx(txid)[0] + if claim['name'] == name: + claim['txid'] = txid + return claim + else: + raise ValueError @_catch_connection_error def _claim_name(self, name, value, amount): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 3c4d81ad9..2fe922da2 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -803,7 +803,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): # if not os.path.isfile(metadata['file_path']): # return defer.fail() - if not type(metadata['bid']) is float and metadata['bid'] > 0.0: + if not isinstance(metadata['bid'], float) and metadata['bid'] > 0.0: return defer.fail() name = metadata['name'] @@ -880,6 +880,14 @@ class LBRYDaemon(xmlrpc.XMLRPC): return d + def xmlrpc_toggle_fetcher_verbose(self): + if self.fetcher.verbose: + self.fetcher.verbose = False + else: + self.fetcher.verbose = True + + return self.fetcher.verbose + def main(): daemon = LBRYDaemon() diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 93bfbfa09..c880616bb 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -94,7 +94,8 @@ class GetStream(object): class FetcherDaemon(object): - def __init__(self, session, lbry_file_manager, lbry_file_metadata_manager, wallet, sd_identifier, autofetcher_conf): + def __init__(self, session, lbry_file_manager, lbry_file_metadata_manager, wallet, sd_identifier, autofetcher_conf, + verbose=False): self.autofetcher_conf = autofetcher_conf self.max_key_fee = 0.0 self.sd_identifier = sd_identifier @@ -107,6 +108,7 @@ class FetcherDaemon(object): self.search = None self.first_run = True self.is_running = False + self.verbose = verbose self._get_autofetcher_conf() def start(self): @@ -152,13 +154,16 @@ class FetcherDaemon(object): " | stream hash: " + str(json.loads(claim['value'])['stream_hash']) print msg log.debug(msg) - rtn.append(claim) + rtn.append([claim['name'], t['txid']]) self.seen.append(claim) + else: + if self.verbose: + print "[" + str(datetime.now()) + "] No claims in block", c['bestblockhash'] self.lastbestblock = c if len(rtn): - return defer.succeed(rtn) + return defer.DeferredList([self.wallet.get_stream_info_for_name(name, txid=t) for name, t in rtn]) def _download_claims(self, claims): if claims: @@ -166,13 +171,13 @@ class FetcherDaemon(object): download = defer.Deferred() stream = GetStream(self.sd_identifier, self.session, self.wallet, self.lbry_file_manager, self.max_key_fee, pay_key=False) - download.addCallback(lambda _: stream.start(claim)) + download.addCallback(lambda _: stream.start(claim[1])) download.callback(None) return defer.succeed(None) def _looped_search(self): - d = defer.Deferred(None) + d = defer.Deferred() d.addCallback(lambda _: self._get_names()) d.addCallback(self._download_claims) d.callback(None) From 97976cbee970eaf8b7757b84537c52b4a48ae706 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Thu, 4 Feb 2016 15:18:47 -0500 Subject: [PATCH 004/462] lower the minimum rate --- lbrynet/conf.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index b4d91b5b8..6d0349297 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -13,10 +13,10 @@ MAX_RESPONSE_INFO_SIZE = 2**16 MAX_BLOB_INFOS_TO_REQUEST = 20 BLOBFILES_DIR = ".blobfiles" BLOB_SIZE = 2**21 -MIN_BLOB_DATA_PAYMENT_RATE = .5 # points/megabyte -MIN_BLOB_INFO_PAYMENT_RATE = 2.0 # points/1000 infos -MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = 5.0 # points/1000 infos -MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = 5.0 # points/1000 infos +MIN_BLOB_DATA_PAYMENT_RATE = .005 # points/megabyte +MIN_BLOB_INFO_PAYMENT_RATE = .02 # points/1000 infos +MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .05 # points/1000 infos +MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .05 # points/1000 infos MAX_CONNECTIONS_PER_STREAM = 5 POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' From 407ebeb699c005dccbd23722ae999f47c35f4316 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 11 Feb 2016 08:32:48 -0500 Subject: [PATCH 005/462] Update daemon --- lbrynet/core/LBRYcrdWallet.py | 7 ++++ lbrynet/lbrynet_daemon/LBRYDaemon.py | 44 +++++++++++++++++---- lbrynet/lbrynet_daemon/LBRYDaemonStopper.py | 6 +-- lbrynet/lbrynet_daemon/LBRYPublisher.py | 2 +- 4 files changed, 47 insertions(+), 12 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 3ac3e9f70..61de7f5c8 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -359,6 +359,9 @@ class LBRYcrdWallet(object): def get_nametrie(self): return self._get_nametrie() + # def update_name(self, name_value): + # return self._update_name(name_value) + def get_name_and_validity_for_sd_hash(self, sd_hash): d = self._get_claim_metadata_for_sd_hash(sd_hash) d.addCallback(lambda name_txid: threads.deferToThread(self._get_status_of_claim, name_txid[1], name_txid[0], sd_hash) if name_txid is not None else None) @@ -607,6 +610,10 @@ class LBRYcrdWallet(object): else: raise ValueError + # def _update_name(self, name_value): + # rpc_conn = self._get_rpc_conn() + # return rpc_conn.updatename(name_value) + @_catch_connection_error def _claim_name(self, name, value, amount): rpc_conn = self._get_rpc_conn() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 2fe922da2..731e58390 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -27,6 +27,7 @@ import sys import json import binascii import webbrowser +import xmlrpclib from decimal import Decimal log = logging.getLogger(__name__) @@ -98,7 +99,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): os.mkdir(self.db_dir) self.created_data_dir = True self.session_settings = None - self.data_rate = 0.5 + self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE self.max_key_fee = 100.0 self.query_handlers = {} @@ -424,8 +425,11 @@ class LBRYDaemon(xmlrpc.XMLRPC): l = json.loads(f.read()) f.close() file_name = l['stream_name'].decode('hex') - lbry_file = [file for file in self.lbry_file_manager.lbry_files if file.stream_name == file_name][0] - return lbry_file + lbry_file = [file for file in self.lbry_file_manager.lbry_files if file.stream_name == file_name] + if lbry_file: + return lbry_file[0] + else: + return None def _check(info): stream_hash = info['stream_hash'] @@ -543,7 +547,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.fetcher.start() print '[' + str(datetime.now()) + '] Start autofetcher' - return str('Started autofetching') + return 'Started autofetching' def xmlrpc_stop_fetcher(self): """ @@ -552,7 +556,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.fetcher.stop() print '[' + str(datetime.now()) + '] Stop autofetcher' - return str('Started autofetching') + return 'Stopped autofetching' def xmlrpc_fetcher_status(self): """ @@ -777,7 +781,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): def xmlrpc_delete_lbry_file(self, file_name): def _disp(file_name): print '[' + str(datetime.now()) + '] Deleted: ' + file_name - return defer.succeed(str('Deleted: ' + file_name)) + return defer.succeed('Deleted: ' + file_name) lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files if file_name == f.file_name] d = defer.DeferredList(lbry_files) @@ -865,7 +869,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _clean(claims): for c in claims: for k in c.keys(): - if type(c[k]) == Decimal: + if isinstance(c[k], Decimal): c[k] = float(c[k]) return claims @@ -880,6 +884,26 @@ class LBRYDaemon(xmlrpc.XMLRPC): return d + # def xmlrpc_update_name(self, metadata): + # def _disp(x): + # print x + # return x + # + # metadata = json.loads(metadata) + # + # required = ['name', 'file_path', 'bid'] + # + # for r in required: + # if not r in metadata.keys(): + # return defer.fail() + # + # d = defer.Deferred() + # d.addCallback(lambda _: self.session.wallet.update_name(metadata)) + # d.addCallback(_disp) + # d.callback(None) + # + # return d + def xmlrpc_toggle_fetcher_verbose(self): if self.fetcher.verbose: self.fetcher.verbose = False @@ -890,6 +914,12 @@ class LBRYDaemon(xmlrpc.XMLRPC): def main(): + try: + d = xmlrpclib.ServerProxy('http://localhost:7080') + d.stop() + except: + pass + daemon = LBRYDaemon() daemon.setup() reactor.listenTCP(7080, server.Site(daemon)) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py b/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py index 4e42708e9..78d8597dd 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py @@ -10,10 +10,8 @@ def main(): is_running = False if is_running: - try: - daemon.stop() - except: - print "LBRYnet daemon stopped" + daemon.stop() + print "LBRYnet daemon stopped" else: print "LBRYnet daemon wasn't running" diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index 81a8e59e3..54ade2274 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -38,7 +38,7 @@ class Publisher(object): key_fee=None, key_fee_address=None, content_license=None): def _show_result(): - message = "[" + str(datetime.now()) + " ] Published " + self.file_name + " --> lbry://" + \ + message = "[" + str(datetime.now()) + "] Published " + self.file_name + " --> lbry://" + \ str(self.publish_name) + " with txid: " + str(self.tx_hash) print message return defer.succeed(message) From d544199eff9798f9919f9b0a0ba15962750acdd2 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 15 Feb 2016 10:12:49 -0500 Subject: [PATCH 006/462] Fix search issue and move some settings to conf.py --- lbrynet/conf.py | 4 ++++ lbrynet/lbrynet_daemon/LBRYDaemon.py | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 6d0349297..c172884e6 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -18,6 +18,10 @@ MIN_BLOB_INFO_PAYMENT_RATE = .02 # points/1000 infos MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .05 # points/1000 infos MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .05 # points/1000 infos MAX_CONNECTIONS_PER_STREAM = 5 +DEFAULT_MAX_SEARCH_RESULTS = 25 +DEFAULT_MAX_KEY_FEE = 100.0 + +KNOWN_DHT_NODES = [('104.236.42.182', 4000)] POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' #POINTTRADER_SERVER = 'http://127.0.0.1:2424' diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 731e58390..2a4d50695 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -11,7 +11,7 @@ from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream, FetcherDaemon from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher from lbrynet.core.utils import generate_id from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings -from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE +from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet @@ -53,7 +53,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.current_db_revision = 1 self.run_server = True self.session = None - self.known_dht_nodes = [('104.236.42.182', 4000)] + self.known_dht_nodes = KNOWN_DHT_NODES self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 @@ -100,7 +100,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.created_data_dir = True self.session_settings = None self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE - self.max_key_fee = 100.0 + self.max_key_fee = DEFAULT_MAX_KEY_FEE + self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS + self.search_timeout = 3.0 self.query_handlers = {} return defer.succeed(None) @@ -497,8 +499,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _add_key_fee(data_cost): d = self.session.wallet.get_stream_info_for_name(name) - d.addCallback(lambda info: info['key_fee'] if 'key_fee' in info.keys() else 0.0) - d.addCallback(lambda key_fee: key_fee + data_cost) + d.addCallback(lambda info: data_cost + info['key_fee'] if 'key_fee' in info.keys() else data_cost) return d d = self.session.wallet.get_stream_info_for_name(name) @@ -510,7 +511,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda info: int(info['stream_size'])/1000000*self.data_rate) d.addCallback(_add_key_fee) d.addErrback(lambda _: _add_key_fee(0.0)) - reactor.callLater(3.0, _check_est, d, name) + reactor.callLater(self.search_timeout, _check_est, d, name) return d @@ -764,11 +765,11 @@ class LBRYDaemon(xmlrpc.XMLRPC): print '[' + str(datetime.now()) + '] Search nametrie: ' + search filtered_results = [n for n in self.session.wallet.get_nametrie() if n['name'].startswith(search)] - if len(filtered_results) > 25: - filtered_results = filtered_results[:25] + if len(filtered_results) > self.max_search_results: + filtered_results = filtered_results[:self.max_search_results] filtered_results = [n for n in filtered_results if 'txid' in n.keys()] resolved_results = [defer.DeferredList([_return_d(n), self._resolve_name_wc(n['name']), - self._get_est_cost(n['name'])]) + self._get_est_cost(n['name'])], consumeErrors=True) for n in filtered_results] d = defer.DeferredList(resolved_results) From a0a0e2d32471dd0efc4ab0ffd15cf1f3b8d40962 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 15 Feb 2016 21:31:29 -0500 Subject: [PATCH 007/462] add daemon update script --- .../lbrynet_daemon/scripts/update_daemon.sh | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 lbrynet/lbrynet_daemon/scripts/update_daemon.sh diff --git a/lbrynet/lbrynet_daemon/scripts/update_daemon.sh b/lbrynet/lbrynet_daemon/scripts/update_daemon.sh new file mode 100644 index 000000000..c2135c609 --- /dev/null +++ b/lbrynet/lbrynet_daemon/scripts/update_daemon.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +lbrynet_directory="/Users/${SUDO_USER}/Library/Application Support/lbrynet" + +current_version=$(git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1) + +if [ -d "$lbrynet_directory" ]; then + if [ -f "${lbrynet_directory}/version.txt" ]; then + if grep -Fxq "$current_version" "${lbrynet_directory}/version.txt"; then + echo "LBRYnet version $current_version is up to date" + exit + fi + fi +fi + +tmp=$(mktemp -d) +cd $tmp + +echo "Downloading update" + +git clone https://github.com/lbryio/lbry.git &>/dev/null +cd lbry + +version=$(git rev-parse HEAD) + +echo "Updating lbrynet" +sudo python setup.py install &>/dev/null +mkdir -p "$lbrynet_directory" +echo $version > "${lbrynet_directory}/version.txt" + +echo "Cleaning up" + +cd ../../ +rm -rf $tmp + +echo "Restarting lbrynet-daemon" +sudo lbrynet-daemon \ No newline at end of file From 1a1c101b9abf2ebbe5c2c451f667d3b804039940 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 15 Feb 2016 23:00:24 -0500 Subject: [PATCH 008/462] Add lbry app update script --- .../lbrynet_daemon/scripts/restart_daemon.sh | 4 ++ lbrynet/lbrynet_daemon/scripts/update_lbry.sh | 40 +++++++++++++++++++ .../{update_daemon.sh => update_lbrynet.sh} | 11 ++--- 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 lbrynet/lbrynet_daemon/scripts/restart_daemon.sh create mode 100644 lbrynet/lbrynet_daemon/scripts/update_lbry.sh rename lbrynet/lbrynet_daemon/scripts/{update_daemon.sh => update_lbrynet.sh} (70%) diff --git a/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh b/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh new file mode 100644 index 000000000..74d4ab102 --- /dev/null +++ b/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo "Restarting lbrynet-daemon" +sudo lbrynet-daemon \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh new file mode 100644 index 000000000..c56675c40 --- /dev/null +++ b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +lbrycrd_directory="/Users/${SUDO_USER}/Library/Application Support/lbrycrd" + +current_version=$(git ls-remote https://github.com/jackrobison/lbrynet-app.git | grep HEAD | cut -f 1) + +if [ -d "$lbrycrd_directory" ]; then + if [ -f "${lbrycrd_directory}/lbry_app_version.txt" ]; then + if grep -Fxq "$current_version" "${lbrycrd_directory}/lbry_app_version.txt"; then + echo "LBRY version $current_version is up to date" + exit + fi + fi +fi + +echo "Updating LBRY" + +tmp=$(mktemp -d) +cd $tmp + +echo "Downloading update" +git clone https://github.com/jackrobison/lbrynet-app.git &>/dev/null +cd lbrynet-app +unzip LBRY.app.zip &>/dev/null +unzip LBRYURIHandler.app.zip &>/dev/null +unzip LBRY\ Updater.app.zip &>/dev/null + +echo "Installing update" +rm -rf /Applications/LBRY.app &>/dev/null +rm -rf /Applications/LBRYURIHandler.app &>/dev/null +rm -rf /Applications/LBRY\ Updater.app &>/dev/null + +mv -f LBRY.app /Applications +mv -f LBRYURIHandler.app /Applications +mv -f LBRY\ Updater.app /Applications + +echo "Cleaning up" + +cd ../../ +rm -rf $tmp \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/scripts/update_daemon.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh similarity index 70% rename from lbrynet/lbrynet_daemon/scripts/update_daemon.sh rename to lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh index c2135c609..8d84a8f50 100644 --- a/lbrynet/lbrynet_daemon/scripts/update_daemon.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -5,8 +5,8 @@ lbrynet_directory="/Users/${SUDO_USER}/Library/Application Support/lbrynet" current_version=$(git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1) if [ -d "$lbrynet_directory" ]; then - if [ -f "${lbrynet_directory}/version.txt" ]; then - if grep -Fxq "$current_version" "${lbrynet_directory}/version.txt"; then + if [ -f "${lbrynet_directory}/lbrynet_version.txt" ]; then + if grep -Fxq "$current_version" "${lbrynet_directory}/lbrynet_version.txt"; then echo "LBRYnet version $current_version is up to date" exit fi @@ -26,12 +26,9 @@ version=$(git rev-parse HEAD) echo "Updating lbrynet" sudo python setup.py install &>/dev/null mkdir -p "$lbrynet_directory" -echo $version > "${lbrynet_directory}/version.txt" +echo $version > "${lbrynet_directory}/lbrynet_version.txt" echo "Cleaning up" cd ../../ -rm -rf $tmp - -echo "Restarting lbrynet-daemon" -sudo lbrynet-daemon \ No newline at end of file +rm -rf $tmp \ No newline at end of file From 8ca8c73868f10eda4646ff932d649f7f454ddc97 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 16 Feb 2016 09:09:50 -0500 Subject: [PATCH 009/462] update scripts --- lbrynet/lbrynet_daemon/scripts/update_lbry.sh | 4 ++++ lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh index c56675c40..6ae5fefee 100644 --- a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh @@ -26,6 +26,10 @@ unzip LBRYURIHandler.app.zip &>/dev/null unzip LBRY\ Updater.app.zip &>/dev/null echo "Installing update" + +mkdir -p "$lbrycrd_directory" +echo $current_version > "${lbrycrd_directory}/lbry_app_version.txt" + rm -rf /Applications/LBRY.app &>/dev/null rm -rf /Applications/LBRYURIHandler.app &>/dev/null rm -rf /Applications/LBRY\ Updater.app &>/dev/null diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh index 8d84a8f50..1cf48f540 100644 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -21,12 +21,10 @@ echo "Downloading update" git clone https://github.com/lbryio/lbry.git &>/dev/null cd lbry -version=$(git rev-parse HEAD) - echo "Updating lbrynet" sudo python setup.py install &>/dev/null mkdir -p "$lbrynet_directory" -echo $version > "${lbrynet_directory}/lbrynet_version.txt" +echo $current_version > "${lbrynet_directory}/lbrynet_version.txt" echo "Cleaning up" From 4dcfeca0927b5c0fdf4e8d2d2761be7bd991105f Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 16 Feb 2016 10:25:15 -0500 Subject: [PATCH 010/462] speed up update scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit don’t download repo history, only what’s current. --- lbrynet/lbrynet_daemon/scripts/update_lbry.sh | 6 ++---- lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh index 6ae5fefee..291dfb3ba 100644 --- a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh @@ -13,13 +13,11 @@ if [ -d "$lbrycrd_directory" ]; then fi fi -echo "Updating LBRY" - tmp=$(mktemp -d) cd $tmp -echo "Downloading update" -git clone https://github.com/jackrobison/lbrynet-app.git &>/dev/null +echo "Downloading LBRY update" +git clone --depth 1 https://github.com/jackrobison/lbrynet-app.git &>/dev/null cd lbrynet-app unzip LBRY.app.zip &>/dev/null unzip LBRYURIHandler.app.zip &>/dev/null diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh index 1cf48f540..ad418f04d 100644 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -16,12 +16,12 @@ fi tmp=$(mktemp -d) cd $tmp -echo "Downloading update" +echo "Downloading LBRYnet update" -git clone https://github.com/lbryio/lbry.git &>/dev/null +git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry -echo "Updating lbrynet" +echo "Updating LBRYnet" sudo python setup.py install &>/dev/null mkdir -p "$lbrynet_directory" echo $current_version > "${lbrynet_directory}/lbrynet_version.txt" From 20b516b5fa9cb84cf90a53e16879a79d678219e6 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 16 Feb 2016 13:39:08 -0500 Subject: [PATCH 011/462] automatic updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds automatic updates to lbrynet-daemon, this will check if the installed versions of LBRY applications as well as the lbrynet python installation are up to date. If they aren’t, it downloads the new versions, installs them, and restarts the daemon. --- lbrynet/lbrynet_console/LBRYConsole.py | 7 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 139 +++++++++++++----- .../lbrynet_daemon/scripts/update_lbrynet.sh | 2 +- 3 files changed, 112 insertions(+), 36 deletions(-) diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index 0005e30a7..47401e86e 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -544,7 +544,7 @@ def launch_lbry_console(): action="store_true") parser.add_argument("--data_dir", help=("The full path to the directory in which lbrynet data and metadata will be stored. " - "Default: ~/.lbrynet"), + "Default: ~/.lbrynet on linux, ~/Library/Application Support/lbrynet on OS X"), type=str) parser.add_argument("--lbrycrdd_path", help="The path to lbrycrdd, which will be launched if it isn't running, unless " @@ -573,7 +573,10 @@ def launch_lbry_console(): created_data_dir = False if not args.data_dir: - data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + if sys.platform == "darwin": + data_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") + else: + data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: data_dir = args.data_dir if not os.path.exists(data_dir): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 2a4d50695..b92518b59 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -29,17 +29,22 @@ import binascii import webbrowser import xmlrpclib from decimal import Decimal +import subprocess +from StringIO import StringIO +from zipfile import ZipFile +from urllib import urlopen log = logging.getLogger(__name__) -#TODO add login credentials in a conf file -#issues with delete: -#TODO when stream is stopped the generated file is deleted +# TODO add login credentials in a conf file -#functions to add: -#TODO send credits to address -#TODO alert if your copy of a lbry file is out of date with the name record +# issues with delete: +# TODO when stream is stopped the generated file is deleted + +# functions to add: +# TODO send credits to address +# TODO alert if your copy of a lbry file is out of date with the name record class LBRYDaemon(xmlrpc.XMLRPC): @@ -54,7 +59,10 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.run_server = True self.session = None self.known_dht_nodes = KNOWN_DHT_NODES - self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + if sys.platform != "darwin": + self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + else: + self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 self.dht_node_port = 4444 @@ -102,14 +110,19 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE self.max_key_fee = DEFAULT_MAX_KEY_FEE self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS + self.restart_message = "" self.search_timeout = 3.0 self.query_handlers = {} return defer.succeed(None) def _disp_startup(): - print "Started LBRYnet daemon" - print "The daemon can be shut down by running 'stop-lbrynet-daemon' in a terminal" + if self.restart_message: + print self.restart_message + else: + print "Started LBRYnet daemon" + print "The daemon can be shut down by running 'stop-lbrynet-daemon' in a terminal" + return defer.succeed(None) d = defer.Deferred() @@ -125,12 +138,79 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda _: self._setup_lbry_file_opener()) d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) + if sys.platform == "darwin": + d.addCallback(lambda _: self._update()) d.addCallback(lambda _: self._setup_fetcher()) d.addCallback(lambda _: _disp_startup()) d.callback(None) return defer.succeed(None) + def _update(self): + def _check_for_updater(): + if os.path.isdir("/Applications/LBRY Updater.app"): + print "Found LBRY updater" + return defer.succeed(None) + + print "LBRY updater not found, downloading and installing..." + url = urlopen("https://rawgit.com/jackrobison/lbrynet-app/master/LBRY%20Updater.app.zip") + zipped_app = ZipFile(StringIO(url.read())) + zipped_app.extractall("/Applications") + return defer.succeed(None) + + def _update_lbrynet(): + git_version = subprocess.check_output( + "git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1", + shell=True) + if os.path.isfile(os.path.join(self.db_dir, "lbrynet_version.txt")): + f = open(os.path.join(self.db_dir, "lbrynet_version.txt"), 'r') + current_version = f.read() + f.close() + if git_version == current_version: + print "LBRYnet installation version " + current_version[:-1] + " is up to date" + return defer.succeed(None) + + print "Update LBRYnet version " + current_version[:-1] + " --> " + git_version[:-1] + self.restart_message = "Updates available" + return defer.succeed(None) + + def _update_lbrycrdd(): + git_version = subprocess.check_output( + "git ls-remote https://github.com/jackrobison/lbrynet-app.git | grep HEAD | cut -f 1", + shell=True) + if os.path.isfile(os.path.join(self.wallet_dir, "lbry_app_version.txt")): + f = open(os.path.join(self.wallet_dir, "lbry_app_version.txt"), 'r') + current_version = f.read() + f.close() + if git_version == current_version: + print "LBRY installation version " + current_version[:-1] + " is up to date" + return defer.succeed(None) + + print "Update LBRY version " + current_version[:-1] + " --> " + git_version[:-1] + self.restart_message = "Updates available" + return defer.succeed(None) + + d = _check_for_updater() + d.addCallback(lambda _: _update_lbrynet()) + d.addCallback(lambda _: _update_lbrycrdd()) + d.addCallback(lambda _: os.system("open /Applications/LBRY\ Updater.app &>/dev/null") if self.restart_message + else defer.succeed(None)) + d.addCallbacks(lambda _: self._restart() if self.restart_message else defer.succeed(None)) + + return defer.succeed(None) + + def _restart(self): + def _disp_shutdown(): + print 'Restarting lbrynet daemon' + return defer.succeed(None) + + # LBRY Updater.app will restart the daemon + d = self._shutdown() + d.addCallback(lambda _: _disp_shutdown()) + d.addCallback(lambda _: reactor.callLater(1.0, reactor.stop)) + + return d + def _start_server(self): if self.peer_port is not None: @@ -166,10 +246,10 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _setup_query_handlers(self): handlers = [ - #CryptBlobInfoQueryHandlerFactory(self.lbry_file_metadata_manager, self.session.wallet, + # CryptBlobInfoQueryHandlerFactory(self.lbry_file_metadata_manager, self.session.wallet, # self._server_payment_rate_manager), BlobAvailabilityHandlerFactory(self.session.blob_manager), - #BlobRequestHandlerFactory(self.session.blob_manager, self.session.wallet, + # BlobRequestHandlerFactory(self.session.blob_manager, self.session.wallet, # self._server_payment_rate_manager), self.session.wallet.get_wallet_info_query_handler_factory(), ] @@ -287,8 +367,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _get_session(self): def get_default_data_rate(): d = self.settings.get_default_data_payment_rate() - d.addCallback(lambda rate: {"default_data_payment_rate": - rate if rate is not None else MIN_BLOB_DATA_PAYMENT_RATE}) + d.addCallback(lambda rate: {"default_data_payment_rate": rate if rate is not None else + MIN_BLOB_DATA_PAYMENT_RATE}) return d def get_wallet(): @@ -358,18 +438,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_opener_factory) return defer.succeed(None) - def _setup_lbry_file_manager(self): - self.lbry_file_metadata_manager = DBLBRYFileMetadataManager(self.db_dir) - d = self.lbry_file_metadata_manager.setup() - - def set_lbry_file_manager(): - self.lbry_file_manager = LBRYFileManager(self.session, self.lbry_file_metadata_manager, self.sd_identifier) - return self.lbry_file_manager.setup() - - d.addCallback(lambda _: set_lbry_file_manager()) - - return d - def _setup_lbry_file_opener(self): downloader_factory = LBRYFileOpenerFactory(self.session.peer_finder, self.session.rate_limiter, @@ -391,7 +459,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self.session.wallet.get_stream_info_for_name(name) stream = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, - max_key_fee=self.max_key_fee, data_rate=self.data_rate) + max_key_fee=self.max_key_fee, data_rate=self.data_rate) d.addCallback(_disp) d.addCallback(lambda stream_info: stream.start(stream_info)) d.addCallback(lambda _: self._path_from_name(name)) @@ -440,7 +508,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): print "[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash return defer.succeed(_get_lbry_file(path)) else: - print "[" + str(datetime.now()) + "] Search for lbry_file didn't return anything" + print "[" + str(datetime.now()) + "] Search for lbry_file didn't return anything" return defer.succeed(False) d = self._resolve_name(name) @@ -471,7 +539,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self._check_history(name) d.addCallback(lambda lbry_file: {'stream_hash': lbry_file.stream_hash, 'path': os.path.join(self.download_directory, lbry_file.file_name)} - if lbry_file else defer.fail(UnknownNameError)) + if lbry_file else defer.fail(UnknownNameError)) return d def _path_from_lbry_file(self, lbry_file): @@ -508,7 +576,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) d.addCallback(lambda metadata: metadata.validator.info_to_show()) d.addCallback(_to_dict) - d.addCallback(lambda info: int(info['stream_size'])/1000000*self.data_rate) + d.addCallback(lambda info: int(info['stream_size']) / 1000000 * self.data_rate) d.addCallback(_add_key_fee) d.addErrback(lambda _: _add_key_fee(0.0)) reactor.callLater(self.search_timeout, _check_est, d, name) @@ -708,8 +776,10 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _disp_err(err): print str(err.getTraceback()) return err + d = defer.Deferred() - d.addCallback(lambda _: webbrowser.open("file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) + d.addCallback(lambda _: webbrowser.open( + "file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) d.addErrback(_disp_err) d.callback(None) @@ -748,7 +818,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): if 'thumbnail' in meta.keys(): t['img'] = meta['thumbnail'] else: - t['img'] = 'File://' + str(os.path.join(self.download_directory, "lbryio/web/img/Free-speech-flag.svg")) + t['img'] = 'File://' + str( + os.path.join(self.download_directory, "lbryio/web/img/Free-speech-flag.svg")) if 'name' in meta.keys(): t['title'] = meta['name'] if 'description' in meta.keys(): @@ -770,7 +841,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): filtered_results = [n for n in filtered_results if 'txid' in n.keys()] resolved_results = [defer.DeferredList([_return_d(n), self._resolve_name_wc(n['name']), self._get_est_cost(n['name'])], consumeErrors=True) - for n in filtered_results] + for n in filtered_results] d = defer.DeferredList(resolved_results) d.addCallback(_clean) @@ -915,6 +986,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): def main(): + # shut down existing instance of lbrynet-daemon if there is one try: d = xmlrpclib.ServerProxy('http://localhost:7080') d.stop() @@ -926,5 +998,6 @@ def main(): reactor.listenTCP(7080, server.Site(daemon)) reactor.run() + if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh index ad418f04d..90e87f079 100644 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -21,7 +21,7 @@ echo "Downloading LBRYnet update" git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry -echo "Updating LBRYnet" +echo "Installing update" sudo python setup.py install &>/dev/null mkdir -p "$lbrynet_directory" echo $current_version > "${lbrynet_directory}/lbrynet_version.txt" From 9a9a7c9d7a5658e7886cb56ba28c1f4aab8fbca5 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 16 Feb 2016 13:55:46 -0500 Subject: [PATCH 012/462] update daemon --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index b92518b59..0ac2c9d20 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -169,9 +169,12 @@ class LBRYDaemon(xmlrpc.XMLRPC): if git_version == current_version: print "LBRYnet installation version " + current_version[:-1] + " is up to date" return defer.succeed(None) + print "Update LBRYnet version " + current_version[:-1] + " --> " + git_version[:-1] + self.restart_message = "Updates available" + else: + print "Update LBRYnet to version " + git_version[:-1] + self.restart_message = "Updates available" - print "Update LBRYnet version " + current_version[:-1] + " --> " + git_version[:-1] - self.restart_message = "Updates available" return defer.succeed(None) def _update_lbrycrdd(): @@ -185,9 +188,12 @@ class LBRYDaemon(xmlrpc.XMLRPC): if git_version == current_version: print "LBRY installation version " + current_version[:-1] + " is up to date" return defer.succeed(None) + print "Update LBRY version " + current_version[:-1] + " --> " + git_version[:-1] + self.restart_message = "Updates available" + else: + print "Update LBRY to version " + git_version[:-1] + self.restart_message = "Updates available" - print "Update LBRY version " + current_version[:-1] + " --> " + git_version[:-1] - self.restart_message = "Updates available" return defer.succeed(None) d = _check_for_updater() From c245c693899f8030849ce66a409882811f48ff1b Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 16 Feb 2016 14:48:10 -0500 Subject: [PATCH 013/462] Add check-for-new-version function to daemon --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0ac2c9d20..0ba94e73f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -990,6 +990,36 @@ class LBRYDaemon(xmlrpc.XMLRPC): return self.fetcher.verbose + def xmlrpc_check_for_new_version(self): + message = "" + + git_version = subprocess.check_output("git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1", shell=True) + if os.path.isfile(os.path.join(self.db_dir, "lbrynet_version.txt")): + f = open(os.path.join(self.db_dir, "lbrynet_version.txt"), 'r') + current_version = f.read() + f.close() + + if git_version == current_version: + message += "LBRYnet is up to date\n" + else: + message += "LBRYnet version is out of date, restart the daemon to update\n" + else: + message += "Unknown version of LBRYnet, try running installer again\n" + + git_version = subprocess.check_output("git ls-remote https://github.com/jackrobison/lbrynet-app.git | grep HEAD | cut -f 1", shell=True) + if os.path.isfile(os.path.join(self.wallet_dir, "lbry_app_version.txt")): + f = open(os.path.join(self.wallet_dir, "lbry_app_version.txt"), 'r') + current_version = f.read() + f.close() + + if git_version == current_version: + message += "LBRY is up to date" + else: + message += "LBRY version is out of date, restart the daemon to update" + else: + message += "Unknown version of LBRYnet, try running installer again\n" + + return message def main(): # shut down existing instance of lbrynet-daemon if there is one From ec3e365b3c95933df8d1996b58d3e638cd5a0757 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Tue, 16 Feb 2016 23:10:26 -0500 Subject: [PATCH 014/462] fix up LBRYcrdWallet and split it into two classes --- lbrynet/core/LBRYcrdWallet.py | 644 ++++++++++++--------- lbrynet/lbrynet_console/ControlHandlers.py | 6 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 41 +- lbrynet/lbrynet_daemon/LBRYDownloader.py | 74 ++- 4 files changed, 461 insertions(+), 304 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 61de7f5c8..bc7c51a92 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -39,11 +39,11 @@ def _catch_connection_error(f): return w -class LBRYcrdWallet(object): +class LBRYWallet(object): """This class implements the LBRYWallet interface for the LBRYcrd payment system""" implements(ILBRYWallet) - def __init__(self, db_dir, wallet_dir=None, wallet_conf=None, lbrycrdd_path=None): + def __init__(self, db_dir): self.db_dir = db_dir self.db = None @@ -58,44 +58,29 @@ class LBRYcrdWallet(object): # incremental_amount(float)) self.max_expected_payment_time = datetime.timedelta(minutes=3) self.stopped = True - self.started_lbrycrdd = False - self.wallet_dir = wallet_dir - self.wallet_conf = wallet_conf - self.lbrycrdd = None - self.manage_running = False - self.lbrycrdd_path = lbrycrdd_path - settings = self.get_rpc_conf() - rpc_user = settings["username"] - rpc_pass = settings["password"] - rpc_port = settings["rpc_port"] - rpc_url = "127.0.0.1" - self.rpc_conn_string = "http://%s:%s@%s:%s" % (rpc_user, rpc_pass, rpc_url, str(rpc_port)) + self.manage_running = False def start(self): - def make_connection(): - alert.info("Connecting to lbrycrdd...") - if self.lbrycrdd_path is not None: - self._start_daemon() - self._get_info() - log.info("Connected!") - alert.info("Connected to lbrycrdd.") - def start_manage(): self.stopped = False self.manage() return True d = self._open_db() - d.addCallback(lambda _: threads.deferToThread(make_connection)) + d.addCallback(lambda _: self._start()) d.addCallback(lambda _: start_manage()) return d - def stop(self): + def _start(self): + pass - def log_stop_error(err): - log.error("An error occurred stopping the wallet. %s", err.getTraceback()) + @staticmethod + def log_stop_error(err): + log.error("An error occurred stopping the wallet: %s", err.getTraceback()) + + def stop(self): self.stopped = True # If self.next_manage_call is None, then manage is currently running or else @@ -105,12 +90,14 @@ class LBRYcrdWallet(object): self.next_manage_call = None d = self.manage() - d.addErrback(log_stop_error) - if self.lbrycrdd_path is not None: - d.addCallback(lambda _: self._stop_daemon()) - d.addErrback(log_stop_error) + d.addErrback(self.log_stop_error) + d.addCallback(lambda _: self._stop()) + d.addErrback(self.log_stop_error) return d + def _stop(self): + pass + def manage(self): log.info("Doing manage") self.next_manage_call = None @@ -137,7 +124,7 @@ class LBRYcrdWallet(object): d.addCallback(lambda _: self._send_payments()) - d.addCallback(lambda _: threads.deferToThread(self._get_wallet_balance)) + d.addCallback(lambda _: self.get_balance()) def set_wallet_balance(balance): self.wallet_balance = balance @@ -164,16 +151,6 @@ class LBRYcrdWallet(object): d.addBoth(set_manage_not_running) return d - def get_info_exchanger(self): - return LBRYcrdAddressRequester(self) - - def get_wallet_info_query_handler_factory(self): - return LBRYcrdAddressQueryHandlerFactory(self) - - def get_balance(self): - d = threads.deferToThread(self._get_wallet_balance) - return d - def reserve_points(self, identifier, amount): """ Ensure a certain amount of points are available to be sent as payment, before the service is rendered @@ -263,37 +240,66 @@ class LBRYcrdWallet(object): def set_address_for_peer(address): self.current_address_given_to_peer[peer] = address return address - d = threads.deferToThread(self._get_new_address) + d = self.get_new_address() d.addCallback(set_address_for_peer) return d - def get_stream_info_for_name(self, name, txid=None): + def _send_payments(self): + log.info("Trying to send payments, if there are any to be sent") - def get_stream_info_from_value(result): - r_dict = {} - if 'value' in result: - value = result['value'] - try: - value_dict = json.loads(value) - except ValueError: - return Failure(InvalidStreamInfoError(name)) - known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', - 'content_license'] - for field in known_fields: - if field in value_dict: - r_dict[field] = value_dict[field] - if 'stream_hash' in r_dict and 'txid' in result: - d = self._save_name_metadata(name, r_dict['stream_hash'], str(result['txid'])) - else: - d = defer.succeed(True) - d.addCallback(lambda _: r_dict) - return d + payments_to_send = {} + for address, points in self.queued_payments.items(): + log.info("Should be sending %s points to %s", str(points), str(address)) + payments_to_send[address] = float(points) + self.total_reserved_points -= points + self.wallet_balance -= points + del self.queued_payments[address] + if payments_to_send: + log.info("Creating a transaction with outputs %s", str(payments_to_send)) + return self._do_send_many(payments_to_send) + log.info("There were no payments to send") + return defer.succeed(True) + + def get_stream_info_for_name(self, name): + d = self._get_value_for_name(name) + d.addCallback(self._get_stream_info_from_value, name) + return d + + def get_stream_info_from_txid(self, name, txid): + d = self._get_claims_from_tx(txid) + + def get_claim_for_name(claims): + for claim in claims: + if claim['name'] == name: + claim['txid'] = txid + return claim return Failure(UnknownNameError(name)) - d = threads.deferToThread(self._get_value_for_name, name, txid) - d.addCallback(get_stream_info_from_value) + d.addCallback(get_claim_for_name) + d.addCallback(self._get_stream_info_from_value, name) return d + def _get_stream_info_from_value(self, result, name): + r_dict = {} + if 'value' in result: + value = result['value'] + try: + value_dict = json.loads(value) + except ValueError: + return Failure(InvalidStreamInfoError(name)) + known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', + 'content_license'] + for field in known_fields: + if field in value_dict: + r_dict[field] = value_dict[field] + if 'stream_hash' in r_dict and 'txid' in result: + d = self._save_name_metadata(name, r_dict['stream_hash'], str(result['txid'])) + else: + d = defer.succeed(True) + d.addCallback(lambda _: r_dict) + return d + return Failure(UnknownNameError(name)) + def claim_name(self, name, sd_hash, amount, description=None, key_fee=None, key_fee_address=None, thumbnail=None, content_license=None): value = {"stream_hash": sd_hash} @@ -308,7 +314,7 @@ class LBRYcrdWallet(object): if content_license is not None: value['content_license'] = content_license - d = threads.deferToThread(self._claim_name, name, json.dumps(value), amount) + d = self._send_name_claim(name, json.dumps(value), amount) def _save_metadata(txid): d = self._save_name_metadata(name, sd_hash, txid) @@ -319,70 +325,230 @@ class LBRYcrdWallet(object): return d def abandon_name(self, txid): - address = self._get_new_address() - raw = self._get_raw_tx(txid) - transaction = self._get_decoded_tx(raw) - amount = float(transaction['vout'][1]['value']) - return self._abandon_name(txid, address, amount) + d1 = self.get_new_address() + d2 = self._get_claims_from_tx(txid) + + def get_txout_of_claim(claims): + for claim in claims: + if 'name' in claim and 'nOut' in claim: + return claim['nOut'] + return defer.fail(ValueError("No claims in tx")) + + def get_value_of_txout(nOut): + d = self._get_raw_tx(txid) + d.addCallback(self._get_decoded_tx) + d.addCallback(lambda tx: tx['vout'][nOut]['value']) + return d + + d2.addCallback(get_txout_of_claim) + d2.addCallback(get_value_of_txout) + dl = defer.DeferredList([d1, d2], consumeErrors=True) + + def abandon(results): + if results[0][0] and results[1][0]: + address = results[0][1] + amount = results[1][1] + return self._send_abandon(txid, address, amount) + elif results[0][0] is False: + return defer.fail(Failure(ValueError("Couldn't get a new address"))) + else: + return results[1][1] + + dl.addCallback(abandon) + return dl def get_tx(self, txid): - raw = self._get_raw_tx(txid) - return self._get_decoded_tx(raw) - - def get_name_claims(self): - return threads.deferToThread(self._get_name_claims) - - def start_miner(self): - if not self._get_gen_status(): - return self._set_gen_status(True) - else: - return "Miner was already running" - - def stop_miner(self): - if self._get_gen_status(): - return self._set_gen_status(False) - else: - return "Miner wasn't running" - - def get_miner_status(self): - return self._get_gen_status() - - def get_block(self, blockhash): - return self._get_block(blockhash) - - def get_blockchain_info(self): - return self._get_blockchain_info() - - def get_claims_for_tx(self, txid): - return self._get_claims_for_tx(txid) - - def get_nametrie(self): - return self._get_nametrie() + d = self._get_raw_tx(txid) + d.addCallback(self._get_decoded_tx) + return d # def update_name(self, name_value): # return self._update_name(name_value) def get_name_and_validity_for_sd_hash(self, sd_hash): d = self._get_claim_metadata_for_sd_hash(sd_hash) - d.addCallback(lambda name_txid: threads.deferToThread(self._get_status_of_claim, name_txid[1], name_txid[0], sd_hash) if name_txid is not None else None) + d.addCallback(lambda name_txid: self._get_status_of_claim(name_txid[1], name_txid[0], sd_hash) if name_txid is not None else None) return d def get_available_balance(self): return float(self.wallet_balance - self.total_reserved_points) - def get_new_address(self): - return threads.deferToThread(self._get_new_address) + def _get_status_of_claim(self, txid, name, sd_hash): + d = self._get_claims_from_tx(txid) - def check_first_run(self): - d = threads.deferToThread(self._get_wallet_balance) - d.addCallback(lambda bal: threads.deferToThread(self._get_num_addresses) if bal == 0 else 2) - d.addCallback(lambda num_addresses: True if num_addresses <= 1 else False) + def get_status(claims): + if claims is None: + claims = [] + for claim in claims: + if 'in claim trie' in claim: + if 'name' in claim and str(claim['name']) == name and 'value' in claim: + try: + value_dict = json.loads(claim['value']) + except ValueError: + return None + if 'stream_hash' in value_dict and str(value_dict['stream_hash']) == sd_hash: + if 'is controlling' in claim and claim['is controlling']: + return name, "valid" + if claim['in claim trie']: + return name, "invalid" + if 'in queue' in claim and claim['in queue']: + return name, "pending" + return name, "unconfirmed" + return None + + d.addCallback(get_status) return d - def get_most_recent_blocktime(self): - return threads.deferToThread(self._get_best_block_time) + def _check_expected_balances(self): + now = datetime.datetime.now() + balances_to_check = [] + try: + while self.expected_balance_at_time[0][3] < now: + balances_to_check.append(self.expected_balance_at_time.popleft()) + except IndexError: + pass + ds = [] + for balance_to_check in balances_to_check: + log.info("Checking balance of address %s", str(balance_to_check[1])) + d = self._get_balance_for_address(balance_to_check[1]) + d.addCallback(lambda bal: bal >= balance_to_check[2]) + ds.append(d) + dl = defer.DeferredList(ds) - def get_rpc_conf(self): + def handle_checks(results): + from future_builtins import zip + for balance, (success, result) in zip(balances_to_check, results): + peer = balance[0] + if success is True: + if result is False: + if balance[4] <= 1: # first or second strike, give them another chance + new_expected_balance = (balance[0], + balance[1], + balance[2], + datetime.datetime.now() + self.max_expected_payment_time, + balance[4] + 1, + balance[5]) + self.expected_balance_at_time.append(new_expected_balance) + peer.update_score(-5.0) + else: + peer.update_score(-50.0) + else: + if balance[4] == 0: + peer.update_score(balance[5]) + peer.update_stats('points_received', balance[5]) + else: + log.warning("Something went wrong checking a balance. Peer: %s, account: %s," + "expected balance: %s, expected time: %s, count: %s, error: %s", + str(balance[0]), str(balance[1]), str(balance[2]), str(balance[3]), + str(balance[4]), str(result.getErrorMessage())) + + dl.addCallback(handle_checks) + return dl + + def _open_db(self): + self.db = adbapi.ConnectionPool('sqlite3', os.path.join(self.db_dir, "blockchainname.db"), + check_same_thread=False) + return self.db.runQuery("create table if not exists name_metadata (" + + " name text, " + + " txid text, " + + " sd_hash text)") + + def _save_name_metadata(self, name, sd_hash, txid): + d = self.db.runQuery("insert into name_metadata values (?, ?, ?)", + (name, txid, sd_hash)) + return d + + def _get_claim_metadata_for_sd_hash(self, sd_hash): + d = self.db.runQuery("select name, txid from name_metadata where sd_hash=?", (sd_hash,)) + d.addCallback(lambda r: r[0] if len(r) else None) + return d + + ######### Must be overridden ######### + + def get_info_exchanger(self): + return defer.fail(NotImplementedError()) + + def get_wallet_info_query_handler_factory(self): + return defer.fail(NotImplementedError()) + + def get_balance(self): + return defer.fail(NotImplementedError()) + + def get_new_address(self): + return defer.fail(NotImplementedError()) + + def get_block(self, blockhash): + return defer.fail(NotImplementedError()) + + def get_most_recent_blocktime(self): + return defer.fail(NotImplementedError()) + + def get_blockchain_info(self): + return defer.fail(NotImplementedError()) + + def get_name_claims(self): + return defer.fail(NotImplementedError()) + + def check_first_run(self): + return defer.fail(NotImplementedError()) + + def _get_raw_tx(self, txid): + return defer.fail(NotImplementedError()) + + def _send_name_claim(self, name, val, amount): + return defer.fail(NotImplementedError()) + + def _get_decoded_tx(self, raw_tx): + return defer.fail(NotImplementedError()) + + def _send_abandon(self, txid, address, amount): + return defer.fail(NotImplementedError()) + + def _do_send_many(self, payments_to_send): + return defer.fail(NotImplementedError()) + + def _get_value_for_name(self, name): + return defer.fail(NotImplementedError()) + + def _get_claims_from_tx(self, txid): + return defer.fail(NotImplementedError()) + + def _get_balance_for_address(self, address): + return defer.fail(NotImplementedError()) + + +class LBRYcrdWallet(LBRYWallet): + def __init__(self, db_dir, wallet_dir=None, wallet_conf=None, lbrycrdd_path=None): + LBRYWallet.__init__(self, db_dir) + self.started_lbrycrdd = False + self.wallet_dir = wallet_dir + self.wallet_conf = wallet_conf + self.lbrycrdd = None + self.lbrycrdd_path = lbrycrdd_path + + settings = self._get_rpc_conf() + rpc_user = settings["username"] + rpc_pass = settings["password"] + rpc_port = settings["rpc_port"] + rpc_url = "127.0.0.1" + self.rpc_conn_string = "http://%s:%s@%s:%s" % (rpc_user, rpc_pass, rpc_url, str(rpc_port)) + + def _start(self): + return threads.deferToThread(self._make_connection) + + def _stop(self): + if self.lbrycrdd_path is not None: + return self._stop_daemon() + + def _make_connection(self): + alert.info("Connecting to lbrycrdd...") + if self.lbrycrdd_path is not None: + self._start_daemon() + self._get_info_rpc() + log.info("Connected!") + alert.info("Connected to lbrycrdd.") + + def _get_rpc_conf(self): settings = {"username": "rpcuser", "password": "rpcpassword", "rpc_port": 8332} @@ -397,6 +563,82 @@ class LBRYcrdWallet(object): settings["rpc_port"] = int(l[8:].rstrip('\n')) return settings + def get_info_exchanger(self): + return LBRYcrdAddressRequester(self) + + def get_wallet_info_query_handler_factory(self): + return LBRYcrdAddressQueryHandlerFactory(self) + + def check_first_run(self): + d = self.get_balance() + d.addCallback(lambda bal: threads.deferToThread(self._get_num_addresses_rpc) if bal == 0 else 2) + d.addCallback(lambda num_addresses: True if num_addresses <= 1 else False) + return d + + def get_new_address(self): + return threads.deferToThread(self._get_new_address_rpc) + + def get_balance(self): + return threads.deferToThread(self._get_wallet_balance_rpc) + + def get_most_recent_blocktime(self): + d = threads.deferToThread(self._get_best_blockhash_rpc) + d.addCallback(lambda blockhash: threads.deferToThread(self._get_block_rpc, blockhash)) + d.addCallback( + lambda block: block['time'] if 'time' in block else Failure(ValueError("Could not get a block time"))) + return d + + def get_name_claims(self): + return threads.deferToThread(self._get_name_claims_rpc) + + def get_block(self, blockhash): + return threads.deferToThread(self._get_block_rpc, blockhash) + + def get_blockchain_info(self): + return threads.deferToThread(self._get_blockchain_info_rpc) + + def get_nametrie(self): + return threads.deferToThread(self._get_nametrie_rpc) + + def start_miner(self): + d = threads.deferToThread(self._get_gen_status_rpc) + d.addCallback(lambda status: threads.deferToThread(self._set_gen_status_rpc, True) if not status + else "Miner was already running") + return d + + def stop_miner(self): + d = threads.deferToThread(self._get_gen_status_rpc) + d.addCallback(lambda status: threads.deferToThread(self._set_gen_status_rpc, False) if status + else "Miner wasn't running") + return d + + def get_miner_status(self): + return threads.deferToThread(self._get_gen_status_rpc) + + def _get_balance_for_address(self, address): + return threads.deferToThread(self._get_balance_for_address_rpc, address) + + def _do_send_many(self, payments_to_send): + return threads.deferToThread(self._do_send_many_rpc, payments_to_send) + + def _send_name_claim(self, name, value, amount): + return threads.deferToThread(self._send_name_claim_rpc, name, value, amount) + + def _get_raw_tx(self, txid): + return threads.deferToThread(self._get_raw_tx_rpc, txid) + + def _get_decoded_tx(self, raw_tx): + return threads.deferToThread(self._get_decoded_tx_rpc, raw_tx) + + def _send_abandon(self, txid, address, amount): + return threads.deferToThread(self._send_abandon_rpc, txid, address, amount) + + def _get_claims_from_tx(self, txid): + return threads.deferToThread(self._get_claims_from_tx_rpc, txid) + + def _get_value_for_name(self, name): + return threads.deferToThread(self._get_value_for_name_rpc, name) + def _get_rpc_conn(self): return AuthServiceProxy(self.rpc_conn_string) @@ -450,101 +692,41 @@ class LBRYcrdWallet(object): def _stop_daemon(self): if self.lbrycrdd is not None and self.started_lbrycrdd is True: alert.info("Stopping lbrycrdd...") - d = threads.deferToThread(self._rpc_stop) + d = threads.deferToThread(self._stop_rpc) d.addCallback(lambda _: alert.info("Stopped lbrycrdd.")) return d return defer.succeed(True) - def _check_expected_balances(self): - now = datetime.datetime.now() - balances_to_check = [] - try: - while self.expected_balance_at_time[0][3] < now: - balances_to_check.append(self.expected_balance_at_time.popleft()) - except IndexError: - pass - ds = [] - for balance_to_check in balances_to_check: - d = threads.deferToThread(self._check_expected_balance, balance_to_check) - ds.append(d) - dl = defer.DeferredList(ds) - - def handle_checks(results): - from future_builtins import zip - for balance, (success, result) in zip(balances_to_check, results): - peer = balance[0] - if success is True: - if result is False: - if balance[4] <= 1: # first or second strike, give them another chance - new_expected_balance = (balance[0], - balance[1], - balance[2], - datetime.datetime.now() + self.max_expected_payment_time, - balance[4] + 1, - balance[5]) - self.expected_balance_at_time.append(new_expected_balance) - peer.update_score(-5.0) - else: - peer.update_score(-50.0) - else: - if balance[4] == 0: - peer.update_score(balance[5]) - peer.update_stats('points_received', balance[5]) - else: - log.warning("Something went wrong checking a balance. Peer: %s, account: %s," - "expected balance: %s, expected time: %s, count: %s, error: %s", - str(balance[0]), str(balance[1]), str(balance[2]), str(balance[3]), - str(balance[4]), str(result.getErrorMessage())) - - dl.addCallback(handle_checks) - return dl - @_catch_connection_error - def _check_expected_balance(self, expected_balance): + def _get_balance_for_address_rpc(self, address): rpc_conn = self._get_rpc_conn() - log.info("Checking balance of address %s", str(expected_balance[1])) - balance = rpc_conn.getreceivedbyaddress(expected_balance[1]) - log.debug("received balance: %s", str(balance)) - log.debug("expected balance: %s", str(expected_balance[2])) - return balance >= expected_balance[2] - - def _send_payments(self): - log.info("Trying to send payments, if there are any to be sent") - - def do_send(payments): - rpc_conn = self._get_rpc_conn() - rpc_conn.sendmany("", payments) - - payments_to_send = {} - for address, points in self.queued_payments.items(): - log.info("Should be sending %s points to %s", str(points), str(address)) - payments_to_send[address] = float(points) - self.total_reserved_points -= points - self.wallet_balance -= points - del self.queued_payments[address] - if payments_to_send: - log.info("Creating a transaction with outputs %s", str(payments_to_send)) - return threads.deferToThread(do_send, payments_to_send) - log.info("There were no payments to send") - return defer.succeed(True) + balance = rpc_conn.getreceivedbyaddress(address) + log.debug("received balance for %s: %s", str(address), str(balance)) + return balance @_catch_connection_error - def _get_info(self): + def _do_send_many_rpc(self, payments): + rpc_conn = self._get_rpc_conn() + rpc_conn.sendmany("", payments) + return True + + @_catch_connection_error + def _get_info_rpc(self): rpc_conn = self._get_rpc_conn() return rpc_conn.getinfo() @_catch_connection_error - def _get_name_claims(self): + def _get_name_claims_rpc(self): rpc_conn = self._get_rpc_conn() return rpc_conn.listnameclaims() @_catch_connection_error - def _get_gen_status(self): + def _get_gen_status_rpc(self): rpc_conn = self._get_rpc_conn() return rpc_conn.getgenerate() @_catch_connection_error - def _set_gen_status(self, b): + def _set_gen_status_rpc(self, b): if b: log.info("Starting miner") else: @@ -553,69 +735,61 @@ class LBRYcrdWallet(object): return rpc_conn.setgenerate(b) @_catch_connection_error - def _get_raw_tx(self, txid): + def _get_raw_tx_rpc(self, txid): rpc_conn = self._get_rpc_conn() return rpc_conn.getrawtransaction(txid) @_catch_connection_error - def _get_decoded_tx(self, raw): + def _get_decoded_tx_rpc(self, raw): rpc_conn = self._get_rpc_conn() return rpc_conn.decoderawtransaction(raw) @_catch_connection_error - def _abandon_name(self, txid, address, amount): + def _send_abandon_rpc(self, txid, address, amount): rpc_conn = self._get_rpc_conn() return rpc_conn.abandonname(txid, address, amount) @_catch_connection_error - def _get_blockchain_info(self): + def _get_blockchain_info_rpc(self): rpc_conn = self._get_rpc_conn() return rpc_conn.getblockchaininfo() @_catch_connection_error - def _get_block(self, blockhash): + def _get_block_rpc(self, blockhash): rpc_conn = self._get_rpc_conn() return rpc_conn.getblock(blockhash) @_catch_connection_error - def _get_claims_for_tx(self, txid): + def _get_claims_from_tx_rpc(self, txid): rpc_conn = self._get_rpc_conn() return rpc_conn.getclaimsfortx(txid) @_catch_connection_error - def _get_nametrie(self): + def _get_nametrie_rpc(self): rpc_conn = self._get_rpc_conn() return rpc_conn.getnametrie() @_catch_connection_error - def _get_wallet_balance(self): + def _get_wallet_balance_rpc(self): rpc_conn = self._get_rpc_conn() return rpc_conn.getbalance("") @_catch_connection_error - def _get_new_address(self): + def _get_new_address_rpc(self): rpc_conn = self._get_rpc_conn() return rpc_conn.getnewaddress() @_catch_connection_error - def _get_value_for_name(self, name, txid=None): + def _get_value_for_name_rpc(self, name): rpc_conn = self._get_rpc_conn() - if not txid: - return rpc_conn.getvalueforname(name) - else: - claim = rpc_conn.getclaimsfortx(txid)[0] - if claim['name'] == name: - claim['txid'] = txid - return claim - else: - raise ValueError + return rpc_conn.getvalueforname(name) - # def _update_name(self, name_value): + # def _update_name_rpc(self, name_value): # rpc_conn = self._get_rpc_conn() # return rpc_conn.updatename(name_value) @_catch_connection_error - def _claim_name(self, name, value, amount): + def _send_name_claim_rpc(self, name, value, amount): rpc_conn = self._get_rpc_conn() try: return str(rpc_conn.claimname(name, value, amount)) @@ -626,45 +800,17 @@ class LBRYcrdWallet(object): raise ValueError(e.error['message']) @_catch_connection_error - def _get_status_of_claim(self, txhash, name, sd_hash): - rpc_conn = self._get_rpc_conn() - claims = rpc_conn.getclaimsfortx(txhash) - if claims is None: - claims = [] - for claim in claims: - if 'in claim trie' in claim: - if 'name' in claim and str(claim['name']) == name and 'value' in claim: - try: - value_dict = json.loads(claim['value']) - except ValueError: - return None - if 'stream_hash' in value_dict and str(value_dict['stream_hash']) == sd_hash: - if 'is controlling' in claim and claim['is controlling']: - return name, "valid" - if claim['in claim trie']: - return name, "invalid" - if 'in queue' in claim and claim['in queue']: - return name, "pending" - return name, "unconfirmed" - return None - - @_catch_connection_error - def _get_num_addresses(self): + def _get_num_addresses_rpc(self): rpc_conn = self._get_rpc_conn() return len(rpc_conn.getaddressesbyaccount("")) @_catch_connection_error - def _get_best_block_time(self): + def _get_best_blockhash_rpc(self): rpc_conn = self._get_rpc_conn() - best_block_hash = rpc_conn.getbestblockhash() - block = rpc_conn.getblock(best_block_hash) - if 'time' in block: - return block['time'] - raise ValueError("Could not get a block time") - + return rpc_conn.getbestblockhash() @_catch_connection_error - def _rpc_stop(self): + def _stop_rpc(self): # check if our lbrycrdd is actually running, or if we connected to one that was already # running and ours failed to start if self.lbrycrdd.poll() is None: @@ -672,24 +818,6 @@ class LBRYcrdWallet(object): rpc_conn.stop() self.lbrycrdd.wait() - def _open_db(self): - self.db = adbapi.ConnectionPool('sqlite3', os.path.join(self.db_dir, "blockchainname.db"), - check_same_thread=False) - return self.db.runQuery("create table if not exists name_metadata (" + - " name text, " + - " txid text, " + - " sd_hash text)") - - def _save_name_metadata(self, name, sd_hash, txid): - d = self.db.runQuery("insert into name_metadata values (?, ?, ?)", - (name, txid, sd_hash)) - return d - - def _get_claim_metadata_for_sd_hash(self, sd_hash): - d = self.db.runQuery("select name, txid from name_metadata where sd_hash=?", (sd_hash,)) - d.addCallback(lambda r: r[0] if len(r) else None) - return d - class LBRYcrdAddressRequester(object): implements([IRequestCreator]) diff --git a/lbrynet/lbrynet_console/ControlHandlers.py b/lbrynet/lbrynet_console/ControlHandlers.py index 926b96f39..6cac5da64 100644 --- a/lbrynet/lbrynet_console/ControlHandlers.py +++ b/lbrynet/lbrynet_console/ControlHandlers.py @@ -766,15 +766,15 @@ class AddStream(CommandHandler): def do_download(stream_downloader): d = stream_downloader.start() - d.addCallback(lambda _: self._download_succeeded(stream_downloader)) + d.addCallback(lambda result: self._download_succeeded(stream_downloader, result)) return d d.addCallback(do_download) d.addErrback(self._handle_download_error) return d - def _download_succeeded(self, stream_downloader): - self.console.sendLine("%s has successfully downloaded." % str(stream_downloader)) + def _download_succeeded(self, stream_downloader, result): + self.console.sendLine("%s: %s." % (str(stream_downloader), str(result))) def _handle_download_error(self, err): if err.check(InsufficientFundsError): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0ba94e73f..0df0e99bc 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -799,12 +799,12 @@ class LBRYDaemon(xmlrpc.XMLRPC): @return: """ - def _return_d(x): - d = defer.Deferred() - d.addCallback(lambda _: x) - d.callback(None) + #def _return_d(x): + # d = defer.Deferred() + # d.addCallback(lambda _: x) + # d.callback(None) - return d + # return d def _clean(n): t = [] @@ -835,21 +835,36 @@ class LBRYDaemon(xmlrpc.XMLRPC): return f + def resolve_claims(claims): + ds = [] + for claim in claims: + d1 = defer.succeed(claim) + d2 = self._resolve_name_wc(claim['name']) + d3 = self._get_est_cost(claim['name']) + dl = defer.DeferredList([d1, d2, d3], consumeErrors=True) + ds.append(dl) + return defer.DeferredList(ds) + def _disp(results): print '[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' results' return results print '[' + str(datetime.now()) + '] Search nametrie: ' + search - filtered_results = [n for n in self.session.wallet.get_nametrie() if n['name'].startswith(search)] - if len(filtered_results) > self.max_search_results: - filtered_results = filtered_results[:self.max_search_results] - filtered_results = [n for n in filtered_results if 'txid' in n.keys()] - resolved_results = [defer.DeferredList([_return_d(n), self._resolve_name_wc(n['name']), - self._get_est_cost(n['name'])], consumeErrors=True) - for n in filtered_results] + d = self.session.wallet.get_nametrie() + d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) + d.addCallback(lambda claims: claims[:self.max_search_results]) + d.addCallback(resolve_claims) - d = defer.DeferredList(resolved_results) + #filtered_results = [n for n in self.session.wallet.get_nametrie() if n['name'].startswith(search)] + #if len(filtered_results) > self.max_search_results: + # filtered_results = filtered_results[:self.max_search_results] + #filtered_results = [n for n in filtered_results if 'txid' in n.keys()] + #resolved_results = [defer.DeferredList([_return_d(n), self._resolve_name_wc(n['name']), + # self._get_est_cost(n['name'])], consumeErrors=True) + # for n in filtered_results] + + #d = defer.DeferredList(resolved_results) d.addCallback(_clean) d.addCallback(_parse) d.addCallback(_disp) diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index c880616bb..f5ba68ca6 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -135,52 +135,66 @@ class FetcherDaemon(object): return msg def _get_names(self): - c = self.wallet.get_blockchain_info() - rtn = [] - if self.lastbestblock != c: - block = self.wallet.get_block(c['bestblockhash']) - txids = block['tx'] - transactions = [self.wallet.get_tx(t) for t in txids] - for t in transactions: - claims = self.wallet.get_claims_for_tx(t['txid']) + d = self.wallet.get_blockchain_info() + d.addCallback(lambda c: get_new_streams if c != self.lastbestblock else []) + + def get_new_streams(c): + self.lastbestblock = c + d = self.wallet.get_block(c['bestblockhash']) + d.addCallback(lambda block: get_new_streams_in_txes(block['tx'], c)) + return d + + def get_new_streams_in_txes(txids, c): + ds = [] + for t in txids: + d = self.wallet.get_claims_from_tx(t) + d.addCallback(get_new_streams_in_tx, t, c) + ds.append(d) + d = defer.DeferredList(ds, consumeErrors=True) + d.addCallback(lambda result: [r[1] for r in result if r[0]]) + d.addCallback(lambda stream_lists: [stream for streams in stream_lists for stream in streams]) + return d + + def get_new_streams_in_tx(claims, t, c): + #claims = self.wallet.get_claims_for_tx(t['txid']) # if self.first_run: # # claims = self.rpc_conn.getclaimsfortx("96aca2c60efded5806b7336430c5987b9092ffbea9c6ed444e3bf8e008993e11") # # claims = self.rpc_conn.getclaimsfortx("cc9c7f5225ecb38877e6ca7574d110b23214ac3556b9d65784065ad3a85b4f74") # self.first_run = False - if claims: - for claim in claims: - if claim not in self.seen: - msg = "[" + str(datetime.now()) + "] New claim | lbry://" + str(claim['name']) + \ - " | stream hash: " + str(json.loads(claim['value'])['stream_hash']) - print msg - log.debug(msg) - rtn.append([claim['name'], t['txid']]) - self.seen.append(claim) - else: - if self.verbose: - print "[" + str(datetime.now()) + "] No claims in block", c['bestblockhash'] + rtn = [] + if claims: + for claim in claims: + if claim not in self.seen: + msg = "[" + str(datetime.now()) + "] New claim | lbry://" + str(claim['name']) + \ + " | stream hash: " + str(json.loads(claim['value'])['stream_hash']) + print msg + log.debug(msg) + rtn.append((claim['name'], t)) + self.seen.append(claim) + else: + if self.verbose: + print "[" + str(datetime.now()) + "] No claims in block", c['bestblockhash'] + return rtn - self.lastbestblock = c - - if len(rtn): - return defer.DeferredList([self.wallet.get_stream_info_for_name(name, txid=t) for name, t in rtn]) + d.addCallback(lambda streams: defer.DeferredList( + [self.wallet.get_stream_info_from_txid(name, t) for name, t in streams])) + # if len(rtn): + # return defer.DeferredList([self.wallet.get_stream_info_for_name(name, txid=t) for name, t in rtn]) + return d def _download_claims(self, claims): if claims: for claim in claims: - download = defer.Deferred() stream = GetStream(self.sd_identifier, self.session, self.wallet, self.lbry_file_manager, self.max_key_fee, pay_key=False) - download.addCallback(lambda _: stream.start(claim[1])) - download.callback(None) + stream.start(claim[1]) return defer.succeed(None) def _looped_search(self): - d = defer.Deferred() - d.addCallback(lambda _: self._get_names()) + d = self._get_names() d.addCallback(self._download_claims) - d.callback(None) + return d def _get_autofetcher_conf(self): settings = {"maxkey": "0.0"} From d4193c55ae81159a7ecc84a79a2212cbcfb2471c Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 17 Feb 2016 10:45:33 -0500 Subject: [PATCH 015/462] update fetcher --- lbrynet/core/LBRYcrdWallet.py | 3 +++ lbrynet/lbrynet_daemon/LBRYDownloader.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index bc7c51a92..d53ac3ab6 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -600,6 +600,9 @@ class LBRYcrdWallet(LBRYWallet): def get_nametrie(self): return threads.deferToThread(self._get_nametrie_rpc) + def get_claims_from_tx(self, txid): + return self._get_claims_from_tx(txid) + def start_miner(self): d = threads.deferToThread(self._get_gen_status_rpc) d.addCallback(lambda status: threads.deferToThread(self._set_gen_status_rpc, True) if not status diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index f5ba68ca6..43b66cbbf 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -136,7 +136,7 @@ class FetcherDaemon(object): def _get_names(self): d = self.wallet.get_blockchain_info() - d.addCallback(lambda c: get_new_streams if c != self.lastbestblock else []) + d.addCallback(lambda c: get_new_streams(c) if c != self.lastbestblock else []) def get_new_streams(c): self.lastbestblock = c From 4451978af64f1596bc4ea1a4ae01017657be921e Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Wed, 17 Feb 2016 11:47:39 -0500 Subject: [PATCH 016/462] make wallet method public and move common functions back into parent wallet class --- lbrynet/core/LBRYcrdWallet.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index d53ac3ab6..d6b09d73d 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -151,6 +151,12 @@ class LBRYWallet(object): d.addBoth(set_manage_not_running) return d + def get_info_exchanger(self): + return LBRYcrdAddressRequester(self) + + def get_wallet_info_query_handler_factory(self): + return LBRYcrdAddressQueryHandlerFactory(self) + def reserve_points(self, identifier, amount): """ Ensure a certain amount of points are available to be sent as payment, before the service is rendered @@ -266,7 +272,7 @@ class LBRYWallet(object): return d def get_stream_info_from_txid(self, name, txid): - d = self._get_claims_from_tx(txid) + d = self.get_claims_from_tx(txid) def get_claim_for_name(claims): for claim in claims: @@ -326,7 +332,7 @@ class LBRYWallet(object): def abandon_name(self, txid): d1 = self.get_new_address() - d2 = self._get_claims_from_tx(txid) + d2 = self.get_claims_from_tx(txid) def get_txout_of_claim(claims): for claim in claims: @@ -374,7 +380,7 @@ class LBRYWallet(object): return float(self.wallet_balance - self.total_reserved_points) def _get_status_of_claim(self, txid, name, sd_hash): - d = self._get_claims_from_tx(txid) + d = self.get_claims_from_tx(txid) def get_status(claims): if claims is None: @@ -465,12 +471,6 @@ class LBRYWallet(object): ######### Must be overridden ######### - def get_info_exchanger(self): - return defer.fail(NotImplementedError()) - - def get_wallet_info_query_handler_factory(self): - return defer.fail(NotImplementedError()) - def get_balance(self): return defer.fail(NotImplementedError()) @@ -510,7 +510,7 @@ class LBRYWallet(object): def _get_value_for_name(self, name): return defer.fail(NotImplementedError()) - def _get_claims_from_tx(self, txid): + def get_claims_from_tx(self, txid): return defer.fail(NotImplementedError()) def _get_balance_for_address(self, address): @@ -563,12 +563,6 @@ class LBRYcrdWallet(LBRYWallet): settings["rpc_port"] = int(l[8:].rstrip('\n')) return settings - def get_info_exchanger(self): - return LBRYcrdAddressRequester(self) - - def get_wallet_info_query_handler_factory(self): - return LBRYcrdAddressQueryHandlerFactory(self) - def check_first_run(self): d = self.get_balance() d.addCallback(lambda bal: threads.deferToThread(self._get_num_addresses_rpc) if bal == 0 else 2) @@ -600,9 +594,6 @@ class LBRYcrdWallet(LBRYWallet): def get_nametrie(self): return threads.deferToThread(self._get_nametrie_rpc) - def get_claims_from_tx(self, txid): - return self._get_claims_from_tx(txid) - def start_miner(self): d = threads.deferToThread(self._get_gen_status_rpc) d.addCallback(lambda status: threads.deferToThread(self._set_gen_status_rpc, True) if not status @@ -636,7 +627,7 @@ class LBRYcrdWallet(LBRYWallet): def _send_abandon(self, txid, address, amount): return threads.deferToThread(self._send_abandon_rpc, txid, address, amount) - def _get_claims_from_tx(self, txid): + def get_claims_from_tx(self, txid): return threads.deferToThread(self._get_claims_from_tx_rpc, txid) def _get_value_for_name(self, name): From 2c51928b9d7e37eb92df48fb8b20a02362fb18f2 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 18 Feb 2016 20:41:23 -0500 Subject: [PATCH 017/462] Status bar for lbrynet daemon --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 85 +++++++++++-------- lbrynet/lbrynet_daemon/scripts/update_lbry.sh | 58 +++++++++++++ 2 files changed, 108 insertions(+), 35 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0df0e99bc..8fd222ec9 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,3 +1,6 @@ +from threading import Thread +from time import sleep + from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory @@ -21,18 +24,11 @@ from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, from twisted.web import xmlrpc, server from twisted.internet import defer, threads, reactor, error from datetime import datetime -import logging -import os -import sys -import json -import binascii -import webbrowser -import xmlrpclib from decimal import Decimal -import subprocess from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen +import os, sys, json, binascii, webbrowser, xmlrpclib, subprocess, logging, rumps log = logging.getLogger(__name__) @@ -565,12 +561,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.cancel() return defer.succeed(None) - def _to_dict(r): - t = {} - for i in r: - t[i[0]] = i[1] - return t - def _add_key_fee(data_cost): d = self.session.wallet.get_stream_info_for_name(name) d.addCallback(lambda info: data_cost + info['key_fee'] if 'key_fee' in info.keys() else data_cost) @@ -581,14 +571,16 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.blob_request_payment_rate_manager)) d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) d.addCallback(lambda metadata: metadata.validator.info_to_show()) - d.addCallback(_to_dict) - d.addCallback(lambda info: int(info['stream_size']) / 1000000 * self.data_rate) + d.addCallback(lambda info: int(dict(info)['stream_size']) / 1000000 * self.data_rate) d.addCallback(_add_key_fee) d.addErrback(lambda _: _add_key_fee(0.0)) reactor.callLater(self.search_timeout, _check_est, d, name) return d + def xmlrpc_is_running(self): + return True + def xmlrpc_get_settings(self): """ Get LBRY payment settings @@ -855,16 +847,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) d.addCallback(lambda claims: claims[:self.max_search_results]) d.addCallback(resolve_claims) - - #filtered_results = [n for n in self.session.wallet.get_nametrie() if n['name'].startswith(search)] - #if len(filtered_results) > self.max_search_results: - # filtered_results = filtered_results[:self.max_search_results] - #filtered_results = [n for n in filtered_results if 'txid' in n.keys()] - #resolved_results = [defer.DeferredList([_return_d(n), self._resolve_name_wc(n['name']), - # self._get_est_cost(n['name'])], consumeErrors=True) - # for n in filtered_results] - - #d = defer.DeferredList(resolved_results) d.addCallback(_clean) d.addCallback(_parse) d.addCallback(_disp) @@ -1036,19 +1018,52 @@ class LBRYDaemon(xmlrpc.XMLRPC): return message -def main(): - # shut down existing instance of lbrynet-daemon if there is one - try: + +class DaemonStatusBarApp(rumps.App): + def __init__(self): + super(DaemonStatusBarApp, self).__init__("LBRYnet", icon=os.path.join(os.path.expanduser("~"), "Downloads/lbryio/lbry.io/web/img/fav/apple-touch-icon.png"), quit_button=None) + self.menu = ["Quit"] + # shut down existing instance of lbrynet-daemon if there is one + try: + d = xmlrpclib.ServerProxy('http://localhost:7080') + d.stop() + except: + pass + + daemon = LBRYDaemon() + daemon.setup() + reactor.listenTCP(7080, server.Site(daemon)) + Thread(target=reactor.run, args=(False,)).start() + + @rumps.clicked('Quit') + def clean_quit(self): d = xmlrpclib.ServerProxy('http://localhost:7080') d.stop() - except: - pass + while True: + try: + d.is_running() + except: + break - daemon = LBRYDaemon() - daemon.setup() - reactor.listenTCP(7080, server.Site(daemon)) - reactor.run() + sleep(1) + rumps.quit_application() +def main(): + if sys.platform == "darwin": + DaemonStatusBarApp().run() + else: + try: + d = xmlrpclib.ServerProxy('http://localhost:7080') + d.stop() + except: + pass + + daemon = LBRYDaemon() + daemon.setup() + reactor.listenTCP(7080, server.Site(daemon)) + reactor.run() + if __name__ == '__main__': main() + diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh index 291dfb3ba..b7a0b2a14 100644 --- a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh @@ -1,5 +1,63 @@ #!/bin/sh +if ! which brew &>/dev/null; then + echo "Installing brew..." + sudo -u ${SUDO_USER} ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null &>/dev/null +else + echo "Updating brew..." + sudo -u ${SUDO_USER} brew update &>/dev/null +fi + +if ! brew list mpfr &>/dev/null; then + echo "Installing mpfr..." + sudo -u ${SUDO_USER} brew install mpfr &>/dev/null +else + echo "mpfr already installed..." +fi + +if ! brew list libmpc &>/dev/null; then + echo "Installing libmpc..." + sudo -u ${SUDO_USER} brew install libmpc &>/dev/null +else + echo "libmpc already installed..." +fi + +if ! brew list openssl &>/dev/null; then + echo "Installing openssl..." + sudo -u ${SUDO_USER} brew install openssl &>/dev/null + sudo -u ${SUDO_USER} brew link --force openssl &>/dev/null +else + echo "openssl already installed..." +fi + +if ! which pip &>/dev/null; then + echo "Installing pip..." + sudo easy_install pip &>/dev/null +else + echo "pip already installed" +fi + +if ! python -c 'import gmpy' &>/dev/null; then + echo "Installing gmpy..." + sudo pip install gmpy &>/dev/null +else + echo "gmpy already installed..." +fi + +if ! python -c 'import service_identity' &>/dev/null; then + echo "Installing service_identity..." + sudo pip install service_identity &>/dev/null +else + echo "gmpy already installed..." +fi + +if ! python -c 'import rumps' &>/dev/null; then + echo "Installing rumps..." + sudo pip install rumps &>/dev/null +else + echo "rumps already installed..." +fi + lbrycrd_directory="/Users/${SUDO_USER}/Library/Application Support/lbrycrd" current_version=$(git ls-remote https://github.com/jackrobison/lbrynet-app.git | grep HEAD | cut -f 1) From cc9fb3ff410acb894fa4b359f9ae38e77994cc19 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 18 Feb 2016 20:45:16 -0500 Subject: [PATCH 018/462] fix icon path --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 8fd222ec9..2e1264e15 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1021,7 +1021,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): class DaemonStatusBarApp(rumps.App): def __init__(self): - super(DaemonStatusBarApp, self).__init__("LBRYnet", icon=os.path.join(os.path.expanduser("~"), "Downloads/lbryio/lbry.io/web/img/fav/apple-touch-icon.png"), quit_button=None) + super(DaemonStatusBarApp, self).__init__("LBRYnet", icon=os.path.join(os.path.expanduser("~"), "Downloads/lbryio/web/img/fav/apple-touch-icon.png"), quit_button=None) self.menu = ["Quit"] # shut down existing instance of lbrynet-daemon if there is one try: @@ -1063,7 +1063,7 @@ def main(): daemon.setup() reactor.listenTCP(7080, server.Site(daemon)) reactor.run() - + if __name__ == '__main__': main() From 3ff0916f49091a87a18ce988904d28fb726875f8 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 18 Feb 2016 20:48:43 -0500 Subject: [PATCH 019/462] remove icon --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 2e1264e15..237909662 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1021,7 +1021,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): class DaemonStatusBarApp(rumps.App): def __init__(self): - super(DaemonStatusBarApp, self).__init__("LBRYnet", icon=os.path.join(os.path.expanduser("~"), "Downloads/lbryio/web/img/fav/apple-touch-icon.png"), quit_button=None) + super(DaemonStatusBarApp, self).__init__("LBRYnet", quit_button=None) self.menu = ["Quit"] # shut down existing instance of lbrynet-daemon if there is one try: From 58aa4887ad1c14030cf867e9850fd6b72b3c5ed4 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 18 Feb 2016 20:55:59 -0500 Subject: [PATCH 020/462] update daemon Start status bar if possible --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 237909662..d23b9f908 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1051,7 +1051,20 @@ class DaemonStatusBarApp(rumps.App): def main(): if sys.platform == "darwin": - DaemonStatusBarApp().run() + try: + DaemonStatusBarApp().run() + except: + print "Couldn't start status bar app" + try: + d = xmlrpclib.ServerProxy('http://localhost:7080') + d.stop() + except: + pass + + daemon = LBRYDaemon() + daemon.setup() + reactor.listenTCP(7080, server.Site(daemon)) + reactor.run() else: try: d = xmlrpclib.ServerProxy('http://localhost:7080') From 0dbf810cfe5bea338137be32243d12b169341b73 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 19 Feb 2016 00:07:19 -0500 Subject: [PATCH 021/462] move status bar stuff --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 72 +++++----------------- lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py | 15 +++++ 2 files changed, 29 insertions(+), 58 deletions(-) create mode 100644 lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index d23b9f908..22e55acca 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,6 +1,3 @@ -from threading import Thread -from time import sleep - from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory @@ -11,6 +8,7 @@ from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream, FetcherDaemon +# from lbrynet.lbrynet_daemon.LBRYOSXStatusBar import DaemonStatusBarApp from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher from lbrynet.core.utils import generate_id from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings @@ -28,7 +26,7 @@ from decimal import Decimal from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen -import os, sys, json, binascii, webbrowser, xmlrpclib, subprocess, logging, rumps +import os, sys, json, binascii, webbrowser, xmlrpclib, subprocess, logging log = logging.getLogger(__name__) @@ -59,6 +57,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") + # self.status_app = DaemonStatusBarApp() self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 self.dht_node_port = 4444 @@ -136,6 +135,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda _: self._setup_server()) if sys.platform == "darwin": d.addCallback(lambda _: self._update()) + # d.addCallback(lambda _: self.status_app.run()) d.addCallback(lambda _: self._setup_fetcher()) d.addCallback(lambda _: _disp_startup()) d.callback(None) @@ -290,6 +290,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self._stop_server() if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) + # if self.status_app: + # d.addCallback(lambda _: self.status_app.stop()) return d def _update_settings(self): @@ -1019,63 +1021,17 @@ class LBRYDaemon(xmlrpc.XMLRPC): return message -class DaemonStatusBarApp(rumps.App): - def __init__(self): - super(DaemonStatusBarApp, self).__init__("LBRYnet", quit_button=None) - self.menu = ["Quit"] - # shut down existing instance of lbrynet-daemon if there is one - try: - d = xmlrpclib.ServerProxy('http://localhost:7080') - d.stop() - except: - pass - - daemon = LBRYDaemon() - daemon.setup() - reactor.listenTCP(7080, server.Site(daemon)) - Thread(target=reactor.run, args=(False,)).start() - - @rumps.clicked('Quit') - def clean_quit(self): +def main(): + try: d = xmlrpclib.ServerProxy('http://localhost:7080') d.stop() - while True: - try: - d.is_running() - except: - break + except: + pass - sleep(1) - - rumps.quit_application() - -def main(): - if sys.platform == "darwin": - try: - DaemonStatusBarApp().run() - except: - print "Couldn't start status bar app" - try: - d = xmlrpclib.ServerProxy('http://localhost:7080') - d.stop() - except: - pass - - daemon = LBRYDaemon() - daemon.setup() - reactor.listenTCP(7080, server.Site(daemon)) - reactor.run() - else: - try: - d = xmlrpclib.ServerProxy('http://localhost:7080') - d.stop() - except: - pass - - daemon = LBRYDaemon() - daemon.setup() - reactor.listenTCP(7080, server.Site(daemon)) - reactor.run() + daemon = LBRYDaemon() + daemon.setup() + reactor.listenTCP(7080, server.Site(daemon)) + reactor.run() if __name__ == '__main__': main() diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py new file mode 100644 index 000000000..f5c9c0e32 --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py @@ -0,0 +1,15 @@ +import rumps +import xmlrpclib +import os + +class DaemonStatusBarApp(rumps.App): + def __init__(self): + super(DaemonStatusBarApp, self).__init__("LBRYnet", icon=os.path.join(os.path.expanduser("~"), "Downloads/lbryio//web/img/fav/apple-touch-icon.png"), quit_button=None) + self.menu = ["Quit"] + + @rumps.clicked('Quit') + def clean_quit(self): + d = xmlrpclib.ServerProxy('http://localhost:7080') + d.stop() + rumps.quit_application() + From bdb0ad4836c0fc31440a5b740ddeec0d8da6ad14 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Fri, 19 Feb 2016 00:44:08 -0500 Subject: [PATCH 022/462] enable lbryum, the lightweight lbrycrd client --- lbrynet/core/LBRYcrdWallet.py | 191 +++++++++++++++++++++-- lbrynet/lbrynet_console/LBRYConsole.py | 15 +- lbrynet/lbrynet_daemon/LBRYDownloader.py | 22 +-- setup.py | 5 +- 4 files changed, 201 insertions(+), 32 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index d6b09d73d..be0a5a694 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -4,6 +4,13 @@ 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 lbryum import SimpleConfig, Network +from lbryum.bitcoin import COIN, TYPE_ADDRESS +from lbryum.wallet import WalletStorage, Wallet +from lbryum.commands import known_commands, Commands +from lbryum.transaction import Transaction + from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException from twisted.internet import threads, reactor, defer, task from twisted.python.failure import Failure @@ -73,9 +80,6 @@ class LBRYWallet(object): d.addCallback(lambda _: start_manage()) return d - def _start(self): - pass - @staticmethod def log_stop_error(err): log.error("An error occurred stopping the wallet: %s", err.getTraceback()) @@ -95,9 +99,6 @@ class LBRYWallet(object): d.addErrback(self.log_stop_error) return d - def _stop(self): - pass - def manage(self): log.info("Doing manage") self.next_manage_call = None @@ -256,13 +257,15 @@ class LBRYWallet(object): payments_to_send = {} for address, points in self.queued_payments.items(): log.info("Should be sending %s points to %s", str(points), str(address)) - payments_to_send[address] = float(points) + payments_to_send[address] = points self.total_reserved_points -= points self.wallet_balance -= points del self.queued_payments[address] if payments_to_send: log.info("Creating a transaction with outputs %s", str(payments_to_send)) - return self._do_send_many(payments_to_send) + d = self._do_send_many(payments_to_send) + d.addCallback(lambda txid: log.debug("Sent transaction %s", txid)) + return d log.info("There were no payments to send") return defer.succeed(True) @@ -304,6 +307,8 @@ class LBRYWallet(object): d = defer.succeed(True) d.addCallback(lambda _: r_dict) return d + elif 'error' in result: + log.warning("Got an error looking up a name: %s", result['error']) return Failure(UnknownNameError(name)) def claim_name(self, name, sd_hash, amount, description=None, key_fee=None, @@ -483,7 +488,7 @@ class LBRYWallet(object): def get_most_recent_blocktime(self): return defer.fail(NotImplementedError()) - def get_blockchain_info(self): + def get_best_blockhash(self): return defer.fail(NotImplementedError()) def get_name_claims(self): @@ -516,6 +521,12 @@ class LBRYWallet(object): def _get_balance_for_address(self, address): return defer.fail(NotImplementedError()) + def _start(self): + pass + + def _stop(self): + pass + class LBRYcrdWallet(LBRYWallet): def __init__(self, db_dir, wallet_dir=None, wallet_conf=None, lbrycrdd_path=None): @@ -588,8 +599,10 @@ class LBRYcrdWallet(LBRYWallet): def get_block(self, blockhash): return threads.deferToThread(self._get_block_rpc, blockhash) - def get_blockchain_info(self): - return threads.deferToThread(self._get_blockchain_info_rpc) + def get_best_blockhash(self): + d = threads.deferToThread(self._get_blockchain_info_rpc) + d.addCallback(lambda blockchain_info: blockchain_info['bestblockhash']) + return d def get_nametrie(self): return threads.deferToThread(self._get_nametrie_rpc) @@ -613,7 +626,8 @@ class LBRYcrdWallet(LBRYWallet): return threads.deferToThread(self._get_balance_for_address_rpc, address) def _do_send_many(self, payments_to_send): - return threads.deferToThread(self._do_send_many_rpc, payments_to_send) + outputs = {address: float(points) for address, points in payments_to_send.iteritems()} + return threads.deferToThread(self._do_send_many_rpc, outputs) def _send_name_claim(self, name, value, amount): return threads.deferToThread(self._send_name_claim_rpc, name, value, amount) @@ -701,8 +715,7 @@ class LBRYcrdWallet(LBRYWallet): @_catch_connection_error def _do_send_many_rpc(self, payments): rpc_conn = self._get_rpc_conn() - rpc_conn.sendmany("", payments) - return True + return rpc_conn.sendmany("", payments) @_catch_connection_error def _get_info_rpc(self): @@ -813,6 +826,156 @@ class LBRYcrdWallet(LBRYWallet): self.lbrycrdd.wait() +class LBRYumWallet(LBRYWallet): + + def __init__(self, db_dir): + LBRYWallet.__init__(self, db_dir) + self.config = None + self.network = None + self.wallet = None + self.cmd_runner = None + self.first_run = False + + def _start(self): + + network_start_d = defer.Deferred() + + def setup_network(): + self.config = SimpleConfig() + self.network = Network(self.config) + return defer.succeed(self.network.start()) + + d = setup_network() + + def check_started(): + if self.network.is_connecting(): + return False + start_check.stop() + if self.network.is_connected(): + network_start_d.callback(True) + else: + network_start_d.errback(ValueError("Failed to connect to network.")) + + start_check = task.LoopingCall(check_started) + + d.addCallback(lambda _: start_check.start(.1)) + d.addCallback(lambda _: network_start_d) + d.addCallback(lambda _: self._load_wallet()) + d.addCallback(lambda _: self._get_cmd_runner()) + return d + + def _stop(self): + d = defer.Deferred() + + def check_stopped(): + if self.network.is_connected(): + return False + stop_check.stop() + self.network = None + d.callback(True) + + self.network.stop() + + stop_check = task.LoopingCall(check_stopped) + stop_check.start(.1) + return d + + def _load_wallet(self): + + def get_wallet(): + path = self.config.get_wallet_path() + storage = WalletStorage(path) + wallet = Wallet(storage) + if not storage.file_exists: + self.first_run = True + seed = wallet.make_seed() + wallet.add_seed(seed, None) + wallet.create_master_keys(None) + wallet.create_main_account() + wallet.synchronize() + self.wallet = wallet + + d = threads.deferToThread(get_wallet) + d.addCallback(self._save_wallet) + d.addCallback(lambda _: self.wallet.start_threads(self.network)) + return d + + def _get_cmd_runner(self): + self.cmd_runner = Commands(self.config, self.wallet, self.network) + + def get_balance(self): + cmd = known_commands['getbalance'] + func = getattr(self.cmd_runner, cmd.name) + d = threads.deferToThread(func) + d.addCallback(lambda result: result['unmatured'] if 'unmatured' in result else result['confirmed']) + d.addCallback(Decimal) + return d + + def get_new_address(self): + d = threads.deferToThread(self.wallet.create_new_address) + d.addCallback(self._save_wallet) + return d + + def get_block(self, blockhash): + return defer.fail(NotImplementedError()) + + def get_most_recent_blocktime(self): + header = self.network.get_header(self.network.get_local_height()) + return defer.succeed(header['timestamp']) + + def get_best_blockhash(self): + height = self.network.get_local_height() + d = threads.deferToThread(self.network.blockchain.read_header, height) + d.addCallback(lambda header: self.network.blockchain.hash_header(header)) + return d + + def get_name_claims(self): + return defer.fail(NotImplementedError()) + + def check_first_run(self): + return defer.succeed(self.first_run) + + def _get_raw_tx(self, txid): + cmd = known_commands['gettransaction'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, txid) + + def _send_name_claim(self, name, val, amount): + return defer.fail(NotImplementedError()) + + def _get_decoded_tx(self, raw_tx): + return defer.fail(NotImplementedError()) + + def _send_abandon(self, txid, address, amount): + return defer.fail(NotImplementedError()) + + def _do_send_many(self, payments_to_send): + log.warning("Doing send many. payments to send: %s", str(payments_to_send)) + outputs = [(TYPE_ADDRESS, address, int(amount*COIN)) for address, amount in payments_to_send.iteritems()] + d = threads.deferToThread(self.wallet.mktx, outputs, None, self.config) + d.addCallback(lambda tx: threads.deferToThread(self.wallet.sendtx, tx)) + d.addCallback(self._save_wallet) + return d + + def _get_value_for_name(self, name): + cmd = known_commands['getvalueforname'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, name) + + def get_claims_from_tx(self, txid): + cmd = known_commands['getclaimsfromtx'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, txid) + + def _get_balance_for_address(self, address): + return defer.succeed(Decimal(self.wallet.get_addr_received(address))/COIN) + + def _save_wallet(self, val): + d = threads.deferToThread(self.wallet.storage.write) + d.addCallback(lambda _: val) + return d + + class LBRYcrdAddressRequester(object): implements([IRequestCreator]) diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index 47401e86e..7eeda8e7b 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -7,6 +7,7 @@ import locale import sys from yapsy.PluginManager import PluginManager from twisted.internet import defer, threads, stdio, task, error +from twisted.python.failure import Failure # from lbrynet.core.client.AutoDownloader import AutoFetcher from lbrynet.lbrynet_console.ConsoleControl import ConsoleControl from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings @@ -40,7 +41,7 @@ from lbrynet.lbrynet_console.ControlHandlers import ShowServerStatusFactory, Mod from lbrynet.lbrynet_console.ControlHandlers import ModifyLBRYFileOptionsChooserFactory, StatusFactory from lbrynet.lbrynet_console.ControlHandlers import PeerStatsAndSettingsChooserFactory, PublishFactory from lbrynet.lbrynet_console.ControlHandlers import BlockchainStatusFactory -from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet +from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet log = logging.getLogger(__name__) @@ -253,8 +254,12 @@ class LBRYConsole(): d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.lbrycrd_dir, wallet_conf=self.lbrycrd_conf, lbrycrdd_path=lbrycrdd_path)) - else: + elif self.wallet_type == 'ptc': d = defer.succeed(PTCWallet(self.db_dir)) + elif self.wallet_type == 'lbryum': + d = defer.succeed(LBRYumWallet(self.db_dir)) + else: + d = defer.fail(Failure(ValueError("Invalid wallet type"))) d.addCallback(lambda wallet: {"wallet": wallet}) return d @@ -372,14 +377,14 @@ class LBRYConsole(): ModifyLBRYFileOptionsChooserFactory(self.lbry_file_manager), AddStreamFromHashFactory(self.sd_identifier, self.session, self.session.wallet), StatusFactory(self, self.session.rate_limiter, self.lbry_file_manager, - self.session.blob_manager, self.session.wallet if self.wallet_type == 'lbrycrd' else None), + self.session.blob_manager, self.session.wallet if self.wallet_type in ['lbrycrd', 'lbryum'] else None), # AutoFetcherStartFactory(self.autofetcher), # AutoFetcherStopFactory(self.autofetcher), # AutoFetcherStatusFactory(self.autofetcher), ImmediateAnnounceAllBlobsFactory(self.session.blob_manager) ] self.add_control_handlers(handlers) - if self.wallet_type == 'lbrycrd': + if self.wallet_type in ['lbrycrd', 'lbryum']: lbrycrd_handlers = [ AddStreamFromLBRYcrdNameFactory(self.sd_identifier, self.session, self.session.wallet), @@ -520,7 +525,7 @@ def launch_lbry_console(): help="The port on which the console will listen for DHT connections.", type=int, default=4444) parser.add_argument("--wallet_type", - help="Either 'lbrycrd' or 'ptc'.", + help="Either 'lbrycrd' or 'ptc' or 'lbryum'.", type=str, default="lbrycrd") parser.add_argument("--lbrycrd_wallet_dir", help="The directory in which lbrycrd data will stored. Used if lbrycrdd is " diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 43b66cbbf..4ea161c81 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -129,33 +129,33 @@ class FetcherDaemon(object): def check_if_running(self): if self.is_running: msg = "Autofetcher is running\n" - msg += "Last block hash: " + str(self.lastbestblock['bestblockhash']) + msg += "Last block hash: " + str(self.lastbestblock) else: msg = "Autofetcher is not running" return msg def _get_names(self): - d = self.wallet.get_blockchain_info() - d.addCallback(lambda c: get_new_streams(c) if c != self.lastbestblock else []) + d = self.wallet.get_best_blockhash() + d.addCallback(lambda blockhash: get_new_streams(blockhash) if blockhash != self.lastbestblock else []) - def get_new_streams(c): - self.lastbestblock = c - d = self.wallet.get_block(c['bestblockhash']) - d.addCallback(lambda block: get_new_streams_in_txes(block['tx'], c)) + def get_new_streams(blockhash): + self.lastbestblock = blockhash + d = self.wallet.get_block(blockhash) + d.addCallback(lambda block: get_new_streams_in_txes(block['tx'], blockhash)) return d - def get_new_streams_in_txes(txids, c): + def get_new_streams_in_txes(txids, blockhash): ds = [] for t in txids: d = self.wallet.get_claims_from_tx(t) - d.addCallback(get_new_streams_in_tx, t, c) + d.addCallback(get_new_streams_in_tx, t, blockhash) ds.append(d) d = defer.DeferredList(ds, consumeErrors=True) d.addCallback(lambda result: [r[1] for r in result if r[0]]) d.addCallback(lambda stream_lists: [stream for streams in stream_lists for stream in streams]) return d - def get_new_streams_in_tx(claims, t, c): + def get_new_streams_in_tx(claims, t, blockhash): #claims = self.wallet.get_claims_for_tx(t['txid']) # if self.first_run: # # claims = self.rpc_conn.getclaimsfortx("96aca2c60efded5806b7336430c5987b9092ffbea9c6ed444e3bf8e008993e11") @@ -173,7 +173,7 @@ class FetcherDaemon(object): self.seen.append(claim) else: if self.verbose: - print "[" + str(datetime.now()) + "] No claims in block", c['bestblockhash'] + print "[" + str(datetime.now()) + "] No claims in block", blockhash return rtn d.addCallback(lambda streams: defer.DeferredList( diff --git a/setup.py b/setup.py index 948dff97d..9b8319da9 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ from setuptools import setup, find_packages setup(name='lbrynet', version='0.0.4', packages=find_packages(), - install_requires=['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb'], + install_requires=['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum'], entry_points={ 'console_scripts': [ 'lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', @@ -43,5 +43,6 @@ setup(name='lbrynet', 'lbrynet/lbrynet_gui/lbry.conf', ] ) - ] + ], + dependency_links=['https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum'], ) \ No newline at end of file From c63aa3033a43d0dcade54cf1b4a3b0df03fa03c3 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 19 Feb 2016 23:26:24 -0500 Subject: [PATCH 023/462] install lbryum --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 20 +++ lbrynet/lbrynet_daemon/scripts/update_lbry.sh | 58 ------- .../lbrynet_daemon/scripts/update_lbrynet.sh | 153 ++++++++++++++++-- 3 files changed, 157 insertions(+), 74 deletions(-) mode change 100644 => 100755 lbrynet/lbrynet_daemon/scripts/update_lbry.sh mode change 100644 => 100755 lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 22e55acca..678282da7 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -192,9 +192,29 @@ class LBRYDaemon(xmlrpc.XMLRPC): return defer.succeed(None) + def _update_lbryum(): + git_version = subprocess.check_output( + "git ls-remote https://github.com/lbryio/lbryum.git | grep HEAD | cut -f 1", + shell=True) + if os.path.isfile(os.path.join(self.db_dir, "lbryum_version.txt")): + f = open(os.path.join(self.db_dir, "lbryum_version.txt"), 'r') + current_version = f.read() + f.close() + if git_version == current_version: + print "LBRYum installation version " + current_version[:-1] + " is up to date" + return defer.succeed(None) + print "Update LBRYum version " + current_version[:-1] + " --> " + git_version[:-1] + self.restart_message = "Updates available" + else: + print "Update LBRYum to version " + git_version[:-1] + self.restart_message = "Updates available" + + return defer.succeed(None) + d = _check_for_updater() d.addCallback(lambda _: _update_lbrynet()) d.addCallback(lambda _: _update_lbrycrdd()) + d.addCallback(lambda _: _update_lbryum()) d.addCallback(lambda _: os.system("open /Applications/LBRY\ Updater.app &>/dev/null") if self.restart_message else defer.succeed(None)) d.addCallbacks(lambda _: self._restart() if self.restart_message else defer.succeed(None)) diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh old mode 100644 new mode 100755 index b7a0b2a14..291dfb3ba --- a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh @@ -1,63 +1,5 @@ #!/bin/sh -if ! which brew &>/dev/null; then - echo "Installing brew..." - sudo -u ${SUDO_USER} ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null &>/dev/null -else - echo "Updating brew..." - sudo -u ${SUDO_USER} brew update &>/dev/null -fi - -if ! brew list mpfr &>/dev/null; then - echo "Installing mpfr..." - sudo -u ${SUDO_USER} brew install mpfr &>/dev/null -else - echo "mpfr already installed..." -fi - -if ! brew list libmpc &>/dev/null; then - echo "Installing libmpc..." - sudo -u ${SUDO_USER} brew install libmpc &>/dev/null -else - echo "libmpc already installed..." -fi - -if ! brew list openssl &>/dev/null; then - echo "Installing openssl..." - sudo -u ${SUDO_USER} brew install openssl &>/dev/null - sudo -u ${SUDO_USER} brew link --force openssl &>/dev/null -else - echo "openssl already installed..." -fi - -if ! which pip &>/dev/null; then - echo "Installing pip..." - sudo easy_install pip &>/dev/null -else - echo "pip already installed" -fi - -if ! python -c 'import gmpy' &>/dev/null; then - echo "Installing gmpy..." - sudo pip install gmpy &>/dev/null -else - echo "gmpy already installed..." -fi - -if ! python -c 'import service_identity' &>/dev/null; then - echo "Installing service_identity..." - sudo pip install service_identity &>/dev/null -else - echo "gmpy already installed..." -fi - -if ! python -c 'import rumps' &>/dev/null; then - echo "Installing rumps..." - sudo pip install rumps &>/dev/null -else - echo "rumps already installed..." -fi - lbrycrd_directory="/Users/${SUDO_USER}/Library/Application Support/lbrycrd" current_version=$(git ls-remote https://github.com/jackrobison/lbrynet-app.git | grep HEAD | cut -f 1) diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh old mode 100644 new mode 100755 index 90e87f079..f1672b093 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -1,32 +1,153 @@ #!/bin/sh +if ! which brew &>/dev/null; then + echo "Installing brew..." + sudo -u ${SUDO_USER} ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null &>/dev/null +else + echo "Updating brew..." + sudo -u ${SUDO_USER} brew update &>/dev/null +fi + +if ! brew list mpfr &>/dev/null; then + echo "Installing mpfr..." + sudo -u ${SUDO_USER} brew install mpfr &>/dev/null +else + echo "mpfr already installed..." +fi + +if ! brew list libmpc &>/dev/null; then + echo "Installing libmpc..." + sudo -u ${SUDO_USER} brew install libmpc &>/dev/null +else + echo "libmpc already installed..." +fi + +if ! brew list openssl &>/dev/null; then + echo "Installing openssl..." + sudo -u ${SUDO_USER} brew install openssl &>/dev/null + sudo -u ${SUDO_USER} brew link --force openssl &>/dev/null +else + echo "openssl already installed..." +fi + +if ! which pip &>/dev/null; then + echo "Installing pip..." + sudo easy_install pip &>/dev/null +else + echo "pip already installed" +fi + +if ! python -c 'import gmpy' &>/dev/null; then + echo "Installing gmpy..." + sudo pip install gmpy &>/dev/null +else + echo "gmpy already installed..." +fi + +if ! python -c 'import service_identity' &>/dev/null; then + echo "Installing service_identity..." + sudo pip install service_identity &>/dev/null +else + echo "gmpy already installed..." +fi + +if ! python -c 'import rumps' &>/dev/null; then + echo "Installing rumps..." + sudo pip install rumps &>/dev/null +else + echo "rumps already installed..." +fi + lbrynet_directory="/Users/${SUDO_USER}/Library/Application Support/lbrynet" -current_version=$(git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1) +lbrynet_current_version=$(git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1) if [ -d "$lbrynet_directory" ]; then if [ -f "${lbrynet_directory}/lbrynet_version.txt" ]; then - if grep -Fxq "$current_version" "${lbrynet_directory}/lbrynet_version.txt"; then - echo "LBRYnet version $current_version is up to date" - exit + if grep -Fxq "$lbrynet_current_version" "${lbrynet_directory}/lbrynet_version.txt"; then + echo "LBRYnet version $lbrynet_current_version is up to date" + else + tmp=$(mktemp -d) + cd $tmp + + echo "Downloading LBRYnet update" + + git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null + cd lbry + + echo "Installing update" + sudo python setup.py install &>/dev/null + mkdir -p "$lbrynet_directory" + echo $lbrynet_current_version > "${lbrynet_directory}/lbrynet_version.txt" + + echo "Cleaning up" + + cd ../../ + rm -rf $tmp fi + else + tmp=$(mktemp -d) + cd $tmp + + echo "Downloading LBRYnet update" + + git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null + cd lbry + + echo "Installing update" + sudo python setup.py install &>/dev/null + mkdir -p "$lbrynet_directory" + echo $lbrynet_current_version > "${lbrynet_directory}/lbrynet_version.txt" + + echo "Cleaning up" + + cd ../../ + rm -rf $tmp fi fi -tmp=$(mktemp -d) -cd $tmp +lbryum_current_version=$(git ls-remote https://github.com/lbryio/lbryum.git | grep HEAD | cut -f 1) -echo "Downloading LBRYnet update" +if [ -d "$lbrynet_directory" ]; then + if [ -f "${lbrynet_directory}/lbryum_version.txt" ]; then + if grep -Fxq "$lbryum_current_version" "${lbrynet_directory}/lbryum_version.txt"; then + echo "LBRYum version $lbryum_current_version is up to date" + else + tmp=$(mktemp -d) + cd $tmp -git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null -cd lbry + echo "Downloading LBRYum update" -echo "Installing update" -sudo python setup.py install &>/dev/null -mkdir -p "$lbrynet_directory" -echo $current_version > "${lbrynet_directory}/lbrynet_version.txt" + git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + cd lbryum -echo "Cleaning up" + echo "Installing update" + sudo python setup.py install &>/dev/null + mkdir -p "$lbrynet_directory" + echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" -cd ../../ -rm -rf $tmp \ No newline at end of file + echo "Cleaning up" + + cd ../../ + rm -rf $tmp + fi + else + tmp=$(mktemp -d) + cd $tmp + + echo "Downloading LBRYum update" + + git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + cd lbryum + + echo "Installing update" + sudo python setup.py install &>/dev/null + mkdir -p "$lbrynet_directory" + echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" + + echo "Cleaning up" + + cd ../../ + rm -rf $tmp + fi +fi \ No newline at end of file From 8fc1cbfc549dc4382eeaed3a36ee1cfb3d374f72 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 20 Feb 2016 15:30:30 -0500 Subject: [PATCH 024/462] check for six 1.9.0 --- lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh index f1672b093..c35723a75 100755 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -58,6 +58,17 @@ else echo "rumps already installed..." fi +if ! python -c "import six; exit(0) if six.__version__ == '1.9.0' else exit(1)" &>/dev/null; then + echo "Installing six 1.9.0 for python" + curl -O https://pypi.python.org/packages/source/s/six/six-1.9.0.tar.gz &>/dev/null + tar xf six-1.9.0.tar.gz &>/dev/null + cd six-1.9.0 + sudo python setup.py install &>/dev/null + cd .. + rm -rf six-1.9.0 + rm six-1.9.0.tar.gz +fi + lbrynet_directory="/Users/${SUDO_USER}/Library/Application Support/lbrynet" lbrynet_current_version=$(git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1) From 8aab844a4825b2392c9d7c17649721a862fb2c70 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 22 Feb 2016 10:05:55 -0500 Subject: [PATCH 025/462] add bdb --- lbrynet/lbrynet_daemon/scripts/update_lbry.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh index 291dfb3ba..09177e79c 100755 --- a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh @@ -13,6 +13,14 @@ if [ -d "$lbrycrd_directory" ]; then fi fi +if ! brew list berkeley-db4 &>/dev/null; then + echo "Installing berkeley-db4" + sudo -u ${SUDO_USER} brew install https://rawgit.com/jackrobison/homebrew/master/Library/Formula/berkeley-db4.rb &>/dev/null + sudo -u ${SUDO_USER} brew link --force berkeley-db4 &>/dev/null +else + echo "berkeley-db4 already installed" +fi + tmp=$(mktemp -d) cd $tmp From ddd213dda3ab7c47910e4c62245934acdbeb1098 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Mon, 22 Feb 2016 10:28:33 -0500 Subject: [PATCH 026/462] update manual build instructions to check out lbrycrd alpha --- RUNNING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RUNNING.md b/RUNNING.md index 1d839bc9e..137487360 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -25,7 +25,7 @@ To stop lbrycrdd: `./lbrycrd/src/lbrycrd-cli stop` ### Installing lbrycrd from source ``` -git clone --depth=1 https://github.com/lbryio/lbrycrd.git +git clone --depth=1 -b alpha https://github.com/lbryio/lbrycrd.git cd lbrycrd sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev libboost-all-dev libdb-dev libdb++-dev libqt4-dev libprotobuf-dev protobuf-compiler ./autogen.sh From c32f8d099815ac4b43aadc36c27ca198bd8c2ce6 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Mon, 22 Feb 2016 12:18:28 -0500 Subject: [PATCH 027/462] implement getblock --- lbrynet/core/LBRYcrdWallet.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index be0a5a694..74bbb4923 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -917,7 +917,9 @@ class LBRYumWallet(LBRYWallet): return d def get_block(self, blockhash): - return defer.fail(NotImplementedError()) + cmd = known_commands['getblock'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, blockhash) def get_most_recent_blocktime(self): header = self.network.get_header(self.network.get_local_height()) From b419b8f642f75042280beabb2339304b2f8878cc Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Mon, 22 Feb 2016 14:24:49 -0500 Subject: [PATCH 028/462] enable getting the whole claim trie --- lbrynet/core/LBRYcrdWallet.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 74bbb4923..9123c886e 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -972,6 +972,11 @@ class LBRYumWallet(LBRYWallet): def _get_balance_for_address(self, address): return defer.succeed(Decimal(self.wallet.get_addr_received(address))/COIN) + def get_nametrie(self): + cmd = known_commands['getnametrie'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func) + def _save_wallet(self, val): d = threads.deferToThread(self.wallet.storage.write) d.addCallback(lambda _: val) From 9b0c95e37d91f449dff782de61fa5fec9a9e6428 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Mon, 22 Feb 2016 18:21:39 -0500 Subject: [PATCH 029/462] make sure the correct version of six gets installed --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9b8319da9..7c3cfa358 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ from setuptools import setup, find_packages setup(name='lbrynet', version='0.0.4', packages=find_packages(), - install_requires=['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum'], + install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum'], entry_points={ 'console_scripts': [ 'lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', From 485be533c9c99f5b27d5e75aefa203277a6d859c Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 22 Feb 2016 22:32:07 -0500 Subject: [PATCH 030/462] make lbryum default wallet for daemon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit with optional command line argument —wallet= --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 39 +++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 678282da7..632c14b83 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -16,7 +16,7 @@ from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet -from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet +from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager from twisted.web import xmlrpc, server @@ -26,7 +26,8 @@ from decimal import Decimal from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen -import os, sys, json, binascii, webbrowser, xmlrpclib, subprocess, logging + +import os, sys, json, binascii, webbrowser, xmlrpclib, subprocess, logging, argparse, getopt log = logging.getLogger(__name__) @@ -46,8 +47,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): LBRYnet daemon """ - def setup(self): - def _set_vars(): + def setup(self, wallet_type): + def _set_vars(wallet_type): self.fetcher = None self.current_db_revision = 1 self.run_server = True @@ -93,7 +94,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.lbry_file_metadata_manager = None self.lbry_file_manager = None self.settings = LBRYSettings(self.db_dir) - self.wallet_type = "lbrycrd" + self.wallet_type = wallet_type self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") self.files = [] @@ -121,7 +122,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): return defer.succeed(None) d = defer.Deferred() - d.addCallback(lambda _: _set_vars()) + d.addCallback(lambda _: _set_vars(wallet_type)) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) d.addCallback(lambda _: self._get_settings()) @@ -397,6 +398,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): def get_wallet(): if self.wallet_type == "lbrycrd": + print "Using lbrycrd wallet" lbrycrdd_path = None if self.start_lbrycrdd is True: lbrycrdd_path = self.lbrycrdd_path @@ -404,8 +406,15 @@ class LBRYDaemon(xmlrpc.XMLRPC): lbrycrdd_path = self.default_lbrycrdd_path d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.lbrycrd_conf, lbrycrdd_path=lbrycrdd_path)) - else: + elif self.wallet_type == "lbryum": + print "Using lbryum wallet" + d = defer.succeed(LBRYumWallet(self.db_dir)) + elif self.wallet_type == "ptc": + print "Using PTC wallet" d = defer.succeed(PTCWallet(self.db_dir)) + else: + d = defer.fail() + d.addCallback(lambda wallet: {"wallet": wallet}) return d @@ -1042,17 +1051,23 @@ class LBRYDaemon(xmlrpc.XMLRPC): def main(): + parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") + parser.add_argument("--wallet", + help="Either lbrycrd or lbryum", + type=str, + default="lbryum") + args = parser.parse_args() + try: - d = xmlrpclib.ServerProxy('http://localhost:7080') - d.stop() + daemon = xmlrpclib.ServerProxy("http://localhost:7080") + daemon.stop() except: pass daemon = LBRYDaemon() - daemon.setup() + daemon.setup(args.wallet) reactor.listenTCP(7080, server.Site(daemon)) reactor.run() if __name__ == '__main__': - main() - + main() \ No newline at end of file From 671e6da104874b1fc7241aa03f16d7a37d7a51eb Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Mon, 22 Feb 2016 23:31:07 -0500 Subject: [PATCH 031/462] show when the wallet is starting/started --- lbrynet/core/LBRYcrdWallet.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 9123c886e..922246714 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -835,6 +835,7 @@ class LBRYumWallet(LBRYWallet): self.wallet = None self.cmd_runner = None self.first_run = False + self.printed_retrieving_headers = False def _start(self): @@ -843,15 +844,20 @@ class LBRYumWallet(LBRYWallet): def setup_network(): self.config = SimpleConfig() self.network = Network(self.config) + alert.info("Starting the wallet...") return defer.succeed(self.network.start()) d = setup_network() def check_started(): if self.network.is_connecting(): + if not self.printed_retrieving_headers and self.network.blockchain.retrieving_headers: + alert.info("Running the wallet for the first time...this may take a moment.") + self.printed_retrieving_headers = True return False start_check.stop() if self.network.is_connected(): + alert.info("Wallet started.") network_start_d.callback(True) else: network_start_d.errback(ValueError("Failed to connect to network.")) From b0bfb5e4439b19ffe50446784b1bd12d07db8734 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Tue, 23 Feb 2016 15:13:34 -0500 Subject: [PATCH 032/462] use electrum as default --- .../client/CryptStreamDownloader.py | 3 +- lbrynet/lbrynet_console/LBRYConsole.py | 101 +++++------------- 2 files changed, 27 insertions(+), 77 deletions(-) diff --git a/lbrynet/cryptstream/client/CryptStreamDownloader.py b/lbrynet/cryptstream/client/CryptStreamDownloader.py index 19a565f1f..e0091598f 100644 --- a/lbrynet/cryptstream/client/CryptStreamDownloader.py +++ b/lbrynet/cryptstream/client/CryptStreamDownloader.py @@ -96,8 +96,9 @@ class CryptStreamDownloader(object): self.starting = True self.completed = False self.finished_deferred = defer.Deferred() + fd = self.finished_deferred d = self._start() - d.addCallback(lambda _: self.finished_deferred) + d.addCallback(lambda _: fd) return d def stop(self, err=None): diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index 7eeda8e7b..d3fa39b2b 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -7,8 +7,6 @@ import locale import sys from yapsy.PluginManager import PluginManager from twisted.internet import defer, threads, stdio, task, error -from twisted.python.failure import Failure -# from lbrynet.core.client.AutoDownloader import AutoFetcher from lbrynet.lbrynet_console.ConsoleControl import ConsoleControl from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager @@ -24,10 +22,8 @@ from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifi from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileOpenerFactory from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager -#from lbrynet.lbrylive.PaymentRateManager import LiveStreamPaymentRateManager from lbrynet.lbrynet_console.ControlHandlers import ApplicationStatusFactory, GetWalletBalancesFactory, ShutDownFactory -#from lbrynet.lbrynet_console.ControlHandlers import AutoFetcherStartFactory, AutoFetcherStopFactory -from lbrynet.lbrynet_console.ControlHandlers import ImmediateAnnounceAllBlobsFactory #, AutoFetcherStatusFactory +from lbrynet.lbrynet_console.ControlHandlers import ImmediateAnnounceAllBlobsFactory from lbrynet.lbrynet_console.ControlHandlers import LBRYFileStatusFactory, DeleteLBRYFileChooserFactory from lbrynet.lbrynet_console.ControlHandlers import ToggleLBRYFileRunningChooserFactory from lbrynet.lbrynet_console.ControlHandlers import ModifyApplicationDefaultsFactory @@ -50,9 +46,9 @@ alert = logging.getLogger("lbryalert." + __name__) class LBRYConsole(): """A class which can upload and download file streams to and from the network""" - def __init__(self, peer_port, dht_node_port, known_dht_nodes, wallet_type, + def __init__(self, peer_port, dht_node_port, known_dht_nodes, fake_wallet, lbrycrd_conf, lbrycrd_dir, use_upnp, data_dir, created_data_dir, - lbrycrdd_path, start_lbrycrdd): + lbrycrdd_path): """ @param peer_port: the network port on which to listen for peers @@ -63,7 +59,7 @@ class LBRYConsole(): self.peer_port = peer_port self.dht_node_port = dht_node_port self.known_dht_nodes = known_dht_nodes - self.wallet_type = wallet_type + self.fake_wallet = fake_wallet self.lbrycrd_conf = lbrycrd_conf self.lbrycrd_dir = lbrycrd_dir if not self.lbrycrd_dir: @@ -73,10 +69,7 @@ class LBRYConsole(): self.lbrycrd_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") if not self.lbrycrd_conf: self.lbrycrd_conf = os.path.join(self.lbrycrd_dir, "lbrycrd.conf") - # self.autofetcher_conf = os.path.join(self.lbrycrd_dir, "autofetcher.conf") self.lbrycrdd_path = lbrycrdd_path - self.default_lbrycrdd_path = "./lbrycrdd" - self.start_lbrycrdd = start_lbrycrdd self.use_upnp = use_upnp self.lbry_server_port = None self.session = None @@ -100,7 +93,6 @@ class LBRYConsole(): self.sd_identifier = StreamDescriptorIdentifier() self.plugin_objects = [] self.db_migration_revisions = None - # self.autofetcher = None def start(self): """Initialize the session and restore everything to its saved state""" @@ -112,7 +104,6 @@ class LBRYConsole(): d.addCallback(lambda _: add_lbry_file_to_sd_identifier(self.sd_identifier)) d.addCallback(lambda _: self._setup_lbry_file_manager()) d.addCallback(lambda _: self._setup_lbry_file_opener()) - #d.addCallback(lambda _: self._get_autofetcher()) d.addCallback(lambda _: self._setup_control_handlers()) d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._load_plugins()) @@ -121,10 +112,6 @@ class LBRYConsole(): d.addErrback(self._show_start_error) return d - # def _get_autofetcher(self): - # self.autofetcher = AutoFetcher(self.session, self.lbry_file_manager, self.lbry_file_metadata_manager, - # self.session.wallet, self.sd_identifier, self.autofetcher_conf) - def _show_start_error(self, error): print error.getTraceback() log.error("An error occurred during start up: %s", error.getTraceback()) @@ -201,30 +188,6 @@ class LBRYConsole(): d = self.settings.start() d.addCallback(lambda _: self.settings.get_lbryid()) d.addCallback(self.set_lbryid) - d.addCallback(lambda _: self.get_lbrycrdd_path()) - return d - - def get_lbrycrdd_path(self): - - if not self.start_lbrycrdd: - return defer.succeed(None) - - def get_lbrycrdd_path_conf_file(): - lbrycrdd_path_conf_path = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf") - if not os.path.exists(lbrycrdd_path_conf_path): - return "" - lbrycrdd_path_conf = open(lbrycrdd_path_conf_path) - lines = lbrycrdd_path_conf.readlines() - return lines - - d = threads.deferToThread(get_lbrycrdd_path_conf_file) - - def load_lbrycrdd_path(conf): - for line in conf: - if len(line.strip()) and line.strip()[0] != "#": - self.lbrycrdd_path = line.strip() - - d.addCallback(load_lbrycrdd_path) return d def set_lbryid(self, lbryid): @@ -245,21 +208,14 @@ class LBRYConsole(): return d def get_wallet(): - if self.wallet_type == "lbrycrd": - lbrycrdd_path = None - if self.start_lbrycrdd is True: - lbrycrdd_path = self.lbrycrdd_path - if not lbrycrdd_path: - lbrycrdd_path = self.default_lbrycrdd_path + if self.fake_wallet: + d = defer.succeed(PTCWallet(self.db_dir)) + elif self.lbrycrdd_path is not None: d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.lbrycrd_dir, wallet_conf=self.lbrycrd_conf, - lbrycrdd_path=lbrycrdd_path)) - elif self.wallet_type == 'ptc': - d = defer.succeed(PTCWallet(self.db_dir)) - elif self.wallet_type == 'lbryum': - d = defer.succeed(LBRYumWallet(self.db_dir)) + lbrycrdd_path=self.lbrycrdd_path)) else: - d = defer.fail(Failure(ValueError("Invalid wallet type"))) + d = defer.succeed(LBRYumWallet(self.db_dir)) d.addCallback(lambda wallet: {"wallet": wallet}) return d @@ -377,14 +333,11 @@ class LBRYConsole(): ModifyLBRYFileOptionsChooserFactory(self.lbry_file_manager), AddStreamFromHashFactory(self.sd_identifier, self.session, self.session.wallet), StatusFactory(self, self.session.rate_limiter, self.lbry_file_manager, - self.session.blob_manager, self.session.wallet if self.wallet_type in ['lbrycrd', 'lbryum'] else None), - # AutoFetcherStartFactory(self.autofetcher), - # AutoFetcherStopFactory(self.autofetcher), - # AutoFetcherStatusFactory(self.autofetcher), + self.session.blob_manager, self.session.wallet if not self.fake_wallet else None), ImmediateAnnounceAllBlobsFactory(self.session.blob_manager) ] self.add_control_handlers(handlers) - if self.wallet_type in ['lbrycrd', 'lbryum']: + if not self.fake_wallet: lbrycrd_handlers = [ AddStreamFromLBRYcrdNameFactory(self.sd_identifier, self.session, self.session.wallet), @@ -524,15 +477,9 @@ def launch_lbry_console(): parser.add_argument("--dht_node_port", help="The port on which the console will listen for DHT connections.", type=int, default=4444) - parser.add_argument("--wallet_type", - help="Either 'lbrycrd' or 'ptc' or 'lbryum'.", - type=str, default="lbrycrd") - parser.add_argument("--lbrycrd_wallet_dir", - help="The directory in which lbrycrd data will stored. Used if lbrycrdd is " - "launched by this application.") - parser.add_argument("--lbrycrd_wallet_conf", - help="The configuration file for the LBRYcrd wallet. Default: ~/.lbrycrd/lbrycrd.conf", - type=str) + parser.add_argument("--fake_wallet", + help="Testing purposes only. Use a non-blockchain wallet.", + action="store_true") parser.add_argument("--no_dht_bootstrap", help="Don't try to connect to the DHT", action="store_true") @@ -552,12 +499,15 @@ def launch_lbry_console(): "Default: ~/.lbrynet on linux, ~/Library/Application Support/lbrynet on OS X"), type=str) parser.add_argument("--lbrycrdd_path", - help="The path to lbrycrdd, which will be launched if it isn't running, unless " - "launching lbrycrdd is disabled by --disable_launch_lbrycrdd. By default, " - "the file ~/.lbrycrddpath.conf will be checked, and if no path is found " - "there, it will be ./lbrycrdd") - parser.add_argument("--disable_launch_lbrycrdd", - help="Don't launch lbrycrdd even if it's not running.") + help="The path to lbrycrdd, which will be launched if it isn't running. If" + "this option is chosen, lbrycrdd will be used as the interface to the" + "blockchain. By default, a lightweight interface is used.") + parser.add_argument("--lbrycrd_wallet_dir", + help="The directory in which lbrycrd data will stored. Used if lbrycrdd is " + "launched by this application.") + parser.add_argument("--lbrycrd_wallet_conf", + help="The configuration file for the LBRYcrd wallet. Default: ~/.lbrycrd/lbrycrd.conf", + type=str) args = parser.parse_args() @@ -599,11 +549,10 @@ def launch_lbry_console(): file_handler.addFilter(logging.Filter("lbrynet")) logger.addHandler(file_handler) - console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, wallet_type=args.wallet_type, + console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, fake_wallet=args.fake_wallet, lbrycrd_conf=args.lbrycrd_wallet_conf, lbrycrd_dir=args.lbrycrd_wallet_dir, use_upnp=not args.disable_upnp, data_dir=data_dir, - created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path, - start_lbrycrdd=not args.disable_launch_lbrycrdd) + created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path) d = task.deferLater(reactor, 0, console.start) From eeef183cc6b2a8b63a1e538492b064748dc4380e Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 25 Feb 2016 17:17:07 -0500 Subject: [PATCH 033/462] update lbrynet-daemon --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 65 +++++++---- lbrynet/lbrynet_daemon/LBRYDaemonStopper.py | 7 +- lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py | 16 ++- .../lbrynet_daemon/scripts/update_lbrynet.sh | 103 +++++++++--------- 4 files changed, 113 insertions(+), 78 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 632c14b83..6ae30a0f1 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -8,7 +8,6 @@ from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream, FetcherDaemon -# from lbrynet.lbrynet_daemon.LBRYOSXStatusBar import DaemonStatusBarApp from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher from lbrynet.core.utils import generate_id from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings @@ -27,9 +26,10 @@ from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen -import os, sys, json, binascii, webbrowser, xmlrpclib, subprocess, logging, argparse, getopt +import os, sys, json, binascii, webbrowser, xmlrpclib, subprocess, logging, argparse log = logging.getLogger(__name__) +# logging.basicConfig(level=logging.DEBUG) # TODO add login credentials in a conf file @@ -47,8 +47,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): LBRYnet daemon """ - def setup(self, wallet_type): - def _set_vars(wallet_type): + def setup(self, wallet_type, check_for_updates): + def _set_vars(wallet_type, check_for_updates): self.fetcher = None self.current_db_revision = 1 self.run_server = True @@ -58,7 +58,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") - # self.status_app = DaemonStatusBarApp() + from lbrynet.lbrynet_daemon.LBRYOSXStatusBar import DaemonStatusBarApp + self.status_app = DaemonStatusBarApp() self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 self.dht_node_port = 4444 @@ -95,9 +96,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.lbry_file_manager = None self.settings = LBRYSettings(self.db_dir) self.wallet_type = wallet_type + self.check_for_updates = check_for_updates self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") - self.files = [] self.created_data_dir = False if not os.path.exists(self.db_dir): os.mkdir(self.db_dir) @@ -118,11 +119,14 @@ class LBRYDaemon(xmlrpc.XMLRPC): else: print "Started LBRYnet daemon" print "The daemon can be shut down by running 'stop-lbrynet-daemon' in a terminal" + log.info('[' + str(datetime.now()) + '] Started lbrynet-daemon') return defer.succeed(None) + log.info('[' + str(datetime.now()) + '] Starting lbrynet-daemon') + d = defer.Deferred() - d.addCallback(lambda _: _set_vars(wallet_type)) + d.addCallback(lambda _: _set_vars(wallet_type, check_for_updates)) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) d.addCallback(lambda _: self._get_settings()) @@ -135,8 +139,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) if sys.platform == "darwin": - d.addCallback(lambda _: self._update()) - # d.addCallback(lambda _: self.status_app.run()) + d.addCallback(lambda _: self._update() if self.check_for_updates == "True" else defer.succeed(None)) + # d.addCallback(lambda _: defer.succeed(self.status_app.run())) d.addCallback(lambda _: self._setup_fetcher()) d.addCallback(lambda _: _disp_startup()) d.callback(None) @@ -399,6 +403,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): def get_wallet(): if self.wallet_type == "lbrycrd": print "Using lbrycrd wallet" + log.info("Using lbrycrd wallet") lbrycrdd_path = None if self.start_lbrycrdd is True: lbrycrdd_path = self.lbrycrdd_path @@ -408,9 +413,11 @@ class LBRYDaemon(xmlrpc.XMLRPC): lbrycrdd_path=lbrycrdd_path)) elif self.wallet_type == "lbryum": print "Using lbryum wallet" + log.info("Using lbryum wallet") d = defer.succeed(LBRYumWallet(self.db_dir)) elif self.wallet_type == "ptc": print "Using PTC wallet" + log.info("Using PTC wallet") d = defer.succeed(PTCWallet(self.db_dir)) else: d = defer.fail() @@ -539,9 +546,11 @@ class LBRYDaemon(xmlrpc.XMLRPC): path = os.path.join(self.blobfile_dir, stream_hash) if os.path.isfile(path): print "[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash + log.info("[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash) return defer.succeed(_get_lbry_file(path)) else: print "[" + str(datetime.now()) + "] Search for lbry_file didn't return anything" + log.info("[" + str(datetime.now()) + "] Search for lbry_file didn't return anything") return defer.succeed(False) d = self._resolve_name(name) @@ -587,8 +596,10 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _check_est(d, name): if type(d.result) is float: print '[' + str(datetime.now()) + '] Cost est for lbry://' + name + ': ' + str(d.result) + 'LBC' + log.info('[' + str(datetime.now()) + '] Cost est for lbry://' + name + ': ' + str(d.result) + 'LBC') else: print '[' + str(datetime.now()) + '] Timeout estimating cost for lbry://' + name + ', using key fee' + log.info('[' + str(datetime.now()) + '] Timeout estimating cost for lbry://' + name + ', using key fee') d.cancel() return defer.succeed(None) @@ -645,6 +656,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.fetcher.start() print '[' + str(datetime.now()) + '] Start autofetcher' + log.info('[' + str(datetime.now()) + '] Start autofetcher') return 'Started autofetching' def xmlrpc_stop_fetcher(self): @@ -654,6 +666,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.fetcher.stop() print '[' + str(datetime.now()) + '] Stop autofetcher' + log.info('[' + str(datetime.now()) + '] Stop autofetcher') return 'Stopped autofetching' def xmlrpc_fetcher_status(self): @@ -678,6 +691,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): """ def _disp_shutdown(): + log.info('Shutting down lbrynet daemon') print 'Shutting down lbrynet daemon' d = self._shutdown() @@ -720,7 +734,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): """ def _disp(info): - log.debug('[' + str(datetime.now()) + ']' + ' Resolved info: ' + str(info['stream_hash'])) print '[' + str(datetime.now()) + ']' + ' Resolved info: ' + str(info['stream_hash']) return info @@ -822,13 +835,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): @return: """ - #def _return_d(x): - # d = defer.Deferred() - # d.addCallback(lambda _: x) - # d.callback(None) - - # return d - def _clean(n): t = [] for i in n: @@ -870,9 +876,13 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _disp(results): print '[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' results' + log.info('[' + str(datetime.now()) + '] Search results: ') + for r in results: + log.info(str(r)) return results print '[' + str(datetime.now()) + '] Search nametrie: ' + search + log.info('[' + str(datetime.now()) + '] Search nametrie: ' + search) d = self.session.wallet.get_nametrie() d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) @@ -953,6 +963,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): else: content_license = None + log.info('[' + str(datetime.now()) + '] Publish: ', name, file_path, bid, title, description, thumbnail, + key_fee, key_fee_address, content_license) + p = Publisher(self.session, self.lbry_file_manager, self.session.wallet) d = p.start(name, file_path, bid, title, description, thumbnail, key_fee, key_fee_address, content_license) @@ -990,6 +1003,15 @@ class LBRYDaemon(xmlrpc.XMLRPC): return d + def xmlrpc_get_new_address(self): + def _disp(address): + print "[" + str(datetime.now()) + "] Got new wallet address: " + address + return address + + d = self.session.wallet.get_new_address() + d.addCallback(_disp) + return d + # def xmlrpc_update_name(self, metadata): # def _disp(x): # print x @@ -1053,9 +1075,14 @@ class LBRYDaemon(xmlrpc.XMLRPC): def main(): parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") parser.add_argument("--wallet", - help="Either lbrycrd or lbryum", + help="lbrycrd or lbryum, default lbryum", type=str, default="lbryum") + parser.add_argument("--update", + help="True or false, default true", + type=str, + default="True") + args = parser.parse_args() try: @@ -1065,7 +1092,7 @@ def main(): pass daemon = LBRYDaemon() - daemon.setup(args.wallet) + daemon.setup(args.wallet, args.update) reactor.listenTCP(7080, server.Site(daemon)) reactor.run() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py b/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py index 78d8597dd..fe33c1cd6 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py @@ -4,12 +4,11 @@ import xmlrpclib def main(): daemon = xmlrpclib.ServerProxy("http://localhost:7080/") try: - b = daemon.get_balance() - is_running = True + status = daemon.is_running() except: - is_running = False + status = False - if is_running: + if status: daemon.stop() print "LBRYnet daemon stopped" else: diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py index f5c9c0e32..6cc33b679 100644 --- a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py +++ b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py @@ -1,15 +1,25 @@ import rumps import xmlrpclib import os +import webbrowser class DaemonStatusBarApp(rumps.App): def __init__(self): super(DaemonStatusBarApp, self).__init__("LBRYnet", icon=os.path.join(os.path.expanduser("~"), "Downloads/lbryio//web/img/fav/apple-touch-icon.png"), quit_button=None) - self.menu = ["Quit"] + self.menu = ["Open UI", "Quit"] + + @rumps.clicked('Open UI') + def get_ui(self): + webbrowser.open("lbry://lbry") @rumps.clicked('Quit') def clean_quit(self): - d = xmlrpclib.ServerProxy('http://localhost:7080') - d.stop() + daemon = xmlrpclib.ServerProxy('http://localhost:7080') + daemon.stop() rumps.quit_application() +def main(): + DaemonStatusBarApp().run() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh index c35723a75..58f5cba6e 100755 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -59,7 +59,7 @@ else fi if ! python -c "import six; exit(0) if six.__version__ == '1.9.0' else exit(1)" &>/dev/null; then - echo "Installing six 1.9.0 for python" + echo "Installing six 1.9.0 for python..." curl -O https://pypi.python.org/packages/source/s/six/six-1.9.0.tar.gz &>/dev/null tar xf six-1.9.0.tar.gz &>/dev/null cd six-1.9.0 @@ -70,6 +70,51 @@ if ! python -c "import six; exit(0) if six.__version__ == '1.9.0' else exit(1)" fi lbrynet_directory="/Users/${SUDO_USER}/Library/Application Support/lbrynet" +lbryum_current_version=$(git ls-remote https://github.com/lbryio/lbryum.git | grep HEAD | cut -f 1) + +if [ -d "$lbrynet_directory" ]; then + if [ -f "${lbrynet_directory}/lbryum_version.txt" ]; then + if grep -Fxq "$lbryum_current_version" "${lbrynet_directory}/lbryum_version.txt"; then + echo "LBRYum version $lbryum_current_version is up to date" + else + tmp=$(mktemp -d) + cd $tmp + + echo "Downloading LBRYum update..." + + git clone -b search --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + cd lbryum + + echo "Installing update..." + sudo python setup.py install &>/dev/null + mkdir -p "$lbrynet_directory" + echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" + + echo "Cleaning up..." + + cd ../../ + rm -rf $tmp + fi + else + tmp=$(mktemp -d) + cd $tmp + + echo "Downloading LBRYum..." + + git clone -b search--depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + cd lbryum + + echo "Installing..." + sudo python setup.py install &>/dev/null + mkdir -p "$lbrynet_directory" + echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" + + echo "Cleaning up..." + + cd ../../ + rm -rf $tmp + fi +fi lbrynet_current_version=$(git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1) @@ -86,12 +131,12 @@ if [ -d "$lbrynet_directory" ]; then git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry - echo "Installing update" + echo "Installing update..." sudo python setup.py install &>/dev/null mkdir -p "$lbrynet_directory" echo $lbrynet_current_version > "${lbrynet_directory}/lbrynet_version.txt" - echo "Cleaning up" + echo "Cleaning up..." cd ../../ rm -rf $tmp @@ -100,63 +145,17 @@ if [ -d "$lbrynet_directory" ]; then tmp=$(mktemp -d) cd $tmp - echo "Downloading LBRYnet update" + echo "Downloading LBRYnet..." git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry - echo "Installing update" + echo "Installing..." sudo python setup.py install &>/dev/null mkdir -p "$lbrynet_directory" echo $lbrynet_current_version > "${lbrynet_directory}/lbrynet_version.txt" - echo "Cleaning up" - - cd ../../ - rm -rf $tmp - fi -fi - -lbryum_current_version=$(git ls-remote https://github.com/lbryio/lbryum.git | grep HEAD | cut -f 1) - -if [ -d "$lbrynet_directory" ]; then - if [ -f "${lbrynet_directory}/lbryum_version.txt" ]; then - if grep -Fxq "$lbryum_current_version" "${lbrynet_directory}/lbryum_version.txt"; then - echo "LBRYum version $lbryum_current_version is up to date" - else - tmp=$(mktemp -d) - cd $tmp - - echo "Downloading LBRYum update" - - git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null - cd lbryum - - echo "Installing update" - sudo python setup.py install &>/dev/null - mkdir -p "$lbrynet_directory" - echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" - - echo "Cleaning up" - - cd ../../ - rm -rf $tmp - fi - else - tmp=$(mktemp -d) - cd $tmp - - echo "Downloading LBRYum update" - - git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null - cd lbryum - - echo "Installing update" - sudo python setup.py install &>/dev/null - mkdir -p "$lbrynet_directory" - echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" - - echo "Cleaning up" + echo "Cleaning up..." cd ../../ rm -rf $tmp From 7cf3f8c7cd066d6c6e6ca154453305039e151029 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 25 Feb 2016 17:18:18 -0500 Subject: [PATCH 034/462] fix typo --- lbrynet/core/LBRYcrdWallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 922246714..6920cb4f2 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -979,7 +979,7 @@ class LBRYumWallet(LBRYWallet): return defer.succeed(Decimal(self.wallet.get_addr_received(address))/COIN) def get_nametrie(self): - cmd = known_commands['getnametrie'] + cmd = known_commands['getclaimtrie'] func = getattr(self.cmd_runner, cmd.name) return threads.deferToThread(func) From 9991f79326600d77e12e7bbab74acfd9fa0e1725 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Fri, 26 Feb 2016 01:45:52 -0500 Subject: [PATCH 035/462] add support for claiming names, abandoning names, and listing claimed names to the LBRYumWallet --- lbrynet/core/LBRYcrdWallet.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 922246714..8869a319d 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -938,7 +938,9 @@ class LBRYumWallet(LBRYWallet): return d def get_name_claims(self): - return defer.fail(NotImplementedError()) + cmd = known_commands['getnameclaims'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func) def check_first_run(self): return defer.succeed(self.first_run) @@ -949,13 +951,38 @@ class LBRYumWallet(LBRYWallet): return threads.deferToThread(func, txid) def _send_name_claim(self, name, val, amount): - return defer.fail(NotImplementedError()) + def send_claim(address): + cmd = known_commands['claimname'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, address, amount, name, val) + d = self.get_new_address() + d.addCallback(send_claim) + d.addCallback(self._broadcast_transaction) + return d def _get_decoded_tx(self, raw_tx): - return defer.fail(NotImplementedError()) + tx = Transaction(raw_tx) + decoded_tx = {} + decoded_tx['vout'] = [] + for output in tx.outputs(): + out = {} + out['value'] = output[2] + decoded_tx['vout'].append(out) + return decoded_tx def _send_abandon(self, txid, address, amount): - return defer.fail(NotImplementedError()) + cmd = known_commands['abandonclaim'] + func = getattr(self.cmd_runner, cmd.name) + d = threads.deferToThread(func, txid, address, amount) + d.addCallback(self._broadcast_transaction) + return d + + def _broadcast_transaction(self, raw_tx): + cmd = known_commands['broadcast'] + func = getattr(self.cmd_runner, cmd.name) + d = threads.deferToThread(func, raw_tx) + d.addCallback(self._save_wallet) + return d def _do_send_many(self, payments_to_send): log.warning("Doing send many. payments to send: %s", str(payments_to_send)) From b4c8a317d0b9d39cbdfe91f5547a629e7265816c Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 26 Feb 2016 10:39:56 -0500 Subject: [PATCH 036/462] switch branch --- lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh index 58f5cba6e..3ce6dd15b 100755 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -82,7 +82,7 @@ if [ -d "$lbrynet_directory" ]; then echo "Downloading LBRYum update..." - git clone -b search --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + git clone -b development --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null cd lbryum echo "Installing update..." @@ -101,7 +101,7 @@ if [ -d "$lbrynet_directory" ]; then echo "Downloading LBRYum..." - git clone -b search--depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + git clone -b development --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null cd lbryum echo "Installing..." @@ -128,7 +128,7 @@ if [ -d "$lbrynet_directory" ]; then echo "Downloading LBRYnet update" - git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null + git clone -b development --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry echo "Installing update..." @@ -147,7 +147,7 @@ if [ -d "$lbrynet_directory" ]; then echo "Downloading LBRYnet..." - git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null + git clone -b development --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry echo "Installing..." From a18f3bf08a796d91039c48410ff83a76374ae474 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 26 Feb 2016 10:59:09 -0500 Subject: [PATCH 037/462] disable status bar --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 6ae30a0f1..0422c47c3 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -58,8 +58,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") - from lbrynet.lbrynet_daemon.LBRYOSXStatusBar import DaemonStatusBarApp - self.status_app = DaemonStatusBarApp() + # from lbrynet.lbrynet_daemon.LBRYOSXStatusBar import DaemonStatusBarApp + # self.status_app = DaemonStatusBarApp() self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 self.dht_node_port = 4444 From 8f04d3418a6525ee3ac0eb8210dda89d33600936 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 26 Feb 2016 11:20:31 -0500 Subject: [PATCH 038/462] get channel id from slack for test bot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit in case i accidentally delete the bot conf file again and don’t want to look up the channel id --- tests/lbrynet_test_bot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/lbrynet_test_bot.py b/tests/lbrynet_test_bot.py index a76a366d8..46e5d9f0a 100644 --- a/tests/lbrynet_test_bot.py +++ b/tests/lbrynet_test_bot.py @@ -7,9 +7,8 @@ from slackclient import SlackClient def get_conf(): f = open('testbot.conf', 'r') token = f.readline().replace('\n', '') - channel = f.readline().replace('\n', '') f.close() - return token, channel + return token def test_lbrynet(lbry, slack, channel): logfile = open('lbrynet_test_log.txt', 'a') @@ -48,10 +47,11 @@ def test_lbrynet(lbry, slack, channel): lbry.delete_lbry_file('test.jpg') logfile.close() -token, channel = get_conf() +token = get_conf() sc = SlackClient(token) sc.rtm_connect() +channel = [c['id'] for c in json.loads(sc.api_call('channels.list'))['channels'] if c['name'] == 'tech'][0] print 'Connected to slack' daemon = xmlrpclib.ServerProxy("http://localhost:7080") From 33c1a002da477b99153e612acc93d7e0a4d451a8 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 27 Feb 2016 13:19:26 -0500 Subject: [PATCH 039/462] update daemon only let local user connect to lbrynet-daemon --- .gitignore | 6 ++++++ lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 23ba8568c..819306a87 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,9 @@ lbrynet.egg-info/PKG-INFO /build /dist + +*.so + +*.pem + +*.decTest diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0422c47c3..0a049b3f1 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1093,7 +1093,7 @@ def main(): daemon = LBRYDaemon() daemon.setup(args.wallet, args.update) - reactor.listenTCP(7080, server.Site(daemon)) + reactor.listenTCP(7080, server.Site(daemon), interface='localhost') reactor.run() if __name__ == '__main__': From 59d08a92d00ec8e6c08419c078c139639c842d2c Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 27 Feb 2016 17:49:49 -0500 Subject: [PATCH 040/462] update status bar app --- lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py | 58 +++++++++++++++++++--- setup.py | 36 +++++++------- 2 files changed, 71 insertions(+), 23 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py index 6cc33b679..02cae63f7 100644 --- a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py +++ b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py @@ -3,23 +3,69 @@ import xmlrpclib import os import webbrowser + class DaemonStatusBarApp(rumps.App): def __init__(self): - super(DaemonStatusBarApp, self).__init__("LBRYnet", icon=os.path.join(os.path.expanduser("~"), "Downloads/lbryio//web/img/fav/apple-touch-icon.png"), quit_button=None) - self.menu = ["Open UI", "Quit"] + icon_path = os.path.join(os.path.expanduser("~"), "Downloads/lbryio/web/img/fav/apple-touch-icon.png") + if os.path.isfile(icon_path): + rumps.App.__init__(self, name="LBRY", icon=icon_path, quit_button=None, + menu=["Open", "Preferences", "View balance", "Quit"]) + else: + rumps.App.__init__(self, name="LBRY", title="LBRY", quit_button=None, + menu=["Open", "Preferences", "View balance", "Quit"]) - @rumps.clicked('Open UI') + @rumps.clicked('Open') def get_ui(self): - webbrowser.open("lbry://lbry") + try: + daemon = xmlrpclib.ServerProxy('http://localhost:7080') + daemon.is_running() + webbrowser.open("lbry://lbry") + except: + try: + rumps.notification(title='LBRY', subtitle='status', message="Couldn't connect to lbrynet daemon", sound=True) + except: + rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") + + @rumps.clicked("Preferences") + def prefs(self, _): + try: + daemon = xmlrpclib.ServerProxy('http://localhost:7080') + daemon.is_running() + webbrowser.open("lbry://settings") + except: + rumps.notification(title='LBRY', subtitle='status', message="Couldn't connect to lbrynet daemon", sound=True) + + @rumps.clicked("View balance") + def disp_balance(self): + daemon = xmlrpclib.ServerProxy('http://localhost:7080') + try: + balance = daemon.get_balance() + try: + rumps.notification(title='LBRY', subtitle='status', message="Your balance is " + str(balance), sound=False) + except: + rumps.alert(title='LBRY', message="Your balance is " + str(balance)) + + except: + try: + rumps.notification(title='LBRY', subtitle='status', message="Couldn't connect to lbrynet daemon", sound=True) + except: + rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") @rumps.clicked('Quit') def clean_quit(self): daemon = xmlrpclib.ServerProxy('http://localhost:7080') - daemon.stop() + try: + daemon.stop() + except: + pass + rumps.quit_application() + def main(): - DaemonStatusBarApp().run() + status_app = DaemonStatusBarApp() + status_app.run() + if __name__ == '__main__': main() \ No newline at end of file diff --git a/setup.py b/setup.py index 7c3cfa358..95219a418 100644 --- a/setup.py +++ b/setup.py @@ -2,29 +2,31 @@ import ez_setup ez_setup.use_setuptools() - from setuptools import setup, find_packages +import sys + +console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', + 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', + 'lbrynet-stdout-downloader = lbrynet.lbrynet_console.LBRYStdoutDownloader:launch_stdout_downloader', + 'lbrynet-create-network = lbrynet.create_network:main', + 'lbrynet-launch-node = lbrynet.dht.node:main', + 'lbrynet-launch-rpc-node = lbrynet.rpc_node:main', + 'lbrynet-rpc-node-cli = lbrynet.node_rpc_cli:main', + 'lbrynet-gui = lbrynet.lbrynet_gui.gui:start_gui', + 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', + 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', + 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:main', + 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonStopper:main'] + +if sys.platform == 'darwin': + console_scripts.append('lbrynet-daemon-status = lbrynet.lbrynet_daemon.LBRYOSXStatusBar:main') + setup(name='lbrynet', version='0.0.4', packages=find_packages(), install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum'], - entry_points={ - 'console_scripts': [ - 'lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', - 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', - 'lbrynet-stdout-downloader = lbrynet.lbrynet_console.LBRYStdoutDownloader:launch_stdout_downloader', - 'lbrynet-create-network = lbrynet.create_network:main', - 'lbrynet-launch-node = lbrynet.dht.node:main', - 'lbrynet-launch-rpc-node = lbrynet.rpc_node:main', - 'lbrynet-rpc-node-cli = lbrynet.node_rpc_cli:main', - 'lbrynet-gui = lbrynet.lbrynet_gui.gui:start_gui', - 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', - 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', - 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:main', - 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonStopper:main', - ] - }, + entry_points={'console_scripts': console_scripts}, data_files=[ ('lbrynet/lbrynet_console/plugins', [ From 7d1cf7975a18ee8c5923297b5f1d62d1d06817d4 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sun, 28 Feb 2016 12:45:21 -0500 Subject: [PATCH 041/462] update win32 setup script to include lbryum --- setup_win32.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup_win32.py b/setup_win32.py index b414606a7..1fc0f0d58 100644 --- a/setup_win32.py +++ b/setup_win32.py @@ -7,7 +7,7 @@ import os import sys from cx_Freeze import setup, Executable - +import requests.certs def find_data_file(filename): if getattr(sys, 'frozen', False): @@ -48,9 +48,9 @@ bdist_msi_options = { build_exe_options = { 'include_msvcr': True, 'includes': [], - 'packages': ['os', 'twisted', 'miniupnpc', 'unqlite', 'seccure', + 'packages': ['six', 'os', 'twisted', 'miniupnpc', 'unqlite', 'seccure', 'requests', 'bitcoinrpc', 'txjsonrpc', 'win32api', 'Crypto', - 'gmpy', 'yapsy'], + 'gmpy', 'yapsy', 'lbryum', 'google.protobuf'], 'excludes': ['zope.interface._zope_interface_coptimizations'], 'include_files': [os.path.join('lbrynet', 'lbrynet_gui', 'close.gif'), os.path.join('lbrynet', 'lbrynet_gui', 'close1.png'), @@ -63,6 +63,7 @@ build_exe_options = { os.path.join('lbrynet', 'lbrynet_gui', 'show_options.gif'), os.path.join('lbrycrdd.exe'), # Not included in repo os.path.join('lbrycrd-cli.exe'), # Not included in repo + (requests.certs.where(), 'cacert.pem'), ], 'namespace_packages': ['zope']} From a22e29532cd95cce8fa6e80972207f853870a22d Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 28 Feb 2016 23:49:37 -0500 Subject: [PATCH 042/462] start daemon from status bar --- lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py index 02cae63f7..432ec2fbc 100644 --- a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py +++ b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py @@ -2,6 +2,7 @@ import rumps import xmlrpclib import os import webbrowser +import subprocess class DaemonStatusBarApp(rumps.App): @@ -63,6 +64,7 @@ class DaemonStatusBarApp(rumps.App): def main(): + subprocess.Popen("screen -dmS lbry bash -c 'lbrynet-daemon --update=False'", shell=True) status_app = DaemonStatusBarApp() status_app.run() From 6f1ee786231274625f86154d8ada27aa37c9c674 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 29 Feb 2016 13:25:47 -0500 Subject: [PATCH 043/462] update daemon --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 57 ++++++++++++++++----- lbrynet/lbrynet_daemon/LBRYDaemonStopper.py | 18 ------- lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py | 21 +++++++- setup.py | 2 +- 4 files changed, 64 insertions(+), 34 deletions(-) delete mode 100644 lbrynet/lbrynet_daemon/LBRYDaemonStopper.py diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0a049b3f1..8e44960eb 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,3 +1,22 @@ +import os +import sys +import json +import binascii +import webbrowser +import xmlrpclib +import subprocess +import logging +import argparse + +from twisted.web import xmlrpc, server +from twisted.internet import defer, threads, reactor, error + +from datetime import datetime +from decimal import Decimal +from StringIO import StringIO +from zipfile import ZipFile +from urllib import urlopen + from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory @@ -18,15 +37,7 @@ from lbrynet.core.PTCWallet import PTCWallet from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager -from twisted.web import xmlrpc, server -from twisted.internet import defer, threads, reactor, error -from datetime import datetime -from decimal import Decimal -from StringIO import StringIO -from zipfile import ZipFile -from urllib import urlopen -import os, sys, json, binascii, webbrowser, xmlrpclib, subprocess, logging, argparse log = logging.getLogger(__name__) # logging.basicConfig(level=logging.DEBUG) @@ -138,9 +149,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda _: self._setup_lbry_file_opener()) d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) - if sys.platform == "darwin": - d.addCallback(lambda _: self._update() if self.check_for_updates == "True" else defer.succeed(None)) - # d.addCallback(lambda _: defer.succeed(self.status_app.run())) + d.addCallback(lambda _: self._update() if self.check_for_updates == "True" and sys.platform == "darwin" + else defer.succeed(None)) d.addCallback(lambda _: self._setup_fetcher()) d.addCallback(lambda _: _disp_startup()) d.callback(None) @@ -218,8 +228,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = _check_for_updater() d.addCallback(lambda _: _update_lbrynet()) - d.addCallback(lambda _: _update_lbrycrdd()) - d.addCallback(lambda _: _update_lbryum()) + d.addCallback(lambda _: _update_lbrycrdd() if self.wallet_type == 'lbrycrd' else _update_lbryum()) d.addCallback(lambda _: os.system("open /Applications/LBRY\ Updater.app &>/dev/null") if self.restart_message else defer.succeed(None)) d.addCallbacks(lambda _: self._restart() if self.restart_message else defer.succeed(None)) @@ -1071,6 +1080,27 @@ class LBRYDaemon(xmlrpc.XMLRPC): return message + def xmlrpc_start_status_bar_app(self): + if sys.platform == 'darwin': + subprocess.Popen("screen -dmS lbry-status bash -c 'lbrynet-daemon-status --startdaemon=False'", shell=True) + return "Started" + else: + return "Status bar not implemented on non OS X" + + +def stop(): + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") + try: + status = daemon.is_running() + except: + status = False + + if status: + daemon.stop() + print "LBRYnet daemon stopped" + else: + print "LBRYnet daemon wasn't running" + def main(): parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") @@ -1096,5 +1126,6 @@ def main(): reactor.listenTCP(7080, server.Site(daemon), interface='localhost') reactor.run() + if __name__ == '__main__': main() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py b/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py deleted file mode 100644 index fe33c1cd6..000000000 --- a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py +++ /dev/null @@ -1,18 +0,0 @@ -import xmlrpclib - - -def main(): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - status = daemon.is_running() - except: - status = False - - if status: - daemon.stop() - print "LBRYnet daemon stopped" - else: - print "LBRYnet daemon wasn't running" - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py index 432ec2fbc..d233b8e62 100644 --- a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py +++ b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py @@ -3,11 +3,19 @@ import xmlrpclib import os import webbrowser import subprocess +import argparse class DaemonStatusBarApp(rumps.App): def __init__(self): - icon_path = os.path.join(os.path.expanduser("~"), "Downloads/lbryio/web/img/fav/apple-touch-icon.png") + #detect if being run as root, if so find the correct icon path + if os.path.expanduser("~") != '/var/root': + icon_path = os.path.join(os.path.expanduser("~"), "Downloads/lbryio/web/img/fav/apple-touch-icon.png") + else: + icon_path = os.path.join("/Users", + subprocess.check_output('echo $SUDO_USER', shell=True)[:-1], + "Downloads/lbryio/web/img/fav/apple-touch-icon.png") + if os.path.isfile(icon_path): rumps.App.__init__(self, name="LBRY", icon=icon_path, quit_button=None, menu=["Open", "Preferences", "View balance", "Quit"]) @@ -64,7 +72,16 @@ class DaemonStatusBarApp(rumps.App): def main(): - subprocess.Popen("screen -dmS lbry bash -c 'lbrynet-daemon --update=False'", shell=True) + parser = argparse.ArgumentParser(description="Launch lbrynet status bar application") + parser.add_argument("--startdaemon", + help="true or false, default true", + type=str, + default="true") + args = parser.parse_args() + + if args.startdaemon.lower() == "true": + subprocess.Popen("screen -dmS lbrynet bash -c 'lbrynet-daemon'", shell=True) + status_app = DaemonStatusBarApp() status_app.run() diff --git a/setup.py b/setup.py index 95219a418..68d476243 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:main', - 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonStopper:main'] + 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:stop'] if sys.platform == 'darwin': console_scripts.append('lbrynet-daemon-status = lbrynet.lbrynet_daemon.LBRYOSXStatusBar:main') From a243ac7a01efef9b483a7a9576f5acfb59b06fcb Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 29 Feb 2016 15:04:20 -0500 Subject: [PATCH 044/462] fix bug starting daemon from status bar --- lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py index d233b8e62..fd9494c19 100644 --- a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py +++ b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py @@ -79,7 +79,7 @@ def main(): default="true") args = parser.parse_args() - if args.startdaemon.lower() == "true": + if str(args.startdaemon).lower() == "true": subprocess.Popen("screen -dmS lbrynet bash -c 'lbrynet-daemon'", shell=True) status_app = DaemonStatusBarApp() From 2fc3ec7224ab966f30bad05ea4164a7a6bce4525 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 2 Mar 2016 02:00:45 -0500 Subject: [PATCH 045/462] update status bar app --- app.icns | Bin 0 -> 8362 bytes lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py | 13 +++++------- setup_osx.py | 22 +++++++++++++++++++++ 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 app.icns create mode 100644 setup_osx.py diff --git a/app.icns b/app.icns new file mode 100644 index 0000000000000000000000000000000000000000..b4d00d2f26dc4f4221a0d3e93e988a15b0ada3b8 GIT binary patch literal 8362 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 literal 0 HcmV?d00001 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 8e44960eb..3fd99104a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -7,6 +7,7 @@ import xmlrpclib import subprocess import logging import argparse +import pwd from twisted.web import xmlrpc, server from twisted.internet import defer, threads, reactor, error @@ -69,8 +70,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") - # from lbrynet.lbrynet_daemon.LBRYOSXStatusBar import DaemonStatusBarApp - # self.status_app = DaemonStatusBarApp() self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 self.dht_node_port = 4444 @@ -813,6 +812,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = defer.Deferred() d.addCallback(lambda _: _make_file(html, path)) + d.addCallback(lambda _: os.chown(path, pwd.getpwuid(os.getuid()).pw_uid, pwd.getpwuid(os.getuid()).pw_gid)) d.addCallback(lambda _: webbrowser.open('file://' + path)) d.addErrback(_disp_err) d.callback(None) diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py index fd9494c19..c7ba526d8 100644 --- a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py +++ b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py @@ -8,13 +8,7 @@ import argparse class DaemonStatusBarApp(rumps.App): def __init__(self): - #detect if being run as root, if so find the correct icon path - if os.path.expanduser("~") != '/var/root': - icon_path = os.path.join(os.path.expanduser("~"), "Downloads/lbryio/web/img/fav/apple-touch-icon.png") - else: - icon_path = os.path.join("/Users", - subprocess.check_output('echo $SUDO_USER', shell=True)[:-1], - "Downloads/lbryio/web/img/fav/apple-touch-icon.png") + icon_path = 'app.icns' if os.path.isfile(icon_path): rumps.App.__init__(self, name="LBRY", icon=icon_path, quit_button=None, @@ -80,7 +74,10 @@ def main(): args = parser.parse_args() if str(args.startdaemon).lower() == "true": - subprocess.Popen("screen -dmS lbrynet bash -c 'lbrynet-daemon'", shell=True) + subprocess.Popen("screen -dmS lbrynet bash -c " + "'PYTHONPATH=$PYTHONPATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.python_path`; " + "PATH=$PATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.lbry_bin_path`; " + "lbrynet-daemon --update=False'", shell=True) status_app = DaemonStatusBarApp() status_app.run() diff --git a/setup_osx.py b/setup_osx.py new file mode 100644 index 000000000..03490e622 --- /dev/null +++ b/setup_osx.py @@ -0,0 +1,22 @@ +import os +from setuptools import setup + +APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'LBRYOSXStatusBar.py')] +DATA_FILES = [] +OPTIONS = { + 'argv_emulation': True, + 'iconfile': 'app.icns', + 'plist': { + 'LSUIElement': True, + }, + 'includes': ['rumps'] +} + + +setup( + name='LBRY', + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +) \ No newline at end of file From 02d8dc22e9e0b2bdce2207642a2d4689ef89ab80 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 2 Mar 2016 18:32:48 -0500 Subject: [PATCH 046/462] update daemon -notify user when connected to lbrynet -check for updates from status bar app --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 124 +++++++++++++++------ lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py | 53 ++++++--- 2 files changed, 128 insertions(+), 49 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 3fd99104a..71f2c5cc8 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,3 +1,4 @@ +import locale import os import sys import json @@ -8,10 +9,10 @@ import subprocess import logging import argparse import pwd +import requests from twisted.web import xmlrpc, server from twisted.internet import defer, threads, reactor, error - from datetime import datetime from decimal import Decimal from StringIO import StringIO @@ -39,8 +40,9 @@ from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager - log = logging.getLogger(__name__) + + # logging.basicConfig(level=logging.DEBUG) @@ -118,6 +120,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.max_key_fee = DEFAULT_MAX_KEY_FEE self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS self.restart_message = "" + self.startup_message = "" + self.announced_startup = False self.search_timeout = 3.0 self.query_handlers = {} @@ -229,7 +233,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda _: _update_lbrynet()) d.addCallback(lambda _: _update_lbrycrdd() if self.wallet_type == 'lbrycrd' else _update_lbryum()) d.addCallback(lambda _: os.system("open /Applications/LBRY\ Updater.app &>/dev/null") if self.restart_message - else defer.succeed(None)) + else defer.succeed(None)) d.addCallbacks(lambda _: self._restart() if self.restart_message else defer.succeed(None)) return defer.succeed(None) @@ -323,8 +327,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self._stop_server() if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) - # if self.status_app: - # d.addCallback(lambda _: self.status_app.stop()) return d def _update_settings(self): @@ -453,8 +455,45 @@ class LBRYDaemon(xmlrpc.XMLRPC): dl.addCallback(combine_results) dl.addCallback(create_session) dl.addCallback(lambda _: self.session.setup()) + dl.addCallback(lambda _: self._check_first_run()) + dl.addCallback(self._show_first_run_result) return dl + def _check_first_run(self): + d = self.session.wallet.check_first_run() + d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else 0.0) + return d + + def _do_first_run(self): + d = self.session.wallet.get_new_address() + + def send_request(url, data): + r = requests.post(url, json=data) + if r.status_code == 200: + return r.json()['credits_sent'] + return 0.0 + + def log_error(err): + log.warning("unable to request free credits. %s", err.getErrorMessage()) + return 0.0 + + def request_credits(address): + url = "http://credreq.lbry.io/requestcredits" + data = {"address": address} + d = threads.deferToThread(send_request, url, data) + d.addErrback(log_error) + return d + + d.addCallback(request_credits) + return d + + def _show_first_run_result(self, credits_received): + if credits_received != 0.0: + points_string = locale.format_string("%.2f LBC", (round(credits_received, 2),), grouping=True) + self.startup_message = "Thank you for testing the alpha version of LBRY! You have been given %s for free because we love you. Please give them a few minutes to show up while you catch up with our blockchain." % points_string + else: + self.startup_message = "Connected to LBRYnet" + def _get_lbrycrdd_path(self): def get_lbrycrdd_path_conf_file(): lbrycrdd_path_conf_path = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf") @@ -629,7 +668,14 @@ class LBRYDaemon(xmlrpc.XMLRPC): return d def xmlrpc_is_running(self): - return True + if self.startup_message != "" and self.announced_startup == False: + print "Startup message:", self.startup_message + self.announced_startup = True + return self.startup_message + elif self.announced_startup: + return True + else: + return False def xmlrpc_get_settings(self): """ @@ -973,7 +1019,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): content_license = None log.info('[' + str(datetime.now()) + '] Publish: ', name, file_path, bid, title, description, thumbnail, - key_fee, key_fee_address, content_license) + key_fee, key_fee_address, content_license) p = Publisher(self.session, self.lbry_file_manager, self.session.wallet) d = p.start(name, file_path, bid, title, description, thumbnail, key_fee, key_fee_address, content_license) @@ -1050,40 +1096,52 @@ class LBRYDaemon(xmlrpc.XMLRPC): return self.fetcher.verbose def xmlrpc_check_for_new_version(self): - message = "" + def _check_for_updates(package): + git_version = subprocess.check_output("git ls-remote " + package['git'] + " | grep HEAD | cut -f 1", shell=True) + up_to_date = False + if os.path.isfile(package['version_file']): + f = open(package['version_file'], 'r') + current_version = f.read() + f.close() - git_version = subprocess.check_output("git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1", shell=True) - if os.path.isfile(os.path.join(self.db_dir, "lbrynet_version.txt")): - f = open(os.path.join(self.db_dir, "lbrynet_version.txt"), 'r') - current_version = f.read() - f.close() - - if git_version == current_version: - message += "LBRYnet is up to date\n" + if git_version == current_version: + r = package['name'] + " is up to date" + up_to_date = True + else: + r = package['name'] + " version is out of date" else: - message += "LBRYnet version is out of date, restart the daemon to update\n" - else: - message += "Unknown version of LBRYnet, try running installer again\n" + r = "Unknown version of " + package['name'] - git_version = subprocess.check_output("git ls-remote https://github.com/jackrobison/lbrynet-app.git | grep HEAD | cut -f 1", shell=True) - if os.path.isfile(os.path.join(self.wallet_dir, "lbry_app_version.txt")): - f = open(os.path.join(self.wallet_dir, "lbry_app_version.txt"), 'r') - current_version = f.read() - f.close() + return (up_to_date, r) - if git_version == current_version: - message += "LBRY is up to date" - else: - message += "LBRY version is out of date, restart the daemon to update" - else: - message += "Unknown version of LBRYnet, try running installer again\n" + package_infos = { + "lbrynet": {"name": "LBRYnet", + "git": "https://github.com/lbryio/lbry.git", + "version_file": os.path.join(self.db_dir, ".lbrynet_version"), + "clone": ".lbrygit", + }, + "lbryum": {"name": "lbryum", + "git": "https://github.com/lbryio/lbryum.git", + "version_file": os.path.join(self.db_dir, ".lbryum_version"), + "clone": ".lbryumgit", + }, + "lbry": {"name": "LBRY", + "git": "https://github.com/jackrobison/lbrynet-app.git", + "version_file": os.path.join(self.db_dir, ".lbry_app_version"), + "clone": None, + }, + } - return message + return [_check_for_updates(package_infos[p]) for p in package_infos.keys()] def xmlrpc_start_status_bar_app(self): if sys.platform == 'darwin': - subprocess.Popen("screen -dmS lbry-status bash -c 'lbrynet-daemon-status --startdaemon=False'", shell=True) - return "Started" + if os.path.isdir("/Applications/LBRY.app"): + # subprocess.Popen("screen -dmS lbry-status bash -c 'lbrynet-daemon-status --startdaemon=False'", shell=True) + subprocess.Popen("screen -dmS lbry-status bash -c 'open /Applications/LBRY.app'") + return "Started" + else: + return "Couldn't find LBRY.app, try running the installer" else: return "Status bar not implemented on non OS X" diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py index c7ba526d8..c55fed0d2 100644 --- a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py +++ b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py @@ -9,7 +9,6 @@ import argparse class DaemonStatusBarApp(rumps.App): def __init__(self): icon_path = 'app.icns' - if os.path.isfile(icon_path): rumps.App.__init__(self, name="LBRY", icon=icon_path, quit_button=None, menu=["Open", "Preferences", "View balance", "Quit"]) @@ -17,51 +16,69 @@ class DaemonStatusBarApp(rumps.App): rumps.App.__init__(self, name="LBRY", title="LBRY", quit_button=None, menu=["Open", "Preferences", "View balance", "Quit"]) + @rumps.timer(1) + def alert_daemon_start(self): + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") + try: + start_msg = daemon.is_running() + if isinstance(start_msg, str): + rumps.notification(title='LBRY', subtitle='', message=str(start_msg), sound=True) + update_info = daemon.check_for_new_version() + update_msg = "" + for p in update_info: + if not p[0]: + update_msg += p[1] + "\n" + if update_msg: + update_msg += "\n Try running the installer again to fix this" + rumps.notification(title='LBRY', subtitle='', message=update_msg, sound=True) + except: + pass + @rumps.clicked('Open') def get_ui(self): + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") try: - daemon = xmlrpclib.ServerProxy('http://localhost:7080') daemon.is_running() webbrowser.open("lbry://lbry") except: try: - rumps.notification(title='LBRY', subtitle='status', message="Couldn't connect to lbrynet daemon", sound=True) + rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) except: rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") @rumps.clicked("Preferences") - def prefs(self, _): + def prefs(self): + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") try: - daemon = xmlrpclib.ServerProxy('http://localhost:7080') daemon.is_running() webbrowser.open("lbry://settings") except: - rumps.notification(title='LBRY', subtitle='status', message="Couldn't connect to lbrynet daemon", sound=True) + rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) @rumps.clicked("View balance") def disp_balance(self): - daemon = xmlrpclib.ServerProxy('http://localhost:7080') + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") try: balance = daemon.get_balance() + r = round(float(balance), 2) try: - rumps.notification(title='LBRY', subtitle='status', message="Your balance is " + str(balance), sound=False) + rumps.notification(title='LBRY', subtitle='', message=str("Your balance is %.2f LBC" % r), sound=False) except: - rumps.alert(title='LBRY', message="Your balance is " + str(balance)) + rumps.alert(title='LBRY', message=str("Your balance is %.2f LBC" % r)) except: try: - rumps.notification(title='LBRY', subtitle='status', message="Couldn't connect to lbrynet daemon", sound=True) + rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) except: rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") @rumps.clicked('Quit') def clean_quit(self): - daemon = xmlrpclib.ServerProxy('http://localhost:7080') + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") try: daemon.stop() except: pass - rumps.quit_application() @@ -74,10 +91,14 @@ def main(): args = parser.parse_args() if str(args.startdaemon).lower() == "true": - subprocess.Popen("screen -dmS lbrynet bash -c " - "'PYTHONPATH=$PYTHONPATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.python_path`; " - "PATH=$PATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.lbry_bin_path`; " - "lbrynet-daemon --update=False'", shell=True) + daemon = xmlrpclib.ServerProxy('http://localhost:7080') + try: + daemon.is_running() + except: + subprocess.Popen("screen -dmS lbrynet bash -c " + "'PYTHONPATH=$PYTHONPATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.python_path`; " + "PATH=$PATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.lbry_bin_path`; " + "lbrynet-daemon --update=False'", shell=True) status_app = DaemonStatusBarApp() status_app.run() From e4f23b3fcedf042743e909d76789e8d6f8c7b36d Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 3 Mar 2016 00:16:51 -0500 Subject: [PATCH 047/462] better organize apps --- .../{ => Apps}/LBRYOSXStatusBar.py | 0 lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 52 ++++++++ lbrynet/lbrynet_daemon/Apps/__init__.py | 0 lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 +- .../LBRYURIHandler/LBRYURIHandler.py | 59 --------- .../LBRYURIHandler.app/Contents/Info.plist | 117 ------------------ .../lbrynet_daemon/LBRYURIHandler/setup.py | 19 --- setup_osx.py | 2 +- setup_uri_handler.py | 22 ++++ 9 files changed, 77 insertions(+), 198 deletions(-) rename lbrynet/lbrynet_daemon/{ => Apps}/LBRYOSXStatusBar.py (100%) create mode 100644 lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py create mode 100644 lbrynet/lbrynet_daemon/Apps/__init__.py delete mode 100644 lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py delete mode 100644 lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist delete mode 100644 lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py create mode 100644 setup_uri_handler.py diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py similarity index 100% rename from lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py rename to lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py new file mode 100644 index 000000000..eea94cb94 --- /dev/null +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -0,0 +1,52 @@ +import os +import json +import webbrowser +import xmlrpclib, sys + +def render_video(path): + r = r'
' + return r + + +def main(args): + if len(args) == 0: + args.append('lbry://wonderfullife') + + daemon = xmlrpclib.ServerProxy('http://localhost:7080/') + + try: + daemon.is_running() + + if len(args) > 1: + exit(1) + + if args[0][7:] == 'lbry': + daemon.render_gui() + + elif args[0][7:] == 'settings': + r = daemon.get_settings() + html = "" + json.dumps(r) + "" + daemon.render_html(html) + + else: + r = daemon.get(args[0][7:]) + path = r['path'] + if path[0] != '/': + path = '/' + path + + filename = os.path.basename(path) + extension = os.path.splitext(filename)[1] + + if extension in ['mp4', 'flv', 'mov']: + html = render_video(path) + daemon.render_html(html) + + else: + webbrowser.open('file://' + str(path)) + + except: + webbrowser.open('http://lbry.io/get') + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/lbrynet/lbrynet_daemon/Apps/__init__.py b/lbrynet/lbrynet_daemon/Apps/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 71f2c5cc8..1e68f0f76 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -152,8 +152,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda _: self._setup_lbry_file_opener()) d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) - d.addCallback(lambda _: self._update() if self.check_for_updates == "True" and sys.platform == "darwin" - else defer.succeed(None)) + # d.addCallback(lambda _: self._update() if self.check_for_updates == "True" and sys.platform == "darwin" + # else defer.succeed(None)) d.addCallback(lambda _: self._setup_fetcher()) d.addCallback(lambda _: _disp_startup()) d.callback(None) diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py deleted file mode 100644 index 62ded697b..000000000 --- a/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import json -import webbrowser -import xmlrpclib, sys - - -def render_video(path): - r = r'
' - return r - - -def main(args): - if len(args) == 0: - args.append('lbry://wonderfullife') - - daemon = xmlrpclib.ServerProxy('http://localhost:7080/') - - try: - balance = daemon.get_balance() - is_running = True - if len(args) > 1: - print 'Too many args', args - - elif is_running: - if args[0][7:] == 'lbry': - daemon.render_gui() - - elif args[0][7:] == 'settings': - r = daemon.get_settings() - html = "" + json.dumps(r) + "" - r = daemon.render_html(html) - else: - if float(balance) > 0.0: - r = daemon.get(args[0][7:]) - print r - path = r['path'] - if path[0] != '/': - path = '/' + path - - print path - filename = path.split('/')[len(path.split('/')) - 1] - extension = path.split('.')[len(path.split('.')) - 1] - - if extension in ['mp4', 'flv', 'mov']: - html = render_video(path) - daemon.render_html(html) - - else: - webbrowser.open('file://' + str(path)) - - except: - webbrowser.open('http://lbry.io/get') - is_running = False - - - - -if __name__ == "__main__": - main(sys.argv[1:]) diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist b/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist deleted file mode 100644 index 4f5993ff1..000000000 --- a/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist +++ /dev/null @@ -1,117 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleDisplayName - LBRYURIHandler - CFBundleDocumentTypes - - - CFBundleTypeOSTypes - - **** - fold - disk - - CFBundleTypeRole - Viewer - - - CFBundleExecutable - LBRYURIHandler - -CFBundleURLTypes - - - CFBundleURLName - LBRYURIHandler - CFBundleURLSchemes - - lbry - - - -NSUIElement - - - CFBundleIconFile - PythonApplet.icns - CFBundleIdentifier - org.pythonmac.unspecified.LBRYURIHandler - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - LBRYURIHandler - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.0.0 - CFBundleSignature - ???? - CFBundleVersion - 0.0.0 - LSHasLocalizedDisplayName - - NSAppleScriptEnabled - - NSHumanReadableCopyright - Copyright not specified - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - PyMainFileNames - - __boot__ - - PyOptions - - alias - - argv_emulation - - emulate_shell_environment - - no_chdir - - prefer_ppc - - site_packages - - use_faulthandler - - use_pythonpath - - verbose - - - PyResourcePackages - - - PyRuntimeLocations - - @executable_path/../Frameworks/Python.framework/Versions/2.7/Python - - PythonInfoDict - - PythonExecutable - /Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python - PythonLongVersion - 2.7.10 (v2.7.10:15c95b7d81dc, May 23 2015, 09:33:12) -[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] - PythonShortVersion - 2.7 - py2app - - alias - - template - app - version - 0.9 - - - - diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py b/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py deleted file mode 100644 index fbb83f5dc..000000000 --- a/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -This is a setup.py script generated by py2applet - -Usage: - python setup.py py2app -""" - -from setuptools import setup - -APP = ['LBRYURIHandler.py'] -DATA_FILES = [] -OPTIONS = {'argv_emulation': True} - -setup( - app=APP, - data_files=DATA_FILES, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], -) diff --git a/setup_osx.py b/setup_osx.py index 03490e622..052b51235 100644 --- a/setup_osx.py +++ b/setup_osx.py @@ -1,7 +1,7 @@ import os from setuptools import setup -APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'LBRYOSXStatusBar.py')] +APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'Apps', 'LBRYOSXStatusBar.py')] DATA_FILES = [] OPTIONS = { 'argv_emulation': True, diff --git a/setup_uri_handler.py b/setup_uri_handler.py new file mode 100644 index 000000000..37a38826e --- /dev/null +++ b/setup_uri_handler.py @@ -0,0 +1,22 @@ +from setuptools import setup +import os + +APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'Apps', 'LBRYURIHandler.py')] +DATA_FILES = [] +OPTIONS = {'argv_emulation': True, + 'plist': { + 'CFBundleURLTypes': [ + { + 'CFBundleURLTypes': 'LBRYURIHandler', + 'CFBundleURLSchemes': ['lbry'] + } + ] + } + } + +setup( + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +) \ No newline at end of file From d76feeb3206cfd7c76927930ddf6e4ad256ca562 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 3 Mar 2016 13:55:03 -0500 Subject: [PATCH 048/462] use master branches --- lbrynet/lbrynet_daemon/scripts/restart_daemon.sh | 2 +- lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh b/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh index 74d4ab102..7353c0a20 100644 --- a/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh +++ b/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh @@ -1,4 +1,4 @@ #!/bin/sh echo "Restarting lbrynet-daemon" -sudo lbrynet-daemon \ No newline at end of file +lbrynet-daemon \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh index 3ce6dd15b..fbb7d2b17 100755 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -82,7 +82,7 @@ if [ -d "$lbrynet_directory" ]; then echo "Downloading LBRYum update..." - git clone -b development --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null cd lbryum echo "Installing update..." @@ -101,7 +101,7 @@ if [ -d "$lbrynet_directory" ]; then echo "Downloading LBRYum..." - git clone -b development --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null cd lbryum echo "Installing..." @@ -128,7 +128,7 @@ if [ -d "$lbrynet_directory" ]; then echo "Downloading LBRYnet update" - git clone -b development --depth 1 https://github.com/lbryio/lbry.git &>/dev/null + git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry echo "Installing update..." @@ -147,7 +147,7 @@ if [ -d "$lbrynet_directory" ]; then echo "Downloading LBRYnet..." - git clone -b development --depth 1 https://github.com/lbryio/lbry.git &>/dev/null + git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry echo "Installing..." From 27ccb37646ef06cb5df485227d5a7061966d94f0 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 8 Mar 2016 12:15:49 -0500 Subject: [PATCH 049/462] update daemon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check that managed files still exist, if they don’t remove them from the manager --- lbrynet/lbryfilemanager/LBRYFileManager.py | 29 ++++++++++-- .../lbrynet_daemon/Apps/LBRYOSXStatusBar.py | 4 +- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 23 +++++---- lbrynet/lbrynet_daemon/LBRYDaemon.py | 47 ++++++++++++++----- lbrynet/lbrynet_daemon/LBRYDownloader.py | 5 +- 5 files changed, 76 insertions(+), 32 deletions(-) diff --git a/lbrynet/lbryfilemanager/LBRYFileManager.py b/lbrynet/lbryfilemanager/LBRYFileManager.py index ed4c75a79..fb14ec199 100644 --- a/lbrynet/lbryfilemanager/LBRYFileManager.py +++ b/lbrynet/lbryfilemanager/LBRYFileManager.py @@ -3,17 +3,19 @@ Keep track of which LBRY Files are downloading and store their LBRY File specifi """ import logging - -from twisted.enterprise import adbapi - +import datetime import os import sys + +from twisted.internet.task import LoopingCall +from twisted.enterprise import adbapi +from twisted.internet import defer, task, reactor +from twisted.python.failure import Failure + from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.core.PaymentRateManager import PaymentRateManager -from twisted.internet import defer, task, reactor -from twisted.python.failure import Failure from lbrynet.cryptstream.client.CryptStreamDownloader import AlreadyStoppedError, CurrentlyStoppingError from lbrynet.core.sqlite_helpers import rerun_if_locked @@ -32,6 +34,7 @@ class LBRYFileManager(object): self.sd_identifier = sd_identifier self.lbry_files = [] self.sql_db = None + self.check_exists_loop = LoopingCall(self.check_files_exist) if sys.platform.startswith("darwin"): self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') else: @@ -39,11 +42,25 @@ class LBRYFileManager(object): log.debug("Download directory for LBRYFileManager: %s", str(self.download_directory)) def setup(self): + self.check_exists_loop.start(1) + d = self._open_db() d.addCallback(lambda _: self._add_to_sd_identifier()) d.addCallback(lambda _: self._start_lbry_files()) return d + def check_files_exist(self): + def _disp(deleted_files): + if deleted_files[0][0]: + for file in bad_files: + print "[" + str(datetime.datetime.now()) + "] Detected " + file.file_name + " was deleted, removing from file manager" + + bad_files = [lbry_file for lbry_file in self.lbry_files + if lbry_file.completed == True and + os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) == False] + d = defer.DeferredList([self.delete_lbry_file(lbry_file) for lbry_file in bad_files], consumeErrors=True) + d.addCallback(lambda files: _disp(files) if len(files) else defer.succeed(None)) + def get_lbry_file_status(self, lbry_file): return self._get_lbry_file_status(lbry_file.rowid) @@ -152,6 +169,8 @@ class LBRYFileManager(object): return defer.fail(Failure(ValueError("Could not find that LBRY file"))) def stop(self): + self.check_exists_loop.stop() + ds = [] def wait_for_finished(lbry_file, count=2): diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py index c55fed0d2..5ca433f72 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py @@ -39,7 +39,7 @@ class DaemonStatusBarApp(rumps.App): daemon = xmlrpclib.ServerProxy("http://localhost:7080/") try: daemon.is_running() - webbrowser.open("lbry://lbry") + webbrowser.get('safari').open("lbry://lbry") except: try: rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) @@ -51,7 +51,7 @@ class DaemonStatusBarApp(rumps.App): daemon = xmlrpclib.ServerProxy("http://localhost:7080/") try: daemon.is_running() - webbrowser.open("lbry://settings") + webbrowser.get('safari').open("lbry://settings") except: rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index eea94cb94..1cc1bf3f8 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -30,22 +30,25 @@ def main(args): else: r = daemon.get(args[0][7:]) - path = r['path'] - if path[0] != '/': - path = '/' + path + if r[0] == 200: + path = r[1]['path'] + if path[0] != '/': + path = '/' + path - filename = os.path.basename(path) - extension = os.path.splitext(filename)[1] + filename = os.path.basename(path) + extension = os.path.splitext(filename)[1] - if extension in ['mp4', 'flv', 'mov']: - html = render_video(path) - daemon.render_html(html) + if extension in ['mp4', 'flv', 'mov']: + html = render_video(path) + daemon.render_html(html) + else: + webbrowser.get('safari').open('file://' + str(path)) else: - webbrowser.open('file://' + str(path)) + webbrowser.get('safari').open('http://lbry.io/get') except: - webbrowser.open('http://lbry.io/get') + webbrowser.get('safari').open('http://lbry.io/get') if __name__ == "__main__": diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 1e68f0f76..410da8a81 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -41,21 +41,21 @@ from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager log = logging.getLogger(__name__) - - # logging.basicConfig(level=logging.DEBUG) # TODO add login credentials in a conf file - -# issues with delete: -# TODO when stream is stopped the generated file is deleted - # functions to add: # TODO send credits to address # TODO alert if your copy of a lbry file is out of date with the name record +BAD_REQUEST = (400, "Bad request") +NOT_FOUND = (404, "Not found") + +OK_CODE = 200 + + class LBRYDaemon(xmlrpc.XMLRPC): """ LBRYnet daemon @@ -556,8 +556,11 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self._check_history(name) d.addCallback(lambda lbry_file: _get_stream(name) if not lbry_file else _disp_file(lbry_file)) d.addCallback(lambda _: self._check_history(name)) - d.addCallback(lambda lbry_file: self._path_from_lbry_file(lbry_file) if lbry_file else 'Not found') - d.addErrback(lambda err: str(err)) + d.addCallback(lambda lbry_file: (OK_CODE, {'stream_hash': lbry_file.stream_hash, + 'path': os.path.join(self.download_directory, + lbry_file.file_name)}) + if lbry_file else NOT_FOUND) + d.addErrback(lambda _: NOT_FOUND) return d @@ -612,6 +615,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): def finish_deletion(lbry_file): d = lbry_file.delete_data() d.addCallback(lambda _: _delete_stream_data(lbry_file)) + d.addCallback(lambda _: _delete_file(lbry_file)) return d def _delete_stream_data(lbry_file): @@ -621,6 +625,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) return d + def _delete_file(lbry_file): + os.remove(os.path.join(self.download_directory, lbry_file.file_name)) + d.addCallback(lambda _: finish_deletion(lbry_file)) return d @@ -807,7 +814,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): if name: d = self._download_name(name) else: - d = defer.succeed('No name provided') + d = defer.succeed(BAD_REQUEST) return d def xmlrpc_stop_lbry_file(self, stream_hash): @@ -875,8 +882,12 @@ class LBRYDaemon(xmlrpc.XMLRPC): return err d = defer.Deferred() - d.addCallback(lambda _: webbrowser.open( - "file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) + if sys.platform == 'darwin': + d.addCallback(lambda _: webbrowser.get('safari').open( + "file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) + else: + d.addCallback(lambda _: webbrowser.open( + "file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) d.addErrback(_disp_err) d.callback(None) @@ -967,13 +978,16 @@ class LBRYDaemon(xmlrpc.XMLRPC): return d def xmlrpc_publish(self, metadata): - metadata = json.loads(metadata) + try: + metadata = json.loads(metadata) + except: + return defer.succeed(BAD_REQUEST) required = ['name', 'file_path', 'bid'] for r in required: if not r in metadata.keys(): - return defer.fail() + return defer.succeed(BAD_REQUEST) # if not os.path.isfile(metadata['file_path']): # return defer.fail() @@ -1023,6 +1037,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): p = Publisher(self.session, self.lbry_file_manager, self.session.wallet) d = p.start(name, file_path, bid, title, description, thumbnail, key_fee, key_fee_address, content_license) + d.addCallback(lambda msg: (OK_CODE, msg)) return d @@ -1145,6 +1160,12 @@ class LBRYDaemon(xmlrpc.XMLRPC): else: return "Status bar not implemented on non OS X" + def xmlrpc___dir__(self): + return ['is_running', 'get_settings', 'set_settings', 'start_fetcher', 'stop_fetcher', 'fetcher_status', + 'get_balance', 'stop', 'get_lbry_files', 'resolve_name', 'get', 'search_nametrie', + 'delete_lbry_file', 'check', 'publish', 'abandon_name', 'get_name_claims', + 'get_time_behind_blockchain', 'get_new_address', 'toggle_fetcher_verbose', 'check_for_new_version'] + def stop(): daemon = xmlrpclib.ServerProxy("http://localhost:7080/") diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 4ea161c81..c5bfea81f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -7,6 +7,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.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory log = logging.getLogger(__name__) @@ -62,8 +63,8 @@ class GetStream(object): d = defer.Deferred(None) d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) - d.addCallback(lambda metadata: - metadata.factories[1].make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager)) + d.addCallback(lambda metadata: (next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), metadata)) + d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager)) d.addErrback(lambda err: err.trap(defer.CancelledError)) d.addErrback(lambda err: log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback())) d.addCallback(self._start_download) From 5bf895eefe958b06f641f17c03f998896d3ed8af Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Wed, 9 Mar 2016 12:26:07 -0500 Subject: [PATCH 050/462] update documentation to reflect using lbryum by default --- INSTALL.md | 38 ++--------- README.md | 2 +- RUNNING.md | 75 ++++++++++++---------- lbrynet/lbrynet_console/ConsoleControl.py | 12 +--- lbrynet/lbrynet_console/ControlHandlers.py | 8 +-- 5 files changed, 55 insertions(+), 80 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index a964c8a8f..6dc552bcf 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,38 +1,14 @@ -Prerequisites -------------- - -To use the LBRYWallet, which enables spending and accepting LBRYcrds in exchange for data, the -LBRYcrd application (insert link to LBRYcrd website here) must be installed and running. If -this is not desired, the testing client can be used to simulate trading points, which is -built into LBRYnet. - -on Ubuntu: - -``` -sudo apt-get install libgmp3-dev build-essential python-dev python-pip -``` - -Getting the source ------------------- - -Don't you already have it? - -Setting up the environment +#### Installing lbrynet on Linux -------------------------- -It's recommended that you use a virtualenv +The following packages are necessary (the following are their names on Ubuntu): +libgmp3-dev build-essential python2.7 python2.7-dev python-pip -``` -sudo apt-get install python-virtualenv -cd -virtualenv . -source bin/activate -``` +To install them on Ubuntu: +sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip -(to deactivate the virtualenv, enter 'deactivate') - -``` -python setup.py install +python setup.py build bdist_egg +sudo python setup.py install ``` this will install all of the libraries and a few applications diff --git a/README.md b/README.md index d9f7cd98e..dc8b5c81f 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ See [INSTALL](INSTALL.md) ## Developers Documentation: doc.lbry.io -Source code: trac.lbry.io/browser +Source code: https://github.com/lbryio/lbry To contribute to the development of LBRYnet or lbrynet-console, contact jimmy@lbry.io diff --git a/RUNNING.md b/RUNNING.md index 137487360..b9f60acc9 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -9,20 +9,38 @@ Download the file https://raw.githubusercontent.com/lbryio/lbry-setup/master/lbr Once it's done building, type: ``` -./lbrycrd/src/lbrycrdd -server -daemon -lbrynet-gui +lbrynet-console ``` -A window should show up with an entry box +A console application will load, and after a moment you will be presented with a ">" signifying +that the console is ready for commands. -Type wonderfullife into the box, hit go, and choose to stream or save +If it's your first time running lbrynet-console, you should be given some credits to test the +network. If they don't show up within a minute or two, let us know, and we'll send you some. -To stop lbrycrdd: `./lbrycrd/src/lbrycrd-cli stop` +After your credits have shown up, type + +``` +get wonderfullife +``` + +into the prompt and hit enter. + +You will be asked if you want to cancel, change options, save, and perhaps stream the file. + +Enter 's' for save and then hit enter. + +The file should start downloading. Enter the command 'status' to check on the status of files +that are in the process of being downloaded. + +To stop lbrynet-console, enter the command 'exit'. ## Slightly longer install guide -### Installing lbrycrd from source +### Installing lbrycrd, the full blockchain client + +Note: this process takes upwards of an hour and is not necessary to use lbrynet. ``` git clone --depth=1 -b alpha https://github.com/lbryio/lbrycrd.git @@ -89,11 +107,21 @@ sudo python setup.py install ## Slightly longer running guide -###In order to use lbrynet-console or lbrynet-gui, lbyrcrd must be running. +### lbrynet-console can be set to use lbrycrdd instead of the built in lightweight client. -### Running lbrycrd +To run lbrynet-console with lbrycrdd: -If you ran the easy install script, the lbrycrd folder will be in the directory you ran lbry_setup.sh from. Otherwise it is the root of the cloned lbrycrd repository. Go to that directory. +``` +lbrynet-console +``` + +If lbrycrdd is not already running, lbrynet will launch it at that path, and will shut it down +when lbrynet exits. If lbrycrdd is already running, lbrynet will not launch it and will not +shut it down, but it will connect to it and use it. + +### Running lbrycrdd manually + +From the lbrycrd directory, run: ``` ./src/lbrycrdd -server -daemon @@ -105,37 +133,14 @@ If you want to mine LBC, also use the flag '-gen', so: ./src/lbrycrdd -server -daemon -gen ``` +Warning: This will put a heavy load on your CPU + It will take a few minutes for your client to download the whole block chain. -lbrycrdd must be running in order for lbrynet to function. - -To shut lbrycrdd down: from the lbrycrd directory, run +To shut lbrycrdd down: from the lbrycrd directory, run: ``` ./src/lbrycrd-cli stop ``` -### Option 1) Running lbrynet-console - -If you used the virtualenv instructions above, make sure the virtualenv is still active. If not, reactivate it according to the instructions above, under "Installing lbrynet from source" - -In your terminal: `lbrynet-console` - -You should be presented with a prompt. - -Watch It's a Wonderful Life via LBRY - -Type into the prompt: `get wonderfullife` - -To shut it down, press ctrl-c at any time or enter `exit` into the prompt. - -### Option 2) Running lbrynet-gui - -If you used the virtualenv instructions above, make sure the virtualenv is still active. If not, reactivate it according to the instructions above, under "Installing lbrynet from source" - -In your terminal: `lbrynet-gui` - -A window should pop up with an entry box. Type `wonderfullife` into the box, hit go, and then choose to save it or stream it. -Enjoy! - Any questions or problems, email jimmy@lbry.io diff --git a/lbrynet/lbrynet_console/ConsoleControl.py b/lbrynet/lbrynet_console/ConsoleControl.py index 8b6552e6e..be52da843 100644 --- a/lbrynet/lbrynet_console/ConsoleControl.py +++ b/lbrynet/lbrynet_console/ConsoleControl.py @@ -29,16 +29,10 @@ class ConsoleControl(basic.LineReceiver): def send_initial_prompt(self): self.sendLine("") - self.sendLine("In this early release of LBRY, some functions will not work\n" - "until you have downloaded a full copy of our blockchain. To\n" - "check whether you've caught up with the blockchain, use the\n" - "command 'get-blockchain-status'.\n\n" - "If, for example, you are unable to download some files or\n" - "your balance is showing 0 when you know it shouldn't be, it\n" - "is likely that the culprit is the blockchain.\n\n" - "You should have received 1000 LBC the first time you ran\n" + self.sendLine("You should have received 1000 LBC the first time you ran\n" "this program. If you did not, let us know! But first give\n" - "them a couple of minutes to show up.\n\n" + "them a moment to show up. The block time is currently 30\n" + "seconds so they should show up within a couple of minutes.\n\n" "Welcome to lbrynet-console!") self.sendLine("") self.sendLine("Enter a command. Try 'get wonderfullife' or 'help' to see more options.") diff --git a/lbrynet/lbrynet_console/ControlHandlers.py b/lbrynet/lbrynet_console/ControlHandlers.py index 6cac5da64..6542505c2 100644 --- a/lbrynet/lbrynet_console/ControlHandlers.py +++ b/lbrynet/lbrynet_console/ControlHandlers.py @@ -323,7 +323,7 @@ class GetWalletBalances(CommandHandler): def _show_time_behind_blockchain(self, rounded_time): if rounded_time.unit >= RoundedTime.HOUR: self.console.sendLine("\n\nYour balance may be out of date. This application\n" - "is %s behind the LBC blockchain. It should take a few minutes to\n" + "is %s behind the LBC blockchain. It may take a few minutes to\n" "catch up the first time you run this early version of LBRY.\n" "Please be patient =).\n\n" % str(rounded_time)) else: @@ -797,7 +797,7 @@ class AddStream(CommandHandler): self.console.sendLine("\nThis application is %s behind the LBC blockchain, so some of your\n" "funds may not be available. Use 'get-blockchain-status' to check if\n" "your application is up to date with the blockchain.\n\n" - "It should take a few minutes to catch up the first time you run this\n" + "It may take a few minutes to catch up the first time you run this\n" "early version of LBRY. Please be patient =).\n\n" % str(rounded_time)) def _log_recent_blockchain_time_error_download(self, err): @@ -913,7 +913,7 @@ class AddStreamFromLBRYcrdName(AddStreamFromHash): self.console.sendLine("\nThis application is %s behind the LBC blockchain, which may be\n" "preventing this name from being resolved correctly. Use 'get-blockchain-status'\n" "to check if your application is up to date with the blockchain.\n\n" - "It should take a few minutes to catch up the first time you run\n" + "It may take a few minutes to catch up the first time you run\n" "this early version of LBRY. Please be patient =).\n\n" % str(rounded_time)) else: self.console.sendLine("\n") @@ -1850,7 +1850,7 @@ class Publish(CommandHandler): if rounded_time.unit >= RoundedTime.HOUR: self.console.sendLine("This application is %s behind the LBC blockchain\n" "and therefore may not have all of the funds you expect\n" - "available at this time. It should take a few minutes to\n" + "available at this time. It may take a few minutes to\n" "catch up the first time you run this early version of LBRY.\n" "Please be patient =).\n" % str(rounded_time)) From 45f9a10bfbbdcf0ba22fc2431f1e061353106075 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sat, 12 Mar 2016 14:08:15 -0500 Subject: [PATCH 051/462] hide exceptions that show up in the dht and due to bad metadata in the blockchain --- lbrynet/core/LBRYcrdWallet.py | 4 ++-- lbrynet/dht/encoding.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 46279a2e4..09bc1e7bc 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -294,7 +294,7 @@ class LBRYWallet(object): value = result['value'] try: value_dict = json.loads(value) - except ValueError: + except (ValueError, TypeError): return Failure(InvalidStreamInfoError(name)) known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', 'content_license'] @@ -395,7 +395,7 @@ class LBRYWallet(object): if 'name' in claim and str(claim['name']) == name and 'value' in claim: try: value_dict = json.loads(claim['value']) - except ValueError: + except (ValueError, TypeError): return None if 'stream_hash' in value_dict and str(value_dict['stream_hash']) == sd_hash: if 'is controlling' in claim and claim['is controlling']: diff --git a/lbrynet/dht/encoding.py b/lbrynet/dht/encoding.py index a57747628..e0b42ee4f 100644 --- a/lbrynet/dht/encoding.py +++ b/lbrynet/dht/encoding.py @@ -99,7 +99,10 @@ class Bencode(Encoding): """ if len(data) == 0: raise DecodeError, 'Cannot decode empty string' - return self._decodeRecursive(data)[0] + try: + return self._decodeRecursive(data)[0] + except ValueError as e: + raise DecodeError, e.message @staticmethod def _decodeRecursive(data, startIndex=0): From e91f34f09b5cb30e65c11630e798c8dd432997ab Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sat, 12 Mar 2016 14:25:46 -0500 Subject: [PATCH 052/462] change metadata structure to use sources key --- lbrynet/core/LBRYcrdWallet.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 09bc1e7bc..ef7c95baf 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -297,10 +297,19 @@ class LBRYWallet(object): except (ValueError, TypeError): return Failure(InvalidStreamInfoError(name)) known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', - 'content_license'] + 'content_license', 'sources'] + known_sources = ['lbry_sd_hash'] for field in known_fields: if field in value_dict: - r_dict[field] = value_dict[field] + if field == 'sources': + for source in known_sources: + if source in value_dict[field]: + if source == 'lbry_sd_hash': + r_dict['stream_hash'] = value_dict[field][source] + else: + r_dict[source] = value_dict[field][source] + else: + r_dict[field] = value_dict[field] if 'stream_hash' in r_dict and 'txid' in result: d = self._save_name_metadata(name, r_dict['stream_hash'], str(result['txid'])) else: @@ -312,8 +321,8 @@ class LBRYWallet(object): return Failure(UnknownNameError(name)) def claim_name(self, name, sd_hash, amount, description=None, key_fee=None, - key_fee_address=None, thumbnail=None, content_license=None): - value = {"stream_hash": sd_hash} + key_fee_address=None, thumbnail=None, content_license=None): + value = {"sources": {'lbry_sd_hash': sd_hash}} if description is not None: value['description'] = description if key_fee is not None: @@ -397,7 +406,12 @@ class LBRYWallet(object): value_dict = json.loads(claim['value']) except (ValueError, TypeError): return None - if 'stream_hash' in value_dict and str(value_dict['stream_hash']) == sd_hash: + claim_sd_hash = None + if 'stream_hash' in value_dict: + claim_sd_hash = str(value_dict['stream_hash']) + if 'sources' in value_dict and 'lbrynet_sd_hash' in value_dict['sources']: + claim_sd_hash = str(value_dict['sources']['lbry_sd_hash']) + if claim_sd_hash is not None and claim_sd_hash == sd_hash: if 'is controlling' in claim and claim['is controlling']: return name, "valid" if claim['in claim trie']: From 14ab2b52e94afc9c1a81b0932db224b25c52289a Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sat, 12 Mar 2016 14:42:57 -0500 Subject: [PATCH 053/462] create fee field in metadata --- lbrynet/core/LBRYcrdWallet.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index ef7c95baf..153b27e56 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -297,8 +297,9 @@ class LBRYWallet(object): except (ValueError, TypeError): return Failure(InvalidStreamInfoError(name)) known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', - 'content_license', 'sources'] + 'content_license', 'sources', 'fee'] known_sources = ['lbry_sd_hash'] + known_fee_types = {'LBC': ['amount', 'address']} for field in known_fields: if field in value_dict: if field == 'sources': @@ -308,6 +309,18 @@ class LBRYWallet(object): r_dict['stream_hash'] = value_dict[field][source] else: r_dict[source] = value_dict[field][source] + elif field == 'fee': + fee = value_dict['fee'] + if 'type' in fee: + if fee['type'] in known_fee_types: + fee_fields = known_fee_types[fee['type']] + if all([f in fee for f in fee_fields]): + r_dict['key_fee'] = fee['amount'] + r_dict['key_fee_address'] = fee['address'] + else: + for f in ['key_fee', 'key_fee_address']: + if f in r_dict: + del r_dict[f] else: r_dict[field] = value_dict[field] if 'stream_hash' in r_dict and 'txid' in result: @@ -325,10 +338,8 @@ class LBRYWallet(object): value = {"sources": {'lbry_sd_hash': sd_hash}} 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 + if key_fee is not None and key_fee_address is not None: + value['fee'] = {'type': 'LBC', 'amount': key_fee, 'address': key_fee_address} if thumbnail is not None: value['thumbnail'] = thumbnail if content_license is not None: From dff0fcb1fd9d9d1303036cd361be3d097bd7b4a4 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sat, 12 Mar 2016 14:58:58 -0500 Subject: [PATCH 054/462] ensure the lbryum wallet is caught up before giving the prompt --- lbrynet/core/LBRYcrdWallet.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 153b27e56..88a9686ef 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -869,7 +869,7 @@ class LBRYumWallet(LBRYWallet): def setup_network(): self.config = SimpleConfig() self.network = Network(self.config) - alert.info("Starting the wallet...") + alert.info("Loading the wallet...") return defer.succeed(self.network.start()) d = setup_network() @@ -882,7 +882,6 @@ class LBRYumWallet(LBRYWallet): return False start_check.stop() if self.network.is_connected(): - alert.info("Wallet started.") network_start_d.callback(True) else: network_start_d.errback(ValueError("Failed to connect to network.")) @@ -926,9 +925,24 @@ class LBRYumWallet(LBRYWallet): wallet.synchronize() self.wallet = wallet + blockchain_caught_d = defer.Deferred() + + def check_caught_up(): + local_height = self.network.get_local_height() + remote_height = self.network.get_server_height() + + if remote_height != 0 and remote_height - local_height <= 5: + alert.info('Wallet loaded.') + catch_up_check.stop() + blockchain_caught_d.callback(True) + + catch_up_check = task.LoopingCall(check_caught_up) + d = threads.deferToThread(get_wallet) d.addCallback(self._save_wallet) d.addCallback(lambda _: self.wallet.start_threads(self.network)) + d.addCallback(lambda _: catch_up_check.start(.1)) + d.addCallback(lambda _: blockchain_caught_d) return d def _get_cmd_runner(self): From 42df2fe242b3a9e540bc78074026f619c5f1ebeb Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 14 Mar 2016 12:30:22 -0400 Subject: [PATCH 055/462] update daemon convert to jsonrpc, fix a few issues, remove hacky functions --- lbrynet/conf.py | 12 +- lbrynet/core/LBRYcrdWallet.py | 8 +- lbrynet/lbryfilemanager/LBRYFileManager.py | 19 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 606 +++++++++----------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 51 ++ lbrynet/lbrynet_daemon/LBRYDownloader.py | 39 +- lbrynet/lbrynet_daemon/LBRYPublisher.py | 4 +- setup.py | 16 +- 8 files changed, 398 insertions(+), 357 deletions(-) create mode 100644 lbrynet/lbrynet_daemon/LBRYDaemonControl.py diff --git a/lbrynet/conf.py b/lbrynet/conf.py index c172884e6..8af383387 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -26,4 +26,14 @@ KNOWN_DHT_NODES = [('104.236.42.182', 4000)] POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' #POINTTRADER_SERVER = 'http://127.0.0.1:2424' -CRYPTSD_FILE_EXTENSION = ".cryptsd" \ No newline at end of file +CRYPTSD_FILE_EXTENSION = ".cryptsd" + +API_INTERFACE = "localhost" +API_ADDRESS = "lbryapi" +API_PORT = 5279 +ICON_PATH = "app.icns" +APP_NAME = "LBRY" +DEFAULT_WALLET = "lbryum" + +API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRESS) +PROTOCOL_PREFIX = "lbry" \ No newline at end of file diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 46279a2e4..543551c36 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -874,13 +874,15 @@ class LBRYumWallet(LBRYWallet): d = defer.Deferred() def check_stopped(): - if self.network.is_connected(): - return False + if self.network: + if self.network.is_connected(): + return False stop_check.stop() self.network = None d.callback(True) - self.network.stop() + if self.network: + self.network.stop() stop_check = task.LoopingCall(check_stopped) stop_check.start(.1) diff --git a/lbrynet/lbryfilemanager/LBRYFileManager.py b/lbrynet/lbryfilemanager/LBRYFileManager.py index fb14ec199..f741558dc 100644 --- a/lbrynet/lbryfilemanager/LBRYFileManager.py +++ b/lbrynet/lbryfilemanager/LBRYFileManager.py @@ -3,9 +3,9 @@ Keep track of which LBRY Files are downloading and store their LBRY File specifi """ import logging -import datetime import os import sys +from datetime import datetime from twisted.internet.task import LoopingCall from twisted.enterprise import adbapi @@ -28,12 +28,13 @@ class LBRYFileManager(object): Keeps track of currently opened LBRY Files, their options, and their LBRY File specific metadata. """ - def __init__(self, session, stream_info_manager, sd_identifier): + def __init__(self, session, stream_info_manager, sd_identifier, delete_data=False): self.session = session self.stream_info_manager = stream_info_manager self.sd_identifier = sd_identifier self.lbry_files = [] self.sql_db = None + self.delete_data = delete_data self.check_exists_loop = LoopingCall(self.check_files_exist) if sys.platform.startswith("darwin"): self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') @@ -42,7 +43,7 @@ class LBRYFileManager(object): log.debug("Download directory for LBRYFileManager: %s", str(self.download_directory)) def setup(self): - self.check_exists_loop.start(1) + self.check_exists_loop.start(10) d = self._open_db() d.addCallback(lambda _: self._add_to_sd_identifier()) @@ -53,7 +54,14 @@ class LBRYFileManager(object): def _disp(deleted_files): if deleted_files[0][0]: for file in bad_files: - print "[" + str(datetime.datetime.now()) + "] Detected " + file.file_name + " was deleted, removing from file manager" + log.info("[" + str(datetime.now()) + "] Detected " + file.file_name + " was deleted, removing from file manager") + + def _delete_stream_data(lbry_file): + s_h = lbry_file.stream_hash + d = self.get_count_for_stream_hash(s_h) + # TODO: could possibly be a timing issue here + d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) + return d bad_files = [lbry_file for lbry_file in self.lbry_files if lbry_file.completed == True and @@ -61,6 +69,9 @@ class LBRYFileManager(object): d = defer.DeferredList([self.delete_lbry_file(lbry_file) for lbry_file in bad_files], consumeErrors=True) d.addCallback(lambda files: _disp(files) if len(files) else defer.succeed(None)) + if self.delete_data: + d2 = defer.DeferredList([_delete_stream_data(lbry_file) for lbry_file in bad_files], consumeErrors=True) + def get_lbry_file_status(self, lbry_file): return self._get_lbry_file_status(lbry_file.rowid) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 410da8a81..67b5c8c9b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,23 +1,21 @@ import locale import os import sys -import json +import simplejson as json import binascii -import webbrowser -import xmlrpclib import subprocess import logging -import argparse -import pwd import requests -from twisted.web import xmlrpc, server -from twisted.internet import defer, threads, reactor, error +from twisted.web import server, resource, static +from twisted.internet import defer, threads, error, reactor +from txjsonrpc.web import jsonrpc from datetime import datetime from decimal import Decimal from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen +from appdirs import user_data_dir from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory @@ -33,6 +31,7 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE +from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet @@ -41,28 +40,30 @@ from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager log = logging.getLogger(__name__) -# logging.basicConfig(level=logging.DEBUG) +BAD_REQUEST = 400 +NOT_FOUND = 404 +OK_CODE = 200 # TODO add login credentials in a conf file -# functions to add: -# TODO send credits to address # TODO alert if your copy of a lbry file is out of date with the name record -BAD_REQUEST = (400, "Bad request") -NOT_FOUND = (404, "Not found") - -OK_CODE = 200 +class Bunch: + def __init__(self, params): + self.__dict__.update(params) -class LBRYDaemon(xmlrpc.XMLRPC): +class LBRYDaemon(jsonrpc.JSONRPC): """ - LBRYnet daemon + LBRYnet daemon, a jsonrpc interface to lbry functions """ + isLeaf = True def setup(self, wallet_type, check_for_updates): def _set_vars(wallet_type, check_for_updates): + reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) + self.fetcher = None self.current_db_revision = 1 self.run_server = True @@ -71,7 +72,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): if sys.platform != "darwin": self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: - self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") + self.db_dir = user_data_dir("LBRY") + # self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 self.dht_node_port = 4444 @@ -82,7 +84,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") elif sys.platform == "darwin": self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') - self.wallet_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrycrd") + # self.wallet_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrycrd") + self.wallet_dir = user_data_dir("LBRY") else: self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') @@ -129,15 +132,13 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _disp_startup(): if self.restart_message: - print self.restart_message + log.info(self.restart_message) else: - print "Started LBRYnet daemon" - print "The daemon can be shut down by running 'stop-lbrynet-daemon' in a terminal" - log.info('[' + str(datetime.now()) + '] Started lbrynet-daemon') + log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") return defer.succeed(None) - log.info('[' + str(datetime.now()) + '] Starting lbrynet-daemon') + log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") d = defer.Deferred() d.addCallback(lambda _: _set_vars(wallet_type, check_for_updates)) @@ -160,6 +161,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): return defer.succeed(None) + def _initial_setup(self): + return NotImplemented + def _update(self): def _check_for_updater(): if os.path.isdir("/Applications/LBRY Updater.app"): @@ -323,7 +327,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): return dl def _shutdown(self): - print 'Closing lbrynet session' + log.info("Closing lbrynet session") d = self._stop_server() if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) @@ -339,7 +343,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): return defer.succeed(None) def _setup_data_directory(self): - print "Loading databases..." + log.info("Loading databases...") if self.created_data_dir: db_revision = open(os.path.join(self.db_dir, "db_revision"), mode='w') db_revision.write(str(self.current_db_revision)) @@ -356,7 +360,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): old_revision = int(open(db_revision_file).read().strip()) if old_revision < self.current_db_revision: from lbrynet.db_migrator import dbmigrator - print "Upgrading your databases..." + log.info("Upgrading your databases...") d = threads.deferToThread(dbmigrator.migrate_db, self.db_dir, old_revision, self.current_db_revision) def print_success(old_dirs): @@ -367,7 +371,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): success_string += old_dir if i + 1 < len(old_dir): success_string += ", " - print success_string + log.info(success_string) d.addCallback(print_success) return d @@ -396,7 +400,10 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self.lbry_file_metadata_manager.setup() def set_lbry_file_manager(): - self.lbry_file_manager = LBRYFileManager(self.session, self.lbry_file_metadata_manager, self.sd_identifier) + self.lbry_file_manager = LBRYFileManager(self.session, + self.lbry_file_metadata_manager, + self.sd_identifier, + delete_data=True) return self.lbry_file_manager.setup() d.addCallback(lambda _: set_lbry_file_manager()) @@ -407,12 +414,11 @@ class LBRYDaemon(xmlrpc.XMLRPC): def get_default_data_rate(): d = self.settings.get_default_data_payment_rate() d.addCallback(lambda rate: {"default_data_payment_rate": rate if rate is not None else - MIN_BLOB_DATA_PAYMENT_RATE}) + MIN_BLOB_DATA_PAYMENT_RATE}) return d def get_wallet(): if self.wallet_type == "lbrycrd": - print "Using lbrycrd wallet" log.info("Using lbrycrd wallet") lbrycrdd_path = None if self.start_lbrycrdd is True: @@ -422,11 +428,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.lbrycrd_conf, lbrycrdd_path=lbrycrdd_path)) elif self.wallet_type == "lbryum": - print "Using lbryum wallet" log.info("Using lbryum wallet") d = defer.succeed(LBRYumWallet(self.db_dir)) elif self.wallet_type == "ptc": - print "Using PTC wallet" log.info("Using PTC wallet") d = defer.succeed(PTCWallet(self.db_dir)) else: @@ -509,7 +513,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): for line in conf: if len(line.strip()) and line.strip()[0] != "#": self.lbrycrdd_path = line.strip() - print self.lbrycrdd_path d.addCallback(load_lbrycrdd_path) return d @@ -535,13 +538,13 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _download_name(self, name): def _disp_file(file): - print '[' + str(datetime.now()) + ']' + ' Already downloaded: ' + str(file.stream_hash) + log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(file.stream_hash)) d = self._path_from_lbry_file(file) return d def _get_stream(name): def _disp(stream): - print '[' + str(datetime.now()) + ']' + ' Start stream: ' + stream['stream_hash'] + log.info("[" + str(datetime.now()) + "] Start stream: " + stream['stream_hash']) return stream d = self.session.wallet.get_stream_info_for_name(name) @@ -556,12 +559,11 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self._check_history(name) d.addCallback(lambda lbry_file: _get_stream(name) if not lbry_file else _disp_file(lbry_file)) d.addCallback(lambda _: self._check_history(name)) - d.addCallback(lambda lbry_file: (OK_CODE, {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, - lbry_file.file_name)}) - if lbry_file else NOT_FOUND) - d.addErrback(lambda _: NOT_FOUND) - + d.addCallback(lambda lbry_file: ({'stream_hash': lbry_file.stream_hash, + 'path': os.path.join(self.download_directory, + lbry_file.file_name)}) + if lbry_file else defer.fail(NOT_FOUND)) + d.addErrback(lambda err: defer.fail(NOT_FOUND)) return d def _resolve_name(self, name): @@ -595,11 +597,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): stream_hash = info['stream_hash'] path = os.path.join(self.blobfile_dir, stream_hash) if os.path.isfile(path): - print "[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash log.info("[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash) return defer.succeed(_get_lbry_file(path)) else: - print "[" + str(datetime.now()) + "] Search for lbry_file didn't return anything" log.info("[" + str(datetime.now()) + "] Search for lbry_file didn't return anything") return defer.succeed(False) @@ -615,7 +615,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): def finish_deletion(lbry_file): d = lbry_file.delete_data() d.addCallback(lambda _: _delete_stream_data(lbry_file)) - d.addCallback(lambda _: _delete_file(lbry_file)) return d def _delete_stream_data(lbry_file): @@ -623,11 +622,10 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self.lbry_file_manager.get_count_for_stream_hash(s_h) # TODO: could possibly be a timing issue here d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) + d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if + os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) else defer.succeed(None)) return d - def _delete_file(lbry_file): - os.remove(os.path.join(self.download_directory, lbry_file.file_name)) - d.addCallback(lambda _: finish_deletion(lbry_file)) return d @@ -649,11 +647,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _get_est_cost(self, name): def _check_est(d, name): if type(d.result) is float: - print '[' + str(datetime.now()) + '] Cost est for lbry://' + name + ': ' + str(d.result) + 'LBC' - log.info('[' + str(datetime.now()) + '] Cost est for lbry://' + name + ': ' + str(d.result) + 'LBC') + log.info("[" + str(datetime.now()) + "] Cost est for lbry://" + name + ": " + str(d.result) + "LBC") else: - print '[' + str(datetime.now()) + '] Timeout estimating cost for lbry://' + name + ', using key fee' - log.info('[' + str(datetime.now()) + '] Timeout estimating cost for lbry://' + name + ', using key fee') + log.info("[" + str(datetime.now()) + "] Timeout estimating cost for lbry://" + name + ", using key fee") d.cancel() return defer.succeed(None) @@ -674,17 +670,23 @@ class LBRYDaemon(xmlrpc.XMLRPC): return d - def xmlrpc_is_running(self): - if self.startup_message != "" and self.announced_startup == False: - print "Startup message:", self.startup_message - self.announced_startup = True - return self.startup_message - elif self.announced_startup: - return True - else: - return False + def _render_response(self, result, code): + return json.dumps({'result': result, 'code': code}) - def xmlrpc_get_settings(self): + def jsonrpc_is_running(self): + """ + Returns a startup message when the daemon starts, after which it will return True + """ + + if self.startup_message != "" and self.announced_startup == False: + self.announced_startup = True + return self._render_response(self.startup_message, OK_CODE) + elif self.announced_startup: + return self._render_response(True, OK_CODE) + else: + return self._render_response(False, OK_CODE) + + def jsonrpc_get_settings(self): """ Get LBRY payment settings @@ -694,74 +696,82 @@ class LBRYDaemon(xmlrpc.XMLRPC): if not self.session_settings: self.session_settings = {'data_rate': self.data_rate, 'max_key_fee': self.max_key_fee} - print '[' + str(datetime.now()) + '] Get daemon settings' - return self.session_settings + log.info("[" + str(datetime.now()) + "] Get daemon settings") + return self._render_response(self.session_settings, OK_CODE) - def xmlrpc_set_settings(self, settings): + def jsonrpc_set_settings(self, p): """ Set LBRY payment settings - @param settings dict: {'data_rate': float, 'max_key_fee': float} + @param settings: {'settings': {'data_rate': float, 'max_key_fee': float}} """ + params = Bunch(p) - self.session_settings = settings + self.session_settings = params.settings self._update_settings() - print '[' + str(datetime.now()) + '] Set daemon settings' - return 'Set' + log.info("[" + str(datetime.now()) + "] Set daemon settings") + return self._render_response(True, OK_CODE) - def xmlrpc_start_fetcher(self): + def jsonrpc_start_fetcher(self): """ - Start autofetcher + Start automatically downloading new name claims as they happen + + @return: confirmation message """ self.fetcher.start() - print '[' + str(datetime.now()) + '] Start autofetcher' log.info('[' + str(datetime.now()) + '] Start autofetcher') - return 'Started autofetching' + return self._render_response("Started autofetching claims", OK_CODE) - def xmlrpc_stop_fetcher(self): + def jsonrpc_stop_fetcher(self): """ - Stop autofetcher + Stop automatically downloading new name claims as they happen + + @return: confirmation message """ self.fetcher.stop() - print '[' + str(datetime.now()) + '] Stop autofetcher' log.info('[' + str(datetime.now()) + '] Stop autofetcher') - return 'Stopped autofetching' + return self._render_response("Stopped autofetching claims", OK_CODE) - def xmlrpc_fetcher_status(self): + def jsonrpc_fetcher_status(self): """ - Start autofetcher + Get fetcher status + + @return: True/False """ - print '[' + str(datetime.now()) + '] Get fetcher status' - return str(self.fetcher.check_if_running()) + log.info("[" + str(datetime.now()) + "] Get fetcher status") + return self._render_response(self.fetcher.check_if_running(), OK_CODE) - def xmlrpc_get_balance(self): + def jsonrpc_get_balance(self): """ Get LBC balance + + @return: balance """ - print '[' + str(datetime.now()) + '] Get balance' - return str(self.session.wallet.wallet_balance) + log.info("[" + str(datetime.now()) + "] Get balance") + return self._render_response(self.session.wallet.wallet_balance, OK_CODE) - def xmlrpc_stop(self): + def jsonrpc_stop(self): """ Stop lbrynet-daemon + + @return: shutdown message """ def _disp_shutdown(): - log.info('Shutting down lbrynet daemon') - print 'Shutting down lbrynet daemon' + log.info("Shutting down lbrynet daemon") d = self._shutdown() d.addCallback(lambda _: _disp_shutdown()) d.addCallback(lambda _: reactor.callLater(1.0, reactor.stop)) - return defer.succeed('Shutting down') + return self._render_response("Shutting down", OK_CODE) - def xmlrpc_get_lbry_files(self): + def jsonrpc_get_lbry_files(self): """ Get LBRY files @@ -783,123 +793,83 @@ class LBRYDaemon(xmlrpc.XMLRPC): r.append(json.dumps(t)) - print '[' + str(datetime.now()) + '] Get LBRY files' - return r + log.info("[" + str(datetime.now()) + "] Get LBRY files") + return self._render_response(r, OK_CODE) - def xmlrpc_resolve_name(self, name): + def jsonrpc_resolve_name(self, p): """ Resolve stream info from a LBRY uri - @param: name + @param: {'name': name to look up} @return: info for name claim """ + params = Bunch(p) def _disp(info): - print '[' + str(datetime.now()) + ']' + ' Resolved info: ' + str(info['stream_hash']) - return info + log.info("[" + str(datetime.now()) + "] Resolved info: " + info['stream_hash']) + return self._render_response(info, OK_CODE) - d = self._resolve_name(name) - d.addCallbacks(_disp, lambda _: str('UnknownNameError')) + d = self._resolve_name(params.name) + d.addCallbacks(_disp, lambda _: self._render_response('error', NOT_FOUND)) d.callback(None) return d - def xmlrpc_get(self, name): + def jsonrpc_get(self, p): """ Download stream from a LBRY uri @param: name @return: {'stream_hash': hex string, 'path': path of download} """ + params = Bunch(p) - if name: - d = self._download_name(name) + if params.name: + d = self._download_name(params.name) + d.addCallbacks(lambda message: self._render_response(message, OK_CODE), + lambda err: self._render_response('error', NOT_FOUND)) else: - d = defer.succeed(BAD_REQUEST) + d = self._render_response('error', BAD_REQUEST) return d - def xmlrpc_stop_lbry_file(self, stream_hash): + def jsonrpc_stop_lbry_file(self, p): + params = Bunch(p) + try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == stream_hash][0] + lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] except IndexError: return defer.fail(UnknownNameError) if not lbry_file.stopped: d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - d.addCallback(lambda _: 'Stream has been stopped') - d.addErrback(lambda err: str(err)) + d.addCallback(lambda _: self._render_response("Stream has been stopped", OK_CODE)) + d.addErrback(lambda err: self._render_response(err.getTraceback(), )) return d else: - return defer.succeed('Stream was already stopped') + return json.dumps({'result': 'Stream was already stopped'}) + + def jsonrpc_start_lbry_file(self, p): + params = Bunch(p) - def xmlrpc_start_lbry_file(self, stream_hash): try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == stream_hash][0] + lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] except IndexError: return defer.fail(UnknownNameError) if lbry_file.stopped: d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) d.callback(None) - return defer.succeed('Stream started') + return json.dumps({'result': 'Stream started'}) else: - return defer.succeed('Stream was already running') + return json.dumps({'result': 'Stream was already running'}) - def xmlrpc_render_html(self, html): - """ - Writes html to lbry.html in the downloads directory, then opens it with the browser - - @param html: - """ - - def _make_file(html, path): - f = open(path, 'w') - f.write(html) - f.close() - return defer.succeed(None) - - def _disp_err(err): - print str(err.getTraceback()) - return err - - path = os.path.join(self.download_directory, 'lbry.html') - - d = defer.Deferred() - d.addCallback(lambda _: _make_file(html, path)) - d.addCallback(lambda _: os.chown(path, pwd.getpwuid(os.getuid()).pw_uid, pwd.getpwuid(os.getuid()).pw_gid)) - d.addCallback(lambda _: webbrowser.open('file://' + path)) - d.addErrback(_disp_err) - d.callback(None) - - return d - - def xmlrpc_render_gui(self): - """ - Opens the lbry web ui in a browser - """ - - def _disp_err(err): - print str(err.getTraceback()) - return err - - d = defer.Deferred() - if sys.platform == 'darwin': - d.addCallback(lambda _: webbrowser.get('safari').open( - "file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) - else: - d.addCallback(lambda _: webbrowser.open( - "file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) - d.addErrback(_disp_err) - d.callback(None) - - return d - - def xmlrpc_search_nametrie(self, search): + def jsonrpc_search_nametrie(self, p): """ Search the nametrie for claims beginning with search - @param search: - @return: + @param {'search': search string} + @return: List of search results """ + params = Bunch(p) def _clean(n): t = [] @@ -910,26 +880,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): t.append([i[1][0][1], i[1][1][1], i[1][2][1]]) return t - def _parse(results): - f = [] - for chain, meta, cost_est in results: - t = {} - if 'name' in chain.keys(): - t['name'] = chain['name'] - if 'thumbnail' in meta.keys(): - t['img'] = meta['thumbnail'] - else: - t['img'] = 'File://' + str( - os.path.join(self.download_directory, "lbryio/web/img/Free-speech-flag.svg")) - if 'name' in meta.keys(): - t['title'] = meta['name'] - if 'description' in meta.keys(): - t['description'] = meta['description'] - t['cost_est'] = cost_est - f.append(t) - - return f - def resolve_claims(claims): ds = [] for claim in claims: @@ -941,148 +891,140 @@ class LBRYDaemon(xmlrpc.XMLRPC): return defer.DeferredList(ds) def _disp(results): - print '[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' results' log.info('[' + str(datetime.now()) + '] Search results: ') for r in results: log.info(str(r)) - return results + return self._render_response(results, OK_CODE) - print '[' + str(datetime.now()) + '] Search nametrie: ' + search - log.info('[' + str(datetime.now()) + '] Search nametrie: ' + search) + log.info('[' + str(datetime.now()) + '] Search nametrie: ' + params.search) d = self.session.wallet.get_nametrie() - d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) + d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(params.search) and 'txid' in claim]) d.addCallback(lambda claims: claims[:self.max_search_results]) d.addCallback(resolve_claims) d.addCallback(_clean) - d.addCallback(_parse) d.addCallback(_disp) return d - def xmlrpc_delete_lbry_file(self, file_name): + def jsonrpc_delete_lbry_file(self, p): + """ + Delete a lbry file + + @param {'file_name': string} + @return: confirmation message + """ + + params = Bunch(p) + def _disp(file_name): - print '[' + str(datetime.now()) + '] Deleted: ' + file_name - return defer.succeed('Deleted: ' + file_name) + log.info("[" + str(datetime.now()) + "] Deleted: " + file_name) + return self._render_response("Deleted: " + file_name, OK_CODE) - lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files if file_name == f.file_name] + lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files if params.file_name == f.file_name] d = defer.DeferredList(lbry_files) - d.addCallback(lambda _: _disp(file_name)) + d.addCallback(lambda _: _disp(params.file_name)) return d - def xmlrpc_check(self, name): - d = self._check_history(name) - d.addCallback(lambda lbry_file: self._path_from_lbry_file(lbry_file) if lbry_file else 'Not found') - d.addErrback(lambda err: str(err)) + def jsonrpc_publish(self, p): + """ + Make a new name claim - return d + @param: + @return: + """ + params = Bunch(p) - def xmlrpc_publish(self, metadata): - try: - metadata = json.loads(metadata) - except: - return defer.succeed(BAD_REQUEST) + metadata_fields = {"name": str, "file_path": str, "bid": float, "author": str, "title": str, + "description": str, "thumbnail": str, "key_fee": float, "key_fee_address": str, + "content_license": str} + log.info(params) + log.info(params.__dict__) - required = ['name', 'file_path', 'bid'] + for k in metadata_fields.keys(): + if k in params.__dict__.keys(): + assert isinstance(params.__dict__[k], metadata_fields[k]) + metadata_fields[k] = params.__dict__[k] + else: + metadata_fields[k] = None - for r in required: - if not r in metadata.keys(): - return defer.succeed(BAD_REQUEST) - - # if not os.path.isfile(metadata['file_path']): - # return defer.fail() - - if not isinstance(metadata['bid'], float) and metadata['bid'] > 0.0: - return defer.fail() - - name = metadata['name'] - file_path = metadata['file_path'] - bid = metadata['bid'] - - if 'title' in metadata.keys(): - title = metadata['title'] - else: - title = None - - if 'description' in metadata.keys(): - description = metadata['description'] - else: - description = None - - if 'thumbnail' in metadata.keys(): - thumbnail = metadata['thumbnail'] - else: - thumbnail = None - - if 'key_fee' in metadata.keys(): - if not float(metadata['key_fee']) == 0.0: - if not 'key_fee_address' in metadata.keys(): - return defer.fail() - key_fee = metadata['key_fee'] - else: - key_fee = 0.0 - - if 'key_fee_address' in metadata.keys(): - key_fee_address = metadata['key_fee_address'] - else: - key_fee_address = None - - if 'content_license' in metadata.keys(): - content_license = metadata['content_license'] - else: - content_license = None - - log.info('[' + str(datetime.now()) + '] Publish: ', name, file_path, bid, title, description, thumbnail, - key_fee, key_fee_address, content_license) + log.info("[" + str(datetime.now()) + "] Publish: ", metadata_fields) p = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - d = p.start(name, file_path, bid, title, description, thumbnail, key_fee, key_fee_address, content_license) - d.addCallback(lambda msg: (OK_CODE, msg)) + d = p.start(metadata_fields['name'], metadata_fields['file_path'], metadata_fields['bid'], + metadata_fields['title'], metadata_fields['description'], metadata_fields['thumbnail'], + metadata_fields['key_fee'], metadata_fields['key_fee_address'], metadata_fields['content_license']) + d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), + lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) return d - def xmlrpc_abandon_name(self, txid): + def jsonrpc_abandon_name(self, p): + """ + Abandon and reclaim credits from a name claim + + @param: {'txid': string} + @return: txid + """ + params = Bunch(p) + def _disp(txid, tx): - print '[' + str(datetime.now()) + '] Spent coins from claim tx ' + txid + ' --> ' + tx - return tx + log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + txid) + return self._render_response(txid, OK_CODE) d = defer.Deferred() - d.addCallback(lambda _: self.session.wallet.abandon_name(txid)) - d.addCallback(lambda tx: _disp(txid, tx)) - d.addErrback(lambda err: str(err.getTraceback())) + d.addCallback(lambda _: self.session.wallet.abandon_name(params.txid)) + d.addCallback(lambda tx: _disp(params.txid, tx)) + d.addErrback(lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) d.callback(None) return d - def xmlrpc_get_name_claims(self): + def jsonrpc_get_name_claims(self): + """ + Get name claims + + @return: list of name claims + """ def _clean(claims): for c in claims: for k in c.keys(): if isinstance(c[k], Decimal): c[k] = float(c[k]) - return claims + return self._render_response(claims, OK_CODE) d = self.session.wallet.get_name_claims() d.addCallback(_clean) return d - def xmlrpc_get_time_behind_blockchain(self): + def jsonrpc_get_time_behind_blockchain(self): + """ + Get time behind blockchain + + @return: time behind blockchain + """ d = self.session.wallet.get_most_recent_blocktime() - d.addCallback(get_time_behind_blockchain) + d.addCallbacks(lambda result: self._render_response(result, OK_CODE), + lambda result: self._render_response(result, BAD_REQUEST)) return d - def xmlrpc_get_new_address(self): + def jsonrpc_get_new_address(self): + """ + Generate a new wallet address + + @return: new wallet address + """ def _disp(address): - print "[" + str(datetime.now()) + "] Got new wallet address: " + address - return address + log.info("[" + str(datetime.now()) + "] Got new wallet address: " + address) + return json.dumps(self._render_response(address, OK_CODE)) d = self.session.wallet.get_new_address() d.addCallback(_disp) return d - # def xmlrpc_update_name(self, metadata): + # def jsonrpc_update_name(self, metadata): # def _disp(x): # print x # return x @@ -1102,15 +1044,15 @@ class LBRYDaemon(xmlrpc.XMLRPC): # # return d - def xmlrpc_toggle_fetcher_verbose(self): + def jsonrpc_toggle_fetcher_verbose(self): if self.fetcher.verbose: self.fetcher.verbose = False else: self.fetcher.verbose = True - return self.fetcher.verbose + return self._render_response(self.fetcher.verbose, OK_CODE) - def xmlrpc_check_for_new_version(self): + def jsonrpc_check_for_new_version(self): def _check_for_updates(package): git_version = subprocess.check_output("git ls-remote " + package['git'] + " | grep HEAD | cut -f 1", shell=True) up_to_date = False @@ -1147,64 +1089,90 @@ class LBRYDaemon(xmlrpc.XMLRPC): }, } - return [_check_for_updates(package_infos[p]) for p in package_infos.keys()] + r = [_check_for_updates(package_infos[p]) for p in package_infos.keys()] + log.info("[" + str(datetime.now()) + "] Check for new version: " + json.dumps(r)) + return self._render_response(r, OK_CODE) - def xmlrpc_start_status_bar_app(self): - if sys.platform == 'darwin': - if os.path.isdir("/Applications/LBRY.app"): - # subprocess.Popen("screen -dmS lbry-status bash -c 'lbrynet-daemon-status --startdaemon=False'", shell=True) - subprocess.Popen("screen -dmS lbry-status bash -c 'open /Applications/LBRY.app'") - return "Started" - else: - return "Couldn't find LBRY.app, try running the installer" - else: - return "Status bar not implemented on non OS X" - - def xmlrpc___dir__(self): + def jsonrpc___dir__(self): return ['is_running', 'get_settings', 'set_settings', 'start_fetcher', 'stop_fetcher', 'fetcher_status', 'get_balance', 'stop', 'get_lbry_files', 'resolve_name', 'get', 'search_nametrie', 'delete_lbry_file', 'check', 'publish', 'abandon_name', 'get_name_claims', 'get_time_behind_blockchain', 'get_new_address', 'toggle_fetcher_verbose', 'check_for_new_version'] -def stop(): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - status = daemon.is_running() - except: - status = False +class LBRYDaemonCommandHandler(object): + def __init__(self, command): + self._api = jsonrpc.Proxy(API_CONNECTION_STRING) + self.command = command - if status: - daemon.stop() - print "LBRYnet daemon stopped" - else: - print "LBRYnet daemon wasn't running" + def run(self, params=None): + if params: + d = self._api.callRemote(self.command, params) + else: + d = self._api.callRemote(self.command) + return d -def main(): - parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") - parser.add_argument("--wallet", - help="lbrycrd or lbryum, default lbryum", - type=str, - default="lbryum") - parser.add_argument("--update", - help="True or false, default true", - type=str, - default="True") +class LBRYindex(resource.Resource): + isLeaf = False - args = parser.parse_args() - - try: - daemon = xmlrpclib.ServerProxy("http://localhost:7080") - daemon.stop() - except: - pass - - daemon = LBRYDaemon() - daemon.setup(args.wallet, args.update) - reactor.listenTCP(7080, server.Site(daemon), interface='localhost') - reactor.run() + def _delayed_render(self, request, results): + request.write(str(results)) + request.finish() -if __name__ == '__main__': - main() \ No newline at end of file + def render_GET(self, request): + def _disp(r): + log.info(r) + return "" + ''.join(r) + "" + + + d = LBRYDaemonCommandHandler('__dir__').run() + d.addCallback(lambda functions: ["" % (function, function) for function in functions]) + d.addCallback(_disp) + d.addCallbacks(lambda results: self._delayed_render(request, results), + lambda err: self._delayed_render(request, err.getTraceback())) + + return server.NOT_DONE_YET + + +class LBRYFilePage(resource.Resource): + isLeaf = False + + def _delayed_render(self, request, results): + request.write(str(results)) + request.finish() + + h = "" + + d = LBRYDaemonCommandHandler('get_lbry_files').run() + d.addCallback(lambda r: json.loads(r)['result']) + d.addCallback(lambda lbry_files: [h % (json.loads(lbry_file)['file_name'], json.loads(lbry_file)['file_name']) for lbry_file in lbry_files]) + d.addCallback(lambda r: "
%s
%s
" + ''.join(r) + "") + d.addCallbacks(lambda results: self._delayed_render(request, results), + lambda err: self._delayed_render(request, err.getTraceback())) + + return server.NOT_DONE_YET + + +class LBRYDaemonWeb(resource.Resource): + isLeaf = False + + def _delayed_render(self, request, results): + request.write(str(results)) + request.setResponseCode(json.loads(results)['code']) + request.finish() + + def render_GET(self, request): + func = request.args['function'][0] + del request.args['function'] + + p = {} + for k in request.args.keys(): + p[k] = request.args[k][0] + + d = LBRYDaemonCommandHandler(func).run(p) + d.addCallbacks(lambda results: self._delayed_render(request, results), + lambda err: self._delayed_render(request, json.dumps({'message': err.getTraceback(), 'code': BAD_REQUEST}))) + + return server.NOT_DONE_YET \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py new file mode 100644 index 000000000..4041993eb --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -0,0 +1,51 @@ +import argparse +import logging + +from twisted.web import server +from twisted.internet import reactor, defer +from jsonrpc.proxy import JSONRPCProxy + +from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYDaemonWeb, LBRYFilePage +from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET + +log = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + + +def stop(): + def _disp_shutdown(): + log.info("Shutting down lbrynet-daemon from command line") + + def _disp_not_running(): + log.info("Attempt to shut down lbrynet-daemon from command line when daemon isn't running") + + d = defer.Deferred(None) + d.addCallback(lambda _: JSONRPCProxy.from_url(API_CONNECTION_STRING).stop()) + d.addCallbacks(lambda _: _disp_shutdown(), lambda _: _disp_not_running()) + d.callback(None) + + +def start(): + parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") + parser.add_argument("--wallet", + help="lbrycrd or lbryum, default lbryum", + type=str, + default=DEFAULT_WALLET) + parser.add_argument("--update", + help="True or false, default true", + type=str, + default="True") + + log.info("Starting lbrynet-daemon from command line") + + args = parser.parse_args() + daemon = LBRYDaemon() + daemon.setup(args.wallet, args.update) + + root = LBRYindex() + root.putChild("", root) + root.putChild("webapi", LBRYDaemonWeb()) + root.putChild(API_ADDRESS, daemon) + root.putChild("myfiles", LBRYFilePage()) + reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) + reactor.run() diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index c5bfea81f..a4cb2a980 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -49,13 +49,13 @@ class GetStream(object): self.stream_hash = self.stream_info['stream_hash'] else: - print 'InvalidStreamInfoError' + log.error("InvalidStreamInfoError in autofetcher: ", stream_info) raise InvalidStreamInfoError(self.stream_info) if self.key_fee > self.max_key_fee: if self.pay_key: - print "Key fee (" + str(self.key_fee) + ") above limit of " + str( - self.max_key_fee) + ", didn't download lbry://" + str(self.resolved_name) + log.info("Key fee (" + str(self.key_fee) + ") above limit of " + str( + self.max_key_fee) + ", didn't download lbry://" + str(self.resolved_name)) return defer.fail(None) else: pass @@ -78,7 +78,7 @@ class GetStream(object): reserved_points = self.wallet.reserve_points(self.key_fee_address, self.key_fee) if reserved_points is None: return defer.fail(InsufficientFundsError()) - print 'Key fee: ' + str(self.key_fee) + ' | ' + str(self.key_fee_address) + log.info("Key fee: " + str(self.key_fee) + " | " + str(self.key_fee_address)) return self.wallet.send_points_to_address(reserved_points, self.key_fee) return defer.succeed(None) @@ -89,7 +89,7 @@ class GetStream(object): downloader.start() - print "Downloading", self.stream_hash, "-->", os.path.join(downloader.download_directory, downloader.file_name) + log.info("Downloading", self.stream_hash, "-->", os.path.join(downloader.download_directory, downloader.file_name)) return d @@ -117,15 +117,16 @@ class FetcherDaemon(object): self.is_running = True self.search = LoopingCall(self._looped_search) self.search.start(1) + log.info("Starting autofetcher") else: - print "Autofetcher is already running" + log.info("Autofetcher is already running") def stop(self): if self.is_running: self.search.stop() self.is_running = False else: - print "Autofetcher isn't running, there's nothing to stop" + log.info("Autofetcher isn't running, there's nothing to stop") def check_if_running(self): if self.is_running: @@ -157,19 +158,15 @@ class FetcherDaemon(object): return d def get_new_streams_in_tx(claims, t, blockhash): - #claims = self.wallet.get_claims_for_tx(t['txid']) - # if self.first_run: - # # claims = self.rpc_conn.getclaimsfortx("96aca2c60efded5806b7336430c5987b9092ffbea9c6ed444e3bf8e008993e11") - # # claims = self.rpc_conn.getclaimsfortx("cc9c7f5225ecb38877e6ca7574d110b23214ac3556b9d65784065ad3a85b4f74") - # self.first_run = False rtn = [] if claims: for claim in claims: if claim not in self.seen: msg = "[" + str(datetime.now()) + "] New claim | lbry://" + str(claim['name']) + \ " | stream hash: " + str(json.loads(claim['value'])['stream_hash']) - print msg - log.debug(msg) + log.info(msg) + if self.verbose: + print msg rtn.append((claim['name'], t)) self.seen.append(claim) else: @@ -179,8 +176,6 @@ class FetcherDaemon(object): d.addCallback(lambda streams: defer.DeferredList( [self.wallet.get_stream_info_from_txid(name, t) for name, t in streams])) - # if len(rtn): - # return defer.DeferredList([self.wallet.get_stream_info_for_name(name, txid=t) for name, t in rtn]) return d def _download_claims(self, claims): @@ -204,12 +199,12 @@ class FetcherDaemon(object): for l in conf: if l.startswith("maxkey="): settings["maxkey"] = float(l[7:].rstrip('\n')) - print "Autofetcher using max key price of", settings["maxkey"], ", to start call start_fetcher()" + conf.close() else: - print "Autofetcher using default max key price of 0.0" - print "To change this create the file:" - print str(self.autofetcher_conf) - print "Example contents of conf file:" - print "maxkey=1.0" + conf = open(self.autofetcher_conf, "w") + conf.write("maxkey=10.0") + conf.close() + settings["maxkey"] = 10.0 + log.info("No autofetcher conf file found, making one with max key fee of 10.0") self.max_key_fee = settings["maxkey"] diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index 54ade2274..391985522 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -40,7 +40,7 @@ class Publisher(object): def _show_result(): message = "[" + str(datetime.now()) + "] Published " + self.file_name + " --> lbry://" + \ str(self.publish_name) + " with txid: " + str(self.tx_hash) - print message + log.info(message) return defer.succeed(message) self.publish_name = name @@ -122,6 +122,6 @@ class Publisher(object): else: d = defer.succeed(True) error_message = err.getErrorMessage() - print message % (str(self.file_name), str(self.publish_name), error_message) + log.error(error_message) log.error(message, str(self.file_name), str(self.publish_name), err.getTraceback()) return d diff --git a/setup.py b/setup.py index 68d476243..577a83c81 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,11 @@ #!/usr/bin/env python import ez_setup -ez_setup.use_setuptools() -from setuptools import setup, find_packages import sys +import os +from setuptools import setup, find_packages + +ez_setup.use_setuptools() console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', @@ -15,8 +17,8 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch 'lbrynet-gui = lbrynet.lbrynet_gui.gui:start_gui', 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', - 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:main', - 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:stop'] + 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:start', + 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:stop'] if sys.platform == 'darwin': console_scripts.append('lbrynet-daemon-status = lbrynet.lbrynet_daemon.LBRYOSXStatusBar:main') @@ -25,7 +27,9 @@ if sys.platform == 'darwin': setup(name='lbrynet', version='0.0.4', packages=find_packages(), - install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum'], + install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', + 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', + 'leveldb', 'lbryum'], entry_points={'console_scripts': console_scripts}, data_files=[ ('lbrynet/lbrynet_console/plugins', @@ -47,4 +51,4 @@ setup(name='lbrynet', ) ], dependency_links=['https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum'], - ) \ No newline at end of file + ) From 36e299b4cfd366aa7586dc0bc78ff76194b95e6c Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 17 Mar 2016 00:44:04 -0400 Subject: [PATCH 056/462] fix files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -don’t automatically remove files that can’t be found from LBRYFileManager -add timeout to GetStream --- lbrynet/core/LBRYcrdWallet.py | 10 ++- lbrynet/lbryfilemanager/LBRYFileManager.py | 50 +++++++------- lbrynet/lbrynet_daemon/LBRYDaemon.py | 80 +++++++++++++++------- lbrynet/lbrynet_daemon/LBRYDownloader.py | 47 +++++++++---- lbrynet/lbrynet_daemon/LBRYPublisher.py | 11 ++- 5 files changed, 127 insertions(+), 71 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 0d8e48a47..20cd5ffa2 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -297,8 +297,8 @@ class LBRYWallet(object): except (ValueError, TypeError): return Failure(InvalidStreamInfoError(name)) known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', - 'content_license', 'sources', 'fee'] - known_sources = ['lbry_sd_hash'] + 'content_license', 'sources', 'fee', 'author'] + known_sources = ['lbry_sd_hash', 'btih', 'url'] known_fee_types = {'LBC': ['amount', 'address']} for field in known_fields: if field in value_dict: @@ -334,7 +334,7 @@ class LBRYWallet(object): return Failure(UnknownNameError(name)) def claim_name(self, name, sd_hash, amount, description=None, key_fee=None, - key_fee_address=None, thumbnail=None, content_license=None): + key_fee_address=None, thumbnail=None, content_license=None, author=None, sources=None): value = {"sources": {'lbry_sd_hash': sd_hash}} if description is not None: value['description'] = description @@ -344,6 +344,10 @@ class LBRYWallet(object): value['thumbnail'] = thumbnail if content_license is not None: value['content_license'] = content_license + if author is not None: + value['author'] = author + if sources is not None: + value['sources'] = sources d = self._send_name_claim(name, json.dumps(value), amount) diff --git a/lbrynet/lbryfilemanager/LBRYFileManager.py b/lbrynet/lbryfilemanager/LBRYFileManager.py index f741558dc..59f972df3 100644 --- a/lbrynet/lbryfilemanager/LBRYFileManager.py +++ b/lbrynet/lbryfilemanager/LBRYFileManager.py @@ -34,8 +34,8 @@ class LBRYFileManager(object): self.sd_identifier = sd_identifier self.lbry_files = [] self.sql_db = None - self.delete_data = delete_data - self.check_exists_loop = LoopingCall(self.check_files_exist) + # self.delete_data = delete_data + # self.check_exists_loop = LoopingCall(self.check_files_exist) if sys.platform.startswith("darwin"): self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') else: @@ -43,34 +43,34 @@ class LBRYFileManager(object): log.debug("Download directory for LBRYFileManager: %s", str(self.download_directory)) def setup(self): - self.check_exists_loop.start(10) + # self.check_exists_loop.start(10) d = self._open_db() d.addCallback(lambda _: self._add_to_sd_identifier()) d.addCallback(lambda _: self._start_lbry_files()) return d - def check_files_exist(self): - def _disp(deleted_files): - if deleted_files[0][0]: - for file in bad_files: - log.info("[" + str(datetime.now()) + "] Detected " + file.file_name + " was deleted, removing from file manager") - - def _delete_stream_data(lbry_file): - s_h = lbry_file.stream_hash - d = self.get_count_for_stream_hash(s_h) - # TODO: could possibly be a timing issue here - d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) - return d - - bad_files = [lbry_file for lbry_file in self.lbry_files - if lbry_file.completed == True and - os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) == False] - d = defer.DeferredList([self.delete_lbry_file(lbry_file) for lbry_file in bad_files], consumeErrors=True) - d.addCallback(lambda files: _disp(files) if len(files) else defer.succeed(None)) - - if self.delete_data: - d2 = defer.DeferredList([_delete_stream_data(lbry_file) for lbry_file in bad_files], consumeErrors=True) + # def check_files_exist(self): + # def _disp(deleted_files): + # if deleted_files[0][0]: + # for file in bad_files: + # log.info("[" + str(datetime.now()) + "] Detected " + file.file_name + " was deleted, removing from file manager") + # + # def _delete_stream_data(lbry_file): + # s_h = lbry_file.stream_hash + # d = self.get_count_for_stream_hash(s_h) + # # TODO: could possibly be a timing issue here + # d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) + # return d + # + # bad_files = [lbry_file for lbry_file in self.lbry_files + # if lbry_file.completed == True and + # os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) == False] + # d = defer.DeferredList([self.delete_lbry_file(lbry_file) for lbry_file in bad_files], consumeErrors=True) + # d.addCallback(lambda files: _disp(files) if len(files) else defer.succeed(None)) + # + # if self.delete_data: + # d2 = defer.DeferredList([_delete_stream_data(lbry_file) for lbry_file in bad_files], consumeErrors=True) def get_lbry_file_status(self, lbry_file): return self._get_lbry_file_status(lbry_file.rowid) @@ -180,7 +180,7 @@ class LBRYFileManager(object): return defer.fail(Failure(ValueError("Could not find that LBRY file"))) def stop(self): - self.check_exists_loop.stop() + # self.check_exists_loop.stop() ds = [] diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 67b5c8c9b..a3ba38900 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -6,10 +6,14 @@ import binascii import subprocess import logging import requests +# import rumps +# import httplib2 from twisted.web import server, resource, static from twisted.internet import defer, threads, error, reactor from txjsonrpc.web import jsonrpc +from jsonrpc.proxy import JSONRPCProxy + from datetime import datetime from decimal import Decimal from StringIO import StringIO @@ -537,10 +541,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _download_name(self, name): - def _disp_file(file): - log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(file.stream_hash)) - d = self._path_from_lbry_file(file) - return d + def _disp_file(f): + file_path = os.path.join(self.download_directory, f.file_name) + log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) + return defer.succeed(f) def _get_stream(name): def _disp(stream): @@ -558,12 +562,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self._check_history(name) d.addCallback(lambda lbry_file: _get_stream(name) if not lbry_file else _disp_file(lbry_file)) - d.addCallback(lambda _: self._check_history(name)) - d.addCallback(lambda lbry_file: ({'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, - lbry_file.file_name)}) - if lbry_file else defer.fail(NOT_FOUND)) + d.addCallback(lambda _: self._path_from_name(name)) d.addErrback(lambda err: defer.fail(NOT_FOUND)) + return d def _resolve_name(self, name): @@ -586,12 +587,19 @@ class LBRYDaemon(jsonrpc.JSONRPC): f = open(path, 'r') l = json.loads(f.read()) f.close() + file_name = l['stream_name'].decode('hex') - lbry_file = [file for file in self.lbry_file_manager.lbry_files if file.stream_name == file_name] - if lbry_file: - return lbry_file[0] + for lbry_file in self.lbry_file_manager.lbry_files: + if lbry_file.stream_name == file_name: + if sys.platform == "darwin": + if os.path.isfile(os.path.join(self.download_directory, lbry_file.stream_name)): + return lbry_file + else: + return False + else: + return lbry_file else: - return None + return False def _check(info): stream_hash = info['stream_hash'] @@ -604,7 +612,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(False) d = self._resolve_name(name) - d.addCallbacks(_check, lambda _: False) + d.addCallback(_check) d.callback(None) return d @@ -633,7 +641,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self._check_history(name) d.addCallback(lambda lbry_file: {'stream_hash': lbry_file.stream_hash, 'path': os.path.join(self.download_directory, lbry_file.file_name)} - if lbry_file else defer.fail(UnknownNameError)) + if lbry_file else defer.fail(UnknownNameError)) return d def _path_from_lbry_file(self, lbry_file): @@ -673,6 +681,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _render_response(self, result, code): return json.dumps({'result': result, 'code': code}) + # def _log_to_slack(self, msg): + # URL = "https://hooks.slack.com/services/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" + # h = httplib2.Http() + # h.request(URL, 'POST', json.dumps({"text": msg}), headers={'Content-Type': 'application/json'}) + def jsonrpc_is_running(self): """ Returns a startup message when the daemon starts, after which it will return True @@ -722,6 +735,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.fetcher.start() log.info('[' + str(datetime.now()) + '] Start autofetcher') + # self._log_to_slack('[' + str(datetime.now()) + '] Start autofetcher') return self._render_response("Started autofetching claims", OK_CODE) def jsonrpc_stop_fetcher(self): @@ -829,6 +843,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): lambda err: self._render_response('error', NOT_FOUND)) else: d = self._render_response('error', BAD_REQUEST) + return d def jsonrpc_stop_lbry_file(self, p): @@ -933,27 +948,40 @@ class LBRYDaemon(jsonrpc.JSONRPC): @param: @return: """ + params = Bunch(p) - metadata_fields = {"name": str, "file_path": str, "bid": float, "author": str, "title": str, - "description": str, "thumbnail": str, "key_fee": float, "key_fee_address": str, - "content_license": str} - log.info(params) - log.info(params.__dict__) + metadata_fields = {"name": unicode, "file_path": unicode, "bid": float, "author": unicode, "title": unicode, + "description": unicode, "thumbnail": unicode, "key_fee": float, "key_fee_address": unicode, + "content_license": unicode, "sources": dict} for k in metadata_fields.keys(): if k in params.__dict__.keys(): - assert isinstance(params.__dict__[k], metadata_fields[k]) - metadata_fields[k] = params.__dict__[k] + if isinstance(params.__dict__[k], metadata_fields[k]): + if type(params.__dict__[k]) == unicode: + metadata_fields[k] = str(params.__dict__[k]) + else: + metadata_fields[k] = params.__dict__[k] + else: + metadata_fields[k] = None else: metadata_fields[k] = None log.info("[" + str(datetime.now()) + "] Publish: ", metadata_fields) p = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - d = p.start(metadata_fields['name'], metadata_fields['file_path'], metadata_fields['bid'], - metadata_fields['title'], metadata_fields['description'], metadata_fields['thumbnail'], - metadata_fields['key_fee'], metadata_fields['key_fee_address'], metadata_fields['content_license']) + d = p.start(name=metadata_fields['name'], + file_path=metadata_fields['file_path'], + bid=metadata_fields['bid'], + title=metadata_fields['title'], + description=metadata_fields['description'], + thumbnail=metadata_fields['thumbnail'], + key_fee=metadata_fields['key_fee'], + key_fee_address=metadata_fields['key_fee_address'], + content_license=metadata_fields['content_license'], + author=metadata_fields['author'], + sources=metadata_fields['sources']) + d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) @@ -1175,4 +1203,4 @@ class LBRYDaemonWeb(resource.Resource): d.addCallbacks(lambda results: self._delayed_render(request, results), lambda err: self._delayed_render(request, json.dumps({'message': err.getTraceback(), 'code': BAD_REQUEST}))) - return server.NOT_DONE_YET \ No newline at end of file + return server.NOT_DONE_YET diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index a4cb2a980..fc1636d46 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -13,7 +13,8 @@ log = logging.getLogger(__name__) class GetStream(object): - def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5): + def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5, + timeout=30): self.wallet = wallet self.resolved_name = None self.description = None @@ -30,8 +31,25 @@ class GetStream(object): self.max_key_fee = max_key_fee self.stream_info = None self.stream_info_manager = None + self.d = defer.Deferred(None) + self.timeout = timeout + self.timeout_counter = 0 + self.download_path = None + self.checker = LoopingCall(self.check_status) + def check_status(self): + self.timeout_counter += 1 + + if self.download_path and os.path.isfile(self.download_path): + self.checker.stop() + return defer.succeed(True) + + elif self.timeout_counter >= self.timeout: + log.info("Timeout downloading " + str(self.stream_info)) + self.checker.stop() + self.d.cancel() + def start(self, stream_info): self.stream_info = stream_info if 'stream_hash' in self.stream_info.keys(): @@ -60,17 +78,18 @@ class GetStream(object): else: pass - d = defer.Deferred(None) - d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) - d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) - d.addCallback(lambda metadata: (next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), metadata)) - d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager)) - d.addErrback(lambda err: err.trap(defer.CancelledError)) - d.addErrback(lambda err: log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback())) - d.addCallback(self._start_download) - d.callback(None) + self.checker.start(1) - return d + self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) + self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) + self.d.addCallback(lambda metadata: (next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), metadata)) + self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager)) + self.d.addErrback(lambda err: err.trap(defer.CancelledError)) + self.d.addErrback(lambda err: log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback())) + self.d.addCallback(self._start_download) + self.d.callback(None) + + return self.d def _start_download(self, downloader): def _pay_key_fee(): @@ -87,9 +106,9 @@ class GetStream(object): else: d = defer.Deferred() - downloader.start() - - log.info("Downloading", self.stream_hash, "-->", os.path.join(downloader.download_directory, downloader.file_name)) + self.download_path = os.path.join(downloader.download_directory, downloader.file_name) + d.addCallback(lambda _: downloader.start()) + d.addCallback(lambda _: log.info("Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) return d diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index 391985522..ea7c015a3 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -33,9 +33,11 @@ class Publisher(object): self.sd_hash = None self.tx_hash = None self.content_license = None + self.author = None + self.sources = None def start(self, name, file_path, bid, title=None, description=None, thumbnail=None, - key_fee=None, key_fee_address=None, content_license=None): + key_fee=None, key_fee_address=None, content_license=None, author=None, sources=None): def _show_result(): message = "[" + str(datetime.now()) + "] Published " + self.file_name + " --> lbry://" + \ @@ -52,6 +54,8 @@ class Publisher(object): self.key_fee = key_fee self.key_fee_address = key_fee_address self.content_license = content_license + self.author = author + self.sources = sources d = self._check_file_path(self.file_path) d.addCallback(lambda _: create_lbry_file(self.session, self.lbry_file_manager, @@ -104,10 +108,11 @@ class Publisher(object): return d def _claim_name(self): - d = self.wallet.claim_name(self.publish_name, self.sd_hash, self.bid_amount, + d = self.wallet.claim_name(self.publish_name, {'sd_hash': self.sd_hash}, self.bid_amount, description=self.description, key_fee=self.key_fee, key_fee_address=self.key_fee_address, thumbnail=self.thumbnail, - content_license=self.content_license) + content_license=self.content_license, author=self.author, + sources=self.sources) def set_tx_hash(tx_hash): self.tx_hash = tx_hash From 02bde900d60a789a90a83f1e02b8140fb8ca6283 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 17 Mar 2016 10:19:36 -0400 Subject: [PATCH 057/462] update daemon and console MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -don’t start console if daemon is running, display a message alerting user -move os x data directory to match app -add timeout parameter to get function --- lbrynet/conf.py | 4 +- lbrynet/lbrynet_console/LBRYConsole.py | 55 ++++++++++++++---------- lbrynet/lbrynet_daemon/LBRYDaemon.py | 11 +++-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 8 +++- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 8af383387..227589def 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -36,4 +36,6 @@ APP_NAME = "LBRY" DEFAULT_WALLET = "lbryum" API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRESS) -PROTOCOL_PREFIX = "lbry" \ No newline at end of file +PROTOCOL_PREFIX = "lbry" + +DEFAULT_TIMEOUT = 30 \ No newline at end of file diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index d3fa39b2b..43325bc73 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -1,16 +1,21 @@ import logging -from lbrynet.core.Session import LBRYSession import os.path import argparse import requests import locale import sys + +if sys.platform == "darwin": + from appdirs import user_data_dir from yapsy.PluginManager import PluginManager from twisted.internet import defer, threads, stdio, task, error +from jsonrpc.proxy import JSONRPCProxy + +from lbrynet.core.Session import LBRYSession from lbrynet.lbrynet_console.ConsoleControl import ConsoleControl from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager -from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE # , MIN_BLOB_INFO_PAYMENT_RATE +from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, API_CONNECTION_STRING # , MIN_BLOB_INFO_PAYMENT_RATE from lbrynet.core.utils import generate_id from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier from lbrynet.core.PaymentRateManager import PaymentRateManager @@ -461,7 +466,6 @@ class LBRYConsole(): def launch_lbry_console(): - from twisted.internet import reactor parser = argparse.ArgumentParser(description="Launch a lbrynet console") @@ -529,7 +533,7 @@ def launch_lbry_console(): created_data_dir = False if not args.data_dir: if sys.platform == "darwin": - data_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") + data_dir = user_data_dir("LBRY") else: data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: @@ -538,28 +542,35 @@ def launch_lbry_console(): os.mkdir(data_dir) created_data_dir = True + daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING) + try: + daemon.is_running() + log.info("Attempt to start lbrynet-console while lbrynet-daemon is running") + print "lbrynet-daemon is running, you must turn it off before using lbrynet-console" + print "If you're running the app, quit before starting lbrynet-console" + print "If you're running lbrynet-daemon in a terminal, run 'stop-lbrynet-daemon' to turn it off" - log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s" - formatter = logging.Formatter(log_format) + except: + log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s" + formatter = logging.Formatter(log_format) - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - file_handler = logging.FileHandler(os.path.join(data_dir, "console.log")) - file_handler.setFormatter(formatter) - file_handler.addFilter(logging.Filter("lbrynet")) - logger.addHandler(file_handler) + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + file_handler = logging.FileHandler(os.path.join(data_dir, "console.log")) + file_handler.setFormatter(formatter) + file_handler.addFilter(logging.Filter("lbrynet")) + logger.addHandler(file_handler) - console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, fake_wallet=args.fake_wallet, - lbrycrd_conf=args.lbrycrd_wallet_conf, lbrycrd_dir=args.lbrycrd_wallet_dir, - use_upnp=not args.disable_upnp, data_dir=data_dir, - created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path) - d = task.deferLater(reactor, 0, console.start) + console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, fake_wallet=args.fake_wallet, + lbrycrd_conf=args.lbrycrd_wallet_conf, lbrycrd_dir=args.lbrycrd_wallet_dir, + use_upnp=not args.disable_upnp, data_dir=data_dir, + created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path) - d.addErrback(lambda _: reactor.stop()) - - reactor.addSystemEventTrigger('before', 'shutdown', console.shut_down) - reactor.run() + d = task.deferLater(reactor, 0, console.start) + d.addErrback(lambda _: reactor.stop()) + reactor.addSystemEventTrigger('before', 'shutdown', console.shut_down) + reactor.run() if __name__ == "__main__": - launch_lbry_console() \ No newline at end of file + launch_lbry_console() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index a3ba38900..e3b9a437b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -35,7 +35,7 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE -from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS +from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS, DEFAULT_TIMEOUT from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet @@ -540,7 +540,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory) return defer.succeed(True) - def _download_name(self, name): + def _download_name(self, name, timeout=DEFAULT_TIMEOUT): def _disp_file(f): file_path = os.path.join(self.download_directory, f.file_name) log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) @@ -553,7 +553,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.session.wallet.get_stream_info_for_name(name) stream = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, - max_key_fee=self.max_key_fee, data_rate=self.data_rate) + max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout) d.addCallback(_disp) d.addCallback(lambda stream_info: stream.start(stream_info)) d.addCallback(lambda _: self._path_from_name(name)) @@ -837,8 +837,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ params = Bunch(p) + if 'timeout' not in p.keys(): + params.timeout = DEFAULT_TIMEOUT + if params.name: - d = self._download_name(params.name) + d = self._download_name(params.name, timeout=params.timeout) d.addCallbacks(lambda message: self._render_response(message, OK_CODE), lambda err: self._render_response('error', NOT_FOUND)) else: diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index fc1636d46..4552cd4af 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -1,20 +1,23 @@ import json import logging import os + from datetime import datetime from twisted.internet import defer 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.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory +from lbrynet.conf import DEFAULT_TIMEOUT log = logging.getLogger(__name__) class GetStream(object): def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5, - timeout=30): + timeout=DEFAULT_TIMEOUT): self.wallet = wallet self.resolved_name = None self.description = None @@ -106,8 +109,9 @@ class GetStream(object): else: d = defer.Deferred() + downloader.start() + self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d.addCallback(lambda _: downloader.start()) d.addCallback(lambda _: log.info("Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) return d From 0200ed13416fe330e52959e95f9ed37242251350 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Thu, 17 Mar 2016 20:19:13 -0400 Subject: [PATCH 058/462] refresh wallet balance every 3 seconds instead of 60 --- lbrynet/core/LBRYcrdWallet.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 88a9686ef..4b0002181 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -67,6 +67,9 @@ class LBRYWallet(object): self.stopped = True self.manage_running = False + self._manage_count = 0 + self._balance_refresh_time = 3 + self._batch_count = 60 def start(self): @@ -93,16 +96,20 @@ class LBRYWallet(object): self.next_manage_call.cancel() self.next_manage_call = None - d = self.manage() + d = self.manage(do_full=True) d.addErrback(self.log_stop_error) d.addCallback(lambda _: self._stop()) d.addErrback(self.log_stop_error) return d - def manage(self): + def manage(self, do_full=False): log.info("Doing manage") self.next_manage_call = None have_set_manage_running = [False] + self._manage_count += 1 + if self._manage_count % self._batch_count == 0: + self._manage_count = 0 + do_full = True def check_if_manage_running(): @@ -113,6 +120,8 @@ class LBRYWallet(object): self.manage_running = True have_set_manage_running[0] = True d.callback(True) + elif do_full is False: + d.callback(False) else: task.deferLater(reactor, 1, fire_if_not_running) @@ -121,20 +130,26 @@ class LBRYWallet(object): d = check_if_manage_running() - d.addCallback(lambda _: self._check_expected_balances()) + def do_manage(): + if do_full: + d = self._check_expected_balances() + d.addCallback(lambda _: self._send_payments()) + else: + d = defer.succeed(True) - d.addCallback(lambda _: self._send_payments()) + d.addCallback(lambda _: self.get_balance()) - d.addCallback(lambda _: self.get_balance()) + def set_wallet_balance(balance): + self.wallet_balance = balance - def set_wallet_balance(balance): - self.wallet_balance = balance + d.addCallback(set_wallet_balance) + return d - d.addCallback(set_wallet_balance) + d.addCallback(lambda should_run: do_manage() if should_run else None) def set_next_manage_call(): if not self.stopped: - self.next_manage_call = reactor.callLater(60, self.manage) + self.next_manage_call = reactor.callLater(self._balance_refresh_time, self.manage) d.addCallback(lambda _: set_next_manage_call()) From 4449bf3c75a5186ffe43ce02fc8abd21f128d9b5 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Thu, 17 Mar 2016 21:55:06 -0400 Subject: [PATCH 059/462] change manage timer back to 60 seconds --- lbrynet/core/LBRYcrdWallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 4b0002181..89fd3c98a 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -69,7 +69,7 @@ class LBRYWallet(object): self.manage_running = False self._manage_count = 0 self._balance_refresh_time = 3 - self._batch_count = 60 + self._batch_count = 20 def start(self): From 7756d12993e1a4df2bd066438e4c91ad3bb752c0 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 17 Mar 2016 22:03:56 -0400 Subject: [PATCH 060/462] check if daemon is running before starting console --- lbrynet/lbrynet_console/LBRYConsole.py | 193 +++++++++++++------------ lbrynet/lbrynet_daemon/LBRYDaemon.py | 6 +- 2 files changed, 107 insertions(+), 92 deletions(-) diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index 7eeda8e7b..9e73a054b 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -5,9 +5,13 @@ import argparse import requests import locale import sys +import webbrowser + +from xmlrpclib import ServerProxy from yapsy.PluginManager import PluginManager from twisted.internet import defer, threads, stdio, task, error from twisted.python.failure import Failure + # from lbrynet.core.client.AutoDownloader import AutoFetcher from lbrynet.lbrynet_console.ConsoleControl import ConsoleControl from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings @@ -508,109 +512,120 @@ class LBRYConsole(): def launch_lbry_console(): + try: + daemon = ServerProxy("http://localhost:7080") + daemon.is_running() - from twisted.internet import reactor + print "lbrynet-daemon is already running" + print "To use lbrynet-console first close the LBRY status bar app, " \ + "or run stop-lbrynet-daemon if you started it via command line" + print "" + print "Launching browser interface" - parser = argparse.ArgumentParser(description="Launch a lbrynet console") - parser.add_argument("--no_listen_peer", - help="Don't listen for incoming data connections.", - action="store_true") - parser.add_argument("--peer_port", - help="The port on which the console will listen for incoming data connections.", - type=int, default=3333) - parser.add_argument("--no_listen_dht", - help="Don't listen for incoming DHT connections.", - action="store_true") - parser.add_argument("--dht_node_port", - help="The port on which the console will listen for DHT connections.", - type=int, default=4444) - parser.add_argument("--wallet_type", - help="Either 'lbrycrd' or 'ptc' or 'lbryum'.", - type=str, default="lbrycrd") - parser.add_argument("--lbrycrd_wallet_dir", - help="The directory in which lbrycrd data will stored. Used if lbrycrdd is " - "launched by this application.") - parser.add_argument("--lbrycrd_wallet_conf", - help="The configuration file for the LBRYcrd wallet. Default: ~/.lbrycrd/lbrycrd.conf", - type=str) - parser.add_argument("--no_dht_bootstrap", - help="Don't try to connect to the DHT", - action="store_true") - parser.add_argument("--dht_bootstrap_host", - help="The hostname of a known DHT node, to be used to bootstrap into the DHT. " - "Must be used with --dht_bootstrap_port", - type=str, default='104.236.42.182') - parser.add_argument("--dht_bootstrap_port", - help="The port of a known DHT node, to be used to bootstrap into the DHT. Must " - "be used with --dht_bootstrap_host", - type=int, default=4000) - parser.add_argument("--disable_upnp", - help="Don't try to use UPnP to enable incoming connections through the firewall", - action="store_true") - parser.add_argument("--data_dir", - help=("The full path to the directory in which lbrynet data and metadata will be stored. " - "Default: ~/.lbrynet on linux, ~/Library/Application Support/lbrynet on OS X"), - type=str) - parser.add_argument("--lbrycrdd_path", - help="The path to lbrycrdd, which will be launched if it isn't running, unless " - "launching lbrycrdd is disabled by --disable_launch_lbrycrdd. By default, " - "the file ~/.lbrycrddpath.conf will be checked, and if no path is found " - "there, it will be ./lbrycrdd") - parser.add_argument("--disable_launch_lbrycrdd", - help="Don't launch lbrycrdd even if it's not running.") + webbrowser.open("lbry://lbry") - args = parser.parse_args() + except: + from twisted.internet import reactor - if args.no_dht_bootstrap: - bootstrap_nodes = [] - else: - bootstrap_nodes = [(args.dht_bootstrap_host, args.dht_bootstrap_port)] + parser = argparse.ArgumentParser(description="Launch a lbrynet console") + parser.add_argument("--no_listen_peer", + help="Don't listen for incoming data connections.", + action="store_true") + parser.add_argument("--peer_port", + help="The port on which the console will listen for incoming data connections.", + type=int, default=3333) + parser.add_argument("--no_listen_dht", + help="Don't listen for incoming DHT connections.", + action="store_true") + parser.add_argument("--dht_node_port", + help="The port on which the console will listen for DHT connections.", + type=int, default=4444) + parser.add_argument("--wallet_type", + help="Either 'lbrycrd' or 'ptc' or 'lbryum'.", + type=str, default="lbrycrd") + parser.add_argument("--lbrycrd_wallet_dir", + help="The directory in which lbrycrd data will stored. Used if lbrycrdd is " + "launched by this application.") + parser.add_argument("--lbrycrd_wallet_conf", + help="The configuration file for the LBRYcrd wallet. Default: ~/.lbrycrd/lbrycrd.conf", + type=str) + parser.add_argument("--no_dht_bootstrap", + help="Don't try to connect to the DHT", + action="store_true") + parser.add_argument("--dht_bootstrap_host", + help="The hostname of a known DHT node, to be used to bootstrap into the DHT. " + "Must be used with --dht_bootstrap_port", + type=str, default='104.236.42.182') + parser.add_argument("--dht_bootstrap_port", + help="The port of a known DHT node, to be used to bootstrap into the DHT. Must " + "be used with --dht_bootstrap_host", + type=int, default=4000) + parser.add_argument("--disable_upnp", + help="Don't try to use UPnP to enable incoming connections through the firewall", + action="store_true") + parser.add_argument("--data_dir", + help=("The full path to the directory in which lbrynet data and metadata will be stored. " + "Default: ~/.lbrynet on linux, ~/Library/Application Support/lbrynet on OS X"), + type=str) + parser.add_argument("--lbrycrdd_path", + help="The path to lbrycrdd, which will be launched if it isn't running, unless " + "launching lbrycrdd is disabled by --disable_launch_lbrycrdd. By default, " + "the file ~/.lbrycrddpath.conf will be checked, and if no path is found " + "there, it will be ./lbrycrdd") + parser.add_argument("--disable_launch_lbrycrdd", + help="Don't launch lbrycrdd even if it's not running.") - if args.no_listen_peer: - peer_port = None - else: - peer_port = args.peer_port + args = parser.parse_args() - if args.no_listen_dht: - dht_node_port = None - else: - dht_node_port = args.dht_node_port - - created_data_dir = False - if not args.data_dir: - if sys.platform == "darwin": - data_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") + if args.no_dht_bootstrap: + bootstrap_nodes = [] else: - data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") - else: - data_dir = args.data_dir - if not os.path.exists(data_dir): - os.mkdir(data_dir) - created_data_dir = True + bootstrap_nodes = [(args.dht_bootstrap_host, args.dht_bootstrap_port)] + if args.no_listen_peer: + peer_port = None + else: + peer_port = args.peer_port - log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s" - formatter = logging.Formatter(log_format) + if args.no_listen_dht: + dht_node_port = None + else: + dht_node_port = args.dht_node_port - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - file_handler = logging.FileHandler(os.path.join(data_dir, "console.log")) - file_handler.setFormatter(formatter) - file_handler.addFilter(logging.Filter("lbrynet")) - logger.addHandler(file_handler) + created_data_dir = False + if not args.data_dir: + if sys.platform == "darwin": + data_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") + else: + data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + else: + data_dir = args.data_dir + if not os.path.exists(data_dir): + os.mkdir(data_dir) + created_data_dir = True - console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, wallet_type=args.wallet_type, - lbrycrd_conf=args.lbrycrd_wallet_conf, lbrycrd_dir=args.lbrycrd_wallet_dir, - use_upnp=not args.disable_upnp, data_dir=data_dir, - created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path, - start_lbrycrdd=not args.disable_launch_lbrycrdd) + log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s" + formatter = logging.Formatter(log_format) - d = task.deferLater(reactor, 0, console.start) + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + file_handler = logging.FileHandler(os.path.join(data_dir, "console.log")) + file_handler.setFormatter(formatter) + file_handler.addFilter(logging.Filter("lbrynet")) + logger.addHandler(file_handler) - d.addErrback(lambda _: reactor.stop()) + console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, wallet_type=args.wallet_type, + lbrycrd_conf=args.lbrycrd_wallet_conf, lbrycrd_dir=args.lbrycrd_wallet_dir, + use_upnp=not args.disable_upnp, data_dir=data_dir, + created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path, + start_lbrycrdd=not args.disable_launch_lbrycrdd) - reactor.addSystemEventTrigger('before', 'shutdown', console.shut_down) - reactor.run() + d = task.deferLater(reactor, 0, console.start) + + d.addErrback(lambda _: reactor.stop()) + + reactor.addSystemEventTrigger('before', 'shutdown', console.shut_down) + reactor.run() if __name__ == "__main__": launch_lbry_console() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 678282da7..cc0d05138 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -133,9 +133,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda _: self._setup_lbry_file_opener()) d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) - if sys.platform == "darwin": - d.addCallback(lambda _: self._update()) - # d.addCallback(lambda _: self.status_app.run()) + # if sys.platform == "darwin": + # d.addCallback(lambda _: self._update()) + # d.addCallback(lambda _: self.status_app.run()) d.addCallback(lambda _: self._setup_fetcher()) d.addCallback(lambda _: _disp_startup()) d.callback(None) From e3225f712399bd8eaa64f0efcf97295e834ca0b3 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 17 Mar 2016 22:50:15 -0400 Subject: [PATCH 061/462] add missing commits --- .gitignore | 6 + app.icns | Bin 0 -> 8362 bytes lbrynet/core/LBRYcrdWallet.py | 54 ++++++-- .../client/CryptStreamDownloader.py | 3 +- lbrynet/dht/encoding.py | 5 +- .../lbrynet_daemon/Apps/LBRYOSXStatusBar.py | 108 ++++++++++++++++ lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 52 ++++++++ lbrynet/lbrynet_daemon/Apps/__init__.py | 0 lbrynet/lbrynet_daemon/LBRYDaemonStopper.py | 19 --- lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py | 15 --- .../LBRYURIHandler/LBRYURIHandler.py | 59 --------- .../LBRYURIHandler.app/Contents/Info.plist | 117 ------------------ .../lbrynet_daemon/LBRYURIHandler/setup.py | 19 --- .../lbrynet_daemon/scripts/restart_daemon.sh | 2 +- .../lbrynet_daemon/scripts/update_lbrynet.sh | 103 ++++++++------- setup.py | 36 +++--- setup_osx.py | 22 ++++ setup_uri_handler.py | 22 ++++ setup_win32.py | 7 +- tests/lbrynet_test_bot.py | 6 +- 20 files changed, 341 insertions(+), 314 deletions(-) create mode 100644 app.icns create mode 100644 lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py create mode 100644 lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py create mode 100644 lbrynet/lbrynet_daemon/Apps/__init__.py delete mode 100644 lbrynet/lbrynet_daemon/LBRYDaemonStopper.py delete mode 100644 lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py delete mode 100644 lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py delete mode 100644 lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist delete mode 100644 lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py create mode 100644 setup_osx.py create mode 100644 setup_uri_handler.py diff --git a/.gitignore b/.gitignore index 23ba8568c..819306a87 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,9 @@ lbrynet.egg-info/PKG-INFO /build /dist + +*.so + +*.pem + +*.decTest diff --git a/app.icns b/app.icns new file mode 100644 index 0000000000000000000000000000000000000000..b4d00d2f26dc4f4221a0d3e93e988a15b0ada3b8 GIT binary patch literal 8362 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 literal 0 HcmV?d00001 diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index be0a5a694..09bc1e7bc 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -294,7 +294,7 @@ class LBRYWallet(object): value = result['value'] try: value_dict = json.loads(value) - except ValueError: + except (ValueError, TypeError): return Failure(InvalidStreamInfoError(name)) known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', 'content_license'] @@ -395,7 +395,7 @@ class LBRYWallet(object): if 'name' in claim and str(claim['name']) == name and 'value' in claim: try: value_dict = json.loads(claim['value']) - except ValueError: + except (ValueError, TypeError): return None if 'stream_hash' in value_dict and str(value_dict['stream_hash']) == sd_hash: if 'is controlling' in claim and claim['is controlling']: @@ -835,6 +835,7 @@ class LBRYumWallet(LBRYWallet): self.wallet = None self.cmd_runner = None self.first_run = False + self.printed_retrieving_headers = False def _start(self): @@ -843,15 +844,20 @@ class LBRYumWallet(LBRYWallet): def setup_network(): self.config = SimpleConfig() self.network = Network(self.config) + alert.info("Starting the wallet...") return defer.succeed(self.network.start()) d = setup_network() def check_started(): if self.network.is_connecting(): + if not self.printed_retrieving_headers and self.network.blockchain.retrieving_headers: + alert.info("Running the wallet for the first time...this may take a moment.") + self.printed_retrieving_headers = True return False start_check.stop() if self.network.is_connected(): + alert.info("Wallet started.") network_start_d.callback(True) else: network_start_d.errback(ValueError("Failed to connect to network.")) @@ -917,7 +923,9 @@ class LBRYumWallet(LBRYWallet): return d def get_block(self, blockhash): - return defer.fail(NotImplementedError()) + cmd = known_commands['getblock'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, blockhash) def get_most_recent_blocktime(self): header = self.network.get_header(self.network.get_local_height()) @@ -930,7 +938,9 @@ class LBRYumWallet(LBRYWallet): return d def get_name_claims(self): - return defer.fail(NotImplementedError()) + cmd = known_commands['getnameclaims'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func) def check_first_run(self): return defer.succeed(self.first_run) @@ -941,13 +951,38 @@ class LBRYumWallet(LBRYWallet): return threads.deferToThread(func, txid) def _send_name_claim(self, name, val, amount): - return defer.fail(NotImplementedError()) + def send_claim(address): + cmd = known_commands['claimname'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, address, amount, name, val) + d = self.get_new_address() + d.addCallback(send_claim) + d.addCallback(self._broadcast_transaction) + return d def _get_decoded_tx(self, raw_tx): - return defer.fail(NotImplementedError()) + tx = Transaction(raw_tx) + decoded_tx = {} + decoded_tx['vout'] = [] + for output in tx.outputs(): + out = {} + out['value'] = output[2] + decoded_tx['vout'].append(out) + return decoded_tx def _send_abandon(self, txid, address, amount): - return defer.fail(NotImplementedError()) + cmd = known_commands['abandonclaim'] + func = getattr(self.cmd_runner, cmd.name) + d = threads.deferToThread(func, txid, address, amount) + d.addCallback(self._broadcast_transaction) + return d + + def _broadcast_transaction(self, raw_tx): + cmd = known_commands['broadcast'] + func = getattr(self.cmd_runner, cmd.name) + d = threads.deferToThread(func, raw_tx) + d.addCallback(self._save_wallet) + return d def _do_send_many(self, payments_to_send): log.warning("Doing send many. payments to send: %s", str(payments_to_send)) @@ -970,6 +1005,11 @@ class LBRYumWallet(LBRYWallet): def _get_balance_for_address(self, address): return defer.succeed(Decimal(self.wallet.get_addr_received(address))/COIN) + def get_nametrie(self): + cmd = known_commands['getclaimtrie'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func) + def _save_wallet(self, val): d = threads.deferToThread(self.wallet.storage.write) d.addCallback(lambda _: val) diff --git a/lbrynet/cryptstream/client/CryptStreamDownloader.py b/lbrynet/cryptstream/client/CryptStreamDownloader.py index 19a565f1f..e0091598f 100644 --- a/lbrynet/cryptstream/client/CryptStreamDownloader.py +++ b/lbrynet/cryptstream/client/CryptStreamDownloader.py @@ -96,8 +96,9 @@ class CryptStreamDownloader(object): self.starting = True self.completed = False self.finished_deferred = defer.Deferred() + fd = self.finished_deferred d = self._start() - d.addCallback(lambda _: self.finished_deferred) + d.addCallback(lambda _: fd) return d def stop(self, err=None): diff --git a/lbrynet/dht/encoding.py b/lbrynet/dht/encoding.py index a57747628..e0b42ee4f 100644 --- a/lbrynet/dht/encoding.py +++ b/lbrynet/dht/encoding.py @@ -99,7 +99,10 @@ class Bencode(Encoding): """ if len(data) == 0: raise DecodeError, 'Cannot decode empty string' - return self._decodeRecursive(data)[0] + try: + return self._decodeRecursive(data)[0] + except ValueError as e: + raise DecodeError, e.message @staticmethod def _decodeRecursive(data, startIndex=0): diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py new file mode 100644 index 000000000..c55fed0d2 --- /dev/null +++ b/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py @@ -0,0 +1,108 @@ +import rumps +import xmlrpclib +import os +import webbrowser +import subprocess +import argparse + + +class DaemonStatusBarApp(rumps.App): + def __init__(self): + icon_path = 'app.icns' + if os.path.isfile(icon_path): + rumps.App.__init__(self, name="LBRY", icon=icon_path, quit_button=None, + menu=["Open", "Preferences", "View balance", "Quit"]) + else: + rumps.App.__init__(self, name="LBRY", title="LBRY", quit_button=None, + menu=["Open", "Preferences", "View balance", "Quit"]) + + @rumps.timer(1) + def alert_daemon_start(self): + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") + try: + start_msg = daemon.is_running() + if isinstance(start_msg, str): + rumps.notification(title='LBRY', subtitle='', message=str(start_msg), sound=True) + update_info = daemon.check_for_new_version() + update_msg = "" + for p in update_info: + if not p[0]: + update_msg += p[1] + "\n" + if update_msg: + update_msg += "\n Try running the installer again to fix this" + rumps.notification(title='LBRY', subtitle='', message=update_msg, sound=True) + except: + pass + + @rumps.clicked('Open') + def get_ui(self): + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") + try: + daemon.is_running() + webbrowser.open("lbry://lbry") + except: + try: + rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) + except: + rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") + + @rumps.clicked("Preferences") + def prefs(self): + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") + try: + daemon.is_running() + webbrowser.open("lbry://settings") + except: + rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) + + @rumps.clicked("View balance") + def disp_balance(self): + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") + try: + balance = daemon.get_balance() + r = round(float(balance), 2) + try: + rumps.notification(title='LBRY', subtitle='', message=str("Your balance is %.2f LBC" % r), sound=False) + except: + rumps.alert(title='LBRY', message=str("Your balance is %.2f LBC" % r)) + + except: + try: + rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) + except: + rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") + + @rumps.clicked('Quit') + def clean_quit(self): + daemon = xmlrpclib.ServerProxy("http://localhost:7080/") + try: + daemon.stop() + except: + pass + rumps.quit_application() + + +def main(): + parser = argparse.ArgumentParser(description="Launch lbrynet status bar application") + parser.add_argument("--startdaemon", + help="true or false, default true", + type=str, + default="true") + args = parser.parse_args() + + if str(args.startdaemon).lower() == "true": + daemon = xmlrpclib.ServerProxy('http://localhost:7080') + try: + daemon.is_running() + except: + subprocess.Popen("screen -dmS lbrynet bash -c " + "'PYTHONPATH=$PYTHONPATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.python_path`; " + "PATH=$PATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.lbry_bin_path`; " + "lbrynet-daemon --update=False'", shell=True) + + status_app = DaemonStatusBarApp() + status_app.run() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py new file mode 100644 index 000000000..eea94cb94 --- /dev/null +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -0,0 +1,52 @@ +import os +import json +import webbrowser +import xmlrpclib, sys + +def render_video(path): + r = r'
' + return r + + +def main(args): + if len(args) == 0: + args.append('lbry://wonderfullife') + + daemon = xmlrpclib.ServerProxy('http://localhost:7080/') + + try: + daemon.is_running() + + if len(args) > 1: + exit(1) + + if args[0][7:] == 'lbry': + daemon.render_gui() + + elif args[0][7:] == 'settings': + r = daemon.get_settings() + html = "" + json.dumps(r) + "" + daemon.render_html(html) + + else: + r = daemon.get(args[0][7:]) + path = r['path'] + if path[0] != '/': + path = '/' + path + + filename = os.path.basename(path) + extension = os.path.splitext(filename)[1] + + if extension in ['mp4', 'flv', 'mov']: + html = render_video(path) + daemon.render_html(html) + + else: + webbrowser.open('file://' + str(path)) + + except: + webbrowser.open('http://lbry.io/get') + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/lbrynet/lbrynet_daemon/Apps/__init__.py b/lbrynet/lbrynet_daemon/Apps/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py b/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py deleted file mode 100644 index 78d8597dd..000000000 --- a/lbrynet/lbrynet_daemon/LBRYDaemonStopper.py +++ /dev/null @@ -1,19 +0,0 @@ -import xmlrpclib - - -def main(): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - b = daemon.get_balance() - is_running = True - except: - is_running = False - - if is_running: - daemon.stop() - print "LBRYnet daemon stopped" - else: - print "LBRYnet daemon wasn't running" - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py deleted file mode 100644 index f5c9c0e32..000000000 --- a/lbrynet/lbrynet_daemon/LBRYOSXStatusBar.py +++ /dev/null @@ -1,15 +0,0 @@ -import rumps -import xmlrpclib -import os - -class DaemonStatusBarApp(rumps.App): - def __init__(self): - super(DaemonStatusBarApp, self).__init__("LBRYnet", icon=os.path.join(os.path.expanduser("~"), "Downloads/lbryio//web/img/fav/apple-touch-icon.png"), quit_button=None) - self.menu = ["Quit"] - - @rumps.clicked('Quit') - def clean_quit(self): - d = xmlrpclib.ServerProxy('http://localhost:7080') - d.stop() - rumps.quit_application() - diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py deleted file mode 100644 index 62ded697b..000000000 --- a/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import json -import webbrowser -import xmlrpclib, sys - - -def render_video(path): - r = r'
' - return r - - -def main(args): - if len(args) == 0: - args.append('lbry://wonderfullife') - - daemon = xmlrpclib.ServerProxy('http://localhost:7080/') - - try: - balance = daemon.get_balance() - is_running = True - if len(args) > 1: - print 'Too many args', args - - elif is_running: - if args[0][7:] == 'lbry': - daemon.render_gui() - - elif args[0][7:] == 'settings': - r = daemon.get_settings() - html = "" + json.dumps(r) + "" - r = daemon.render_html(html) - else: - if float(balance) > 0.0: - r = daemon.get(args[0][7:]) - print r - path = r['path'] - if path[0] != '/': - path = '/' + path - - print path - filename = path.split('/')[len(path.split('/')) - 1] - extension = path.split('.')[len(path.split('.')) - 1] - - if extension in ['mp4', 'flv', 'mov']: - html = render_video(path) - daemon.render_html(html) - - else: - webbrowser.open('file://' + str(path)) - - except: - webbrowser.open('http://lbry.io/get') - is_running = False - - - - -if __name__ == "__main__": - main(sys.argv[1:]) diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist b/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist deleted file mode 100644 index 4f5993ff1..000000000 --- a/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist +++ /dev/null @@ -1,117 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleDisplayName - LBRYURIHandler - CFBundleDocumentTypes - - - CFBundleTypeOSTypes - - **** - fold - disk - - CFBundleTypeRole - Viewer - - - CFBundleExecutable - LBRYURIHandler - -CFBundleURLTypes - - - CFBundleURLName - LBRYURIHandler - CFBundleURLSchemes - - lbry - - - -NSUIElement - - - CFBundleIconFile - PythonApplet.icns - CFBundleIdentifier - org.pythonmac.unspecified.LBRYURIHandler - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - LBRYURIHandler - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.0.0 - CFBundleSignature - ???? - CFBundleVersion - 0.0.0 - LSHasLocalizedDisplayName - - NSAppleScriptEnabled - - NSHumanReadableCopyright - Copyright not specified - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - PyMainFileNames - - __boot__ - - PyOptions - - alias - - argv_emulation - - emulate_shell_environment - - no_chdir - - prefer_ppc - - site_packages - - use_faulthandler - - use_pythonpath - - verbose - - - PyResourcePackages - - - PyRuntimeLocations - - @executable_path/../Frameworks/Python.framework/Versions/2.7/Python - - PythonInfoDict - - PythonExecutable - /Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python - PythonLongVersion - 2.7.10 (v2.7.10:15c95b7d81dc, May 23 2015, 09:33:12) -[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] - PythonShortVersion - 2.7 - py2app - - alias - - template - app - version - 0.9 - - - - diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py b/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py deleted file mode 100644 index fbb83f5dc..000000000 --- a/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -This is a setup.py script generated by py2applet - -Usage: - python setup.py py2app -""" - -from setuptools import setup - -APP = ['LBRYURIHandler.py'] -DATA_FILES = [] -OPTIONS = {'argv_emulation': True} - -setup( - app=APP, - data_files=DATA_FILES, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], -) diff --git a/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh b/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh index 74d4ab102..7353c0a20 100644 --- a/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh +++ b/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh @@ -1,4 +1,4 @@ #!/bin/sh echo "Restarting lbrynet-daemon" -sudo lbrynet-daemon \ No newline at end of file +lbrynet-daemon \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh index c35723a75..fbb7d2b17 100755 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh @@ -59,7 +59,7 @@ else fi if ! python -c "import six; exit(0) if six.__version__ == '1.9.0' else exit(1)" &>/dev/null; then - echo "Installing six 1.9.0 for python" + echo "Installing six 1.9.0 for python..." curl -O https://pypi.python.org/packages/source/s/six/six-1.9.0.tar.gz &>/dev/null tar xf six-1.9.0.tar.gz &>/dev/null cd six-1.9.0 @@ -70,6 +70,51 @@ if ! python -c "import six; exit(0) if six.__version__ == '1.9.0' else exit(1)" fi lbrynet_directory="/Users/${SUDO_USER}/Library/Application Support/lbrynet" +lbryum_current_version=$(git ls-remote https://github.com/lbryio/lbryum.git | grep HEAD | cut -f 1) + +if [ -d "$lbrynet_directory" ]; then + if [ -f "${lbrynet_directory}/lbryum_version.txt" ]; then + if grep -Fxq "$lbryum_current_version" "${lbrynet_directory}/lbryum_version.txt"; then + echo "LBRYum version $lbryum_current_version is up to date" + else + tmp=$(mktemp -d) + cd $tmp + + echo "Downloading LBRYum update..." + + git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + cd lbryum + + echo "Installing update..." + sudo python setup.py install &>/dev/null + mkdir -p "$lbrynet_directory" + echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" + + echo "Cleaning up..." + + cd ../../ + rm -rf $tmp + fi + else + tmp=$(mktemp -d) + cd $tmp + + echo "Downloading LBRYum..." + + git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null + cd lbryum + + echo "Installing..." + sudo python setup.py install &>/dev/null + mkdir -p "$lbrynet_directory" + echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" + + echo "Cleaning up..." + + cd ../../ + rm -rf $tmp + fi +fi lbrynet_current_version=$(git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1) @@ -86,12 +131,12 @@ if [ -d "$lbrynet_directory" ]; then git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry - echo "Installing update" + echo "Installing update..." sudo python setup.py install &>/dev/null mkdir -p "$lbrynet_directory" echo $lbrynet_current_version > "${lbrynet_directory}/lbrynet_version.txt" - echo "Cleaning up" + echo "Cleaning up..." cd ../../ rm -rf $tmp @@ -100,63 +145,17 @@ if [ -d "$lbrynet_directory" ]; then tmp=$(mktemp -d) cd $tmp - echo "Downloading LBRYnet update" + echo "Downloading LBRYnet..." git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null cd lbry - echo "Installing update" + echo "Installing..." sudo python setup.py install &>/dev/null mkdir -p "$lbrynet_directory" echo $lbrynet_current_version > "${lbrynet_directory}/lbrynet_version.txt" - echo "Cleaning up" - - cd ../../ - rm -rf $tmp - fi -fi - -lbryum_current_version=$(git ls-remote https://github.com/lbryio/lbryum.git | grep HEAD | cut -f 1) - -if [ -d "$lbrynet_directory" ]; then - if [ -f "${lbrynet_directory}/lbryum_version.txt" ]; then - if grep -Fxq "$lbryum_current_version" "${lbrynet_directory}/lbryum_version.txt"; then - echo "LBRYum version $lbryum_current_version is up to date" - else - tmp=$(mktemp -d) - cd $tmp - - echo "Downloading LBRYum update" - - git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null - cd lbryum - - echo "Installing update" - sudo python setup.py install &>/dev/null - mkdir -p "$lbrynet_directory" - echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" - - echo "Cleaning up" - - cd ../../ - rm -rf $tmp - fi - else - tmp=$(mktemp -d) - cd $tmp - - echo "Downloading LBRYum update" - - git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null - cd lbryum - - echo "Installing update" - sudo python setup.py install &>/dev/null - mkdir -p "$lbrynet_directory" - echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" - - echo "Cleaning up" + echo "Cleaning up..." cd ../../ rm -rf $tmp diff --git a/setup.py b/setup.py index 7c3cfa358..68d476243 100644 --- a/setup.py +++ b/setup.py @@ -2,29 +2,31 @@ import ez_setup ez_setup.use_setuptools() - from setuptools import setup, find_packages +import sys + +console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', + 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', + 'lbrynet-stdout-downloader = lbrynet.lbrynet_console.LBRYStdoutDownloader:launch_stdout_downloader', + 'lbrynet-create-network = lbrynet.create_network:main', + 'lbrynet-launch-node = lbrynet.dht.node:main', + 'lbrynet-launch-rpc-node = lbrynet.rpc_node:main', + 'lbrynet-rpc-node-cli = lbrynet.node_rpc_cli:main', + 'lbrynet-gui = lbrynet.lbrynet_gui.gui:start_gui', + 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', + 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', + 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:main', + 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:stop'] + +if sys.platform == 'darwin': + console_scripts.append('lbrynet-daemon-status = lbrynet.lbrynet_daemon.LBRYOSXStatusBar:main') + setup(name='lbrynet', version='0.0.4', packages=find_packages(), install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum'], - entry_points={ - 'console_scripts': [ - 'lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', - 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', - 'lbrynet-stdout-downloader = lbrynet.lbrynet_console.LBRYStdoutDownloader:launch_stdout_downloader', - 'lbrynet-create-network = lbrynet.create_network:main', - 'lbrynet-launch-node = lbrynet.dht.node:main', - 'lbrynet-launch-rpc-node = lbrynet.rpc_node:main', - 'lbrynet-rpc-node-cli = lbrynet.node_rpc_cli:main', - 'lbrynet-gui = lbrynet.lbrynet_gui.gui:start_gui', - 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', - 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', - 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:main', - 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonStopper:main', - ] - }, + entry_points={'console_scripts': console_scripts}, data_files=[ ('lbrynet/lbrynet_console/plugins', [ diff --git a/setup_osx.py b/setup_osx.py new file mode 100644 index 000000000..052b51235 --- /dev/null +++ b/setup_osx.py @@ -0,0 +1,22 @@ +import os +from setuptools import setup + +APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'Apps', 'LBRYOSXStatusBar.py')] +DATA_FILES = [] +OPTIONS = { + 'argv_emulation': True, + 'iconfile': 'app.icns', + 'plist': { + 'LSUIElement': True, + }, + 'includes': ['rumps'] +} + + +setup( + name='LBRY', + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +) \ No newline at end of file diff --git a/setup_uri_handler.py b/setup_uri_handler.py new file mode 100644 index 000000000..37a38826e --- /dev/null +++ b/setup_uri_handler.py @@ -0,0 +1,22 @@ +from setuptools import setup +import os + +APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'Apps', 'LBRYURIHandler.py')] +DATA_FILES = [] +OPTIONS = {'argv_emulation': True, + 'plist': { + 'CFBundleURLTypes': [ + { + 'CFBundleURLTypes': 'LBRYURIHandler', + 'CFBundleURLSchemes': ['lbry'] + } + ] + } + } + +setup( + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +) \ No newline at end of file diff --git a/setup_win32.py b/setup_win32.py index b414606a7..1fc0f0d58 100644 --- a/setup_win32.py +++ b/setup_win32.py @@ -7,7 +7,7 @@ import os import sys from cx_Freeze import setup, Executable - +import requests.certs def find_data_file(filename): if getattr(sys, 'frozen', False): @@ -48,9 +48,9 @@ bdist_msi_options = { build_exe_options = { 'include_msvcr': True, 'includes': [], - 'packages': ['os', 'twisted', 'miniupnpc', 'unqlite', 'seccure', + 'packages': ['six', 'os', 'twisted', 'miniupnpc', 'unqlite', 'seccure', 'requests', 'bitcoinrpc', 'txjsonrpc', 'win32api', 'Crypto', - 'gmpy', 'yapsy'], + 'gmpy', 'yapsy', 'lbryum', 'google.protobuf'], 'excludes': ['zope.interface._zope_interface_coptimizations'], 'include_files': [os.path.join('lbrynet', 'lbrynet_gui', 'close.gif'), os.path.join('lbrynet', 'lbrynet_gui', 'close1.png'), @@ -63,6 +63,7 @@ build_exe_options = { os.path.join('lbrynet', 'lbrynet_gui', 'show_options.gif'), os.path.join('lbrycrdd.exe'), # Not included in repo os.path.join('lbrycrd-cli.exe'), # Not included in repo + (requests.certs.where(), 'cacert.pem'), ], 'namespace_packages': ['zope']} diff --git a/tests/lbrynet_test_bot.py b/tests/lbrynet_test_bot.py index a76a366d8..46e5d9f0a 100644 --- a/tests/lbrynet_test_bot.py +++ b/tests/lbrynet_test_bot.py @@ -7,9 +7,8 @@ from slackclient import SlackClient def get_conf(): f = open('testbot.conf', 'r') token = f.readline().replace('\n', '') - channel = f.readline().replace('\n', '') f.close() - return token, channel + return token def test_lbrynet(lbry, slack, channel): logfile = open('lbrynet_test_log.txt', 'a') @@ -48,10 +47,11 @@ def test_lbrynet(lbry, slack, channel): lbry.delete_lbry_file('test.jpg') logfile.close() -token, channel = get_conf() +token = get_conf() sc = SlackClient(token) sc.rtm_connect() +channel = [c['id'] for c in json.loads(sc.api_call('channels.list'))['channels'] if c['name'] == 'tech'][0] print 'Connected to slack' daemon = xmlrpclib.ServerProxy("http://localhost:7080") From 3cd74c06d6b8d7e325005086324928f3c80b9212 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sat, 19 Mar 2016 18:55:17 -0400 Subject: [PATCH 062/462] convert description in ControlHandlers to str so that the prompt does not get converted to unicode --- lbrynet/lbrynet_console/ControlHandlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_console/ControlHandlers.py b/lbrynet/lbrynet_console/ControlHandlers.py index 6cac5da64..54e072ec9 100644 --- a/lbrynet/lbrynet_console/ControlHandlers.py +++ b/lbrynet/lbrynet_console/ControlHandlers.py @@ -955,7 +955,7 @@ class AddStreamFromLBRYcrdName(AddStreamFromHash): def _get_info_to_show(self): i = AddStream._get_info_to_show(self) if self.description is not None: - i.append(("description", self.description)) + i.append(("description", str(self.description))) if self.key_fee is None or self.key_fee_address is None: i.append(("decryption key fee", "Free")) else: From a68d8917703d7fb9cc351b02dae45a3067fe0c8a Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sat, 19 Mar 2016 21:24:44 -0400 Subject: [PATCH 063/462] enable checking first run status multiple times --- lbrynet/core/LBRYcrdWallet.py | 24 +++++++++++++++++++++--- lbrynet/lbrynet_console/LBRYConsole.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- lbrynet/lbrynet_gui/LBRYGui.py | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 89fd3c98a..be3f96b69 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -50,6 +50,10 @@ class LBRYWallet(object): """This class implements the LBRYWallet interface for the LBRYcrd payment system""" implements(ILBRYWallet) + _FIRST_RUN_UNKNOWN = 0 + _FIRST_RUN_YES = 1 + _FIRST_RUN_NO = 2 + def __init__(self, db_dir): self.db_dir = db_dir @@ -70,6 +74,7 @@ class LBRYWallet(object): self._manage_count = 0 self._balance_refresh_time = 3 self._batch_count = 20 + self._first_run = self._FIRST_RUN_UNKNOWN def start(self): @@ -419,6 +424,19 @@ class LBRYWallet(object): def get_available_balance(self): return float(self.wallet_balance - self.total_reserved_points) + def is_first_run(self): + if self._first_run == self._FIRST_RUN_UNKNOWN: + d = self._check_first_run() + + def set_first_run(is_first): + self._first_run = self._FIRST_RUN_YES if is_first else self._FIRST_RUN_NO + + d.addCallback(set_first_run) + else: + d = defer.succeed(None) + d.addCallback(lambda _: self._first_run == self._FIRST_RUN_YES) + return d + def _get_status_of_claim(self, txid, name, sd_hash): d = self.get_claims_from_tx(txid) @@ -534,7 +552,7 @@ class LBRYWallet(object): def get_name_claims(self): return defer.fail(NotImplementedError()) - def check_first_run(self): + def _check_first_run(self): return defer.fail(NotImplementedError()) def _get_raw_tx(self, txid): @@ -614,7 +632,7 @@ class LBRYcrdWallet(LBRYWallet): settings["rpc_port"] = int(l[8:].rstrip('\n')) return settings - def check_first_run(self): + def _check_first_run(self): d = self.get_balance() d.addCallback(lambda bal: threads.deferToThread(self._get_num_addresses_rpc) if bal == 0 else 2) d.addCallback(lambda num_addresses: True if num_addresses <= 1 else False) @@ -996,7 +1014,7 @@ class LBRYumWallet(LBRYWallet): func = getattr(self.cmd_runner, cmd.name) return threads.deferToThread(func) - def check_first_run(self): + def _check_first_run(self): return defer.succeed(self.first_run) def _get_raw_tx(self, txid): diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index d3fa39b2b..18bd6a8fc 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -253,7 +253,7 @@ class LBRYConsole(): return dl def check_first_run(self): - d = self.session.wallet.check_first_run() + d = self.session.wallet.is_first_run() d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else 0.0) return d diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 410da8a81..85c441826 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -460,7 +460,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): return dl def _check_first_run(self): - d = self.session.wallet.check_first_run() + d = self.session.wallet.is_first_run() d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else 0.0) return d diff --git a/lbrynet/lbrynet_gui/LBRYGui.py b/lbrynet/lbrynet_gui/LBRYGui.py index dcc20f649..3c83308a8 100644 --- a/lbrynet/lbrynet_gui/LBRYGui.py +++ b/lbrynet/lbrynet_gui/LBRYGui.py @@ -362,7 +362,7 @@ class LBRYDownloader(object): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_opener_factory) def check_first_run(self): - d = self.session.wallet.check_first_run() + d = self.session.wallet.is_first_run() d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else 0.0) return d From d36036a9fea7c02ddebb931d28207bf02518739a Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sat, 19 Mar 2016 21:50:38 -0400 Subject: [PATCH 064/462] Tell user to be patient if it's the first run and user has no funds --- lbrynet/lbrynet_console/ControlHandlers.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lbrynet/lbrynet_console/ControlHandlers.py b/lbrynet/lbrynet_console/ControlHandlers.py index 78e53b0dd..dd01f41b2 100644 --- a/lbrynet/lbrynet_console/ControlHandlers.py +++ b/lbrynet/lbrynet_console/ControlHandlers.py @@ -783,6 +783,9 @@ class AddStream(CommandHandler): d.addCallback(get_time_behind_blockchain) d.addCallback(self._show_time_behind_blockchain_download) d.addErrback(self._log_recent_blockchain_time_error_download) + d.addCallback(lambda _: self.wallet.is_first_run()) + d.addCallback(self._show_first_run_insufficient_funds) + d.addErrback(self._log_first_run_check_error) else: log.error("An unexpected error has caused the download to stop: %s" % err.getTraceback()) log_file = get_log_file() @@ -803,6 +806,16 @@ class AddStream(CommandHandler): def _log_recent_blockchain_time_error_download(self, err): log.error("An error occurred trying to look up the most recent blocktime: %s", err.getTraceback()) + def _show_first_run_insufficient_funds(self, is_first_run): + if is_first_run: + self.console.sendLine("\nThis appears to be the first time you have run LBRY. It can take\n" + "a few minutes for your testing LBC to show up. If you haven't\n" + "received them after a few minutes, please let us know.\n\n" + "Thank you for your patience.\n\n") + + def _log_first_run_check_error(self, err): + log.error("An error occurred checking if this was the first run: %s", err.getTraceback()) + class AddStreamFromSD(AddStream): #prompt_description = "Add a stream from a stream descriptor file" From a0b75d7ada0ae756425a8fa0ccad9ae8422bd921 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sat, 19 Mar 2016 22:30:55 -0400 Subject: [PATCH 065/462] Tell user to be patient if it's the first run and user has no funds, in two more places --- lbrynet/lbrynet_console/ControlHandlers.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lbrynet/lbrynet_console/ControlHandlers.py b/lbrynet/lbrynet_console/ControlHandlers.py index dd01f41b2..cbe5dd270 100644 --- a/lbrynet/lbrynet_console/ControlHandlers.py +++ b/lbrynet/lbrynet_console/ControlHandlers.py @@ -862,6 +862,9 @@ class AddStreamFromHash(AddStream): d.addCallback(get_time_behind_blockchain) d.addCallback(self._show_time_behind_blockchain_download) d.addErrback(self._log_recent_blockchain_time_error_download) + d.addCallback(lambda _: self.wallet.is_first_run()) + d.addCallback(self._show_first_run_insufficient_funds) + d.addErrback(self._log_first_run_check_error) d.addCallback(lambda _: self.console.sendLine("\n")) d.chainDeferred(self.finished_deferred) return @@ -1870,6 +1873,16 @@ class Publish(CommandHandler): def _log_best_blocktime_error(self, err): log.error("An error occurred checking the best time of the blockchain: %s", err.getTraceback()) + def _show_first_run_insufficient_funds(self, is_first_run): + if is_first_run: + self.console.sendLine("\nThis appears to be the first time you have run LBRY. It can take\n" + "a few minutes for your testing LBC to show up. If you haven't\n" + "received them after a few minutes, please let us know.\n\n" + "Thank you for your patience.\n\n") + + def _log_first_run_check_error(self, err): + log.error("An error occurred checking if this was the first run: %s", err.getTraceback()) + def _show_publish_error(self, err): message = "An error occurred publishing %s to %s. Error: %s." if err.check(InsufficientFundsError): @@ -1877,6 +1890,9 @@ class Publish(CommandHandler): d.addCallback(get_time_behind_blockchain) d.addCallback(self._show_time_behind_blockchain) d.addErrback(self._log_best_blocktime_error) + d.addCallback(lambda _: self.wallet.is_first_run()) + d.addCallback(self._show_first_run_insufficient_funds) + d.addErrback(self._log_first_run_check_error) error_message = "Insufficient funds" else: d = defer.succeed(True) From 246647641a43b1811672257a94eca2b1c74b7b43 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Sat, 19 Mar 2016 22:39:47 -0400 Subject: [PATCH 066/462] remove some log statements that are useless, instead log whenever balance changes --- lbrynet/core/LBRYcrdWallet.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index be3f96b69..3c0c5f03f 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -108,7 +108,6 @@ class LBRYWallet(object): return d def manage(self, do_full=False): - log.info("Doing manage") self.next_manage_call = None have_set_manage_running = [False] self._manage_count += 1 @@ -145,6 +144,8 @@ class LBRYWallet(object): d.addCallback(lambda _: self.get_balance()) def set_wallet_balance(balance): + if self.wallet_balance != balance: + log.info("Got a new balance: %s", str(balance)) self.wallet_balance = balance d.addCallback(set_wallet_balance) @@ -272,8 +273,6 @@ class LBRYWallet(object): return d def _send_payments(self): - log.info("Trying to send payments, if there are any to be sent") - payments_to_send = {} for address, points in self.queued_payments.items(): log.info("Should be sending %s points to %s", str(points), str(address)) From 5072f503c467fbaf1bb4610025b430bd514eb6e4 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 20 Mar 2016 22:53:01 -0400 Subject: [PATCH 067/462] fix cross scripting problem add access-control-allow-origin: http://localhost:5279 --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 32 +++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index e3b9a437b..1d647a917 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -11,6 +11,7 @@ import requests from twisted.web import server, resource, static from twisted.internet import defer, threads, error, reactor +from txjsonrpc import jsonrpclib from txjsonrpc.web import jsonrpc from jsonrpc.proxy import JSONRPCProxy @@ -64,6 +65,35 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ isLeaf = True + def render(self, request): + request.content.seek(0, 0) + # Unmarshal the JSON-RPC data. + content = request.content.read() + parsed = jsonrpclib.loads(content) + functionPath = parsed.get("method") + args = parsed.get('params') + id = parsed.get('id') + version = parsed.get('jsonrpc') + if version: + version = int(float(version)) + elif id and not version: + version = jsonrpclib.VERSION_1 + else: + version = jsonrpclib.VERSION_PRE1 + # XXX this all needs to be re-worked to support logic for multiple + # versions... + try: + function = self._getFunction(functionPath) + except jsonrpclib.Fault, f: + self._cbRender(f, request, id, version) + else: + request.setHeader('Access-Control-Allow-Origin', ('http://localhost' + ':' + str(API_PORT))) + request.setHeader("content-type", "text/json") + d = defer.maybeDeferred(function, *args) + d.addErrback(self._ebRender, id) + d.addCallback(self._cbRender, request, id, version) + return server.NOT_DONE_YET + def setup(self, wallet_type, check_for_updates): def _set_vars(wallet_type, check_for_updates): reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) @@ -1206,4 +1236,4 @@ class LBRYDaemonWeb(resource.Resource): d.addCallbacks(lambda results: self._delayed_render(request, results), lambda err: self._delayed_render(request, json.dumps({'message': err.getTraceback(), 'code': BAD_REQUEST}))) - return server.NOT_DONE_YET + return server.NOT_DONE_YET \ No newline at end of file From 34eaa822a01f1b28f1893b30451fb9489a87e0d8 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 21 Mar 2016 10:12:01 -0400 Subject: [PATCH 068/462] handle null parameter --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 1d647a917..5838b4945 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -89,7 +89,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: request.setHeader('Access-Control-Allow-Origin', ('http://localhost' + ':' + str(API_PORT))) request.setHeader("content-type", "text/json") - d = defer.maybeDeferred(function, *args) + if args == [{}]: + d = defer.maybeDeferred(function) + else: + d = defer.maybeDeferred(function, *args) d.addErrback(self._ebRender, id) d.addCallback(self._cbRender, request, id, version) return server.NOT_DONE_YET @@ -917,6 +920,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): @param {'search': search string} @return: List of search results """ + params = Bunch(p) def _clean(n): From 55f25c261380bbd1adec2929d15196c0aaeb8ccf Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 21 Mar 2016 12:06:19 -0400 Subject: [PATCH 069/462] consolidate search output consolidate search output into a list of dicts --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 5838b4945..363ce87d1 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -943,10 +943,19 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.DeferredList(ds) def _disp(results): - log.info('[' + str(datetime.now()) + '] Search results: ') + log.info('[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' search results') + consolidated_results = [] for r in results: - log.info(str(r)) - return self._render_response(results, OK_CODE) + t = {} + t.update(r[0]) + if 'name' in r[1].keys(): + r[1]['stream_name'] = r[1]['name'] + del r[1]['name'] + t.update(r[1]) + t['cost_est'] = r[2] + consolidated_results.append(t) + # log.info(str(t)) + return self._render_response(consolidated_results, OK_CODE) log.info('[' + str(datetime.now()) + '] Search nametrie: ' + params.search) From df5b889f7d26ecfaf0db1b5bdb8d07470c647746 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Mon, 21 Mar 2016 21:33:55 -0400 Subject: [PATCH 070/462] add a version to lbrynet --- lbrynet/__init__.py | 1 + setup.py | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index c53c52efe..38cda8bd9 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -1,4 +1,5 @@ import logging +from version import __version__ logging.getLogger(__name__).addHandler(logging.NullHandler()) \ No newline at end of file diff --git a/setup.py b/setup.py index 68d476243..8dde41775 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,13 @@ import ez_setup ez_setup.use_setuptools() from setuptools import setup, find_packages import sys +import os + +base_dir = os.path.abspath(os.path.dirname(__file__)) + + +from lbrynet import __version__ + console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', @@ -18,33 +25,28 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:main', 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:stop'] + if sys.platform == 'darwin': console_scripts.append('lbrynet-daemon-status = lbrynet.lbrynet_daemon.LBRYOSXStatusBar:main') +gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', + 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf'] +gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] + setup(name='lbrynet', - version='0.0.4', - packages=find_packages(), + version='.'.join([str(x) for x in __version__]), + packages=find_packages(base_dir), install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum'], entry_points={'console_scripts': console_scripts}, data_files=[ ('lbrynet/lbrynet_console/plugins', [ - 'lbrynet/lbrynet_console/plugins/blindrepeater.yapsy-plugin', + os.path.join(base_dir, 'lbrynet', 'lbrynet_console', 'plugins', + 'blindrepeater.yapsy-plugin') ] ), - ('lbrynet/lbrynet_gui', - [ - 'lbrynet/lbrynet_gui/close2.gif', - 'lbrynet/lbrynet_gui/lbry-dark-242x80.gif', - 'lbrynet/lbrynet_gui/lbry-dark-icon.xbm', - 'lbrynet/lbrynet_gui/lbry-dark-icon.ico', - 'lbrynet/lbrynet_gui/drop_down.gif', - 'lbrynet/lbrynet_gui/show_options.gif', - 'lbrynet/lbrynet_gui/hide_options.gif', - 'lbrynet/lbrynet_gui/lbry.conf', - ] - ) + ('lbrynet/lbrynet_gui', gui_data_paths) ], dependency_links=['https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum'], ) \ No newline at end of file From e4a30c052eba060b01e0ade14dfdbfeabfeffede Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Mon, 21 Mar 2016 21:37:15 -0400 Subject: [PATCH 071/462] put the actual version in __init__.py --- lbrynet/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 38cda8bd9..f780625b8 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -1,5 +1,7 @@ import logging -from version import __version__ -logging.getLogger(__name__).addHandler(logging.NullHandler()) \ No newline at end of file +logging.getLogger(__name__).addHandler(logging.NullHandler()) + + +__version__ = (0, 2, 0) \ No newline at end of file From ba991b47ea18e30ad62cec1578071703953750e5 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 22 Mar 2016 00:03:17 -0400 Subject: [PATCH 072/462] add /view to dynamically generate a video containing page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -uses UI stored in Resources folder of app -going to /view?name= will download the corresponding stream and display the file in the browser -adds default daemon settings, currently they don’t do anything, to be added to -adds missing packages to setup.py --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 106 ++++++++++++++------ lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 14 ++- setup.py | 2 +- 3 files changed, 87 insertions(+), 35 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 363ce87d1..5dce85822 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -126,7 +126,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') - + self.daemon_conf = os.path.join(self.wallet_dir, 'daemon_settings.conf') self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") self.wallet_user = None self.wallet_password = None @@ -164,6 +164,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.announced_startup = False self.search_timeout = 3.0 self.query_handlers = {} + self.default_settings = { + 'run_on_startup': False, + 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, + 'max_key_fee': 10.0, + 'default_download_directory': self.download_directory, + 'max_upload': 0.0, + 'max_download': 0.0 + } return defer.succeed(None) @@ -178,7 +186,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") d = defer.Deferred() - d.addCallback(lambda _: _set_vars(wallet_type, check_for_updates)) + d.addCallback(lambda _:_set_vars(wallet_type, check_for_updates)) + d.addCallback(lambda _: self._setup_daemon_settings()) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) d.addCallback(lambda _: self._get_settings()) @@ -201,6 +210,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _initial_setup(self): return NotImplemented + def _setup_daemon_settings(self): + self.session_settings = self.default_settings + return defer.succeed(None) + def _update(self): def _check_for_updater(): if os.path.isdir("/Applications/LBRY Updater.app"): @@ -370,9 +383,28 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self.session.shut_down()) return d - def _update_settings(self): - self.data_rate = self.session_settings['data_rate'] - self.max_key_fee = self.session_settings['max_key_fee'] + def _update_settings(self, settings): + if not isinstance(settings['run_on_startup'], bool): + return defer.fail() + elif not isinstance(settings['data_rate'], float): + return defer.fail() + elif not isinstance(settings['max_key_fee'], float): + return defer.fail() + elif not isinstance(settings['default_download_directory'], unicode): + return defer.fail() + elif not isinstance(settings['max_upload'], float): + return defer.fail() + elif not isinstance(settings['max_download'], float): + return defer.fail() + + self.session_settings['run_on_startup'] = settings['run_on_startup'] + self.session_settings['data_rate'] = settings['data_rate'] + self.session_settings['max_key_fee'] = settings['max_key_fee'] + self.session_settings['default_download_directory'] = settings['default_download_directory'] + self.session_settings['max_upload'] = settings['max_upload'] + self.session_settings['max_download'] = settings['max_download'] + + return defer.succeed(True) def _setup_fetcher(self): self.fetcher = FetcherDaemon(self.session, self.lbry_file_manager, self.lbry_file_metadata_manager, @@ -739,9 +771,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): @return {'data_rate': float, 'max_key_fee': float} """ - if not self.session_settings: - self.session_settings = {'data_rate': self.data_rate, 'max_key_fee': self.max_key_fee} - log.info("[" + str(datetime.now()) + "] Get daemon settings") return self._render_response(self.session_settings, OK_CODE) @@ -751,10 +780,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): @param settings: {'settings': {'data_rate': float, 'max_key_fee': float}} """ - params = Bunch(p) - self.session_settings = params.settings - self._update_settings() + d = self._update_settings(p) log.info("[" + str(datetime.now()) + "] Set daemon settings") return self._render_response(True, OK_CODE) @@ -1194,39 +1221,60 @@ class LBRYindex(resource.Resource): request.write(str(results)) request.finish() + def getChild(self, name, request): + if name == '': + return self + return resource.Resource.getChild(self, name, request) def render_GET(self, request): def _disp(r): log.info(r) return "
" + ''.join(r) + "" - - d = LBRYDaemonCommandHandler('__dir__').run() - d.addCallback(lambda functions: ["" % (function, function) for function in functions]) - d.addCallback(_disp) - d.addCallbacks(lambda results: self._delayed_render(request, results), - lambda err: self._delayed_render(request, err.getTraceback())) - - return server.NOT_DONE_YET + return static.File("./dist/index.html").render_GET(request) -class LBRYFilePage(resource.Resource): +class LBRYFileRender(resource.Resource): isLeaf = False + def _render_path(self, path): + return r'
' + def _delayed_render(self, request, results): request.write(str(results)) request.finish() - h = "" + def render_GET(self, request): + if 'name' in request.args.keys(): + api = jsonrpc.Proxy(API_CONNECTION_STRING) + d = api.callRemote("get", {'name': request.args['name'][0]}) + d.addCallback(lambda response: self._delayed_render(request, self._render_path(json.loads(response)['result']['path'])) + if json.loads(response)['code'] == 200 + else self._delayed_render(request, "Error")) - d = LBRYDaemonCommandHandler('get_lbry_files').run() - d.addCallback(lambda r: json.loads(r)['result']) - d.addCallback(lambda lbry_files: [h % (json.loads(lbry_file)['file_name'], json.loads(lbry_file)['file_name']) for lbry_file in lbry_files]) - d.addCallback(lambda r: "
%s
%s
" + ''.join(r) + "") - d.addCallbacks(lambda results: self._delayed_render(request, results), - lambda err: self._delayed_render(request, err.getTraceback())) + return server.NOT_DONE_YET + else: + self._delayed_render(request, "Error") + return server.NOT_DONE_YET - return server.NOT_DONE_YET + +# class LBRYFilePage(resource.Resource): +# isLeaf = False +# +# def _delayed_render(self, request, results): +# request.write(str(results)) +# request.finish() +# +# h = "" +# +# d = LBRYDaemonCommandHandler('get_lbry_files').run() +# d.addCallback(lambda r: json.loads(r)['result']) +# d.addCallback(lambda lbry_files: [h % (json.loads(lbry_file)['file_name'], json.loads(lbry_file)['file_name']) for lbry_file in lbry_files]) +# d.addCallback(lambda r: "
%s
" + ''.join(r) + "") +# d.addCallbacks(lambda results: self._delayed_render(request, results), +# lambda err: self._delayed_render(request, err.getTraceback())) +# +# return server.NOT_DONE_YET class LBRYDaemonWeb(resource.Resource): @@ -1249,4 +1297,4 @@ class LBRYDaemonWeb(resource.Resource): d.addCallbacks(lambda results: self._delayed_render(request, results), lambda err: self._delayed_render(request, json.dumps({'message': err.getTraceback(), 'code': BAD_REQUEST}))) - return server.NOT_DONE_YET \ No newline at end of file + return server.NOT_DONE_YET diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 4041993eb..89b678f68 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -1,11 +1,11 @@ import argparse import logging -from twisted.web import server +from twisted.web import server, static from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy -from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYDaemonWeb, LBRYFilePage +from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYDaemonWeb, LBRYFileRender from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET log = logging.getLogger(__name__) @@ -43,9 +43,13 @@ def start(): daemon.setup(args.wallet, args.update) root = LBRYindex() - root.putChild("", root) - root.putChild("webapi", LBRYDaemonWeb()) + root.putChild("css", static.File("./css")) + root.putChild("font", static.File("./font")) + root.putChild("img", static.File("./img")) + root.putChild("js", static.File("./js")) root.putChild(API_ADDRESS, daemon) - root.putChild("myfiles", LBRYFilePage()) + root.putChild("webapi", LBRYDaemonWeb()) + root.putChild("view", LBRYFileRender()) reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) + reactor.run() diff --git a/setup.py b/setup.py index 577a83c81..b032122fb 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ setup(name='lbrynet', packages=find_packages(), install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', - 'leveldb', 'lbryum'], + 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs'], entry_points={'console_scripts': console_scripts}, data_files=[ ('lbrynet/lbrynet_console/plugins', From 2486ae6d3f690b1164f031483e0ab381496e352c Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 22 Mar 2016 09:40:18 -0400 Subject: [PATCH 073/462] get rid of unused updater function updater function was hacky and not the most reliable, to be replaced by app version checking --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 101 +---------- .../lbrynet_daemon/scripts/restart_daemon.sh | 4 - lbrynet/lbrynet_daemon/scripts/update_lbry.sh | 50 ------ .../lbrynet_daemon/scripts/update_lbrynet.sh | 163 ------------------ 4 files changed, 3 insertions(+), 315 deletions(-) delete mode 100644 lbrynet/lbrynet_daemon/scripts/restart_daemon.sh delete mode 100755 lbrynet/lbrynet_daemon/scripts/update_lbry.sh delete mode 100755 lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 5dce85822..922f3be27 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -159,7 +159,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE self.max_key_fee = DEFAULT_MAX_KEY_FEE self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS - self.restart_message = "" self.startup_message = "" self.announced_startup = False self.search_timeout = 3.0 @@ -176,10 +175,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) def _disp_startup(): - if self.restart_message: - log.info(self.restart_message) - else: - log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") + log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") return defer.succeed(None) @@ -199,8 +195,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._setup_lbry_file_opener()) d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) - # d.addCallback(lambda _: self._update() if self.check_for_updates == "True" and sys.platform == "darwin" - # else defer.succeed(None)) d.addCallback(lambda _: self._setup_fetcher()) d.addCallback(lambda _: _disp_startup()) d.callback(None) @@ -208,102 +202,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) def _initial_setup(self): - return NotImplemented + return defer.fail(NotImplementedError()) def _setup_daemon_settings(self): self.session_settings = self.default_settings return defer.succeed(None) - def _update(self): - def _check_for_updater(): - if os.path.isdir("/Applications/LBRY Updater.app"): - print "Found LBRY updater" - return defer.succeed(None) - - print "LBRY updater not found, downloading and installing..." - url = urlopen("https://rawgit.com/jackrobison/lbrynet-app/master/LBRY%20Updater.app.zip") - zipped_app = ZipFile(StringIO(url.read())) - zipped_app.extractall("/Applications") - return defer.succeed(None) - - def _update_lbrynet(): - git_version = subprocess.check_output( - "git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1", - shell=True) - if os.path.isfile(os.path.join(self.db_dir, "lbrynet_version.txt")): - f = open(os.path.join(self.db_dir, "lbrynet_version.txt"), 'r') - current_version = f.read() - f.close() - if git_version == current_version: - print "LBRYnet installation version " + current_version[:-1] + " is up to date" - return defer.succeed(None) - print "Update LBRYnet version " + current_version[:-1] + " --> " + git_version[:-1] - self.restart_message = "Updates available" - else: - print "Update LBRYnet to version " + git_version[:-1] - self.restart_message = "Updates available" - - return defer.succeed(None) - - def _update_lbrycrdd(): - git_version = subprocess.check_output( - "git ls-remote https://github.com/jackrobison/lbrynet-app.git | grep HEAD | cut -f 1", - shell=True) - if os.path.isfile(os.path.join(self.wallet_dir, "lbry_app_version.txt")): - f = open(os.path.join(self.wallet_dir, "lbry_app_version.txt"), 'r') - current_version = f.read() - f.close() - if git_version == current_version: - print "LBRY installation version " + current_version[:-1] + " is up to date" - return defer.succeed(None) - print "Update LBRY version " + current_version[:-1] + " --> " + git_version[:-1] - self.restart_message = "Updates available" - else: - print "Update LBRY to version " + git_version[:-1] - self.restart_message = "Updates available" - - return defer.succeed(None) - - def _update_lbryum(): - git_version = subprocess.check_output( - "git ls-remote https://github.com/lbryio/lbryum.git | grep HEAD | cut -f 1", - shell=True) - if os.path.isfile(os.path.join(self.db_dir, "lbryum_version.txt")): - f = open(os.path.join(self.db_dir, "lbryum_version.txt"), 'r') - current_version = f.read() - f.close() - if git_version == current_version: - print "LBRYum installation version " + current_version[:-1] + " is up to date" - return defer.succeed(None) - print "Update LBRYum version " + current_version[:-1] + " --> " + git_version[:-1] - self.restart_message = "Updates available" - else: - print "Update LBRYum to version " + git_version[:-1] - self.restart_message = "Updates available" - - return defer.succeed(None) - - d = _check_for_updater() - d.addCallback(lambda _: _update_lbrynet()) - d.addCallback(lambda _: _update_lbrycrdd() if self.wallet_type == 'lbrycrd' else _update_lbryum()) - d.addCallback(lambda _: os.system("open /Applications/LBRY\ Updater.app &>/dev/null") if self.restart_message - else defer.succeed(None)) - d.addCallbacks(lambda _: self._restart() if self.restart_message else defer.succeed(None)) - - return defer.succeed(None) - - def _restart(self): - def _disp_shutdown(): - print 'Restarting lbrynet daemon' - return defer.succeed(None) - - # LBRY Updater.app will restart the daemon - d = self._shutdown() - d.addCallback(lambda _: _disp_shutdown()) - d.addCallback(lambda _: reactor.callLater(1.0, reactor.stop)) - - return d - def _start_server(self): if self.peer_port is not None: @@ -1106,6 +1010,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): @return: time behind blockchain """ d = self.session.wallet.get_most_recent_blocktime() + d.addCallback(get_time_behind_blockchain) d.addCallbacks(lambda result: self._render_response(result, OK_CODE), lambda result: self._render_response(result, BAD_REQUEST)) diff --git a/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh b/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh deleted file mode 100644 index 7353c0a20..000000000 --- a/lbrynet/lbrynet_daemon/scripts/restart_daemon.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -echo "Restarting lbrynet-daemon" -lbrynet-daemon \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh b/lbrynet/lbrynet_daemon/scripts/update_lbry.sh deleted file mode 100755 index 09177e79c..000000000 --- a/lbrynet/lbrynet_daemon/scripts/update_lbry.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh - -lbrycrd_directory="/Users/${SUDO_USER}/Library/Application Support/lbrycrd" - -current_version=$(git ls-remote https://github.com/jackrobison/lbrynet-app.git | grep HEAD | cut -f 1) - -if [ -d "$lbrycrd_directory" ]; then - if [ -f "${lbrycrd_directory}/lbry_app_version.txt" ]; then - if grep -Fxq "$current_version" "${lbrycrd_directory}/lbry_app_version.txt"; then - echo "LBRY version $current_version is up to date" - exit - fi - fi -fi - -if ! brew list berkeley-db4 &>/dev/null; then - echo "Installing berkeley-db4" - sudo -u ${SUDO_USER} brew install https://rawgit.com/jackrobison/homebrew/master/Library/Formula/berkeley-db4.rb &>/dev/null - sudo -u ${SUDO_USER} brew link --force berkeley-db4 &>/dev/null -else - echo "berkeley-db4 already installed" -fi - -tmp=$(mktemp -d) -cd $tmp - -echo "Downloading LBRY update" -git clone --depth 1 https://github.com/jackrobison/lbrynet-app.git &>/dev/null -cd lbrynet-app -unzip LBRY.app.zip &>/dev/null -unzip LBRYURIHandler.app.zip &>/dev/null -unzip LBRY\ Updater.app.zip &>/dev/null - -echo "Installing update" - -mkdir -p "$lbrycrd_directory" -echo $current_version > "${lbrycrd_directory}/lbry_app_version.txt" - -rm -rf /Applications/LBRY.app &>/dev/null -rm -rf /Applications/LBRYURIHandler.app &>/dev/null -rm -rf /Applications/LBRY\ Updater.app &>/dev/null - -mv -f LBRY.app /Applications -mv -f LBRYURIHandler.app /Applications -mv -f LBRY\ Updater.app /Applications - -echo "Cleaning up" - -cd ../../ -rm -rf $tmp \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh b/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh deleted file mode 100755 index fbb7d2b17..000000000 --- a/lbrynet/lbrynet_daemon/scripts/update_lbrynet.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/bin/sh - -if ! which brew &>/dev/null; then - echo "Installing brew..." - sudo -u ${SUDO_USER} ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null &>/dev/null -else - echo "Updating brew..." - sudo -u ${SUDO_USER} brew update &>/dev/null -fi - -if ! brew list mpfr &>/dev/null; then - echo "Installing mpfr..." - sudo -u ${SUDO_USER} brew install mpfr &>/dev/null -else - echo "mpfr already installed..." -fi - -if ! brew list libmpc &>/dev/null; then - echo "Installing libmpc..." - sudo -u ${SUDO_USER} brew install libmpc &>/dev/null -else - echo "libmpc already installed..." -fi - -if ! brew list openssl &>/dev/null; then - echo "Installing openssl..." - sudo -u ${SUDO_USER} brew install openssl &>/dev/null - sudo -u ${SUDO_USER} brew link --force openssl &>/dev/null -else - echo "openssl already installed..." -fi - -if ! which pip &>/dev/null; then - echo "Installing pip..." - sudo easy_install pip &>/dev/null -else - echo "pip already installed" -fi - -if ! python -c 'import gmpy' &>/dev/null; then - echo "Installing gmpy..." - sudo pip install gmpy &>/dev/null -else - echo "gmpy already installed..." -fi - -if ! python -c 'import service_identity' &>/dev/null; then - echo "Installing service_identity..." - sudo pip install service_identity &>/dev/null -else - echo "gmpy already installed..." -fi - -if ! python -c 'import rumps' &>/dev/null; then - echo "Installing rumps..." - sudo pip install rumps &>/dev/null -else - echo "rumps already installed..." -fi - -if ! python -c "import six; exit(0) if six.__version__ == '1.9.0' else exit(1)" &>/dev/null; then - echo "Installing six 1.9.0 for python..." - curl -O https://pypi.python.org/packages/source/s/six/six-1.9.0.tar.gz &>/dev/null - tar xf six-1.9.0.tar.gz &>/dev/null - cd six-1.9.0 - sudo python setup.py install &>/dev/null - cd .. - rm -rf six-1.9.0 - rm six-1.9.0.tar.gz -fi - -lbrynet_directory="/Users/${SUDO_USER}/Library/Application Support/lbrynet" -lbryum_current_version=$(git ls-remote https://github.com/lbryio/lbryum.git | grep HEAD | cut -f 1) - -if [ -d "$lbrynet_directory" ]; then - if [ -f "${lbrynet_directory}/lbryum_version.txt" ]; then - if grep -Fxq "$lbryum_current_version" "${lbrynet_directory}/lbryum_version.txt"; then - echo "LBRYum version $lbryum_current_version is up to date" - else - tmp=$(mktemp -d) - cd $tmp - - echo "Downloading LBRYum update..." - - git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null - cd lbryum - - echo "Installing update..." - sudo python setup.py install &>/dev/null - mkdir -p "$lbrynet_directory" - echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" - - echo "Cleaning up..." - - cd ../../ - rm -rf $tmp - fi - else - tmp=$(mktemp -d) - cd $tmp - - echo "Downloading LBRYum..." - - git clone --depth 1 https://github.com/lbryio/lbryum.git &>/dev/null - cd lbryum - - echo "Installing..." - sudo python setup.py install &>/dev/null - mkdir -p "$lbrynet_directory" - echo $lbryum_current_version > "${lbrynet_directory}/lbryum_version.txt" - - echo "Cleaning up..." - - cd ../../ - rm -rf $tmp - fi -fi - -lbrynet_current_version=$(git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1) - -if [ -d "$lbrynet_directory" ]; then - if [ -f "${lbrynet_directory}/lbrynet_version.txt" ]; then - if grep -Fxq "$lbrynet_current_version" "${lbrynet_directory}/lbrynet_version.txt"; then - echo "LBRYnet version $lbrynet_current_version is up to date" - else - tmp=$(mktemp -d) - cd $tmp - - echo "Downloading LBRYnet update" - - git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null - cd lbry - - echo "Installing update..." - sudo python setup.py install &>/dev/null - mkdir -p "$lbrynet_directory" - echo $lbrynet_current_version > "${lbrynet_directory}/lbrynet_version.txt" - - echo "Cleaning up..." - - cd ../../ - rm -rf $tmp - fi - else - tmp=$(mktemp -d) - cd $tmp - - echo "Downloading LBRYnet..." - - git clone --depth 1 https://github.com/lbryio/lbry.git &>/dev/null - cd lbry - - echo "Installing..." - sudo python setup.py install &>/dev/null - mkdir -p "$lbrynet_directory" - echo $lbrynet_current_version > "${lbrynet_directory}/lbrynet_version.txt" - - echo "Cleaning up..." - - cd ../../ - rm -rf $tmp - fi -fi \ No newline at end of file From a55e3d8578c76b929417c16118a0a1d22d939e6e Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 22 Mar 2016 10:37:13 -0400 Subject: [PATCH 074/462] use UI downloaded to temp folder at startup --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 8 +++++-- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 25 ++++++++++++++++----- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 922f3be27..84ca4478b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1120,6 +1120,10 @@ class LBRYDaemonCommandHandler(object): class LBRYindex(resource.Resource): + def __init__(self, ui_dir): + resource.Resource.__init__(self) + self.ui_dir = ui_dir + isLeaf = False def _delayed_render(self, request, results): @@ -1136,7 +1140,7 @@ class LBRYindex(resource.Resource): log.info(r) return "
" + ''.join(r) + "" - return static.File("./dist/index.html").render_GET(request) + return static.File(os.path.join(self.ui_dir, "index.html")).render_GET(request) class LBRYFileRender(resource.Resource): @@ -1202,4 +1206,4 @@ class LBRYDaemonWeb(resource.Resource): d.addCallbacks(lambda results: self._delayed_render(request, results), lambda err: self._delayed_render(request, json.dumps({'message': err.getTraceback(), 'code': BAD_REQUEST}))) - return server.NOT_DONE_YET + return server.NOT_DONE_YET \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 89b678f68..ce6540d0c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -1,5 +1,12 @@ import argparse import logging +import tempfile +import os +import shutil + +from StringIO import StringIO +from zipfile import ZipFile +from urllib import urlopen from twisted.web import server, static from twisted.internet import reactor, defer @@ -38,18 +45,26 @@ def start(): log.info("Starting lbrynet-daemon from command line") + tmpdir = tempfile.mkdtemp() + url = urlopen("https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip") + z = ZipFile(StringIO(url.read())) + z.extractall(tmpdir) + + args = parser.parse_args() daemon = LBRYDaemon() daemon.setup(args.wallet, args.update) - root = LBRYindex() - root.putChild("css", static.File("./css")) - root.putChild("font", static.File("./font")) - root.putChild("img", static.File("./img")) - root.putChild("js", static.File("./js")) + root = LBRYindex(tmpdir) + root.putChild("css", static.File(os.path.join(tmpdir, "css"))) + root.putChild("font", static.File(os.path.join(tmpdir, "font"))) + root.putChild("img", static.File(os.path.join(tmpdir, "img"))) + root.putChild("js", static.File(os.path.join(tmpdir, "js"))) root.putChild(API_ADDRESS, daemon) root.putChild("webapi", LBRYDaemonWeb()) root.putChild("view", LBRYFileRender()) reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) reactor.run() + + shutil.rmtree(tmpdir) \ No newline at end of file From 13acbfb64f6eee683d1df4de332418e52fbc096f Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 22 Mar 2016 11:51:27 -0400 Subject: [PATCH 075/462] update uri handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -use new jsonrpc daemon, start app if it isn’t running on first lbry:// request --- lbrynet/conf.py | 2 + lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 83 ++++++++++--------- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 +- setup_uri_handler.py | 2 + 4 files changed, 52 insertions(+), 39 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 227589def..f3903ae6a 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -36,6 +36,8 @@ APP_NAME = "LBRY" DEFAULT_WALLET = "lbryum" API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRESS) +UI_ADDRESS = "http://" + API_INTERFACE + ":" + str(API_PORT) + PROTOCOL_PREFIX = "lbry" DEFAULT_TIMEOUT = 30 \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index 1cc1bf3f8..1bc8c3399 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -1,54 +1,61 @@ import os import json import webbrowser -import xmlrpclib, sys +import sys +from time import sleep -def render_video(path): - r = r'
' - return r +from jsonrpc.proxy import JSONRPCProxy +from lbrynet.conf import API_CONNECTION_STRING, UI_ADDRESS -def main(args): - if len(args) == 0: - args.append('lbry://wonderfullife') +class LBRYURIHandler(object): + def __init__(self): + self.started_daemon = False + self.start_timeout = 0 + self.daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING) - daemon = xmlrpclib.ServerProxy('http://localhost:7080/') + def check_status(self): + try: + self.daemon.is_running() - try: - daemon.is_running() - - if len(args) > 1: - exit(1) - - if args[0][7:] == 'lbry': - daemon.render_gui() - - elif args[0][7:] == 'settings': - r = daemon.get_settings() - html = "" + json.dumps(r) + "" - daemon.render_html(html) - - else: - r = daemon.get(args[0][7:]) - if r[0] == 200: - path = r[1]['path'] - if path[0] != '/': - path = '/' + path - - filename = os.path.basename(path) - extension = os.path.splitext(filename)[1] - - if extension in ['mp4', 'flv', 'mov']: - html = render_video(path) - daemon.render_html(html) + except: + if self.started_daemon: + if self.start_timeout < 30: + sleep(1) + self.start_timeout += 1 + self.check_status() else: - webbrowser.get('safari').open('file://' + str(path)) + exit(1) + else: + os.system("open /Applications/LBRY.app") + self.started_daemon = True + self.start_timeout += 1 + self.check_status() + def handle(self, lbry_name): + self.check_status() + + if lbry_name == "lbry": + webbrowser.get('safari').open(UI_ADDRESS) + else: + r = json.loads(self.daemon.get({'name': lbry_name})) + if r['code'] == 200: + path = r['result']['path'].encode('utf-8') + extension = os.path.splitext(path)[1] + if extension in ['mp4', 'flv', 'mov', 'ogv']: + webbrowser.get('safari').open(UI_ADDRESS + "/view?name=" + lbry_name) + else: + webbrowser.get('safari').open('file://' + path) else: webbrowser.get('safari').open('http://lbry.io/get') - except: - webbrowser.get('safari').open('http://lbry.io/get') + +def main(args): + if len(args) != 1: + args = ['lbry://lbry'] + + name = args[0][7:] + LBRYURIHandler().handle(lbry_name=name) if __name__ == "__main__": diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 84ca4478b..b3e37a1f7 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1147,7 +1147,9 @@ class LBRYFileRender(resource.Resource): isLeaf = False def _render_path(self, path): - return r'
' + extension = os.path.splitext(path)[1] + if extension in ['mp4', 'flv', 'mov', 'ogv']: + return r'
' def _delayed_render(self, request, results): request.write(str(results)) diff --git a/setup_uri_handler.py b/setup_uri_handler.py index 37a38826e..02f437e64 100644 --- a/setup_uri_handler.py +++ b/setup_uri_handler.py @@ -4,7 +4,9 @@ import os APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'Apps', 'LBRYURIHandler.py')] DATA_FILES = [] OPTIONS = {'argv_emulation': True, + 'packages': ['lbrynet', 'jsonrpc'], 'plist': { + 'LSUIElement': True, 'CFBundleURLTypes': [ { 'CFBundleURLTypes': 'LBRYURIHandler', From 141162e5aee02da87d7b2e25dbf8c8e51830c9a6 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 22 Mar 2016 22:15:07 -0400 Subject: [PATCH 076/462] fixes so we can build an sdist --- MANIFEST.in | 2 ++ setup.py | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..97304ecca --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include lbrynet/lbrynet_console/plugins/blindrepeater.yapsy-plugin +recursive-include lbrynet/lbrynet_gui *.gif *.ico *.xbm *.conf diff --git a/setup.py b/setup.py index 8dde41775..ed9b2a866 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,5 @@ #!/usr/bin/env python -import ez_setup -ez_setup.use_setuptools() from setuptools import setup, find_packages import sys import os @@ -49,4 +47,4 @@ setup(name='lbrynet', ('lbrynet/lbrynet_gui', gui_data_paths) ], dependency_links=['https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum'], - ) \ No newline at end of file + ) From e85683e40f0ab46fc2afe47ab7e5d4c109acc81a Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Tue, 22 Mar 2016 22:42:45 -0400 Subject: [PATCH 077/462] show more informative messages when blockchain is catching up --- lbrynet/core/LBRYcrdWallet.py | 38 ++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 3c0c5f03f..9015da57f 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -893,6 +893,9 @@ class LBRYumWallet(LBRYWallet): self.cmd_runner = None self.first_run = False self.printed_retrieving_headers = False + self._start_check = None + self._catch_up_check = None + self._caught_up_counter = 0 def _start(self): @@ -912,21 +915,30 @@ class LBRYumWallet(LBRYWallet): alert.info("Running the wallet for the first time...this may take a moment.") self.printed_retrieving_headers = True return False - start_check.stop() + self._start_check.stop() + self._start_check = None if self.network.is_connected(): network_start_d.callback(True) else: network_start_d.errback(ValueError("Failed to connect to network.")) - start_check = task.LoopingCall(check_started) + self._start_check = task.LoopingCall(check_started) - d.addCallback(lambda _: start_check.start(.1)) + d.addCallback(lambda _: self._start_check.start(.1)) d.addCallback(lambda _: network_start_d) d.addCallback(lambda _: self._load_wallet()) d.addCallback(lambda _: self._get_cmd_runner()) return d def _stop(self): + if self._start_check is not None: + self._start_check.stop() + self._start_check = None + + if self._catch_up_check is not None: + self._catch_up_check.stop() + self._catch_up_check = None + d = defer.Deferred() def check_stopped(): @@ -964,16 +976,28 @@ class LBRYumWallet(LBRYWallet): remote_height = self.network.get_server_height() if remote_height != 0 and remote_height - local_height <= 5: - alert.info('Wallet loaded.') - catch_up_check.stop() + msg = "" + if self._caught_up_counter != 0: + msg += "All caught up. " + msg += "Wallet loaded." + alert.info(msg) + self._catch_up_check.stop() + self._catch_up_check = None blockchain_caught_d.callback(True) + elif remote_height != 0: + if self._caught_up_counter == 0: + alert.info('Catching up to the blockchain...showing blocks left...') + if self._caught_up_counter % 30 == 0: + alert.info('%d...', (remote_height - local_height)) + self._caught_up_counter += 1 - catch_up_check = task.LoopingCall(check_caught_up) + + self._catch_up_check = task.LoopingCall(check_caught_up) d = threads.deferToThread(get_wallet) d.addCallback(self._save_wallet) d.addCallback(lambda _: self.wallet.start_threads(self.network)) - d.addCallback(lambda _: catch_up_check.start(.1)) + d.addCallback(lambda _: self._catch_up_check.start(.1)) d.addCallback(lambda _: blockchain_caught_d) return d From 94398071eb389b150e6c2e372c0370865cd94425 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 23 Mar 2016 19:49:14 -0400 Subject: [PATCH 078/462] have uri handler check if app is running MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -checks if LBRY.app is running, starts it if it isn’t -delete unused function in LBRYDaemon --- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 50 +++++++++++++------ lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 -- setup_uri_handler.py | 2 +- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index 1bc8c3399..ba62a78a4 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -3,9 +3,12 @@ import json import webbrowser import sys from time import sleep +import subprocess from jsonrpc.proxy import JSONRPCProxy -from lbrynet.conf import API_CONNECTION_STRING, UI_ADDRESS + +API_CONNECTION_STRING = "http://localhost:5279/lbryapi" +UI_ADDRESS = "http://localhost:5279" class LBRYURIHandler(object): @@ -15,27 +18,42 @@ class LBRYURIHandler(object): self.daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING) def check_status(self): + status = None try: - self.daemon.is_running() - - except: - if self.started_daemon: - if self.start_timeout < 30: - sleep(1) - self.start_timeout += 1 - self.check_status() - else: - exit(1) - else: - os.system("open /Applications/LBRY.app") - self.started_daemon = True + status = json.loads(self.daemon.is_running())['result'] + if self.start_timeout < 30 and not status: + sleep(1) self.start_timeout += 1 self.check_status() + elif status: + return True + else: + exit(1) + except: + if self.start_timeout < 30: + sleep(1) + self.start_timeout += 1 + self.check_status() + else: + exit(1) def handle(self, lbry_name): - self.check_status() + lbry_process = [d for d in subprocess.Popen(['ps','aux'], stdout=subprocess.PIPE).stdout.readlines() + if 'LBRY.app' in d] + try: + status = json.loads(self.daemon.is_running())['result'] + except: + pass - if lbry_name == "lbry": + if lbry_process: + self.check_status() + started = False + else: + os.system("open /Applications/LBRY.app") + self.check_status() + started = True + + if lbry_name == "lbry" or lbry_name == "" and not started: webbrowser.get('safari').open(UI_ADDRESS) else: r = json.loads(self.daemon.get({'name': lbry_name})) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index b3e37a1f7..3391e0dcb 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1136,10 +1136,6 @@ class LBRYindex(resource.Resource): return resource.Resource.getChild(self, name, request) def render_GET(self, request): - def _disp(r): - log.info(r) - return "
" + ''.join(r) + "" - return static.File(os.path.join(self.ui_dir, "index.html")).render_GET(request) diff --git a/setup_uri_handler.py b/setup_uri_handler.py index 02f437e64..1a3c68700 100644 --- a/setup_uri_handler.py +++ b/setup_uri_handler.py @@ -4,7 +4,7 @@ import os APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'Apps', 'LBRYURIHandler.py')] DATA_FILES = [] OPTIONS = {'argv_emulation': True, - 'packages': ['lbrynet', 'jsonrpc'], + 'packages': ['jsonrpc'], 'plist': { 'LSUIElement': True, 'CFBundleURLTypes': [ From 1ccd2a3cc9107d1cd17df66dc31019f3610ed5fd Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Wed, 23 Mar 2016 22:02:02 -0400 Subject: [PATCH 079/462] do version better --- lbrynet/__init__.py | 3 ++- setup.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index f780625b8..eb4d8454c 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,4 +4,5 @@ import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) -__version__ = (0, 2, 0) \ No newline at end of file +version = (0, 2, 0) +__version__ = ".".join([str(x) for x in version]) diff --git a/setup.py b/setup.py index ed9b2a866..4b8047d00 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'l gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] setup(name='lbrynet', - version='.'.join([str(x) for x in __version__]), + version=__version__, packages=find_packages(base_dir), install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum'], entry_points={'console_scripts': console_scripts}, From 543b327d09696b808c694ff6d5884fa415f183c3 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 23 Mar 2016 22:06:04 -0400 Subject: [PATCH 080/462] use only six 1.9 on os x --- setup.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index b032122fb..36000670b 100644 --- a/setup.py +++ b/setup.py @@ -23,13 +23,19 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch if sys.platform == 'darwin': console_scripts.append('lbrynet-daemon-status = lbrynet.lbrynet_daemon.LBRYOSXStatusBar:main') +requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', + 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', + 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs'] + +if sys.platform == 'darwin': + requires.append('six==1.9.0') +else: + requires.append('six>=1.9.0') setup(name='lbrynet', version='0.0.4', packages=find_packages(), - install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', - 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', - 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs'], + install_requires=requires, entry_points={'console_scripts': console_scripts}, data_files=[ ('lbrynet/lbrynet_console/plugins', From c714a3f91c58680147e664622c0f89e1039c2aad Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 23 Mar 2016 22:27:48 -0400 Subject: [PATCH 081/462] merge updates --- lbrynet/core/LBRYcrdWallet.py | 18 +- lbrynet/lbrynet_console/LBRYConsole.py | 55 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 855 ++++++++++++------------- setup.py | 34 +- 4 files changed, 490 insertions(+), 472 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 3c0c5f03f..5ead82cb2 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -316,8 +316,8 @@ class LBRYWallet(object): except (ValueError, TypeError): return Failure(InvalidStreamInfoError(name)) known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', - 'content_license', 'sources', 'fee'] - known_sources = ['lbry_sd_hash'] + 'content_license', 'sources', 'fee', 'author'] + known_sources = ['lbry_sd_hash', 'btih', 'url'] known_fee_types = {'LBC': ['amount', 'address']} for field in known_fields: if field in value_dict: @@ -353,7 +353,7 @@ class LBRYWallet(object): return Failure(UnknownNameError(name)) def claim_name(self, name, sd_hash, amount, description=None, key_fee=None, - key_fee_address=None, thumbnail=None, content_license=None): + key_fee_address=None, thumbnail=None, content_license=None, author=None, sources=None): value = {"sources": {'lbry_sd_hash': sd_hash}} if description is not None: value['description'] = description @@ -363,6 +363,10 @@ class LBRYWallet(object): value['thumbnail'] = thumbnail if content_license is not None: value['content_license'] = content_license + if author is not None: + value['author'] = author + if sources is not None: + value['sources'] = sources d = self._send_name_claim(name, json.dumps(value), amount) @@ -930,13 +934,15 @@ class LBRYumWallet(LBRYWallet): d = defer.Deferred() def check_stopped(): - if self.network.is_connected(): - return False + if self.network: + if self.network.is_connected(): + return False stop_check.stop() self.network = None d.callback(True) - self.network.stop() + if self.network: + self.network.stop() stop_check = task.LoopingCall(check_stopped) stop_check.start(.1) diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index 18bd6a8fc..6cde77c3f 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -1,16 +1,21 @@ import logging -from lbrynet.core.Session import LBRYSession import os.path import argparse import requests import locale import sys + +if sys.platform == "darwin": + from appdirs import user_data_dir from yapsy.PluginManager import PluginManager from twisted.internet import defer, threads, stdio, task, error +from jsonrpc.proxy import JSONRPCProxy + +from lbrynet.core.Session import LBRYSession from lbrynet.lbrynet_console.ConsoleControl import ConsoleControl from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager -from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE # , MIN_BLOB_INFO_PAYMENT_RATE +from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, API_CONNECTION_STRING # , MIN_BLOB_INFO_PAYMENT_RATE from lbrynet.core.utils import generate_id from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier from lbrynet.core.PaymentRateManager import PaymentRateManager @@ -461,7 +466,6 @@ class LBRYConsole(): def launch_lbry_console(): - from twisted.internet import reactor parser = argparse.ArgumentParser(description="Launch a lbrynet console") @@ -529,7 +533,7 @@ def launch_lbry_console(): created_data_dir = False if not args.data_dir: if sys.platform == "darwin": - data_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") + data_dir = user_data_dir("LBRY") else: data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: @@ -538,28 +542,35 @@ def launch_lbry_console(): os.mkdir(data_dir) created_data_dir = True + daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING) + try: + daemon.is_running() + log.info("Attempt to start lbrynet-console while lbrynet-daemon is running") + print "lbrynet-daemon is running, you must turn it off before using lbrynet-console" + print "If you're running the app, quit before starting lbrynet-console" + print "If you're running lbrynet-daemon in a terminal, run 'stop-lbrynet-daemon' to turn it off" - log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s" - formatter = logging.Formatter(log_format) + except: + log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s" + formatter = logging.Formatter(log_format) - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - file_handler = logging.FileHandler(os.path.join(data_dir, "console.log")) - file_handler.setFormatter(formatter) - file_handler.addFilter(logging.Filter("lbrynet")) - logger.addHandler(file_handler) + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + file_handler = logging.FileHandler(os.path.join(data_dir, "console.log")) + file_handler.setFormatter(formatter) + file_handler.addFilter(logging.Filter("lbrynet")) + logger.addHandler(file_handler) - console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, fake_wallet=args.fake_wallet, - lbrycrd_conf=args.lbrycrd_wallet_conf, lbrycrd_dir=args.lbrycrd_wallet_dir, - use_upnp=not args.disable_upnp, data_dir=data_dir, - created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path) - d = task.deferLater(reactor, 0, console.start) + console = LBRYConsole(peer_port, dht_node_port, bootstrap_nodes, fake_wallet=args.fake_wallet, + lbrycrd_conf=args.lbrycrd_wallet_conf, lbrycrd_dir=args.lbrycrd_wallet_dir, + use_upnp=not args.disable_upnp, data_dir=data_dir, + created_data_dir=created_data_dir, lbrycrdd_path=args.lbrycrdd_path) - d.addErrback(lambda _: reactor.stop()) - - reactor.addSystemEventTrigger('before', 'shutdown', console.shut_down) - reactor.run() + d = task.deferLater(reactor, 0, console.start) + d.addErrback(lambda _: reactor.stop()) + reactor.addSystemEventTrigger('before', 'shutdown', console.shut_down) + reactor.run() if __name__ == "__main__": - launch_lbry_console() \ No newline at end of file + launch_lbry_console() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 85c441826..d3b616b75 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,23 +1,26 @@ import locale import os import sys -import json +import simplejson as json import binascii -import webbrowser -import xmlrpclib import subprocess import logging -import argparse -import pwd import requests +# import rumps +# import httplib2 + +from twisted.web import server, resource, static +from twisted.internet import defer, threads, error, reactor +from txjsonrpc import jsonrpclib +from txjsonrpc.web import jsonrpc +from jsonrpc.proxy import JSONRPCProxy -from twisted.web import xmlrpc, server -from twisted.internet import defer, threads, reactor, error from datetime import datetime from decimal import Decimal from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen +from appdirs import user_data_dir from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory @@ -33,6 +36,7 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE +from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS, DEFAULT_TIMEOUT from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet @@ -41,28 +45,62 @@ from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager log = logging.getLogger(__name__) -# logging.basicConfig(level=logging.DEBUG) +BAD_REQUEST = 400 +NOT_FOUND = 404 +OK_CODE = 200 # TODO add login credentials in a conf file -# functions to add: -# TODO send credits to address # TODO alert if your copy of a lbry file is out of date with the name record -BAD_REQUEST = (400, "Bad request") -NOT_FOUND = (404, "Not found") - -OK_CODE = 200 +class Bunch: + def __init__(self, params): + self.__dict__.update(params) -class LBRYDaemon(xmlrpc.XMLRPC): +class LBRYDaemon(jsonrpc.JSONRPC): """ - LBRYnet daemon + LBRYnet daemon, a jsonrpc interface to lbry functions """ + isLeaf = True + + def render(self, request): + request.content.seek(0, 0) + # Unmarshal the JSON-RPC data. + content = request.content.read() + parsed = jsonrpclib.loads(content) + functionPath = parsed.get("method") + args = parsed.get('params') + id = parsed.get('id') + version = parsed.get('jsonrpc') + if version: + version = int(float(version)) + elif id and not version: + version = jsonrpclib.VERSION_1 + else: + version = jsonrpclib.VERSION_PRE1 + # XXX this all needs to be re-worked to support logic for multiple + # versions... + try: + function = self._getFunction(functionPath) + except jsonrpclib.Fault, f: + self._cbRender(f, request, id, version) + else: + request.setHeader('Access-Control-Allow-Origin', ('http://localhost' + ':' + str(API_PORT))) + request.setHeader("content-type", "text/json") + if args == [{}]: + d = defer.maybeDeferred(function) + else: + d = defer.maybeDeferred(function, *args) + d.addErrback(self._ebRender, id) + d.addCallback(self._cbRender, request, id, version) + return server.NOT_DONE_YET def setup(self, wallet_type, check_for_updates): def _set_vars(wallet_type, check_for_updates): + reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) + self.fetcher = None self.current_db_revision = 1 self.run_server = True @@ -71,7 +109,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): if sys.platform != "darwin": self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: - self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") + self.db_dir = user_data_dir("LBRY") + # self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 self.dht_node_port = 4444 @@ -82,11 +121,12 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") elif sys.platform == "darwin": self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') - self.wallet_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrycrd") + # self.wallet_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrycrd") + self.wallet_dir = user_data_dir("LBRY") else: self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') - + self.daemon_conf = os.path.join(self.wallet_dir, 'daemon_settings.conf') self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") self.wallet_user = None self.wallet_password = None @@ -119,28 +159,31 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE self.max_key_fee = DEFAULT_MAX_KEY_FEE self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS - self.restart_message = "" self.startup_message = "" self.announced_startup = False self.search_timeout = 3.0 self.query_handlers = {} + self.default_settings = { + 'run_on_startup': False, + 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, + 'max_key_fee': 10.0, + 'default_download_directory': self.download_directory, + 'max_upload': 0.0, + 'max_download': 0.0 + } return defer.succeed(None) def _disp_startup(): - if self.restart_message: - print self.restart_message - else: - print "Started LBRYnet daemon" - print "The daemon can be shut down by running 'stop-lbrynet-daemon' in a terminal" - log.info('[' + str(datetime.now()) + '] Started lbrynet-daemon') + log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") return defer.succeed(None) - log.info('[' + str(datetime.now()) + '] Starting lbrynet-daemon') + log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") d = defer.Deferred() - d.addCallback(lambda _: _set_vars(wallet_type, check_for_updates)) + d.addCallback(lambda _:_set_vars(wallet_type, check_for_updates)) + d.addCallback(lambda _: self._setup_daemon_settings()) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) d.addCallback(lambda _: self._get_settings()) @@ -152,104 +195,19 @@ class LBRYDaemon(xmlrpc.XMLRPC): d.addCallback(lambda _: self._setup_lbry_file_opener()) d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) - # d.addCallback(lambda _: self._update() if self.check_for_updates == "True" and sys.platform == "darwin" - # else defer.succeed(None)) d.addCallback(lambda _: self._setup_fetcher()) d.addCallback(lambda _: _disp_startup()) d.callback(None) return defer.succeed(None) - def _update(self): - def _check_for_updater(): - if os.path.isdir("/Applications/LBRY Updater.app"): - print "Found LBRY updater" - return defer.succeed(None) - - print "LBRY updater not found, downloading and installing..." - url = urlopen("https://rawgit.com/jackrobison/lbrynet-app/master/LBRY%20Updater.app.zip") - zipped_app = ZipFile(StringIO(url.read())) - zipped_app.extractall("/Applications") - return defer.succeed(None) - - def _update_lbrynet(): - git_version = subprocess.check_output( - "git ls-remote https://github.com/lbryio/lbry.git | grep HEAD | cut -f 1", - shell=True) - if os.path.isfile(os.path.join(self.db_dir, "lbrynet_version.txt")): - f = open(os.path.join(self.db_dir, "lbrynet_version.txt"), 'r') - current_version = f.read() - f.close() - if git_version == current_version: - print "LBRYnet installation version " + current_version[:-1] + " is up to date" - return defer.succeed(None) - print "Update LBRYnet version " + current_version[:-1] + " --> " + git_version[:-1] - self.restart_message = "Updates available" - else: - print "Update LBRYnet to version " + git_version[:-1] - self.restart_message = "Updates available" - - return defer.succeed(None) - - def _update_lbrycrdd(): - git_version = subprocess.check_output( - "git ls-remote https://github.com/jackrobison/lbrynet-app.git | grep HEAD | cut -f 1", - shell=True) - if os.path.isfile(os.path.join(self.wallet_dir, "lbry_app_version.txt")): - f = open(os.path.join(self.wallet_dir, "lbry_app_version.txt"), 'r') - current_version = f.read() - f.close() - if git_version == current_version: - print "LBRY installation version " + current_version[:-1] + " is up to date" - return defer.succeed(None) - print "Update LBRY version " + current_version[:-1] + " --> " + git_version[:-1] - self.restart_message = "Updates available" - else: - print "Update LBRY to version " + git_version[:-1] - self.restart_message = "Updates available" - - return defer.succeed(None) - - def _update_lbryum(): - git_version = subprocess.check_output( - "git ls-remote https://github.com/lbryio/lbryum.git | grep HEAD | cut -f 1", - shell=True) - if os.path.isfile(os.path.join(self.db_dir, "lbryum_version.txt")): - f = open(os.path.join(self.db_dir, "lbryum_version.txt"), 'r') - current_version = f.read() - f.close() - if git_version == current_version: - print "LBRYum installation version " + current_version[:-1] + " is up to date" - return defer.succeed(None) - print "Update LBRYum version " + current_version[:-1] + " --> " + git_version[:-1] - self.restart_message = "Updates available" - else: - print "Update LBRYum to version " + git_version[:-1] - self.restart_message = "Updates available" - - return defer.succeed(None) - - d = _check_for_updater() - d.addCallback(lambda _: _update_lbrynet()) - d.addCallback(lambda _: _update_lbrycrdd() if self.wallet_type == 'lbrycrd' else _update_lbryum()) - d.addCallback(lambda _: os.system("open /Applications/LBRY\ Updater.app &>/dev/null") if self.restart_message - else defer.succeed(None)) - d.addCallbacks(lambda _: self._restart() if self.restart_message else defer.succeed(None)) + def _initial_setup(self): + return defer.fail(NotImplementedError()) + def _setup_daemon_settings(self): + self.session_settings = self.default_settings return defer.succeed(None) - def _restart(self): - def _disp_shutdown(): - print 'Restarting lbrynet daemon' - return defer.succeed(None) - - # LBRY Updater.app will restart the daemon - d = self._shutdown() - d.addCallback(lambda _: _disp_shutdown()) - d.addCallback(lambda _: reactor.callLater(1.0, reactor.stop)) - - return d - def _start_server(self): if self.peer_port is not None: @@ -323,15 +281,34 @@ class LBRYDaemon(xmlrpc.XMLRPC): return dl def _shutdown(self): - print 'Closing lbrynet session' + log.info("Closing lbrynet session") d = self._stop_server() if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) return d - def _update_settings(self): - self.data_rate = self.session_settings['data_rate'] - self.max_key_fee = self.session_settings['max_key_fee'] + def _update_settings(self, settings): + if not isinstance(settings['run_on_startup'], bool): + return defer.fail() + elif not isinstance(settings['data_rate'], float): + return defer.fail() + elif not isinstance(settings['max_key_fee'], float): + return defer.fail() + elif not isinstance(settings['default_download_directory'], unicode): + return defer.fail() + elif not isinstance(settings['max_upload'], float): + return defer.fail() + elif not isinstance(settings['max_download'], float): + return defer.fail() + + self.session_settings['run_on_startup'] = settings['run_on_startup'] + self.session_settings['data_rate'] = settings['data_rate'] + self.session_settings['max_key_fee'] = settings['max_key_fee'] + self.session_settings['default_download_directory'] = settings['default_download_directory'] + self.session_settings['max_upload'] = settings['max_upload'] + self.session_settings['max_download'] = settings['max_download'] + + return defer.succeed(True) def _setup_fetcher(self): self.fetcher = FetcherDaemon(self.session, self.lbry_file_manager, self.lbry_file_metadata_manager, @@ -339,7 +316,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): return defer.succeed(None) def _setup_data_directory(self): - print "Loading databases..." + log.info("Loading databases...") if self.created_data_dir: db_revision = open(os.path.join(self.db_dir, "db_revision"), mode='w') db_revision.write(str(self.current_db_revision)) @@ -356,7 +333,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): old_revision = int(open(db_revision_file).read().strip()) if old_revision < self.current_db_revision: from lbrynet.db_migrator import dbmigrator - print "Upgrading your databases..." + log.info("Upgrading your databases...") d = threads.deferToThread(dbmigrator.migrate_db, self.db_dir, old_revision, self.current_db_revision) def print_success(old_dirs): @@ -367,7 +344,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): success_string += old_dir if i + 1 < len(old_dir): success_string += ", " - print success_string + log.info(success_string) d.addCallback(print_success) return d @@ -396,7 +373,10 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self.lbry_file_metadata_manager.setup() def set_lbry_file_manager(): - self.lbry_file_manager = LBRYFileManager(self.session, self.lbry_file_metadata_manager, self.sd_identifier) + self.lbry_file_manager = LBRYFileManager(self.session, + self.lbry_file_metadata_manager, + self.sd_identifier, + delete_data=True) return self.lbry_file_manager.setup() d.addCallback(lambda _: set_lbry_file_manager()) @@ -407,12 +387,11 @@ class LBRYDaemon(xmlrpc.XMLRPC): def get_default_data_rate(): d = self.settings.get_default_data_payment_rate() d.addCallback(lambda rate: {"default_data_payment_rate": rate if rate is not None else - MIN_BLOB_DATA_PAYMENT_RATE}) + MIN_BLOB_DATA_PAYMENT_RATE}) return d def get_wallet(): if self.wallet_type == "lbrycrd": - print "Using lbrycrd wallet" log.info("Using lbrycrd wallet") lbrycrdd_path = None if self.start_lbrycrdd is True: @@ -422,11 +401,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.lbrycrd_conf, lbrycrdd_path=lbrycrdd_path)) elif self.wallet_type == "lbryum": - print "Using lbryum wallet" log.info("Using lbryum wallet") d = defer.succeed(LBRYumWallet(self.db_dir)) elif self.wallet_type == "ptc": - print "Using PTC wallet" log.info("Using PTC wallet") d = defer.succeed(PTCWallet(self.db_dir)) else: @@ -509,7 +486,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): for line in conf: if len(line.strip()) and line.strip()[0] != "#": self.lbrycrdd_path = line.strip() - print self.lbrycrdd_path d.addCallback(load_lbrycrdd_path) return d @@ -533,20 +509,20 @@ class LBRYDaemon(xmlrpc.XMLRPC): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory) return defer.succeed(True) - def _download_name(self, name): - def _disp_file(file): - print '[' + str(datetime.now()) + ']' + ' Already downloaded: ' + str(file.stream_hash) - d = self._path_from_lbry_file(file) - return d + def _download_name(self, name, timeout=DEFAULT_TIMEOUT): + def _disp_file(f): + file_path = os.path.join(self.download_directory, f.file_name) + log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) + return defer.succeed(f) def _get_stream(name): def _disp(stream): - print '[' + str(datetime.now()) + ']' + ' Start stream: ' + stream['stream_hash'] + log.info("[" + str(datetime.now()) + "] Start stream: " + stream['stream_hash']) return stream d = self.session.wallet.get_stream_info_for_name(name) stream = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, - max_key_fee=self.max_key_fee, data_rate=self.data_rate) + max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout) d.addCallback(_disp) d.addCallback(lambda stream_info: stream.start(stream_info)) d.addCallback(lambda _: self._path_from_name(name)) @@ -555,12 +531,8 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self._check_history(name) d.addCallback(lambda lbry_file: _get_stream(name) if not lbry_file else _disp_file(lbry_file)) - d.addCallback(lambda _: self._check_history(name)) - d.addCallback(lambda lbry_file: (OK_CODE, {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, - lbry_file.file_name)}) - if lbry_file else NOT_FOUND) - d.addErrback(lambda _: NOT_FOUND) + d.addCallback(lambda _: self._path_from_name(name)) + d.addErrback(lambda err: defer.fail(NOT_FOUND)) return d @@ -584,27 +556,32 @@ class LBRYDaemon(xmlrpc.XMLRPC): f = open(path, 'r') l = json.loads(f.read()) f.close() + file_name = l['stream_name'].decode('hex') - lbry_file = [file for file in self.lbry_file_manager.lbry_files if file.stream_name == file_name] - if lbry_file: - return lbry_file[0] + for lbry_file in self.lbry_file_manager.lbry_files: + if lbry_file.stream_name == file_name: + if sys.platform == "darwin": + if os.path.isfile(os.path.join(self.download_directory, lbry_file.stream_name)): + return lbry_file + else: + return False + else: + return lbry_file else: - return None + return False def _check(info): stream_hash = info['stream_hash'] path = os.path.join(self.blobfile_dir, stream_hash) if os.path.isfile(path): - print "[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash log.info("[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash) return defer.succeed(_get_lbry_file(path)) else: - print "[" + str(datetime.now()) + "] Search for lbry_file didn't return anything" log.info("[" + str(datetime.now()) + "] Search for lbry_file didn't return anything") return defer.succeed(False) d = self._resolve_name(name) - d.addCallbacks(_check, lambda _: False) + d.addCallback(_check) d.callback(None) return d @@ -615,7 +592,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): def finish_deletion(lbry_file): d = lbry_file.delete_data() d.addCallback(lambda _: _delete_stream_data(lbry_file)) - d.addCallback(lambda _: _delete_file(lbry_file)) return d def _delete_stream_data(lbry_file): @@ -623,11 +599,10 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self.lbry_file_manager.get_count_for_stream_hash(s_h) # TODO: could possibly be a timing issue here d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) + d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if + os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) else defer.succeed(None)) return d - def _delete_file(lbry_file): - os.remove(os.path.join(self.download_directory, lbry_file.file_name)) - d.addCallback(lambda _: finish_deletion(lbry_file)) return d @@ -635,7 +610,7 @@ class LBRYDaemon(xmlrpc.XMLRPC): d = self._check_history(name) d.addCallback(lambda lbry_file: {'stream_hash': lbry_file.stream_hash, 'path': os.path.join(self.download_directory, lbry_file.file_name)} - if lbry_file else defer.fail(UnknownNameError)) + if lbry_file else defer.fail(UnknownNameError)) return d def _path_from_lbry_file(self, lbry_file): @@ -649,11 +624,9 @@ class LBRYDaemon(xmlrpc.XMLRPC): def _get_est_cost(self, name): def _check_est(d, name): if type(d.result) is float: - print '[' + str(datetime.now()) + '] Cost est for lbry://' + name + ': ' + str(d.result) + 'LBC' - log.info('[' + str(datetime.now()) + '] Cost est for lbry://' + name + ': ' + str(d.result) + 'LBC') + log.info("[" + str(datetime.now()) + "] Cost est for lbry://" + name + ": " + str(d.result) + "LBC") else: - print '[' + str(datetime.now()) + '] Timeout estimating cost for lbry://' + name + ', using key fee' - log.info('[' + str(datetime.now()) + '] Timeout estimating cost for lbry://' + name + ', using key fee') + log.info("[" + str(datetime.now()) + "] Timeout estimating cost for lbry://" + name + ", using key fee") d.cancel() return defer.succeed(None) @@ -674,94 +647,109 @@ class LBRYDaemon(xmlrpc.XMLRPC): return d - def xmlrpc_is_running(self): - if self.startup_message != "" and self.announced_startup == False: - print "Startup message:", self.startup_message - self.announced_startup = True - return self.startup_message - elif self.announced_startup: - return True - else: - return False + def _render_response(self, result, code): + return json.dumps({'result': result, 'code': code}) - def xmlrpc_get_settings(self): + # def _log_to_slack(self, msg): + # URL = "https://hooks.slack.com/services/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" + # h = httplib2.Http() + # h.request(URL, 'POST', json.dumps({"text": msg}), headers={'Content-Type': 'application/json'}) + + def jsonrpc_is_running(self): + """ + Returns a startup message when the daemon starts, after which it will return True + """ + + if self.startup_message != "" and self.announced_startup == False: + self.announced_startup = True + return self._render_response(self.startup_message, OK_CODE) + elif self.announced_startup: + return self._render_response(True, OK_CODE) + else: + return self._render_response(False, OK_CODE) + + def jsonrpc_get_settings(self): """ Get LBRY payment settings @return {'data_rate': float, 'max_key_fee': float} """ - if not self.session_settings: - self.session_settings = {'data_rate': self.data_rate, 'max_key_fee': self.max_key_fee} + log.info("[" + str(datetime.now()) + "] Get daemon settings") + return self._render_response(self.session_settings, OK_CODE) - print '[' + str(datetime.now()) + '] Get daemon settings' - return self.session_settings - - def xmlrpc_set_settings(self, settings): + def jsonrpc_set_settings(self, p): """ Set LBRY payment settings - @param settings dict: {'data_rate': float, 'max_key_fee': float} + @param settings: {'settings': {'data_rate': float, 'max_key_fee': float}} """ - self.session_settings = settings - self._update_settings() + d = self._update_settings(p) - print '[' + str(datetime.now()) + '] Set daemon settings' - return 'Set' + log.info("[" + str(datetime.now()) + "] Set daemon settings") + return self._render_response(True, OK_CODE) - def xmlrpc_start_fetcher(self): + def jsonrpc_start_fetcher(self): """ - Start autofetcher + Start automatically downloading new name claims as they happen + + @return: confirmation message """ self.fetcher.start() - print '[' + str(datetime.now()) + '] Start autofetcher' log.info('[' + str(datetime.now()) + '] Start autofetcher') - return 'Started autofetching' + # self._log_to_slack('[' + str(datetime.now()) + '] Start autofetcher') + return self._render_response("Started autofetching claims", OK_CODE) - def xmlrpc_stop_fetcher(self): + def jsonrpc_stop_fetcher(self): """ - Stop autofetcher + Stop automatically downloading new name claims as they happen + + @return: confirmation message """ self.fetcher.stop() - print '[' + str(datetime.now()) + '] Stop autofetcher' log.info('[' + str(datetime.now()) + '] Stop autofetcher') - return 'Stopped autofetching' + return self._render_response("Stopped autofetching claims", OK_CODE) - def xmlrpc_fetcher_status(self): + def jsonrpc_fetcher_status(self): """ - Start autofetcher + Get fetcher status + + @return: True/False """ - print '[' + str(datetime.now()) + '] Get fetcher status' - return str(self.fetcher.check_if_running()) + log.info("[" + str(datetime.now()) + "] Get fetcher status") + return self._render_response(self.fetcher.check_if_running(), OK_CODE) - def xmlrpc_get_balance(self): + def jsonrpc_get_balance(self): """ Get LBC balance + + @return: balance """ - print '[' + str(datetime.now()) + '] Get balance' - return str(self.session.wallet.wallet_balance) + log.info("[" + str(datetime.now()) + "] Get balance") + return self._render_response(self.session.wallet.wallet_balance, OK_CODE) - def xmlrpc_stop(self): + def jsonrpc_stop(self): """ Stop lbrynet-daemon + + @return: shutdown message """ def _disp_shutdown(): - log.info('Shutting down lbrynet daemon') - print 'Shutting down lbrynet daemon' + log.info("Shutting down lbrynet daemon") d = self._shutdown() d.addCallback(lambda _: _disp_shutdown()) d.addCallback(lambda _: reactor.callLater(1.0, reactor.stop)) - return defer.succeed('Shutting down') + return self._render_response("Shutting down", OK_CODE) - def xmlrpc_get_lbry_files(self): + def jsonrpc_get_lbry_files(self): """ Get LBRY files @@ -783,124 +771,89 @@ class LBRYDaemon(xmlrpc.XMLRPC): r.append(json.dumps(t)) - print '[' + str(datetime.now()) + '] Get LBRY files' - return r + log.info("[" + str(datetime.now()) + "] Get LBRY files") + return self._render_response(r, OK_CODE) - def xmlrpc_resolve_name(self, name): + def jsonrpc_resolve_name(self, p): """ Resolve stream info from a LBRY uri - @param: name + @param: {'name': name to look up} @return: info for name claim """ + params = Bunch(p) def _disp(info): - print '[' + str(datetime.now()) + ']' + ' Resolved info: ' + str(info['stream_hash']) - return info + log.info("[" + str(datetime.now()) + "] Resolved info: " + info['stream_hash']) + return self._render_response(info, OK_CODE) - d = self._resolve_name(name) - d.addCallbacks(_disp, lambda _: str('UnknownNameError')) + d = self._resolve_name(params.name) + d.addCallbacks(_disp, lambda _: self._render_response('error', NOT_FOUND)) d.callback(None) return d - def xmlrpc_get(self, name): + def jsonrpc_get(self, p): """ Download stream from a LBRY uri @param: name @return: {'stream_hash': hex string, 'path': path of download} """ + params = Bunch(p) - if name: - d = self._download_name(name) + if 'timeout' not in p.keys(): + params.timeout = DEFAULT_TIMEOUT + + if params.name: + d = self._download_name(params.name, timeout=params.timeout) + d.addCallbacks(lambda message: self._render_response(message, OK_CODE), + lambda err: self._render_response('error', NOT_FOUND)) else: - d = defer.succeed(BAD_REQUEST) + d = self._render_response('error', BAD_REQUEST) + return d - def xmlrpc_stop_lbry_file(self, stream_hash): + def jsonrpc_stop_lbry_file(self, p): + params = Bunch(p) + try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == stream_hash][0] + lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] except IndexError: return defer.fail(UnknownNameError) if not lbry_file.stopped: d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - d.addCallback(lambda _: 'Stream has been stopped') - d.addErrback(lambda err: str(err)) + d.addCallback(lambda _: self._render_response("Stream has been stopped", OK_CODE)) + d.addErrback(lambda err: self._render_response(err.getTraceback(), )) return d else: - return defer.succeed('Stream was already stopped') + return json.dumps({'result': 'Stream was already stopped'}) + + def jsonrpc_start_lbry_file(self, p): + params = Bunch(p) - def xmlrpc_start_lbry_file(self, stream_hash): try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == stream_hash][0] + lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] except IndexError: return defer.fail(UnknownNameError) if lbry_file.stopped: d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) d.callback(None) - return defer.succeed('Stream started') + return json.dumps({'result': 'Stream started'}) else: - return defer.succeed('Stream was already running') + return json.dumps({'result': 'Stream was already running'}) - def xmlrpc_render_html(self, html): - """ - Writes html to lbry.html in the downloads directory, then opens it with the browser - - @param html: - """ - - def _make_file(html, path): - f = open(path, 'w') - f.write(html) - f.close() - return defer.succeed(None) - - def _disp_err(err): - print str(err.getTraceback()) - return err - - path = os.path.join(self.download_directory, 'lbry.html') - - d = defer.Deferred() - d.addCallback(lambda _: _make_file(html, path)) - d.addCallback(lambda _: os.chown(path, pwd.getpwuid(os.getuid()).pw_uid, pwd.getpwuid(os.getuid()).pw_gid)) - d.addCallback(lambda _: webbrowser.open('file://' + path)) - d.addErrback(_disp_err) - d.callback(None) - - return d - - def xmlrpc_render_gui(self): - """ - Opens the lbry web ui in a browser - """ - - def _disp_err(err): - print str(err.getTraceback()) - return err - - d = defer.Deferred() - if sys.platform == 'darwin': - d.addCallback(lambda _: webbrowser.get('safari').open( - "file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) - else: - d.addCallback(lambda _: webbrowser.open( - "file://" + str(os.path.join(self.download_directory, "lbryio/view/page/gui.html")))) - d.addErrback(_disp_err) - d.callback(None) - - return d - - def xmlrpc_search_nametrie(self, search): + def jsonrpc_search_nametrie(self, p): """ Search the nametrie for claims beginning with search - @param search: - @return: + @param {'search': search string} + @return: List of search results """ + params = Bunch(p) + def _clean(n): t = [] for i in n: @@ -910,26 +863,6 @@ class LBRYDaemon(xmlrpc.XMLRPC): t.append([i[1][0][1], i[1][1][1], i[1][2][1]]) return t - def _parse(results): - f = [] - for chain, meta, cost_est in results: - t = {} - if 'name' in chain.keys(): - t['name'] = chain['name'] - if 'thumbnail' in meta.keys(): - t['img'] = meta['thumbnail'] - else: - t['img'] = 'File://' + str( - os.path.join(self.download_directory, "lbryio/web/img/Free-speech-flag.svg")) - if 'name' in meta.keys(): - t['title'] = meta['name'] - if 'description' in meta.keys(): - t['description'] = meta['description'] - t['cost_est'] = cost_est - f.append(t) - - return f - def resolve_claims(claims): ds = [] for claim in claims: @@ -941,148 +874,163 @@ class LBRYDaemon(xmlrpc.XMLRPC): return defer.DeferredList(ds) def _disp(results): - print '[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' results' - log.info('[' + str(datetime.now()) + '] Search results: ') + log.info('[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' search results') + consolidated_results = [] for r in results: - log.info(str(r)) - return results + t = {} + t.update(r[0]) + if 'name' in r[1].keys(): + r[1]['stream_name'] = r[1]['name'] + del r[1]['name'] + t.update(r[1]) + t['cost_est'] = r[2] + consolidated_results.append(t) + # log.info(str(t)) + return self._render_response(consolidated_results, OK_CODE) - print '[' + str(datetime.now()) + '] Search nametrie: ' + search - log.info('[' + str(datetime.now()) + '] Search nametrie: ' + search) + log.info('[' + str(datetime.now()) + '] Search nametrie: ' + params.search) d = self.session.wallet.get_nametrie() - d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) + d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(params.search) and 'txid' in claim]) d.addCallback(lambda claims: claims[:self.max_search_results]) d.addCallback(resolve_claims) d.addCallback(_clean) - d.addCallback(_parse) d.addCallback(_disp) return d - def xmlrpc_delete_lbry_file(self, file_name): + def jsonrpc_delete_lbry_file(self, p): + """ + Delete a lbry file + + @param {'file_name': string} + @return: confirmation message + """ + + params = Bunch(p) + def _disp(file_name): - print '[' + str(datetime.now()) + '] Deleted: ' + file_name - return defer.succeed('Deleted: ' + file_name) + log.info("[" + str(datetime.now()) + "] Deleted: " + file_name) + return self._render_response("Deleted: " + file_name, OK_CODE) - lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files if file_name == f.file_name] + lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files if params.file_name == f.file_name] d = defer.DeferredList(lbry_files) - d.addCallback(lambda _: _disp(file_name)) + d.addCallback(lambda _: _disp(params.file_name)) return d - def xmlrpc_check(self, name): - d = self._check_history(name) - d.addCallback(lambda lbry_file: self._path_from_lbry_file(lbry_file) if lbry_file else 'Not found') - d.addErrback(lambda err: str(err)) + def jsonrpc_publish(self, p): + """ + Make a new name claim - return d + @param: + @return: + """ - def xmlrpc_publish(self, metadata): - try: - metadata = json.loads(metadata) - except: - return defer.succeed(BAD_REQUEST) + params = Bunch(p) - required = ['name', 'file_path', 'bid'] + metadata_fields = {"name": unicode, "file_path": unicode, "bid": float, "author": unicode, "title": unicode, + "description": unicode, "thumbnail": unicode, "key_fee": float, "key_fee_address": unicode, + "content_license": unicode, "sources": dict} - for r in required: - if not r in metadata.keys(): - return defer.succeed(BAD_REQUEST) + for k in metadata_fields.keys(): + if k in params.__dict__.keys(): + if isinstance(params.__dict__[k], metadata_fields[k]): + if type(params.__dict__[k]) == unicode: + metadata_fields[k] = str(params.__dict__[k]) + else: + metadata_fields[k] = params.__dict__[k] + else: + metadata_fields[k] = None + else: + metadata_fields[k] = None - # if not os.path.isfile(metadata['file_path']): - # return defer.fail() - - if not isinstance(metadata['bid'], float) and metadata['bid'] > 0.0: - return defer.fail() - - name = metadata['name'] - file_path = metadata['file_path'] - bid = metadata['bid'] - - if 'title' in metadata.keys(): - title = metadata['title'] - else: - title = None - - if 'description' in metadata.keys(): - description = metadata['description'] - else: - description = None - - if 'thumbnail' in metadata.keys(): - thumbnail = metadata['thumbnail'] - else: - thumbnail = None - - if 'key_fee' in metadata.keys(): - if not float(metadata['key_fee']) == 0.0: - if not 'key_fee_address' in metadata.keys(): - return defer.fail() - key_fee = metadata['key_fee'] - else: - key_fee = 0.0 - - if 'key_fee_address' in metadata.keys(): - key_fee_address = metadata['key_fee_address'] - else: - key_fee_address = None - - if 'content_license' in metadata.keys(): - content_license = metadata['content_license'] - else: - content_license = None - - log.info('[' + str(datetime.now()) + '] Publish: ', name, file_path, bid, title, description, thumbnail, - key_fee, key_fee_address, content_license) + log.info("[" + str(datetime.now()) + "] Publish: ", metadata_fields) p = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - d = p.start(name, file_path, bid, title, description, thumbnail, key_fee, key_fee_address, content_license) - d.addCallback(lambda msg: (OK_CODE, msg)) + d = p.start(name=metadata_fields['name'], + file_path=metadata_fields['file_path'], + bid=metadata_fields['bid'], + title=metadata_fields['title'], + description=metadata_fields['description'], + thumbnail=metadata_fields['thumbnail'], + key_fee=metadata_fields['key_fee'], + key_fee_address=metadata_fields['key_fee_address'], + content_license=metadata_fields['content_license'], + author=metadata_fields['author'], + sources=metadata_fields['sources']) + + d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), + lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) return d - def xmlrpc_abandon_name(self, txid): + def jsonrpc_abandon_name(self, p): + """ + Abandon and reclaim credits from a name claim + + @param: {'txid': string} + @return: txid + """ + params = Bunch(p) + def _disp(txid, tx): - print '[' + str(datetime.now()) + '] Spent coins from claim tx ' + txid + ' --> ' + tx - return tx + log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + txid) + return self._render_response(txid, OK_CODE) d = defer.Deferred() - d.addCallback(lambda _: self.session.wallet.abandon_name(txid)) - d.addCallback(lambda tx: _disp(txid, tx)) - d.addErrback(lambda err: str(err.getTraceback())) + d.addCallback(lambda _: self.session.wallet.abandon_name(params.txid)) + d.addCallback(lambda tx: _disp(params.txid, tx)) + d.addErrback(lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) d.callback(None) return d - def xmlrpc_get_name_claims(self): + def jsonrpc_get_name_claims(self): + """ + Get name claims + + @return: list of name claims + """ def _clean(claims): for c in claims: for k in c.keys(): if isinstance(c[k], Decimal): c[k] = float(c[k]) - return claims + return self._render_response(claims, OK_CODE) d = self.session.wallet.get_name_claims() d.addCallback(_clean) return d - def xmlrpc_get_time_behind_blockchain(self): + def jsonrpc_get_time_behind_blockchain(self): + """ + Get time behind blockchain + + @return: time behind blockchain + """ d = self.session.wallet.get_most_recent_blocktime() d.addCallback(get_time_behind_blockchain) + d.addCallbacks(lambda result: self._render_response(result, OK_CODE), + lambda result: self._render_response(result, BAD_REQUEST)) return d - def xmlrpc_get_new_address(self): + def jsonrpc_get_new_address(self): + """ + Generate a new wallet address + + @return: new wallet address + """ def _disp(address): - print "[" + str(datetime.now()) + "] Got new wallet address: " + address - return address + log.info("[" + str(datetime.now()) + "] Got new wallet address: " + address) + return json.dumps(self._render_response(address, OK_CODE)) d = self.session.wallet.get_new_address() d.addCallback(_disp) return d - # def xmlrpc_update_name(self, metadata): + # def jsonrpc_update_name(self, metadata): # def _disp(x): # print x # return x @@ -1102,15 +1050,15 @@ class LBRYDaemon(xmlrpc.XMLRPC): # # return d - def xmlrpc_toggle_fetcher_verbose(self): + def jsonrpc_toggle_fetcher_verbose(self): if self.fetcher.verbose: self.fetcher.verbose = False else: self.fetcher.verbose = True - return self.fetcher.verbose + return self._render_response(self.fetcher.verbose, OK_CODE) - def xmlrpc_check_for_new_version(self): + def jsonrpc_check_for_new_version(self): def _check_for_updates(package): git_version = subprocess.check_output("git ls-remote " + package['git'] + " | grep HEAD | cut -f 1", shell=True) up_to_date = False @@ -1147,64 +1095,113 @@ class LBRYDaemon(xmlrpc.XMLRPC): }, } - return [_check_for_updates(package_infos[p]) for p in package_infos.keys()] + r = [_check_for_updates(package_infos[p]) for p in package_infos.keys()] + log.info("[" + str(datetime.now()) + "] Check for new version: " + json.dumps(r)) + return self._render_response(r, OK_CODE) - def xmlrpc_start_status_bar_app(self): - if sys.platform == 'darwin': - if os.path.isdir("/Applications/LBRY.app"): - # subprocess.Popen("screen -dmS lbry-status bash -c 'lbrynet-daemon-status --startdaemon=False'", shell=True) - subprocess.Popen("screen -dmS lbry-status bash -c 'open /Applications/LBRY.app'") - return "Started" - else: - return "Couldn't find LBRY.app, try running the installer" - else: - return "Status bar not implemented on non OS X" - - def xmlrpc___dir__(self): + def jsonrpc___dir__(self): return ['is_running', 'get_settings', 'set_settings', 'start_fetcher', 'stop_fetcher', 'fetcher_status', 'get_balance', 'stop', 'get_lbry_files', 'resolve_name', 'get', 'search_nametrie', 'delete_lbry_file', 'check', 'publish', 'abandon_name', 'get_name_claims', 'get_time_behind_blockchain', 'get_new_address', 'toggle_fetcher_verbose', 'check_for_new_version'] -def stop(): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - status = daemon.is_running() - except: - status = False +class LBRYDaemonCommandHandler(object): + def __init__(self, command): + self._api = jsonrpc.Proxy(API_CONNECTION_STRING) + self.command = command - if status: - daemon.stop() - print "LBRYnet daemon stopped" - else: - print "LBRYnet daemon wasn't running" + def run(self, params=None): + if params: + d = self._api.callRemote(self.command, params) + else: + d = self._api.callRemote(self.command) + return d -def main(): - parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") - parser.add_argument("--wallet", - help="lbrycrd or lbryum, default lbryum", - type=str, - default="lbryum") - parser.add_argument("--update", - help="True or false, default true", - type=str, - default="True") +class LBRYindex(resource.Resource): + def __init__(self, ui_dir): + resource.Resource.__init__(self) + self.ui_dir = ui_dir - args = parser.parse_args() + isLeaf = False - try: - daemon = xmlrpclib.ServerProxy("http://localhost:7080") - daemon.stop() - except: - pass + def _delayed_render(self, request, results): + request.write(str(results)) + request.finish() - daemon = LBRYDaemon() - daemon.setup(args.wallet, args.update) - reactor.listenTCP(7080, server.Site(daemon), interface='localhost') - reactor.run() + def getChild(self, name, request): + if name == '': + return self + return resource.Resource.getChild(self, name, request) + + def render_GET(self, request): + return static.File(os.path.join(self.ui_dir, "index.html")).render_GET(request) -if __name__ == '__main__': - main() \ No newline at end of file +class LBRYFileRender(resource.Resource): + isLeaf = False + + def _render_path(self, path): + extension = os.path.splitext(path)[1] + if extension in ['mp4', 'flv', 'mov', 'ogv']: + return r'
' + + def _delayed_render(self, request, results): + request.write(str(results)) + request.finish() + + def render_GET(self, request): + if 'name' in request.args.keys(): + api = jsonrpc.Proxy(API_CONNECTION_STRING) + d = api.callRemote("get", {'name': request.args['name'][0]}) + d.addCallback(lambda response: self._delayed_render(request, self._render_path(json.loads(response)['result']['path'])) + if json.loads(response)['code'] == 200 + else self._delayed_render(request, "Error")) + + return server.NOT_DONE_YET + else: + self._delayed_render(request, "Error") + return server.NOT_DONE_YET + + +# class LBRYFilePage(resource.Resource): +# isLeaf = False +# +# def _delayed_render(self, request, results): +# request.write(str(results)) +# request.finish() +# +# h = "
" +# +# d = LBRYDaemonCommandHandler('get_lbry_files').run() +# d.addCallback(lambda r: json.loads(r)['result']) +# d.addCallback(lambda lbry_files: [h % (json.loads(lbry_file)['file_name'], json.loads(lbry_file)['file_name']) for lbry_file in lbry_files]) +# d.addCallback(lambda r: "
%s
" + ''.join(r) + "") +# d.addCallbacks(lambda results: self._delayed_render(request, results), +# lambda err: self._delayed_render(request, err.getTraceback())) +# +# return server.NOT_DONE_YET + + +class LBRYDaemonWeb(resource.Resource): + isLeaf = False + + def _delayed_render(self, request, results): + request.write(str(results)) + request.setResponseCode(json.loads(results)['code']) + request.finish() + + def render_GET(self, request): + func = request.args['function'][0] + del request.args['function'] + + p = {} + for k in request.args.keys(): + p[k] = request.args[k][0] + + d = LBRYDaemonCommandHandler(func).run(p) + d.addCallbacks(lambda results: self._delayed_render(request, results), + lambda err: self._delayed_render(request, json.dumps({'message': err.getTraceback(), 'code': BAD_REQUEST}))) + + return server.NOT_DONE_YET \ No newline at end of file diff --git a/setup.py b/setup.py index ed9b2a866..22d73b43e 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,15 @@ #!/usr/bin/env python -from setuptools import setup, find_packages -import sys -import os - -base_dir = os.path.abspath(os.path.dirname(__file__)) - - from lbrynet import __version__ +import ez_setup +import sys +import os +from setuptools import setup, find_packages + +base_dir = os.path.abspath(os.path.dirname(__file__)) + +ez_setup.use_setuptools() console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', @@ -20,22 +21,25 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch 'lbrynet-gui = lbrynet.lbrynet_gui.gui:start_gui', 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', - 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:main', - 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemon:stop'] + 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:start', + 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:stop'] +requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', + 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', + 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs'] if sys.platform == 'darwin': - console_scripts.append('lbrynet-daemon-status = lbrynet.lbrynet_daemon.LBRYOSXStatusBar:main') - + requires.append('six==1.9.0') +else: + requires.append('six>=1.9.0') gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf'] gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] -setup(name='lbrynet', - version='.'.join([str(x) for x in __version__]), +setup(name='lbrynet', version='.'.join([str(x) for x in __version__]), packages=find_packages(base_dir), - install_requires=['six>=1.9.0', 'pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum'], + install_requires=requires, entry_points={'console_scripts': console_scripts}, data_files=[ ('lbrynet/lbrynet_console/plugins', @@ -47,4 +51,4 @@ setup(name='lbrynet', ('lbrynet/lbrynet_gui', gui_data_paths) ], dependency_links=['https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum'], - ) + ) \ No newline at end of file From e0b00c09ce02266c70f7132e1c170bc12c7b8d03 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 23 Mar 2016 22:36:14 -0400 Subject: [PATCH 082/462] fix conflicts --- setup.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/setup.py b/setup.py index 0d34ca21e..d0c995765 100644 --- a/setup.py +++ b/setup.py @@ -7,11 +7,8 @@ import sys import os from setuptools import setup, find_packages -<<<<<<< HEAD -======= base_dir = os.path.abspath(os.path.dirname(__file__)) ->>>>>>> development ez_setup.use_setuptools() console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', @@ -26,20 +23,7 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:start', 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:stop'] -<<<<<<< HEAD -======= -requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', - 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', - 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs'] ->>>>>>> development - -if sys.platform == 'darwin': - requires.append('six==1.9.0') -else: - requires.append('six>=1.9.0') - -<<<<<<< HEAD requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs'] @@ -49,17 +33,12 @@ if sys.platform == 'darwin': else: requires.append('six>=1.9.0') -setup(name='lbrynet', - version='0.0.4', - packages=find_packages(), -======= gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf'] gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] setup(name='lbrynet', version='.'.join([str(x) for x in __version__]), packages=find_packages(base_dir), ->>>>>>> development install_requires=requires, entry_points={'console_scripts': console_scripts}, data_files=[ From 32bb9fd4ec05d4efbc896c6e4d74648d7025a113 Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Thu, 24 Mar 2016 20:00:56 -0400 Subject: [PATCH 083/462] reorder setuptools --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d0c995765..31cbc6f32 100644 --- a/setup.py +++ b/setup.py @@ -3,13 +3,13 @@ from lbrynet import __version__ import ez_setup +ez_setup.use_setuptools() import sys import os from setuptools import setup, find_packages base_dir = os.path.abspath(os.path.dirname(__file__)) -ez_setup.use_setuptools() console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', From b3cf5e86982df373b2d1ad5575f3a54242fd2894 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 24 Mar 2016 20:46:24 -0400 Subject: [PATCH 084/462] add bundle identifier to uri handler --- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 18 +++++++++++++----- setup_uri_handler.py | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index ba62a78a4..9bab2e2d8 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -1,16 +1,24 @@ import os import json import webbrowser -import sys -from time import sleep import subprocess +import sys +from time import sleep from jsonrpc.proxy import JSONRPCProxy API_CONNECTION_STRING = "http://localhost:5279/lbryapi" UI_ADDRESS = "http://localhost:5279" +class Timeout(Exception): + def __init__(self, value): + self.parameter = value + + def __str__(self): + return repr(self.parameter) + + class LBRYURIHandler(object): def __init__(self): self.started_daemon = False @@ -28,18 +36,18 @@ class LBRYURIHandler(object): elif status: return True else: - exit(1) + raise Timeout("LBRY daemon is running, but connection timed out") except: if self.start_timeout < 30: sleep(1) self.start_timeout += 1 self.check_status() else: - exit(1) + raise Timeout("Timed out trying to start LBRY daemon") def handle(self, lbry_name): lbry_process = [d for d in subprocess.Popen(['ps','aux'], stdout=subprocess.PIPE).stdout.readlines() - if 'LBRY.app' in d] + if 'LBRY.app' in d and 'LBRYURIHandler' not in d] try: status = json.loads(self.daemon.is_running())['result'] except: diff --git a/setup_uri_handler.py b/setup_uri_handler.py index 1a3c68700..e9ba6749c 100644 --- a/setup_uri_handler.py +++ b/setup_uri_handler.py @@ -7,6 +7,7 @@ OPTIONS = {'argv_emulation': True, 'packages': ['jsonrpc'], 'plist': { 'LSUIElement': True, + 'CFBundleIdentifier': 'io.lbry.LBRYURIHandler', 'CFBundleURLTypes': [ { 'CFBundleURLTypes': 'LBRYURIHandler', From a882bdb08c26145b4fb322f6e195090fdf57c645 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 25 Mar 2016 09:32:24 -0400 Subject: [PATCH 085/462] fix output of get_time_behind_blockchain function --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index d3b616b75..7dd90a6ba 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -36,7 +36,7 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE -from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS, DEFAULT_TIMEOUT +from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS, DEFAULT_TIMEOUT, UI_ADDRESS from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet @@ -87,7 +87,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): except jsonrpclib.Fault, f: self._cbRender(f, request, id, version) else: - request.setHeader('Access-Control-Allow-Origin', ('http://localhost' + ':' + str(API_PORT))) + request.setHeader('Access-Control-Allow-Origin', UI_ADDRESS) request.setHeader("content-type", "text/json") if args == [{}]: d = defer.maybeDeferred(function) @@ -1011,7 +1011,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ d = self.session.wallet.get_most_recent_blocktime() d.addCallback(get_time_behind_blockchain) - d.addCallbacks(lambda result: self._render_response(result, OK_CODE), + d.addCallbacks(lambda result: self._render_response(str(result), OK_CODE), lambda result: self._render_response(result, BAD_REQUEST)) return d From fa1d4dae0ffa5a4ad1ceef051faa898588ab3c9a Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 25 Mar 2016 21:03:58 -0400 Subject: [PATCH 086/462] add --ui parameter to lbrynet-daemon to set ui path defaults on temp, which will download a .zip of the current ui to a temp directory, which will be deleted when lbrynet-daemon closes --- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 38 +++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index ce6540d0c..d3f9d1e5a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -42,29 +42,39 @@ def start(): help="True or false, default true", type=str, default="True") + parser.add_argument("--ui", + help="temp or path, default temp, path is the path of the dist folder", + default="temp") log.info("Starting lbrynet-daemon from command line") - tmpdir = tempfile.mkdtemp() - url = urlopen("https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip") - z = ZipFile(StringIO(url.read())) - z.extractall(tmpdir) - - args = parser.parse_args() + download_ui = True + + if args.ui != "temp" and os.path.isdir(args.ui): + download_ui = False + ui_dir = args.ui + + if args.ui == "temp" or download_ui: + ui_dir = tempfile.mkdtemp() + url = urlopen("https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip") + z = ZipFile(StringIO(url.read())) + z.extractall(ui_dir) + daemon = LBRYDaemon() daemon.setup(args.wallet, args.update) - root = LBRYindex(tmpdir) - root.putChild("css", static.File(os.path.join(tmpdir, "css"))) - root.putChild("font", static.File(os.path.join(tmpdir, "font"))) - root.putChild("img", static.File(os.path.join(tmpdir, "img"))) - root.putChild("js", static.File(os.path.join(tmpdir, "js"))) + root = LBRYindex(ui_dir) + root.putChild("css", static.File(os.path.join(ui_dir, "css"))) + root.putChild("font", static.File(os.path.join(ui_dir, "font"))) + root.putChild("img", static.File(os.path.join(ui_dir, "img"))) + root.putChild("js", static.File(os.path.join(ui_dir, "js"))) root.putChild(API_ADDRESS, daemon) root.putChild("webapi", LBRYDaemonWeb()) root.putChild("view", LBRYFileRender()) - reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) + reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) reactor.run() - - shutil.rmtree(tmpdir) \ No newline at end of file + + if download_ui: + shutil.rmtree(ui_dir) From 79e382e0645370cdafe6f9891332bf112a163216 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Sun, 27 Mar 2016 17:21:42 -0400 Subject: [PATCH 087/462] add requirements.txt so we can have repeatable builds --- requirements.txt | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..103294fd4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,27 @@ +Twisted==16.0.0 +Yapsy==1.11.223 +appdirs==1.4.0 +argparse==1.2.1 +colorama==0.3.7 +dnspython==1.12.0 +ecdsa==0.13 +gmpy==1.17 +jsonrpc==1.2 +jsonrpclib==0.1.7 +https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum +leveldb==0.193 +miniupnpc==1.9 +pbkdf2==1.3 +protobuf==3.0.0b2 +pycrypto==2.6.1 +python-bitcoinrpc==0.1 +qrcode==5.2.2 +requests==2.9.1 +seccure==0.3.1.3 +simplejson==3.8.2 +six==1.10.0 +slowaes==0.1a1 +txJSON-RPC==0.3.1 +unqlite==0.2.0 +wsgiref==0.1.2 +zope.interface==4.1.3 From c99d7ce89dc2322d83de3cda73ebe73f235c1a11 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Sun, 27 Mar 2016 17:39:14 -0400 Subject: [PATCH 088/462] fix weird version, add required fields for deb package --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 31cbc6f32..679781350 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,11 @@ gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'l 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf'] gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] -setup(name='lbrynet', version='.'.join([str(x) for x in __version__]), +setup(name='lbrynet', + description='A fully decentralized network for distributing data', + version=__version__, + maintainer='Jimmy Kiselak', + maintainer_email='jimmy@lbry.io', packages=find_packages(base_dir), install_requires=requires, entry_points={'console_scripts': console_scripts}, From 63b2c49354ba5b6a6eb17321232f7f047083237e Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 28 Mar 2016 14:21:41 -0400 Subject: [PATCH 089/462] don't start lbry app if daemon is running in console --- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index 9bab2e2d8..f1dc04c00 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -51,9 +51,9 @@ class LBRYURIHandler(object): try: status = json.loads(self.daemon.is_running())['result'] except: - pass + status = None - if lbry_process: + if lbry_process or status: self.check_status() started = False else: From 41a0590182de1445e86a440e23f102896afdc2a7 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 28 Mar 2016 14:53:32 -0400 Subject: [PATCH 090/462] fix daemon directories for linux --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 7dd90a6ba..e4741b0af 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -121,11 +121,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") elif sys.platform == "darwin": self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') - # self.wallet_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrycrd") - self.wallet_dir = user_data_dir("LBRY") + if wallet_type == "lbrycrd": + self.wallet_dir = user_data_dir("lbrycrd") + else: + self.wallet_dir = user_data_dir("LBRY") else: - self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") - self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + if wallet_type == "lbrycrd": + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") + else: + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") + self.download_directory = os.getcwd() self.daemon_conf = os.path.join(self.wallet_dir, 'daemon_settings.conf') self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") self.wallet_user = None From 200d239e0bfebf7429d7c3c37f8bdacde68de17f Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 28 Mar 2016 14:54:01 -0400 Subject: [PATCH 091/462] log ui directory --- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index d3f9d1e5a..789c4ad84 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -54,8 +54,10 @@ def start(): if args.ui != "temp" and os.path.isdir(args.ui): download_ui = False ui_dir = args.ui + log.info("Using user specified UI directory: " + str(ui_dir)) if args.ui == "temp" or download_ui: + log.info("Downloading current web ui to temp directory") ui_dir = tempfile.mkdtemp() url = urlopen("https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip") z = ZipFile(StringIO(url.read())) From 2670279b0d6f94bf25ef34fb654d18c2b3578033 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 28 Mar 2016 16:49:51 -0400 Subject: [PATCH 092/462] change access control allow origin to * --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index e4741b0af..eab1a0bee 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -87,7 +87,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): except jsonrpclib.Fault, f: self._cbRender(f, request, id, version) else: - request.setHeader('Access-Control-Allow-Origin', UI_ADDRESS) + request.setHeader("Access-Control-Allow-Origin", "*") request.setHeader("content-type", "text/json") if args == [{}]: d = defer.maybeDeferred(function) From b3465f3bbfba8e4f831cafa835908e3e24cdfd15 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 29 Mar 2016 16:42:47 -0400 Subject: [PATCH 093/462] fix /view, improve is_running, clean function outputs -Use localhost:5279/view?name=wonderfullife to download and render a file -is_running is now the only function that will work during startup -functions output json, rather than json in your json --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 197 ++++++++++---------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 12 +- 2 files changed, 103 insertions(+), 106 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index eab1a0bee..faf62264c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -6,14 +6,12 @@ import binascii import subprocess import logging import requests -# import rumps -# import httplib2 from twisted.web import server, resource, static from twisted.internet import defer, threads, error, reactor from txjsonrpc import jsonrpclib from txjsonrpc.web import jsonrpc -from jsonrpc.proxy import JSONRPCProxy +from txjsonrpc.web.jsonrpc import Handler from datetime import datetime from decimal import Decimal @@ -82,6 +80,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): version = jsonrpclib.VERSION_PRE1 # XXX this all needs to be re-worked to support logic for multiple # versions... + + if not self.announced_startup: + if functionPath != 'is_running': + request.setHeader("Access-Control-Allow-Origin", "*") + request.setHeader("content-type", "text/json") + s = jsonrpclib.dumps("Starting up", version=version) + request.setHeader("content-length", str(len(s))) + request.write(s) + request.finish() + return server.NOT_DONE_YET + try: function = self._getFunction(functionPath) except jsonrpclib.Fault, f: @@ -97,6 +106,32 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(self._cbRender, request, id, version) return server.NOT_DONE_YET + def _cbRender(self, result, request, id, version): + if isinstance(result, Handler): + result = result.result + + if isinstance(result, dict): + result = result['result'] + + if version == jsonrpclib.VERSION_PRE1: + if not isinstance(result, jsonrpclib.Fault): + result = (result,) + # Convert the result (python) to JSON-RPC + try: + s = jsonrpclib.dumps(result, version=version) + except: + f = jsonrpclib.Fault(self.FAILURE, "can't serialize output") + s = jsonrpclib.dumps(f, version=version) + request.setHeader("content-length", str(len(s))) + request.write(s) + request.finish() + + def _ebRender(self, failure, id): + if isinstance(failure.value, jsonrpclib.Fault): + return failure.value + log.error(failure) + return jsonrpclib.Fault(self.FAILURE, "error") + def setup(self, wallet_type, check_for_updates): def _set_vars(wallet_type, check_for_updates): reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) @@ -179,9 +214,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) + def _announce_startup(): + self.announced_startup = True + return defer.succeed(None) + def _disp_startup(): log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") - return defer.succeed(None) log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") @@ -201,6 +239,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) d.addCallback(lambda _: self._setup_fetcher()) + d.addCallback(lambda _: _announce_startup()) d.addCallback(lambda _: _disp_startup()) d.callback(None) @@ -653,7 +692,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def _render_response(self, result, code): - return json.dumps({'result': result, 'code': code}) + return defer.succeed({'result': result, 'code': code}) # def _log_to_slack(self, msg): # URL = "https://hooks.slack.com/services/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" @@ -662,13 +701,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_is_running(self): """ - Returns a startup message when the daemon starts, after which it will return True + Returns true if daemon completed startup, otherwise returns false """ - if self.startup_message != "" and self.announced_startup == False: - self.announced_startup = True - return self._render_response(self.startup_message, OK_CODE) - elif self.announced_startup: + if self.announced_startup: return self._render_response(True, OK_CODE) else: return self._render_response(False, OK_CODE) @@ -677,7 +713,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Get LBRY payment settings - @return {'data_rate': float, 'max_key_fee': float} + @return {'data_rate': float, 'max_key_fee': float, 'max_upload': float (0.0 for unlimited), + 'default_download_directory': string, 'run_on_startup': bool, + 'max_download': float (0.0 for unlimited)} """ log.info("[" + str(datetime.now()) + "] Get daemon settings") @@ -687,7 +725,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Set LBRY payment settings - @param settings: {'settings': {'data_rate': float, 'max_key_fee': float}} + @param settings: {'data_rate': float, 'max_key_fee': float, 'max_upload': float (0.0 for unlimited), + 'default_download_directory': string, 'run_on_startup': bool, + 'max_download': float (0.0 for unlimited)} """ d = self._update_settings(p) @@ -736,7 +776,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ log.info("[" + str(datetime.now()) + "] Get balance") - return self._render_response(self.session.wallet.wallet_balance, OK_CODE) + return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE) def jsonrpc_stop(self): """ @@ -818,36 +858,36 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d - def jsonrpc_stop_lbry_file(self, p): - params = Bunch(p) - - try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] - except IndexError: - return defer.fail(UnknownNameError) - - if not lbry_file.stopped: - d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - d.addCallback(lambda _: self._render_response("Stream has been stopped", OK_CODE)) - d.addErrback(lambda err: self._render_response(err.getTraceback(), )) - return d - else: - return json.dumps({'result': 'Stream was already stopped'}) - - def jsonrpc_start_lbry_file(self, p): - params = Bunch(p) - - try: - lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] - except IndexError: - return defer.fail(UnknownNameError) - - if lbry_file.stopped: - d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - d.callback(None) - return json.dumps({'result': 'Stream started'}) - else: - return json.dumps({'result': 'Stream was already running'}) + # def jsonrpc_stop_lbry_file(self, p): + # params = Bunch(p) + # + # try: + # lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] + # except IndexError: + # return defer.fail(UnknownNameError) + # + # if not lbry_file.stopped: + # d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) + # d.addCallback(lambda _: self._render_response("Stream has been stopped", OK_CODE)) + # d.addErrback(lambda err: self._render_response(err.getTraceback(), )) + # return d + # else: + # return json.dumps({'result': 'Stream was already stopped'}) + # + # def jsonrpc_start_lbry_file(self, p): + # params = Bunch(p) + # + # try: + # lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] + # except IndexError: + # return defer.fail(UnknownNameError) + # + # if lbry_file.stopped: + # d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) + # d.callback(None) + # return json.dumps({'result': 'Stream started'}) + # else: + # return json.dumps({'result': 'Stream was already running'}) def jsonrpc_search_nametrie(self, p): """ @@ -891,7 +931,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): t['cost_est'] = r[2] consolidated_results.append(t) # log.info(str(t)) - return self._render_response(consolidated_results, OK_CODE) + return consolidated_results log.info('[' + str(datetime.now()) + '] Search nametrie: ' + params.search) @@ -901,6 +941,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(resolve_claims) d.addCallback(_clean) d.addCallback(_disp) + d.addCallback(lambda results: self._render_response(results, OK_CODE)) return d @@ -918,9 +959,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("[" + str(datetime.now()) + "] Deleted: " + file_name) return self._render_response("Deleted: " + file_name, OK_CODE) - lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files if params.file_name == f.file_name] + lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files + if params.file_name == f.file_name] d = defer.DeferredList(lbry_files) d.addCallback(lambda _: _disp(params.file_name)) + return d def jsonrpc_publish(self, p): @@ -1001,10 +1044,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): for k in c.keys(): if isinstance(c[k], Decimal): c[k] = float(c[k]) - return self._render_response(claims, OK_CODE) + return defer.succeed(claims) d = self.session.wallet.get_name_claims() d.addCallback(_clean) + d.addCallback(lambda claims: self._render_response(claims, OK_CODE)) return d @@ -1029,10 +1073,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ def _disp(address): log.info("[" + str(datetime.now()) + "] Got new wallet address: " + address) - return json.dumps(self._render_response(address, OK_CODE)) + return defer.succeed(address) d = self.session.wallet.get_new_address() d.addCallback(_disp) + d.addCallback(lambda address: self._render_response(address, OK_CODE)) return d # def jsonrpc_update_name(self, metadata): @@ -1147,66 +1192,12 @@ class LBRYindex(resource.Resource): class LBRYFileRender(resource.Resource): isLeaf = False - def _render_path(self, path): - extension = os.path.splitext(path)[1] - if extension in ['mp4', 'flv', 'mov', 'ogv']: - return r'
' - - def _delayed_render(self, request, results): - request.write(str(results)) - request.finish() - def render_GET(self, request): if 'name' in request.args.keys(): api = jsonrpc.Proxy(API_CONNECTION_STRING) d = api.callRemote("get", {'name': request.args['name'][0]}) - d.addCallback(lambda response: self._delayed_render(request, self._render_path(json.loads(response)['result']['path'])) - if json.loads(response)['code'] == 200 - else self._delayed_render(request, "Error")) + d.addCallback(lambda results: static.File(results['path']).render_GET(request)) return server.NOT_DONE_YET else: - self._delayed_render(request, "Error") - return server.NOT_DONE_YET - - -# class LBRYFilePage(resource.Resource): -# isLeaf = False -# -# def _delayed_render(self, request, results): -# request.write(str(results)) -# request.finish() -# -# h = "
" -# -# d = LBRYDaemonCommandHandler('get_lbry_files').run() -# d.addCallback(lambda r: json.loads(r)['result']) -# d.addCallback(lambda lbry_files: [h % (json.loads(lbry_file)['file_name'], json.loads(lbry_file)['file_name']) for lbry_file in lbry_files]) -# d.addCallback(lambda r: "
%s
" + ''.join(r) + "") -# d.addCallbacks(lambda results: self._delayed_render(request, results), -# lambda err: self._delayed_render(request, err.getTraceback())) -# -# return server.NOT_DONE_YET - - -class LBRYDaemonWeb(resource.Resource): - isLeaf = False - - def _delayed_render(self, request, results): - request.write(str(results)) - request.setResponseCode(json.loads(results)['code']) - request.finish() - - def render_GET(self, request): - func = request.args['function'][0] - del request.args['function'] - - p = {} - for k in request.args.keys(): - p[k] = request.args[k][0] - - d = LBRYDaemonCommandHandler(func).run(p) - d.addCallbacks(lambda results: self._delayed_render(request, results), - lambda err: self._delayed_render(request, json.dumps({'message': err.getTraceback(), 'code': BAD_REQUEST}))) - - return server.NOT_DONE_YET \ No newline at end of file + return server.failure diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 789c4ad84..80d3db2d1 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -12,7 +12,7 @@ from twisted.web import server, static from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy -from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYDaemonWeb, LBRYFileRender +from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYFileRender from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET log = logging.getLogger(__name__) @@ -46,6 +46,13 @@ def start(): help="temp or path, default temp, path is the path of the dist folder", default="temp") + try: + JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running() + log.info("lbrynet-daemon is already running") + return + except: + pass + log.info("Starting lbrynet-daemon from command line") args = parser.parse_args() @@ -72,11 +79,10 @@ def start(): root.putChild("img", static.File(os.path.join(ui_dir, "img"))) root.putChild("js", static.File(os.path.join(ui_dir, "js"))) root.putChild(API_ADDRESS, daemon) - root.putChild("webapi", LBRYDaemonWeb()) root.putChild("view", LBRYFileRender()) reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) reactor.run() if download_ui: - shutil.rmtree(ui_dir) + shutil.rmtree(ui_dir) \ No newline at end of file From a7fb434f9feda129e1e412fb7fd763f134d39ce0 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 29 Mar 2016 17:04:01 -0400 Subject: [PATCH 094/462] update uri handler for change in daemon output --- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index f1dc04c00..18e5ae366 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -28,7 +28,7 @@ class LBRYURIHandler(object): def check_status(self): status = None try: - status = json.loads(self.daemon.is_running())['result'] + status = self.daemon.is_running() if self.start_timeout < 30 and not status: sleep(1) self.start_timeout += 1 @@ -49,7 +49,7 @@ class LBRYURIHandler(object): lbry_process = [d for d in subprocess.Popen(['ps','aux'], stdout=subprocess.PIPE).stdout.readlines() if 'LBRY.app' in d and 'LBRYURIHandler' not in d] try: - status = json.loads(self.daemon.is_running())['result'] + status = self.daemon.is_running() except: status = None @@ -64,17 +64,7 @@ class LBRYURIHandler(object): if lbry_name == "lbry" or lbry_name == "" and not started: webbrowser.get('safari').open(UI_ADDRESS) else: - r = json.loads(self.daemon.get({'name': lbry_name})) - if r['code'] == 200: - path = r['result']['path'].encode('utf-8') - extension = os.path.splitext(path)[1] - if extension in ['mp4', 'flv', 'mov', 'ogv']: - webbrowser.get('safari').open(UI_ADDRESS + "/view?name=" + lbry_name) - else: - webbrowser.get('safari').open('file://' + path) - else: - webbrowser.get('safari').open('http://lbry.io/get') - + webbrowser.get('safari').open(UI_ADDRESS + "/view?name=" + lbry_name) def main(args): if len(args) != 1: From 4355b799cba4931be3b806578afebd9af854d32a Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 29 Mar 2016 22:02:36 -0400 Subject: [PATCH 095/462] before is_running is true all other methods return a failure --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index faf62264c..6ee478e84 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -85,7 +85,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): if functionPath != 'is_running': request.setHeader("Access-Control-Allow-Origin", "*") request.setHeader("content-type", "text/json") - s = jsonrpclib.dumps("Starting up", version=version) + s = jsonrpclib.Fault(self.FAILURE, "error") request.setHeader("content-length", str(len(s))) request.write(s) request.finish() From 2540b9969f4409381e570cf4c0b88c6889d27bba Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 1 Apr 2016 22:06:06 -0400 Subject: [PATCH 096/462] check UI version from command line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -check UI version, if out of date get a fresh copy. overridden by —ui flag, which is used to specify a ui directory --- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 101 ++++++++++++++------ 1 file changed, 72 insertions(+), 29 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 80d3db2d1..1899b4419 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -1,19 +1,22 @@ import argparse import logging -import tempfile +import subprocess import os import shutil +import webbrowser +import sys from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen - +from datetime import datetime +from appdirs import user_data_dir from twisted.web import server, static from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYFileRender -from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET +from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET, UI_ADDRESS log = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) @@ -43,8 +46,8 @@ def start(): type=str, default="True") parser.add_argument("--ui", - help="temp or path, default temp, path is the path of the dist folder", - default="temp") + help="path to custom UI folder", + default="") try: JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running() @@ -56,33 +59,73 @@ def start(): log.info("Starting lbrynet-daemon from command line") args = parser.parse_args() - download_ui = True - if args.ui != "temp" and os.path.isdir(args.ui): - download_ui = False - ui_dir = args.ui - log.info("Using user specified UI directory: " + str(ui_dir)) + def getui(ui_dir=None): + if ui_dir: + if os.path.isdir(ui_dir): + log.info("Using user specified UI directory: " + str(ui_dir)) + return defer.succeed(ui_dir) + else: + log.info("User specified UI directory doesn't exist: " + str(ui_dir)) - if args.ui == "temp" or download_ui: - log.info("Downloading current web ui to temp directory") - ui_dir = tempfile.mkdtemp() - url = urlopen("https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip") - z = ZipFile(StringIO(url.read())) - z.extractall(ui_dir) + def download_ui(dest_dir): + url = urlopen("https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip") + z = ZipFile(StringIO(url.read())) + z.extractall(dest_dir) + return defer.succeed(dest_dir) - daemon = LBRYDaemon() - daemon.setup(args.wallet, args.update) + data_dir = user_data_dir("LBRY") + version_dir = os.path.join(data_dir, "ui_version_history") - root = LBRYindex(ui_dir) - root.putChild("css", static.File(os.path.join(ui_dir, "css"))) - root.putChild("font", static.File(os.path.join(ui_dir, "font"))) - root.putChild("img", static.File(os.path.join(ui_dir, "img"))) - root.putChild("js", static.File(os.path.join(ui_dir, "js"))) - root.putChild(API_ADDRESS, daemon) - root.putChild("view", LBRYFileRender()) + git_version = subprocess.check_output( + "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep HEAD | cut -f 1", shell=True) - reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) - reactor.run() + if not os.path.isdir(data_dir): + os.mkdir(data_dir) - if download_ui: - shutil.rmtree(ui_dir) \ No newline at end of file + if not os.path.isdir(os.path.join(data_dir, "ui_version_history")): + os.mkdir(version_dir) + + if not os.path.isfile(os.path.join(version_dir, git_version)): + try: + f = open(os.path.join(version_dir, git_version), "w") + version_message = "[" + str(datetime.now()) + "] Updating UI --> " + git_version + f.write(version_message) + f.close() + log.info(version_message) + except: + log.info("You should have been notified to install xcode command line tools, once it's installed you can start LBRY") + sys.exit(0) + + if os.path.isdir(os.path.join(data_dir, "lbry-web-ui")): + shutil.rmtree(os.path.join(data_dir, "lbry-web-ui")) + else: + version_message = "[" + str(datetime.now()) + "] UI version " + git_version + " up to date" + log.info(version_message) + + if os.path.isdir(os.path.join(data_dir, "lbry-web-ui")): + return defer.succeed(os.path.join(data_dir, "lbry-web-ui")) + else: + return download_ui((os.path.join(data_dir, "lbry-web-ui"))) + + def setupserver(ui_dir): + root = LBRYindex(ui_dir) + root.putChild("css", static.File(os.path.join(ui_dir, "css"))) + root.putChild("font", static.File(os.path.join(ui_dir, "font"))) + root.putChild("img", static.File(os.path.join(ui_dir, "img"))) + root.putChild("js", static.File(os.path.join(ui_dir, "js"))) + root.putChild("view", LBRYFileRender()) + return defer.succeed(root) + + def setupapi(root, wallet): + daemon = LBRYDaemon() + root.putChild(API_ADDRESS, daemon) + reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) + return daemon.setup(wallet, "False") + + d = getui(args.ui) + d.addCallback(setupserver) + d.addCallback(lambda r: setupapi(r, args.wallet)) + d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) + + reactor.run() \ No newline at end of file From d4b7064d2f8f2fbcbe8a6a74ac163c4c4b44ef57 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 1 Apr 2016 22:11:11 -0400 Subject: [PATCH 097/462] fix sources problems --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 83 ++++++++++++------------ lbrynet/lbrynet_daemon/LBRYDownloader.py | 2 + lbrynet/lbrynet_daemon/LBRYPublisher.py | 9 ++- 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 6ee478e84..1ea1e0a62 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -47,7 +47,6 @@ log = logging.getLogger(__name__) BAD_REQUEST = 400 NOT_FOUND = 404 OK_CODE = 200 - # TODO add login credentials in a conf file # TODO alert if your copy of a lbry file is out of date with the name record @@ -83,13 +82,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): if not self.announced_startup: if functionPath != 'is_running': - request.setHeader("Access-Control-Allow-Origin", "*") - request.setHeader("content-type", "text/json") - s = jsonrpclib.Fault(self.FAILURE, "error") - request.setHeader("content-length", str(len(s))) - request.write(s) - request.finish() - return server.NOT_DONE_YET + return server.failure try: function = self._getFunction(functionPath) @@ -561,7 +554,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _get_stream(name): def _disp(stream): - log.info("[" + str(datetime.now()) + "] Start stream: " + stream['stream_hash']) + stream_hash = stream['stream_hash'] + if isinstance(stream_hash, dict): + stream_hash = stream_hash['sd_hash'] + + log.info("[" + str(datetime.now()) + "] Start stream: " + stream_hash) return stream d = self.session.wallet.get_stream_info_for_name(name) @@ -602,6 +599,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): f.close() file_name = l['stream_name'].decode('hex') + for lbry_file in self.lbry_file_manager.lbry_files: if lbry_file.stream_name == file_name: if sys.platform == "darwin": @@ -616,6 +614,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _check(info): stream_hash = info['stream_hash'] + if isinstance(stream_hash, dict): + stream_hash = stream_hash['sd_hash'] + path = os.path.join(self.blobfile_dir, stream_hash) if os.path.isfile(path): log.info("[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash) @@ -680,7 +681,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d d = self.session.wallet.get_stream_info_for_name(name) - d.addCallback(lambda info: download_sd_blob(self.session, info['stream_hash'], + d.addCallback(lambda info: info['stream_hash'] if isinstance(info['stream_hash'], str) + else info['stream_hash']['sd_hash']) + d.addCallback(lambda sd_hash: download_sd_blob(self.session, sd_hash, self.blob_request_payment_rate_manager)) d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) d.addCallback(lambda metadata: metadata.validator.info_to_show()) @@ -829,11 +832,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): params = Bunch(p) def _disp(info): - log.info("[" + str(datetime.now()) + "] Resolved info: " + info['stream_hash']) + stream_hash = info['stream_hash'] + if isinstance(stream_hash, dict): + stream_hash = stream_hash['sd_hash'] + + log.info("[" + str(datetime.now()) + "] Resolved info: " + stream_hash) + return self._render_response(info, OK_CODE) d = self._resolve_name(params.name) - d.addCallbacks(_disp, lambda _: self._render_response('error', NOT_FOUND)) + d.addCallbacks(_disp, lambda _: server.failure) d.callback(None) return d @@ -960,7 +968,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._render_response("Deleted: " + file_name, OK_CODE) lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files - if params.file_name == f.file_name] + if str(params.file_name) == str(f.file_name)] d = defer.DeferredList(lbry_files) d.addCallback(lambda _: _disp(params.file_name)) @@ -974,38 +982,27 @@ class LBRYDaemon(jsonrpc.JSONRPC): @return: """ - params = Bunch(p) + metadata_fields = ["name", "file_path", "bid", "author", "title", + "description", "thumbnail", "key_fee", "key_fee_address", + "content_license", "sources"] - metadata_fields = {"name": unicode, "file_path": unicode, "bid": float, "author": unicode, "title": unicode, - "description": unicode, "thumbnail": unicode, "key_fee": float, "key_fee_address": unicode, - "content_license": unicode, "sources": dict} + for k in metadata_fields: + if k not in p.keys(): + p[k] = None - for k in metadata_fields.keys(): - if k in params.__dict__.keys(): - if isinstance(params.__dict__[k], metadata_fields[k]): - if type(params.__dict__[k]) == unicode: - metadata_fields[k] = str(params.__dict__[k]) - else: - metadata_fields[k] = params.__dict__[k] - else: - metadata_fields[k] = None - else: - metadata_fields[k] = None + pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - log.info("[" + str(datetime.now()) + "] Publish: ", metadata_fields) - - p = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - d = p.start(name=metadata_fields['name'], - file_path=metadata_fields['file_path'], - bid=metadata_fields['bid'], - title=metadata_fields['title'], - description=metadata_fields['description'], - thumbnail=metadata_fields['thumbnail'], - key_fee=metadata_fields['key_fee'], - key_fee_address=metadata_fields['key_fee_address'], - content_license=metadata_fields['content_license'], - author=metadata_fields['author'], - sources=metadata_fields['sources']) + d = pub.start(p['name'], + p['file_path'], + p['bid'], + title=p['title'], + description=p['description'], + thumbnail=p['thumbnail'], + key_fee=p['key_fee'], + key_fee_address=p['key_fee_address'], + content_license=p['content_license'], + author=p['author'], + sources=p['sources']) d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) @@ -1200,4 +1197,4 @@ class LBRYFileRender(resource.Resource): return server.NOT_DONE_YET else: - return server.failure + return server.failure \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 4552cd4af..997dde21c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -68,6 +68,8 @@ class GetStream(object): self.key_fee_address = None self.stream_hash = self.stream_info['stream_hash'] + if isinstance(self.stream_hash, dict): + self.stream_hash = self.stream_hash['sd_hash'] else: log.error("InvalidStreamInfoError in autofetcher: ", stream_info) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index ea7c015a3..f8ee282da 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -43,7 +43,7 @@ class Publisher(object): message = "[" + str(datetime.now()) + "] Published " + self.file_name + " --> lbry://" + \ str(self.publish_name) + " with txid: " + str(self.tx_hash) log.info(message) - return defer.succeed(message) + return defer.succeed(self.tx_hash) self.publish_name = name self.file_path = file_path @@ -103,12 +103,16 @@ class Publisher(object): def set_sd_hash(sd_hash): self.sd_hash = sd_hash + if isinstance(self.sources, dict): + self.sources['lbry_sd_hash'] = sd_hash + else: + self.sources = {'lbry_sd_hash': sd_hash} d.addCallback(set_sd_hash) return d def _claim_name(self): - d = self.wallet.claim_name(self.publish_name, {'sd_hash': self.sd_hash}, self.bid_amount, + d = self.wallet.claim_name(self.publish_name, self.sd_hash, self.bid_amount, description=self.description, key_fee=self.key_fee, key_fee_address=self.key_fee_address, thumbnail=self.thumbnail, content_license=self.content_license, author=self.author, @@ -121,6 +125,7 @@ class Publisher(object): return d def _show_publish_error(self, err): + log.info(err.getTraceback()) message = "An error occurred publishing %s to %s. Error: %s." if err.check(InsufficientFundsError): error_message = "Insufficient funds" From f464b3ae7c1f7604edda69130182682f2277f23a Mon Sep 17 00:00:00 2001 From: Jimmy Kiselak Date: Mon, 4 Apr 2016 22:20:15 -0400 Subject: [PATCH 098/462] when decoding lbryum transactions, return values as Decimal --- lbrynet/core/LBRYcrdWallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index cce937c21..9eb8a7303 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -1067,7 +1067,7 @@ class LBRYumWallet(LBRYWallet): decoded_tx['vout'] = [] for output in tx.outputs(): out = {} - out['value'] = output[2] + out['value'] = Decimal(output[2]) / Decimal(COIN) decoded_tx['vout'].append(out) return decoded_tx From a5362f3170ed4dff7edc92c31424de3f66e252e6 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 7 Apr 2016 03:12:09 -0400 Subject: [PATCH 099/462] daemon settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -set_settings accepts a dict of settings -adds upload_log field, defaulting on true, this uploads the lbry log file to lbry.io to help figure out if and where things aren’t working -default_download_directory is the key of the path where files are saved -gets publish working with sources -adds check_first_run function, returns True/False -previously only is_running would work during startup, check_first_run, get_time_behind_blockchain, and stop have been added -requires six 1.9, 1.10.0 produced errors --- lbrynet/__init__.py | 2 +- lbrynet/core/LBRYcrdWallet.py | 8 +- lbrynet/lbryfilemanager/LBRYFileDownloader.py | 5 +- lbrynet/lbryfilemanager/LBRYFileManager.py | 17 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 272 +++++++++++++----- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 8 + lbrynet/lbrynet_daemon/LBRYDownloader.py | 8 +- lbrynet/lbrynet_daemon/LBRYPublisher.py | 4 - requirements.txt | 2 +- setup.py | 7 +- 10 files changed, 238 insertions(+), 95 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index eb4d8454c..993518cab 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) -version = (0, 2, 0) +version = (0, 2, 1) __version__ = ".".join([str(x) for x in version]) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 9eb8a7303..f487becd0 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -365,7 +365,8 @@ class LBRYWallet(object): value['content_license'] = content_license if author is not None: value['author'] = author - if sources is not None: + if isinstance(sources, dict): + sources['lbry_sd_hash'] = sd_hash value['sources'] = sources d = self._send_name_claim(name, json.dumps(value), amount) @@ -436,7 +437,8 @@ class LBRYWallet(object): d.addCallback(set_first_run) else: - d = defer.succeed(None) + d = defer.succeed(self._FIRST_RUN_YES if self._first_run else self._FIRST_RUN_NO) + d.addCallback(lambda _: self._first_run == self._FIRST_RUN_YES) return d @@ -1072,6 +1074,7 @@ class LBRYumWallet(LBRYWallet): return decoded_tx def _send_abandon(self, txid, address, amount): + log.info("Abandon " + str(txid) + " " + str(address) + " " + str(amount)) cmd = known_commands['abandonclaim'] func = getattr(self.cmd_runner, cmd.name) d = threads.deferToThread(func, txid, address, amount) @@ -1079,6 +1082,7 @@ class LBRYumWallet(LBRYWallet): return d def _broadcast_transaction(self, raw_tx): + log.info("Broadcast: " + str(raw_tx)) cmd = known_commands['broadcast'] func = getattr(self.cmd_runner, cmd.name) d = threads.deferToThread(func, raw_tx) diff --git a/lbrynet/lbryfilemanager/LBRYFileDownloader.py b/lbrynet/lbryfilemanager/LBRYFileDownloader.py index fed0824e5..2afc17588 100644 --- a/lbrynet/lbryfilemanager/LBRYFileDownloader.py +++ b/lbrynet/lbryfilemanager/LBRYFileDownloader.py @@ -119,7 +119,7 @@ class ManagedLBRYFileDownloaderFactory(object): def can_download(self, sd_validator): return True - def make_downloader(self, metadata, options, payment_rate_manager): + def make_downloader(self, metadata, options, payment_rate_manager, download_directory=None): data_rate = options[0] upload_allowed = options[1] @@ -137,7 +137,8 @@ class ManagedLBRYFileDownloaderFactory(object): d.addCallback(lambda stream_hash: self.lbry_file_manager.add_lbry_file(stream_hash, payment_rate_manager, data_rate, - upload_allowed)) + upload_allowed, + download_directory=download_directory)) return d @staticmethod diff --git a/lbrynet/lbryfilemanager/LBRYFileManager.py b/lbrynet/lbryfilemanager/LBRYFileManager.py index 59f972df3..d0dbca1ae 100644 --- a/lbrynet/lbryfilemanager/LBRYFileManager.py +++ b/lbrynet/lbryfilemanager/LBRYFileManager.py @@ -28,7 +28,7 @@ class LBRYFileManager(object): Keeps track of currently opened LBRY Files, their options, and their LBRY File specific metadata. """ - def __init__(self, session, stream_info_manager, sd_identifier, delete_data=False): + def __init__(self, session, stream_info_manager, sd_identifier, delete_data=False, download_directory=None): self.session = session self.stream_info_manager = stream_info_manager self.sd_identifier = sd_identifier @@ -36,8 +36,8 @@ class LBRYFileManager(object): self.sql_db = None # self.delete_data = delete_data # self.check_exists_loop = LoopingCall(self.check_files_exist) - if sys.platform.startswith("darwin"): - self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + if download_directory: + self.download_directory = download_directory else: self.download_directory = os.getcwd() log.debug("Download directory for LBRYFileManager: %s", str(self.download_directory)) @@ -122,7 +122,10 @@ class LBRYFileManager(object): d.addCallback(start_lbry_files) return d - def start_lbry_file(self, rowid, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True): + def start_lbry_file(self, rowid, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True, + download_directory=None): + if not download_directory: + download_directory = self.download_directory payment_rate_manager.min_blob_data_payment_rate = blob_data_rate lbry_file_downloader = ManagedLBRYFileDownloader(rowid, stream_hash, self.session.peer_finder, @@ -130,17 +133,17 @@ class LBRYFileManager(object): self.session.blob_manager, self.stream_info_manager, self, payment_rate_manager, self.session.wallet, - self.download_directory, + download_directory, upload_allowed) self.lbry_files.append(lbry_file_downloader) d = lbry_file_downloader.set_stream_info() d.addCallback(lambda _: lbry_file_downloader) return d - def add_lbry_file(self, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True): + def add_lbry_file(self, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True, download_directory=None): d = self._save_lbry_file(stream_hash, blob_data_rate) d.addCallback(lambda rowid: self.start_lbry_file(rowid, stream_hash, payment_rate_manager, - blob_data_rate, upload_allowed)) + blob_data_rate, upload_allowed, download_directory)) return d def delete_lbry_file(self, lbry_file): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 1ea1e0a62..73dc07d4b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -5,7 +5,12 @@ import simplejson as json import binascii import subprocess import logging +import logging.handlers import requests +import base64 +import base58 +import platform +import json from twisted.web import server, resource, static from twisted.internet import defer, threads, error, reactor @@ -15,11 +20,10 @@ from txjsonrpc.web.jsonrpc import Handler from datetime import datetime from decimal import Decimal -from StringIO import StringIO -from zipfile import ZipFile -from urllib import urlopen from appdirs import user_data_dir +from urllib2 import urlopen +from lbrynet import __version__ from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory @@ -42,8 +46,23 @@ from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager + +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') + log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) + +log.addHandler(handler) + BAD_REQUEST = 400 NOT_FOUND = 404 OK_CODE = 200 @@ -81,7 +100,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): # versions... if not self.announced_startup: - if functionPath != 'is_running': + if functionPath not in ['is_running', 'check_first_run', 'get_time_behind_blockchain', 'stop']: return server.failure try: @@ -129,6 +148,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _set_vars(wallet_type, check_for_updates): reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) + self.log_file = LOG_FILENAME self.fetcher = None self.current_db_revision = 1 self.run_server = True @@ -142,7 +162,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 self.dht_node_port = 4444 - self.first_run = False + self.first_run = "Loading" if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) @@ -154,12 +174,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self.wallet_dir = user_data_dir("LBRY") else: + self.download_directory = os.getcwd() if wallet_type == "lbrycrd": self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") else: self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") - self.download_directory = os.getcwd() - self.daemon_conf = os.path.join(self.wallet_dir, 'daemon_settings.conf') + self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") self.wallet_user = None self.wallet_password = None @@ -197,21 +217,43 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.search_timeout = 3.0 self.query_handlers = {} self.default_settings = { - 'run_on_startup': False, + 'run_on_startup': False, 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, 'max_key_fee': 10.0, 'default_download_directory': self.download_directory, 'max_upload': 0.0, - 'max_download': 0.0 + 'max_download': 0.0, + 'upload_log': True } return defer.succeed(None) + def _log_starting_vals(): + def _get_lbry_files_json(): + r = [] + for f in self.lbry_file_manager.lbry_files: + if f.key: + t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), + 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed} + + else: + t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, + 'points_paid': f.points_paid, + 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, + 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed} + + r.append(t) + return json.dumps(r) + + log.info("LBRY Files: " + _get_lbry_files_json()) + log.info("Starting balance: " + str(self.session.wallet.wallet_balance)) + + return defer.succeed(None) + def _announce_startup(): self.announced_startup = True - return defer.succeed(None) - - def _disp_startup(): log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") return defer.succeed(None) @@ -219,7 +261,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = defer.Deferred() d.addCallback(lambda _:_set_vars(wallet_type, check_for_updates)) - d.addCallback(lambda _: self._setup_daemon_settings()) + d.addCallback(lambda _: self._initial_setup()) + d.addCallback(self._set_daemon_settings) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) d.addCallback(lambda _: self._get_settings()) @@ -232,17 +275,45 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) d.addCallback(lambda _: self._setup_fetcher()) + d.addCallback(lambda _: _log_starting_vals()) d.addCallback(lambda _: _announce_startup()) - d.addCallback(lambda _: _disp_startup()) d.callback(None) return defer.succeed(None) def _initial_setup(self): - return defer.fail(NotImplementedError()) + def _log_platform(): + msg = { + "processor": platform.processor(), + "python version: ": platform.python_version(), + "lbrynet version: ": __version__, + # 'ip': json.load(urlopen('http://jsonip.com'))['ip'], + } + if sys.platform == "darwin": + msg['osx version'] = platform.mac_ver()[0] + " " + platform.mac_ver()[2] + else: + msg['platform'] = platform.platform() - def _setup_daemon_settings(self): - self.session_settings = self.default_settings + log.info("Platform: " + json.dumps(msg)) + return defer.succeed(None) + + def _load_daemon_conf(): + if os.path.isfile(self.daemon_conf): + return json.loads(open(self.daemon_conf, "r").read()) + else: + log.info("Writing default settings : " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.default_settings)) + f.close() + return self.default_settings + + d = _log_platform() + d.addCallback(lambda _: _load_daemon_conf()) + + return d + + def _set_daemon_settings(self, settings): + self.session_settings = settings return defer.succeed(None) def _start_server(self): @@ -317,33 +388,72 @@ class LBRYDaemon(jsonrpc.JSONRPC): dl.addCallback(_set_query_handlers) return dl + def _upload_log(self): + if self.session_settings['upload_log']: + LOG_URL = "https://lbry.io/log-upload" + f = open(self.log_file, "r") + t = datetime.now() + log_name = base58.b58encode(self.lbryid)[:20] + "-" + str(t.month) + "-" + str(t.day) + "-" + str(t.year) + "-" + str(t.hour) + "-" + str(t.minute) + params = {'name': log_name, 'log': f.read()} + f.close() + requests.post(LOG_URL, params) + + return defer.succeed(None) + def _shutdown(self): log.info("Closing lbrynet session") - d = self._stop_server() + d = self._upload_log() + d.addCallback(lambda _: self._stop_server()) + d.addErrback(lambda err: log.info("Bad server shutdown: " + err.getTraceback())) if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) + d.addErrback(lambda err: log.info("Bad session shutdown: " + err.getTraceback())) return d def _update_settings(self, settings): - if not isinstance(settings['run_on_startup'], bool): - return defer.fail() - elif not isinstance(settings['data_rate'], float): - return defer.fail() - elif not isinstance(settings['max_key_fee'], float): - return defer.fail() - elif not isinstance(settings['default_download_directory'], unicode): - return defer.fail() - elif not isinstance(settings['max_upload'], float): - return defer.fail() - elif not isinstance(settings['max_download'], float): - return defer.fail() + for k in settings.keys(): + if k == 'run_on_startup': + if type(settings['run_on_startup']) is bool: + self.session_settings['run_on_startup'] = settings['run_on_startup'] + else: + return defer.fail() + elif k == 'data_rate': + if type(settings['data_rate']) is float: + self.session_settings['data_rate'] = settings['data_rate'] + else: + return defer.fail() + elif k == 'max_key_fee': + if type(settings['max_key_fee']) is float: + self.session_settings['max_key_fee'] = settings['max_key_fee'] + else: + return defer.fail() + elif k == 'default_download_directory': + if type(settings['default_download_directory']) is unicode: + if os.path.isdir(settings['default_download_directory']): + self.session_settings['default_download_directory'] = settings['default_download_directory'] + else: + pass + else: + return defer.fail() + elif k == 'max_upload': + if type(settings['max_upload']) is float: + self.session_settings['max_upload'] = settings['max_upload'] + else: + return defer.fail() + elif k == 'max_download': + if type(settings['max_download']) is float: + self.session_settings['max_download'] = settings['max_download'] + else: + return defer.fail() + elif k == 'upload_log': + if type(settings['upload_log']) is bool: + self.session_settings['upload_log'] = settings['upload_log'] + else: + return defer.fail() - self.session_settings['run_on_startup'] = settings['run_on_startup'] - self.session_settings['data_rate'] = settings['data_rate'] - self.session_settings['max_key_fee'] = settings['max_key_fee'] - self.session_settings['default_download_directory'] = settings['default_download_directory'] - self.session_settings['max_upload'] = settings['max_upload'] - self.session_settings['max_download'] = settings['max_download'] + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() return defer.succeed(True) @@ -398,10 +508,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): if lbryid is None: return self._make_lbryid() else: + log.info("LBRY ID: " + base58.b58encode(lbryid)) self.lbryid = lbryid def _make_lbryid(self): self.lbryid = generate_id() + log.info("Generated new LBRY ID: " + base58.b58encode(self.lbryid)) d = self.settings.save_lbryid(self.lbryid) return d @@ -474,11 +586,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): return dl def _check_first_run(self): + def _set_first_run_false(): + self.first_run = False + return 0.0 + d = self.session.wallet.is_first_run() - d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else 0.0) + + d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else _set_first_run_false()) return d def _do_first_run(self): + self.first_run = True d = self.session.wallet.get_new_address() def send_request(url, data): @@ -530,7 +648,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _setup_stream_identifier(self): file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter, self.session.blob_manager, self.stream_info_manager, - self.session.wallet, self.download_directory) + self.session.wallet, self.session_settings['default_download_directory']) self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_saver_factory) file_opener_factory = LBRYFileOpenerFactory(self.session.peer_finder, self.session.rate_limiter, self.session.blob_manager, self.stream_info_manager, @@ -548,7 +666,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _download_name(self, name, timeout=DEFAULT_TIMEOUT): def _disp_file(f): - file_path = os.path.join(self.download_directory, f.file_name) + file_path = os.path.join(self.session_settings['default_download_directory'], f.file_name) log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) return defer.succeed(f) @@ -563,7 +681,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.session.wallet.get_stream_info_for_name(name) stream = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, - max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout) + max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, + download_directory=self.session_settings['default_download_directory']) d.addCallback(_disp) d.addCallback(lambda stream_info: stream.start(stream_info)) d.addCallback(lambda _: self._path_from_name(name)) @@ -603,7 +722,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): for lbry_file in self.lbry_file_manager.lbry_files: if lbry_file.stream_name == file_name: if sys.platform == "darwin": - if os.path.isfile(os.path.join(self.download_directory, lbry_file.stream_name)): + if os.path.isfile(os.path.join(self.session_settings['default_download_directory'], lbry_file.stream_name)): return lbry_file else: return False @@ -644,8 +763,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.lbry_file_manager.get_count_for_stream_hash(s_h) # TODO: could possibly be a timing issue here d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) - d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if - os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) else defer.succeed(None)) + d.addCallback(lambda _: os.remove(os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)) if + os.path.isfile(os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)) else defer.succeed(None)) return d d.addCallback(lambda _: finish_deletion(lbry_file)) @@ -654,14 +773,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _path_from_name(self, name): d = self._check_history(name) d.addCallback(lambda lbry_file: {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, lbry_file.file_name)} + 'path': os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)} if lbry_file else defer.fail(UnknownNameError)) return d def _path_from_lbry_file(self, lbry_file): if lbry_file: r = {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, lbry_file.file_name)} + 'path': os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)} return defer.succeed(r) else: return defer.fail(UnknownNameError) @@ -697,11 +816,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _render_response(self, result, code): return defer.succeed({'result': result, 'code': code}) - # def _log_to_slack(self, msg): - # URL = "https://hooks.slack.com/services/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" - # h = httplib2.Http() - # h.request(URL, 'POST', json.dumps({"text": msg}), headers={'Content-Type': 'application/json'}) - def jsonrpc_is_running(self): """ Returns true if daemon completed startup, otherwise returns false @@ -712,6 +826,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: return self._render_response(False, OK_CODE) + def jsonrpc_check_first_run(self): + try: + d = self.session.wallet.is_first_run() + except: + d = defer.fail(None) + + d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: self._render_response("Loading", OK_CODE)) + + return d + def jsonrpc_get_settings(self): """ Get LBRY payment settings @@ -733,10 +857,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'max_download': float (0.0 for unlimited)} """ - d = self._update_settings(p) + def _log_settings_change(params): + log.info("[" + str(datetime.now()) + "] Set daemon settings to " + str(params)) - log.info("[" + str(datetime.now()) + "] Set daemon settings") - return self._render_response(True, OK_CODE) + d = self._update_settings(p) + d.addCallback(lambda _: _log_settings_change(p)) + d.addCallback(lambda _: self._render_response(self.session_settings, OK_CODE)) + + return d def jsonrpc_start_fetcher(self): """ @@ -961,18 +1089,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): @return: confirmation message """ - params = Bunch(p) - def _disp(file_name): log.info("[" + str(datetime.now()) + "] Deleted: " + file_name) return self._render_response("Deleted: " + file_name, OK_CODE) - lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files - if str(params.file_name) == str(f.file_name)] - d = defer.DeferredList(lbry_files) - d.addCallback(lambda _: _disp(params.file_name)) - - return d + if "file_name" in p.keys(): + lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files + if p['file_name'] == f.file_name] + d = defer.DeferredList(lbry_files) + d.addCallback(lambda _: _disp(p['file_name'])) + return d def jsonrpc_publish(self, p): """ @@ -1018,14 +1144,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ params = Bunch(p) - def _disp(txid, tx): - log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + txid) - return self._render_response(txid, OK_CODE) + def _disp(x): + log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + str(x)) + return self._render_response(x, OK_CODE) d = defer.Deferred() d.addCallback(lambda _: self.session.wallet.abandon_name(params.txid)) - d.addCallback(lambda tx: _disp(params.txid, tx)) - d.addErrback(lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) + d.addCallback(_disp) d.callback(None) return d @@ -1055,10 +1180,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): @return: time behind blockchain """ - d = self.session.wallet.get_most_recent_blocktime() - d.addCallback(get_time_behind_blockchain) - d.addCallbacks(lambda result: self._render_response(str(result), OK_CODE), - lambda result: self._render_response(result, BAD_REQUEST)) + + def _get_time_behind(): + try: + local_height = self.session.wallet.network.get_local_height() + remote_height = self.session.wallet.network.get_server_height() + return defer.succeed(remote_height - local_height) + except: + return defer.fail() + + d = _get_time_behind() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 1899b4419..b231e10af 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -1,5 +1,6 @@ import argparse import logging +import logging.handlers import subprocess import os import shutil @@ -18,6 +19,13 @@ from jsonrpc.proxy import JSONRPCProxy from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYFileRender from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET, UI_ADDRESS +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') + log = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 997dde21c..5ebc4b59e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -17,7 +17,7 @@ log = logging.getLogger(__name__) class GetStream(object): def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5, - timeout=DEFAULT_TIMEOUT): + timeout=DEFAULT_TIMEOUT, download_directory=None): self.wallet = wallet self.resolved_name = None self.description = None @@ -37,6 +37,7 @@ class GetStream(object): self.d = defer.Deferred(None) self.timeout = timeout self.timeout_counter = 0 + self.download_directory = download_directory self.download_path = None self.checker = LoopingCall(self.check_status) @@ -88,7 +89,10 @@ class GetStream(object): self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) self.d.addCallback(lambda metadata: (next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), metadata)) - self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager)) + self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, + [self.data_rate, True], + self.payment_rate_manager, + download_directory=self.download_directory)) self.d.addErrback(lambda err: err.trap(defer.CancelledError)) self.d.addErrback(lambda err: log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback())) self.d.addCallback(self._start_download) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index f8ee282da..eeb8bbed3 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -103,10 +103,6 @@ class Publisher(object): def set_sd_hash(sd_hash): self.sd_hash = sd_hash - if isinstance(self.sources, dict): - self.sources['lbry_sd_hash'] = sd_hash - else: - self.sources = {'lbry_sd_hash': sd_hash} d.addCallback(set_sd_hash) return d diff --git a/requirements.txt b/requirements.txt index 103294fd4..ffbf27f45 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,7 +19,7 @@ qrcode==5.2.2 requests==2.9.1 seccure==0.3.1.3 simplejson==3.8.2 -six==1.10.0 +six==1.9.0 slowaes==0.1a1 txJSON-RPC==0.3.1 unqlite==0.2.0 diff --git a/setup.py b/setup.py index 679781350..7e806198a 100644 --- a/setup.py +++ b/setup.py @@ -26,12 +26,7 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', - 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs'] - -if sys.platform == 'darwin': - requires.append('six==1.9.0') -else: - requires.append('six>=1.9.0') + 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0'] gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf'] From 9a300d773f3fb4877098e053cb68008e5791be05 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 8 Apr 2016 22:23:37 -0400 Subject: [PATCH 100/462] optional download_directory parameter for get function -optional download_directory parameter to choose the directory a file is saved to, if not given or not a valid directory it uses the default --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 73dc07d4b..e1e352353 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -587,6 +587,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _check_first_run(self): def _set_first_run_false(): + log.info("Not first run") self.first_run = False return 0.0 @@ -600,6 +601,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.session.wallet.get_new_address() def send_request(url, data): + log.info("Requesting first run credits") r = requests.post(url, json=data) if r.status_code == 200: return r.json()['credits_sent'] @@ -664,7 +666,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory) return defer.succeed(True) - def _download_name(self, name, timeout=DEFAULT_TIMEOUT): + def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None): + if not download_directory: + download_directory = self.session_settings['default_download_directory'] + elif not os.path.isdir(download_directory): + download_directory = self.session_settings['default_download_directory'] + def _disp_file(f): file_path = os.path.join(self.session_settings['default_download_directory'], f.file_name) log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) @@ -682,7 +689,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.session.wallet.get_stream_info_for_name(name) stream = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, - download_directory=self.session_settings['default_download_directory']) + download_directory=download_directory) d.addCallback(_disp) d.addCallback(lambda stream_info: stream.start(stream_info)) d.addCallback(lambda _: self._path_from_name(name)) @@ -977,16 +984,23 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Download stream from a LBRY uri - @param: name + @param: name, optional: download_directory @return: {'stream_hash': hex string, 'path': path of download} """ - params = Bunch(p) if 'timeout' not in p.keys(): - params.timeout = DEFAULT_TIMEOUT + timeout = DEFAULT_TIMEOUT + else: + timeout = p['timeout'] - if params.name: - d = self._download_name(params.name, timeout=params.timeout) + if 'download_directory' not in p.keys(): + download_directory = self.session_settings['default_download_directory'] + else: + download_directory = p['download_directory'] + + if 'name' in p.keys(): + name = p['name'] + d = self._download_name(name=name, timeout=timeout, download_directory=download_directory) d.addCallbacks(lambda message: self._render_response(message, OK_CODE), lambda err: self._render_response('error', NOT_FOUND)) else: From 08e25f4622ff251ba09e3a8feac976da697f623f Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 8 Apr 2016 23:38:57 -0400 Subject: [PATCH 101/462] convert ints to floats if given as settings params --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index e1e352353..63d4b2dcc 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -420,11 +420,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): elif k == 'data_rate': if type(settings['data_rate']) is float: self.session_settings['data_rate'] = settings['data_rate'] + elif type(settings['data_rate']) is int: + self.session_settings['data_rate'] = float(settings['data_rate']) else: return defer.fail() elif k == 'max_key_fee': if type(settings['max_key_fee']) is float: self.session_settings['max_key_fee'] = settings['max_key_fee'] + elif type(settings['max_key_fee']) is int: + self.session_settings['max_key_fee'] = float(settings['max_key_fee']) else: return defer.fail() elif k == 'default_download_directory': @@ -438,11 +442,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): elif k == 'max_upload': if type(settings['max_upload']) is float: self.session_settings['max_upload'] = settings['max_upload'] + elif type(settings['max_upload']) is int: + self.session_settings['max_upload'] = float(settings['max_upload']) else: return defer.fail() elif k == 'max_download': if type(settings['max_download']) is float: self.session_settings['max_download'] = settings['max_download'] + if type(settings['max_download']) is int: + self.session_settings['max_download'] = float(settings['max_download']) else: return defer.fail() elif k == 'upload_log': @@ -828,6 +836,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): Returns true if daemon completed startup, otherwise returns false """ + log.info("[" + str(datetime.now()) + "] is_running: " + str(self.announced_startup)) + if self.announced_startup: return self._render_response(True, OK_CODE) else: @@ -1343,4 +1353,4 @@ class LBRYFileRender(resource.Resource): return server.NOT_DONE_YET else: - return server.failure \ No newline at end of file + return server.failure From 162275992b01758e094c1d7346cd944b1be93b36 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 8 Apr 2016 23:52:43 -0400 Subject: [PATCH 102/462] return fail from check_first_run when is_first_run isn't callable --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 63d4b2dcc..093cc54f4 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -849,7 +849,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): except: d = defer.fail(None) - d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: self._render_response("Loading", OK_CODE)) + d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: server.failure) return d From 28a23590efe41d6b01c37b466dec9ba90a9fc4e5 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Apr 2016 01:33:27 -0400 Subject: [PATCH 103/462] add message for startup status -added daemon_status function that returns {'status': startup status message, 'is_running': true/false} --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 093cc54f4..e927d29e7 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -100,7 +100,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): # versions... if not self.announced_startup: - if functionPath not in ['is_running', 'check_first_run', 'get_time_behind_blockchain', 'stop']: + if functionPath not in ['is_running', 'check_first_run', + 'get_time_behind_blockchain', 'stop', + 'daemon_status']: return server.failure try: @@ -212,6 +214,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE self.max_key_fee = DEFAULT_MAX_KEY_FEE self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS + self.startup_status = "Initializing" self.startup_message = "" self.announced_startup = False self.search_timeout = 3.0 @@ -254,6 +257,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _announce_startup(): self.announced_startup = True + self.startus_status = None log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") return defer.succeed(None) @@ -307,6 +311,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): f.close() return self.default_settings + self.startus_status = "Loading configuration" + d = _log_platform() d.addCallback(lambda _: _load_daemon_conf()) @@ -345,6 +351,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._start_server() return defer.succeed(True) + self.startus_status = "Starting lbrynet" + dl = self.settings.get_server_running_status() dl.addCallback(restore_running_status) return dl @@ -471,6 +479,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) def _setup_data_directory(self): + self.startus_status = "Loading databases" log.info("Loading databases...") if self.created_data_dir: db_revision = open(os.path.join(self.db_dir, "db_revision"), mode='w') @@ -526,6 +535,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def _setup_lbry_file_manager(self): + self.startus_status = "Loading file manager" self.lbry_file_metadata_manager = DBLBRYFileMetadataManager(self.db_dir) d = self.lbry_file_metadata_manager.setup() @@ -548,6 +558,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def get_wallet(): + self.startup_status = "Loading wallet" if self.wallet_type == "lbrycrd": log.info("Using lbrycrd wallet") lbrycrdd_path = None @@ -580,6 +591,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return r def create_session(results): + self.startus_status = "Loading lbrynet session" self.session = LBRYSession(results['default_data_payment_rate'], db_dir=self.db_dir, lbryid=self.lbryid, blob_dir=self.blobfile_dir, dht_node_port=self.dht_node_port, known_dht_nodes=self.known_dht_nodes, peer_port=self.peer_port, @@ -843,6 +855,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: return self._render_response(False, OK_CODE) + def jsonrpc_daemon_status(self): + """ + Returns {'status': startup status message, 'is_running': true/false} + """ + + r = {'status': self.startus_status, 'is_running': self.announced_startup} + log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) + return self._render_response(r, OK_CODE) + def jsonrpc_check_first_run(self): try: d = self.session.wallet.is_first_run() From c3277d24c3f9406a6f3d1cefe6cf7e6b6f02079f Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Apr 2016 02:09:30 -0400 Subject: [PATCH 104/462] use status_message and status_code return fields for daemon_status() --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 33 ++++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index e927d29e7..250c82c6d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -63,6 +63,17 @@ handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, ba log.addHandler(handler) +STARTUP_STAGES = [ + ('initializing', 'Initializing...'), + ('loading_db', 'Loading databases...'), + ('loading_wallet', 'Catching up with blockchain... %s'), + ('loading_session', 'Starting session'), + ('loading_file_manager', 'Setting up file manager'), + ('loading_server', 'Starting lbrynet'), + ('started', 'Started lbrynet') + ] + + BAD_REQUEST = 400 NOT_FOUND = 404 OK_CODE = 200 @@ -214,7 +225,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE self.max_key_fee = DEFAULT_MAX_KEY_FEE self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS - self.startup_status = "Initializing" + self.startup_status = STARTUP_STAGES[0] self.startup_message = "" self.announced_startup = False self.search_timeout = 3.0 @@ -257,7 +268,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _announce_startup(): self.announced_startup = True - self.startus_status = None + self.startup_status = STARTUP_STAGES[6] log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") return defer.succeed(None) @@ -311,8 +322,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): f.close() return self.default_settings - self.startus_status = "Loading configuration" - d = _log_platform() d.addCallback(lambda _: _load_daemon_conf()) @@ -351,7 +360,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._start_server() return defer.succeed(True) - self.startus_status = "Starting lbrynet" + self.startup_status = STARTUP_STAGES[5] dl = self.settings.get_server_running_status() dl.addCallback(restore_running_status) @@ -479,7 +488,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) def _setup_data_directory(self): - self.startus_status = "Loading databases" + self.startup_status = STARTUP_STAGES[1] log.info("Loading databases...") if self.created_data_dir: db_revision = open(os.path.join(self.db_dir, "db_revision"), mode='w') @@ -535,7 +544,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def _setup_lbry_file_manager(self): - self.startus_status = "Loading file manager" + self.startup_status = STARTUP_STAGES[4] self.lbry_file_metadata_manager = DBLBRYFileMetadataManager(self.db_dir) d = self.lbry_file_metadata_manager.setup() @@ -558,7 +567,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def get_wallet(): - self.startup_status = "Loading wallet" if self.wallet_type == "lbrycrd": log.info("Using lbrycrd wallet") lbrycrdd_path = None @@ -591,11 +599,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): return r def create_session(results): - self.startus_status = "Loading lbrynet session" + self.startup_status = STARTUP_STAGES[2] self.session = LBRYSession(results['default_data_payment_rate'], db_dir=self.db_dir, lbryid=self.lbryid, blob_dir=self.blobfile_dir, dht_node_port=self.dht_node_port, known_dht_nodes=self.known_dht_nodes, peer_port=self.peer_port, use_upnp=self.use_upnp, wallet=results['wallet']) + self.startup_status = STARTUP_STAGES[3] dl = defer.DeferredList([d1, d2], fireOnOneErrback=True) dl.addCallback(combine_results) @@ -857,10 +866,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_daemon_status(self): """ - Returns {'status': startup status message, 'is_running': true/false} + Returns {'status_message': startup status message, 'status_code': status_code} """ - - r = {'status': self.startus_status, 'is_running': self.announced_startup} + + r = {'status_code': self.startup_status[0], 'status_message': self.startup_status[1]} log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) return self._render_response(r, OK_CODE) From a4c83a08c71104d44d25762e7f9f8d16405320e2 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Apr 2016 04:00:31 -0400 Subject: [PATCH 105/462] change name of check_first_run to is_first_run --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 250c82c6d..0ef030026 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -111,7 +111,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): # versions... if not self.announced_startup: - if functionPath not in ['is_running', 'check_first_run', + if functionPath not in ['is_running', 'is_first_run', 'get_time_behind_blockchain', 'stop', 'daemon_status']: return server.failure @@ -873,7 +873,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) return self._render_response(r, OK_CODE) - def jsonrpc_check_first_run(self): + def jsonrpc_is_first_run(self): try: d = self.session.wallet.is_first_run() except: From f4fe730dc9614f2ecac994a4b206355dc5f77df0 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Apr 2016 04:15:58 -0400 Subject: [PATCH 106/462] add get_start_notice to return startup messages --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0ef030026..247c96414 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -113,7 +113,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): if not self.announced_startup: if functionPath not in ['is_running', 'is_first_run', 'get_time_behind_blockchain', 'stop', - 'daemon_status']: + 'daemon_status', 'get_start_notice']: return server.failure try: @@ -883,6 +883,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d + def jsonrpc_get_start_notice(self): + if self.startup_message: + return self._render_response(self.startup_message, OK_CODE) + else: + return defer.fail(None) + def jsonrpc_get_settings(self): """ Get LBRY payment settings From c11872a2296a6b693499636e3e5906a7182a17e6 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Apr 2016 04:35:34 -0400 Subject: [PATCH 107/462] default on no startup message --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 247c96414..13dae024d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -655,7 +655,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): points_string = locale.format_string("%.2f LBC", (round(credits_received, 2),), grouping=True) self.startup_message = "Thank you for testing the alpha version of LBRY! You have been given %s for free because we love you. Please give them a few minutes to show up while you catch up with our blockchain." % points_string else: - self.startup_message = "Connected to LBRYnet" + self.startup_message = None def _get_lbrycrdd_path(self): def get_lbrycrdd_path_conf_file(): From f0d39e6081ad5592f645f290ff08dcf8bf50fa63 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Apr 2016 15:43:10 -0400 Subject: [PATCH 108/462] log LBRYDaemonControl and return None from get_start_notice if there is no notice to give --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 21 +++++++++++++-------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 5 +++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 13dae024d..5dd8f1ead 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -58,9 +58,7 @@ if not os.path.isdir(log_dir): LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') log = logging.getLogger(__name__) - handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) - log.addHandler(handler) STARTUP_STAGES = [ @@ -226,7 +224,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.max_key_fee = DEFAULT_MAX_KEY_FEE self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS self.startup_status = STARTUP_STAGES[0] - self.startup_message = "" + self.startup_message = None self.announced_startup = False self.search_timeout = 3.0 self.query_handlers = {} @@ -874,20 +872,27 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._render_response(r, OK_CODE) def jsonrpc_is_first_run(self): + """ + Get True/False if can be determined, if wallet still is being set up returns None + """ + + log.info("[" + str(datetime.now()) + "] Check if is first run") try: d = self.session.wallet.is_first_run() except: d = defer.fail(None) - d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: server.failure) + d.addCallbacks(lambda r: self._render_response(r, OK_CODE), lambda _: self._render_response(None, OK_CODE)) return d def jsonrpc_get_start_notice(self): - if self.startup_message: - return self._render_response(self.startup_message, OK_CODE) - else: - return defer.fail(None) + """ + Get any special message to be displayed at startup, such as a first run notice + """ + + log.info("[" + str(datetime.now()) + "] Get startup notice") + return self._render_response(self.startup_message, OK_CODE) def jsonrpc_get_settings(self): """ diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index b231e10af..699bf000b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -24,9 +24,14 @@ if sys.platform != "darwin": else: log_dir = user_data_dir("LBRY") +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) +log.addHandler(handler) logging.basicConfig(level=logging.INFO) From 4140fc4d286fff378c6369de611347f3105bd125 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Apr 2016 15:43:25 -0400 Subject: [PATCH 109/462] add base58 --- requirements.txt | 1 + setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ffbf27f45..d4423b5db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,3 +25,4 @@ txJSON-RPC==0.3.1 unqlite==0.2.0 wsgiref==0.1.2 zope.interface==4.1.3 +base58==0.2.2 diff --git a/setup.py b/setup.py index 7e806198a..877377190 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', - 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0'] + 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf'] From a931f59dda8dd8eb04b559c022e0cd1bef20d7d5 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Apr 2016 16:14:28 -0400 Subject: [PATCH 110/462] only return first run message until credits arrive --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 5dd8f1ead..13fa760b8 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -173,7 +173,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") self.peer_port = 3333 self.dht_node_port = 4444 - self.first_run = "Loading" + self.first_run = None if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) @@ -891,8 +891,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): Get any special message to be displayed at startup, such as a first run notice """ + log.info("[" + str(datetime.now()) + "] Get startup notice") - return self._render_response(self.startup_message, OK_CODE) + + if self.first_run and not self.wallet.balance: + return self._render_response(self.startup_message, OK_CODE) + elif self.first_run: + return self._render_response(None, OK_CODE) + else: + self._render_response(self.startup_message, OK_CODE) def jsonrpc_get_settings(self): """ From e48cc40610f7eebb4c762acf01602728815c5977 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Apr 2016 16:18:06 -0400 Subject: [PATCH 111/462] fix incorrect variable --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 13fa760b8..83ce6d389 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -894,7 +894,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("[" + str(datetime.now()) + "] Get startup notice") - if self.first_run and not self.wallet.balance: + if self.first_run and not self.session.wallet.wallet_balance: return self._render_response(self.startup_message, OK_CODE) elif self.first_run: return self._render_response(None, OK_CODE) From aa8c362cb8839c936a19a0186133ad13ad30abf2 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 11 Apr 2016 16:15:12 -0400 Subject: [PATCH 112/462] add --branch arg to lbrynet-daemon to specify ui branch --- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 699bf000b..3818ff55e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -61,6 +61,9 @@ def start(): parser.add_argument("--ui", help="path to custom UI folder", default="") + parser.add_argument("--branch", + help="Branch of lbry-web-ui repo to use, defaults on HEAD", + default="HEAD") try: JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running() @@ -73,6 +76,14 @@ def start(): args = parser.parse_args() + if args.branch == "HEAD": + GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep %s | cut -f 1" % args.branch + DIST_URL = "https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip" + else: + log.info("Using UI branch: " + args.branch) + GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep refs/heads/%s | cut -f 1" % args.branch + DIST_URL = "https://rawgit.com/lbryio/lbry-web-ui/%s/dist.zip" % args.branch + def getui(ui_dir=None): if ui_dir: if os.path.isdir(ui_dir): @@ -82,16 +93,16 @@ def start(): log.info("User specified UI directory doesn't exist: " + str(ui_dir)) def download_ui(dest_dir): - url = urlopen("https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip") + url = urlopen(DIST_URL) z = ZipFile(StringIO(url.read())) - z.extractall(dest_dir) + names = [i for i in z.namelist() if '.DS_Store' not in i and '__MACOSX' not in i] + z.extractall(dest_dir, members=names) return defer.succeed(dest_dir) data_dir = user_data_dir("LBRY") version_dir = os.path.join(data_dir, "ui_version_history") - git_version = subprocess.check_output( - "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep HEAD | cut -f 1", shell=True) + git_version = subprocess.check_output(GIT_CMD_STRING, shell=True) if not os.path.isdir(data_dir): os.mkdir(data_dir) From 31fa97437972dca87f6b313631759b6e0bdb1b7a Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 11 Apr 2016 22:28:46 -0400 Subject: [PATCH 113/462] version function, ui version tracking, blocks behind blockchain during startup, cleaner set_vars -adds version() function, which can be called after the daemon is running and returns lbrynet, lbryum, and lbry ui version information. -daemon_status now returns how far behind its behind the blockchain when in the loading_wallet phase of startup -cleans up set_vars by moving to init --- lbrynet/core/LBRYcrdWallet.py | 2 + lbrynet/lbrynet_daemon/LBRYDaemon.py | 208 +++++++++++--------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 26 +-- 3 files changed, 130 insertions(+), 106 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index f487becd0..7a53bc47d 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -902,6 +902,7 @@ class LBRYumWallet(LBRYWallet): self._start_check = None self._catch_up_check = None self._caught_up_counter = 0 + self.blocks_behind_alert = 0 def _start(self): @@ -996,6 +997,7 @@ class LBRYumWallet(LBRYWallet): if self._caught_up_counter == 0: alert.info('Catching up to the blockchain...showing blocks left...') if self._caught_up_counter % 30 == 0: + self.blocks_behind_alert = remote_height - local_height alert.info('%d...', (remote_height - local_height)) self._caught_up_counter += 1 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 83ce6d389..f08ed654d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -23,7 +23,8 @@ from decimal import Decimal from appdirs import user_data_dir from urllib2 import urlopen -from lbrynet import __version__ +from lbrynet import __version__ as lbrynet_version +from lbryum.version import ELECTRUM_VERSION as lbryum_version from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory @@ -65,7 +66,6 @@ STARTUP_STAGES = [ ('initializing', 'Initializing...'), ('loading_db', 'Loading databases...'), ('loading_wallet', 'Catching up with blockchain... %s'), - ('loading_session', 'Starting session'), ('loading_file_manager', 'Setting up file manager'), ('loading_server', 'Starting lbrynet'), ('started', 'Started lbrynet') @@ -88,8 +88,92 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ LBRYnet daemon, a jsonrpc interface to lbry functions """ + isLeaf = True + def __init__(self, wallet_type, check_for_updates, ui_version_info): + jsonrpc.JSONRPC.__init__(self) + reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) + + self.log_file = LOG_FILENAME + self.fetcher = None + self.current_db_revision = 1 + self.run_server = True + self.session = None + self.known_dht_nodes = KNOWN_DHT_NODES + if sys.platform != "darwin": + self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + else: + self.db_dir = user_data_dir("LBRY") + self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") + self.peer_port = 3333 + self.dht_node_port = 4444 + self.first_run = None + if os.name == "nt": + from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle + self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) + self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") + elif sys.platform == "darwin": + self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + if wallet_type == "lbrycrd": + self.wallet_dir = user_data_dir("lbrycrd") + else: + self.wallet_dir = user_data_dir("LBRY") + else: + self.download_directory = os.getcwd() + if wallet_type == "lbrycrd": + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") + else: + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") + self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') + self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.wallet_user = None + self.wallet_password = None + self.sd_identifier = StreamDescriptorIdentifier() + self.stream_info_manager = TempLBRYFileMetadataManager() + self.wallet_rpc_port = 8332 + self.downloads = [] + self.stream_frames = [] + self.default_blob_data_payment_rate = MIN_BLOB_DATA_PAYMENT_RATE + self.use_upnp = True + self.start_lbrycrdd = True + if os.name == "nt": + self.lbrycrdd_path = "lbrycrdd.exe" + else: + self.lbrycrdd_path = "./lbrycrdd" + self.delete_blobs_on_remove = True + self.blob_request_payment_rate_manager = None + self.lbry_file_metadata_manager = None + self.lbry_file_manager = None + self.settings = LBRYSettings(self.db_dir) + self.wallet_type = wallet_type + self.check_for_updates = check_for_updates + self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") + self.created_data_dir = False + if not os.path.exists(self.db_dir): + os.mkdir(self.db_dir) + self.created_data_dir = True + self.session_settings = None + self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE + self.max_key_fee = DEFAULT_MAX_KEY_FEE + self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS + self.startup_status = STARTUP_STAGES[0] + self.startup_message = None + self.announced_startup = False + self.search_timeout = 3.0 + self.query_handlers = {} + self.default_settings = { + 'run_on_startup': False, + 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, + 'max_key_fee': 10.0, + 'default_download_directory': self.download_directory, + 'max_upload': 0.0, + 'max_download': 0.0, + 'upload_log': True + } + self.ui_version = ui_version_info.replace('\n', '') + def render(self, request): request.content.seek(0, 0) # Unmarshal the JSON-RPC data. @@ -97,6 +181,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): parsed = jsonrpclib.loads(content) functionPath = parsed.get("method") args = parsed.get('params') + + #TODO convert args to correct types if possible + id = parsed.get('id') version = parsed.get('jsonrpc') if version: @@ -155,91 +242,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.error(failure) return jsonrpclib.Fault(self.FAILURE, "error") - def setup(self, wallet_type, check_for_updates): - def _set_vars(wallet_type, check_for_updates): - reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) - - self.log_file = LOG_FILENAME - self.fetcher = None - self.current_db_revision = 1 - self.run_server = True - self.session = None - self.known_dht_nodes = KNOWN_DHT_NODES - if sys.platform != "darwin": - self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") - else: - self.db_dir = user_data_dir("LBRY") - # self.db_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrynet") - self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") - self.peer_port = 3333 - self.dht_node_port = 4444 - self.first_run = None - if os.name == "nt": - from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle - self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) - self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") - elif sys.platform == "darwin": - self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') - if wallet_type == "lbrycrd": - self.wallet_dir = user_data_dir("lbrycrd") - else: - self.wallet_dir = user_data_dir("LBRY") - else: - self.download_directory = os.getcwd() - if wallet_type == "lbrycrd": - self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") - else: - self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") - self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') - self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.wallet_user = None - self.wallet_password = None - self.sd_identifier = StreamDescriptorIdentifier() - self.stream_info_manager = TempLBRYFileMetadataManager() - self.wallet_rpc_port = 8332 - self.downloads = [] - self.stream_frames = [] - self.default_blob_data_payment_rate = MIN_BLOB_DATA_PAYMENT_RATE - self.use_upnp = True - self.start_lbrycrdd = True - if os.name == "nt": - self.lbrycrdd_path = "lbrycrdd.exe" - else: - self.lbrycrdd_path = "./lbrycrdd" - self.delete_blobs_on_remove = True - self.blob_request_payment_rate_manager = None - self.lbry_file_metadata_manager = None - self.lbry_file_manager = None - self.settings = LBRYSettings(self.db_dir) - self.wallet_type = wallet_type - self.check_for_updates = check_for_updates - self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") - self.created_data_dir = False - if not os.path.exists(self.db_dir): - os.mkdir(self.db_dir) - self.created_data_dir = True - self.session_settings = None - self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE - self.max_key_fee = DEFAULT_MAX_KEY_FEE - self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS - self.startup_status = STARTUP_STAGES[0] - self.startup_message = None - self.announced_startup = False - self.search_timeout = 3.0 - self.query_handlers = {} - self.default_settings = { - 'run_on_startup': False, - 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, - 'max_key_fee': 10.0, - 'default_download_directory': self.download_directory, - 'max_upload': 0.0, - 'max_download': 0.0, - 'upload_log': True - } - - return defer.succeed(None) - + def setup(self): def _log_starting_vals(): def _get_lbry_files_json(): r = [] @@ -266,14 +269,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _announce_startup(): self.announced_startup = True - self.startup_status = STARTUP_STAGES[6] + self.startup_status = STARTUP_STAGES[5] log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") return defer.succeed(None) log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") d = defer.Deferred() - d.addCallback(lambda _:_set_vars(wallet_type, check_for_updates)) d.addCallback(lambda _: self._initial_setup()) d.addCallback(self._set_daemon_settings) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) @@ -299,7 +301,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): msg = { "processor": platform.processor(), "python version: ": platform.python_version(), - "lbrynet version: ": __version__, + "lbrynet version: ": lbrynet_version, + "lbryum version: ": lbryum_version, + "ui_version": self.ui_version, # 'ip': json.load(urlopen('http://jsonip.com'))['ip'], } if sys.platform == "darwin": @@ -358,7 +362,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._start_server() return defer.succeed(True) - self.startup_status = STARTUP_STAGES[5] + self.startup_status = STARTUP_STAGES[4] dl = self.settings.get_server_running_status() dl.addCallback(restore_running_status) @@ -542,7 +546,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def _setup_lbry_file_manager(self): - self.startup_status = STARTUP_STAGES[4] + self.startup_status = STARTUP_STAGES[3] self.lbry_file_metadata_manager = DBLBRYFileMetadataManager(self.db_dir) d = self.lbry_file_metadata_manager.setup() @@ -597,12 +601,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): return r def create_session(results): - self.startup_status = STARTUP_STAGES[2] self.session = LBRYSession(results['default_data_payment_rate'], db_dir=self.db_dir, lbryid=self.lbryid, blob_dir=self.blobfile_dir, dht_node_port=self.dht_node_port, known_dht_nodes=self.known_dht_nodes, peer_port=self.peer_port, use_upnp=self.use_upnp, wallet=results['wallet']) - self.startup_status = STARTUP_STAGES[3] + self.startup_status = STARTUP_STAGES[2] dl = defer.DeferredList([d1, d2], fireOnOneErrback=True) dl.addCallback(combine_results) @@ -868,6 +871,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ r = {'status_code': self.startup_status[0], 'status_message': self.startup_status[1]} + try: + if self.startup_status[0] == 'loading_wallet': + r['status_message'] = r['status_message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") + except: + pass log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) return self._render_response(r, OK_CODE) @@ -901,6 +909,18 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self._render_response(self.startup_message, OK_CODE) + def jsonrpc_version(self): + """ + Get lbry version information + """ + + msg = { + "lbrynet version: ": lbrynet_version, + "lbryum version: ": lbryum_version, + "ui_version": self.ui_version, + } + return self._render_response(msg, OK_CODE) + def jsonrpc_get_settings(self): """ Get LBRY payment settings diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 3818ff55e..fe5c0ea2c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -88,21 +88,23 @@ def start(): if ui_dir: if os.path.isdir(ui_dir): log.info("Using user specified UI directory: " + str(ui_dir)) - return defer.succeed(ui_dir) + ui_version_info = "user-specified" + return defer.succeed([ui_dir, ui_version_info]) else: log.info("User specified UI directory doesn't exist: " + str(ui_dir)) - def download_ui(dest_dir): + def download_ui(dest_dir, ui_version): url = urlopen(DIST_URL) z = ZipFile(StringIO(url.read())) names = [i for i in z.namelist() if '.DS_Store' not in i and '__MACOSX' not in i] z.extractall(dest_dir, members=names) - return defer.succeed(dest_dir) + return defer.succeed([dest_dir, ui_version]) data_dir = user_data_dir("LBRY") version_dir = os.path.join(data_dir, "ui_version_history") git_version = subprocess.check_output(GIT_CMD_STRING, shell=True) + ui_version_info = git_version if not os.path.isdir(data_dir): os.mkdir(data_dir) @@ -128,28 +130,28 @@ def start(): log.info(version_message) if os.path.isdir(os.path.join(data_dir, "lbry-web-ui")): - return defer.succeed(os.path.join(data_dir, "lbry-web-ui")) + return defer.succeed([os.path.join(data_dir, "lbry-web-ui"), ui_version_info]) else: - return download_ui((os.path.join(data_dir, "lbry-web-ui"))) + return download_ui(os.path.join(data_dir, "lbry-web-ui"), ui_version_info) - def setupserver(ui_dir): + def setupserver(ui_dir, ui_version): root = LBRYindex(ui_dir) root.putChild("css", static.File(os.path.join(ui_dir, "css"))) root.putChild("font", static.File(os.path.join(ui_dir, "font"))) root.putChild("img", static.File(os.path.join(ui_dir, "img"))) root.putChild("js", static.File(os.path.join(ui_dir, "js"))) root.putChild("view", LBRYFileRender()) - return defer.succeed(root) + return defer.succeed([root, ui_version]) - def setupapi(root, wallet): - daemon = LBRYDaemon() + def setupapi(root, wallet, ui_version): + daemon = LBRYDaemon(wallet, "False", ui_version) root.putChild(API_ADDRESS, daemon) reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) - return daemon.setup(wallet, "False") + return daemon.setup() d = getui(args.ui) - d.addCallback(setupserver) - d.addCallback(lambda r: setupapi(r, args.wallet)) + d.addCallback(lambda r: setupserver(r[0], r[1])) + d.addCallback(lambda r: setupapi(r[0], args.wallet, r[1])) d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) reactor.run() \ No newline at end of file From c50f83400a68af5b188e932b73680e6e4ab548e6 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 11 Apr 2016 22:37:42 -0400 Subject: [PATCH 114/462] make version callable during startup --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index f08ed654d..568d74827 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -198,7 +198,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): if not self.announced_startup: if functionPath not in ['is_running', 'is_first_run', 'get_time_behind_blockchain', 'stop', - 'daemon_status', 'get_start_notice']: + 'daemon_status', 'get_start_notice', + 'version']: return server.failure try: @@ -919,6 +920,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): "lbryum version: ": lbryum_version, "ui_version": self.ui_version, } + + log.info("[" + str(datetime.now()) + "] Get version info: " + json.dumps(msg)) return self._render_response(msg, OK_CODE) def jsonrpc_get_settings(self): From e04f35af4fb96def95aa482dde0482171fa95477 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 11 Apr 2016 23:13:26 -0400 Subject: [PATCH 115/462] fix check_for_new_version returns true if new version is available, otherwise false check_for_new_version is callable during startup --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 56 +++++++++------------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 568d74827..3c30529d5 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -199,7 +199,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): if functionPath not in ['is_running', 'is_first_run', 'get_time_behind_blockchain', 'stop', 'daemon_status', 'get_start_notice', - 'version']: + 'version', 'check_for_new_version']: return server.failure try: @@ -920,7 +920,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): "lbryum version: ": lbryum_version, "ui_version": self.ui_version, } - + log.info("[" + str(datetime.now()) + "] Get version info: " + json.dumps(msg)) return self._render_response(msg, OK_CODE) @@ -1333,45 +1333,23 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._render_response(self.fetcher.verbose, OK_CODE) def jsonrpc_check_for_new_version(self): - def _check_for_updates(package): - git_version = subprocess.check_output("git ls-remote " + package['git'] + " | grep HEAD | cut -f 1", shell=True) - up_to_date = False - if os.path.isfile(package['version_file']): - f = open(package['version_file'], 'r') - current_version = f.read() - f.close() + def _get_lbryum_version(): + r = urlopen("https://rawgit.com/lbryio/lbryum/master/lib/version.py").read().split('\n') + version = next(line.split("=")[1].split("#")[0].replace(" ", "") + for line in r if "ELECTRUM_VERSION" in line) + version = version.replace("'", "") + return version - if git_version == current_version: - r = package['name'] + " is up to date" - up_to_date = True - else: - r = package['name'] + " version is out of date" - else: - r = "Unknown version of " + package['name'] + def _get_lbrynet_version(): + r = urlopen("https://rawgit.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') + vs = next(i for i in r if 'version =' in i).split("=")[1].replace(" ", "") + vt = tuple(int(x) for x in vs[1:-1].split(',')) + return ".".join([str(x) for x in vt]) - return (up_to_date, r) - - package_infos = { - "lbrynet": {"name": "LBRYnet", - "git": "https://github.com/lbryio/lbry.git", - "version_file": os.path.join(self.db_dir, ".lbrynet_version"), - "clone": ".lbrygit", - }, - "lbryum": {"name": "lbryum", - "git": "https://github.com/lbryio/lbryum.git", - "version_file": os.path.join(self.db_dir, ".lbryum_version"), - "clone": ".lbryumgit", - }, - "lbry": {"name": "LBRY", - "git": "https://github.com/jackrobison/lbrynet-app.git", - "version_file": os.path.join(self.db_dir, ".lbry_app_version"), - "clone": None, - }, - } - - r = [_check_for_updates(package_infos[p]) for p in package_infos.keys()] - log.info("[" + str(datetime.now()) + "] Check for new version: " + json.dumps(r)) - return self._render_response(r, OK_CODE) + if (lbrynet_version >= _get_lbrynet_version()) and (lbryum_version >= _get_lbryum_version()): + return self._render_response(False, OK_CODE) + else: + return self._render_response(True, OK_CODE) def jsonrpc___dir__(self): return ['is_running', 'get_settings', 'set_settings', 'start_fetcher', 'stop_fetcher', 'fetcher_status', From 59002413f85b4494cab8130ce3763fac2408f501 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 12 Apr 2016 02:03:57 -0400 Subject: [PATCH 116/462] organize daemon init, move more into daemon_settings.json -also add wallet.catchup_progress to get a rough percentage of how caught up the daemon is, this is accessed through daemon_status() --- lbrynet/core/LBRYcrdWallet.py | 7 ++ lbrynet/lbrynet_daemon/LBRYDaemon.py | 117 ++++++++++++-------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 2 +- 3 files changed, 78 insertions(+), 48 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 7a53bc47d..c6cb8c4d9 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -903,6 +903,8 @@ class LBRYumWallet(LBRYWallet): self._catch_up_check = None self._caught_up_counter = 0 self.blocks_behind_alert = 0 + self.catchup_progress = 0 + self.max_behind = 0 def _start(self): @@ -998,7 +1000,12 @@ class LBRYumWallet(LBRYWallet): alert.info('Catching up to the blockchain...showing blocks left...') if self._caught_up_counter % 30 == 0: self.blocks_behind_alert = remote_height - local_height + if self.blocks_behind_alert > self.max_behind: + self.max_behind = self.blocks_behind_alert + self.catchup_progress = int(100 * (self.blocks_behind_alert / (5 + self.max_behind))) alert.info('%d...', (remote_height - local_height)) + alert.info("Catching up: " + str(int(100 * (self.blocks_behind_alert / (5 + self.max_behind)))) + "%") + self._caught_up_counter += 1 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 3c30529d5..16ff012e4 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -91,88 +91,110 @@ class LBRYDaemon(jsonrpc.JSONRPC): isLeaf = True - def __init__(self, wallet_type, check_for_updates, ui_version_info): + def __init__(self, ui_version_info, wallet_type="lbryum"): jsonrpc.JSONRPC.__init__(self) reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) + self.startup_status = STARTUP_STAGES[0] + self.startup_message = None + self.announced_startup = False + self.query_handlers = {} + self.ui_version = ui_version_info.replace('\n', '') + self.wallet_type = wallet_type + self.session_settings = None + self.first_run = None self.log_file = LOG_FILENAME self.fetcher = None self.current_db_revision = 1 self.run_server = True self.session = None self.known_dht_nodes = KNOWN_DHT_NODES - if sys.platform != "darwin": - self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") - else: - self.db_dir = user_data_dir("LBRY") - self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") - self.peer_port = 3333 - self.dht_node_port = 4444 - self.first_run = None + if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) - self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") + self.db_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrynet") + self.lbrycrdd_path = "lbrycrdd.exe" + if wallet_type == "lbrycrd": + self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") + else: + self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbryum") elif sys.platform == "darwin": self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + self.db_dir = user_data_dir("LBRY") + self.lbrycrdd_path = "./lbrycrdd" if wallet_type == "lbrycrd": self.wallet_dir = user_data_dir("lbrycrd") else: self.wallet_dir = user_data_dir("LBRY") else: self.download_directory = os.getcwd() + self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + self.lbrycrdd_path = "./lbrycrdd" if wallet_type == "lbrycrd": self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") else: self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") - self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') - self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.wallet_user = None - self.wallet_password = None - self.sd_identifier = StreamDescriptorIdentifier() - self.stream_info_manager = TempLBRYFileMetadataManager() - self.wallet_rpc_port = 8332 - self.downloads = [] - self.stream_frames = [] - self.default_blob_data_payment_rate = MIN_BLOB_DATA_PAYMENT_RATE - self.use_upnp = True - self.start_lbrycrdd = True - if os.name == "nt": - self.lbrycrdd_path = "lbrycrdd.exe" - else: - self.lbrycrdd_path = "./lbrycrdd" - self.delete_blobs_on_remove = True - self.blob_request_payment_rate_manager = None - self.lbry_file_metadata_manager = None - self.lbry_file_manager = None - self.settings = LBRYSettings(self.db_dir) - self.wallet_type = wallet_type - self.check_for_updates = check_for_updates - self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") + self.created_data_dir = False if not os.path.exists(self.db_dir): os.mkdir(self.db_dir) self.created_data_dir = True - self.session_settings = None - self.data_rate = MIN_BLOB_DATA_PAYMENT_RATE - self.max_key_fee = DEFAULT_MAX_KEY_FEE - self.max_search_results = DEFAULT_MAX_SEARCH_RESULTS - self.startup_status = STARTUP_STAGES[0] - self.startup_message = None - self.announced_startup = False - self.search_timeout = 3.0 - self.query_handlers = {} + + self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") + self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") + self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') + self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.wallet_user = None + self.wallet_password = None + + self.sd_identifier = StreamDescriptorIdentifier() + self.stream_info_manager = TempLBRYFileMetadataManager() + self.settings = LBRYSettings(self.db_dir) + self.blob_request_payment_rate_manager = None + self.lbry_file_metadata_manager = None + self.lbry_file_manager = None + + #defaults for settings otherwise loaded from daemon_settings.json self.default_settings = { 'run_on_startup': False, 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, - 'max_key_fee': 10.0, + 'max_key_fee': DEFAULT_MAX_KEY_FEE, 'default_download_directory': self.download_directory, 'max_upload': 0.0, 'max_download': 0.0, - 'upload_log': True + 'upload_log': True, + 'search_timeout': 3.0, + 'max_search_results': DEFAULT_MAX_SEARCH_RESULTS, + 'wallet_type': wallet_type, + 'delete_blobs_on_remove': True, + 'peer_port': 3333, + 'dht_node_port': 4444, + 'use_upnp': True, + 'start_lbrycrdd': True, } - self.ui_version = ui_version_info.replace('\n', '') + if os.path.isfile(self.daemon_conf): + #load given settings, set missing settings to defaults + temp_settings = self.default_settings + f = open(self.daemon_conf, "r") + s = json.loads(f.read()) + f.close() + for k in temp_settings.keys(): + if k in s.keys(): + if type(temp_settings[k]) == type(s[k]): + temp_settings[k] = s[k] + self.__dict__[k] = s[k] + f = open(self.daemon_conf, "w") + f.write(json.dumps(temp_settings)) + f.close() + else: + log.info("Writing default settings: " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.default_settings)) + f.close() + for k in self.default_settings.keys(): + self.__dict__[k] = self.default_settings[k] def render(self, request): request.content.seek(0, 0) @@ -875,6 +897,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): try: if self.startup_status[0] == 'loading_wallet': r['status_message'] = r['status_message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") + r['progress'] = self.session.wallet.catchup_progress except: pass log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index fe5c0ea2c..ada6fa83f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -144,7 +144,7 @@ def start(): return defer.succeed([root, ui_version]) def setupapi(root, wallet, ui_version): - daemon = LBRYDaemon(wallet, "False", ui_version) + daemon = LBRYDaemon(ui_version, wallet_type=wallet) root.putChild(API_ADDRESS, daemon) reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) return daemon.setup() From 925e106c438fad61dc0ddba09b55f01a3192f4cc Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Sun, 10 Apr 2016 22:33:44 -0400 Subject: [PATCH 117/462] add ubuntu package scripts, lbry:// uri handler --- packaging/ubuntu/README.md | 5 ++ packaging/ubuntu/lbry-init.conf | 11 +++++ packaging/ubuntu/lbry-uri-handler | 20 ++++++++ packaging/ubuntu/lbry.desktop | 21 ++++++++ packaging/ubuntu/ubuntu_package_setup.sh | 61 ++++++++++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 packaging/ubuntu/README.md create mode 100644 packaging/ubuntu/lbry-init.conf create mode 100755 packaging/ubuntu/lbry-uri-handler create mode 100644 packaging/ubuntu/lbry.desktop create mode 100755 packaging/ubuntu/ubuntu_package_setup.sh diff --git a/packaging/ubuntu/README.md b/packaging/ubuntu/README.md new file mode 100644 index 000000000..d1a3d81f8 --- /dev/null +++ b/packaging/ubuntu/README.md @@ -0,0 +1,5 @@ +# package scripts + +How to build LBRY packages. + +For best results, run on a fresh image. diff --git a/packaging/ubuntu/lbry-init.conf b/packaging/ubuntu/lbry-init.conf new file mode 100644 index 000000000..c1e192deb --- /dev/null +++ b/packaging/ubuntu/lbry-init.conf @@ -0,0 +1,11 @@ +description "LBRY Daemon" + +#start on (local-filesystems and net-device-up IFACE=eth0) +stop on runlevel [016] + +#expect fork + +respawn +respawn limit 5 20 + +exec /usr/share/python/lbrynet/bin/lbrynet-daemon diff --git a/packaging/ubuntu/lbry-uri-handler b/packaging/ubuntu/lbry-uri-handler new file mode 100755 index 000000000..17d7910ff --- /dev/null +++ b/packaging/ubuntu/lbry-uri-handler @@ -0,0 +1,20 @@ +#!/bin/bash + +set -euo pipefail + +urlencode() { + local LANG=C + local length="${#1}" + for (( i = 0; i < length; i++ )); do + local c="${1:i:1}" + case $c in + [a-zA-Z0-9.~_-]) printf "$c" ;; + *) printf '%%%02X' "'$c" ;; + esac + done +} + +ARG=${1:-} +NAME=$(urlencode "$(echo "$ARG" | cut -c 8-)") + +/usr/bin/xdg-open http://localhost:5279/view?name="$NAME" diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop new file mode 100644 index 000000000..7e5363782 --- /dev/null +++ b/packaging/ubuntu/lbry.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Version=1.0 +Name=LBRY +# Only KDE 4 seems to use GenericName, so we reuse the KDE strings. +# From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413. +GenericName=Filesharing +# Gnome and KDE 3 uses Comment. +Comment=Stream. Share. Earn. +#Exec=/usr/bin/xdg-open http://localhost:5279/view?name=%U +Exec=/usr/share/python/lbrynet/bin/lbry-uri-handler %U +Terminal=false +Icon= +Type=Application +Categories=Network;Internet;Filesharing +MimeType=x-scheme-handler/lbry; +X-Ayatana-Desktop-Shortcuts=NewWindow + +[NewWindow Shortcut Group] +Name=New Window +Exec=/home/grin/lbryhandler/testlbry newwindow %U +TargetEnvironment=Unity diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh new file mode 100755 index 000000000..27b13fd1b --- /dev/null +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Tested on fresh Ubuntu 14.04 install. + +# wget https://raw.githubusercontent.com/lbryio/lbry/master/packaging/ubuntu/ubuntu_package_setup.sh +# bash ubuntu_package_setup.sh master + +set -euo pipefail + +BRANCH=${1:-master} + +# get the required OS packages +sudo add-apt-repository -y ppa:spotify-jyrki/dh-virtualenv +sudo apt-get update +sudo apt-get install -y build-essential git python-dev libffi-dev libssl-dev libgmp3-dev dh-virtualenv debhelper + +# need a modern version of pip (more modern than ubuntu default) +wget https://bootstrap.pypa.io/get-pip.py +sudo python get-pip.py +rm get-pip.py +sudo pip install make-deb + +# check out LBRY +git clone https://github.com/lbryio/lbry.git --branch "$BRANCH" + +# build packages +( + cd lbry + make-deb + dpkg-buildpackage -us -uc +) + + +### insert our extra files + +# extract .deb +PACKAGE="$(ls | grep '.deb')" +ar vx "$PACKAGE" +mkdir control data +tar -xvzf control.tar.gz --directory control +tar -xvJf data.tar.xz --directory data + +# add files +function addfile() { + FILE="$1" + TARGET="$2" + mkdir -p "$(dirname "data/$TARGET")" + cp "$FILE" "data/$TARGET" + echo "$(md5sum "data/$TARGET" | cut -d' ' -f1) $TARGET" >> control/md5sums +} +PACKAGING_DIR='lbry/packaging/ubuntu' +addfile "$PACKAGING_DIR/lbry-uri-handler" usr/share/python/lbrynet/bin/lbry-uri-handler +addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop +#addfile lbry/packaging/ubuntu/lbry-init.conf etc/init/lbry.conf + +# repackage .deb +tar -cvzf control.tar.gz -C control . +tar -cvJf data.tar.xz -C data . +ar r "$PACKAGE" debian-binary control.tar.gz data.tar.xz + +# TODO: we can append to data.tar instead of extracting it all and recompressing From 2ac97736ce289ee3324149fb73477e95b28b6dfc Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 12 Apr 2016 18:50:47 -0400 Subject: [PATCH 118/462] bunch of changes, ready to roll --- packaging/ubuntu/lbry | 32 ++++++++++++++++++++++++ packaging/ubuntu/lbry-uri-handler | 20 --------------- packaging/ubuntu/lbry.desktop | 12 +++------ packaging/ubuntu/ubuntu_package_setup.sh | 8 +++++- 4 files changed, 42 insertions(+), 30 deletions(-) create mode 100755 packaging/ubuntu/lbry delete mode 100755 packaging/ubuntu/lbry-uri-handler diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry new file mode 100755 index 000000000..4ad69c838 --- /dev/null +++ b/packaging/ubuntu/lbry @@ -0,0 +1,32 @@ +#!/bin/bash + +set -euo pipefail + +urlencode() { + local LANG=C + local length="${#1}" + for (( i = 0; i < length; i++ )); do + local c="${1:i:1}" + case $c in + [a-zA-Z0-9.~_-]) printf "$c" ;; + *) printf '%%%02X' "'$c" ;; + esac + done +} + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +if [ -z "$(pgrep lbrynet-daemon)" ]; then + echo "running lbrynet-daemon..." + $DIR/lbrynet-daemon 2>&1 >> "$HOME/.lbrynet/daemon.log" & + sleep 3 # let the daemon load before connecting +fi + +ARG=${1:-} + +if [ -z "$ARG" ]; then + URL="" +else + URL="view?name=$(urlencode "$(echo "$ARG" | cut -c 8-)")" +fi + +/usr/bin/xdg-open "http://localhost:5279/$URL" diff --git a/packaging/ubuntu/lbry-uri-handler b/packaging/ubuntu/lbry-uri-handler deleted file mode 100755 index 17d7910ff..000000000 --- a/packaging/ubuntu/lbry-uri-handler +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -urlencode() { - local LANG=C - local length="${#1}" - for (( i = 0; i < length; i++ )); do - local c="${1:i:1}" - case $c in - [a-zA-Z0-9.~_-]) printf "$c" ;; - *) printf '%%%02X' "'$c" ;; - esac - done -} - -ARG=${1:-} -NAME=$(urlencode "$(echo "$ARG" | cut -c 8-)") - -/usr/bin/xdg-open http://localhost:5279/view?name="$NAME" diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index 7e5363782..33d240950 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=1.0 +Version=0.2 Name=LBRY # Only KDE 4 seems to use GenericName, so we reuse the KDE strings. # From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413. @@ -7,15 +7,9 @@ GenericName=Filesharing # Gnome and KDE 3 uses Comment. Comment=Stream. Share. Earn. #Exec=/usr/bin/xdg-open http://localhost:5279/view?name=%U -Exec=/usr/share/python/lbrynet/bin/lbry-uri-handler %U +Exec=/usr/share/python/lbrynet/bin/lbry %U Terminal=false -Icon= +Icon=/usr/share/python/lbrynet/lbrynet/lbrynet_gui/lbry-dark-icon.ico Type=Application Categories=Network;Internet;Filesharing MimeType=x-scheme-handler/lbry; -X-Ayatana-Desktop-Shortcuts=NewWindow - -[NewWindow Shortcut Group] -Name=New Window -Exec=/home/grin/lbryhandler/testlbry newwindow %U -TargetEnvironment=Unity diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 27b13fd1b..4be10c8a7 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -9,6 +9,10 @@ set -euo pipefail BRANCH=${1:-master} +BUILD_DIR="lbry-build-$(date +%Y%m%d-%H%M%S)" +mkdir "$BUILD_DIR" +cd "$BUILD_DIR" + # get the required OS packages sudo add-apt-repository -y ppa:spotify-jyrki/dh-virtualenv sudo apt-get update @@ -49,13 +53,15 @@ function addfile() { echo "$(md5sum "data/$TARGET" | cut -d' ' -f1) $TARGET" >> control/md5sums } PACKAGING_DIR='lbry/packaging/ubuntu' -addfile "$PACKAGING_DIR/lbry-uri-handler" usr/share/python/lbrynet/bin/lbry-uri-handler +addfile "$PACKAGING_DIR/lbry" usr/share/python/lbrynet/bin/lbry addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop #addfile lbry/packaging/ubuntu/lbry-init.conf etc/init/lbry.conf # repackage .deb +sudo chown -R root:root control data tar -cvzf control.tar.gz -C control . tar -cvJf data.tar.xz -C data . +sudo chown root:root debian-binary control.tar.gz data.tar.xz ar r "$PACKAGE" debian-binary control.tar.gz data.tar.xz # TODO: we can append to data.tar instead of extracting it all and recompressing From b9d5d1041cb2f57c4d7620fe3735c034d40284f3 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 12 Apr 2016 19:27:26 -0400 Subject: [PATCH 119/462] bump version, better icon --- lbrynet/lbrynet_gui/lbry.png | Bin 0 -> 10651 bytes packaging/ubuntu/lbry | 2 +- packaging/ubuntu/lbry.desktop | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 lbrynet/lbrynet_gui/lbry.png diff --git a/lbrynet/lbrynet_gui/lbry.png b/lbrynet/lbrynet_gui/lbry.png new file mode 100644 index 0000000000000000000000000000000000000000..16c7c4683affa3e73c5bcf4fbd0b6bbb0778f275 GIT binary patch literal 10651 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 lz#oNlK}QRNn5{lM7K&1 >> "$HOME/.lbrynet/daemon.log" & + $DIR/lbrynet-daemon --branch=settings-page & sleep 3 # let the daemon load before connecting fi diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index 33d240950..f034b97c6 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.2 +Version=0.2.1 Name=LBRY # Only KDE 4 seems to use GenericName, so we reuse the KDE strings. # From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413. @@ -9,7 +9,7 @@ Comment=Stream. Share. Earn. #Exec=/usr/bin/xdg-open http://localhost:5279/view?name=%U Exec=/usr/share/python/lbrynet/bin/lbry %U Terminal=false -Icon=/usr/share/python/lbrynet/lbrynet/lbrynet_gui/lbry-dark-icon.ico +Icon=/usr/share/python/lbrynet/lbrynet/lbrynet_gui/lbry.png Type=Application Categories=Network;Internet;Filesharing MimeType=x-scheme-handler/lbry; From b32d096be2185ef95b3c8d63b7a9955c9921e63a Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 12 Apr 2016 20:44:47 -0400 Subject: [PATCH 120/462] add png to setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 877377190..e4a9432f0 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', - 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf'] + 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf', 'lbry.png'] gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] setup(name='lbrynet', From df99d06d11066c89471115c4d6fa566a6e63bafe Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 13 Apr 2016 02:35:31 -0400 Subject: [PATCH 121/462] fix links in check_for_new_version and log version info -also handle situation where function is given a null argument --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 31 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 16ff012e4..56d790626 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -231,7 +231,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: request.setHeader("Access-Control-Allow-Origin", "*") request.setHeader("content-type", "text/json") - if args == [{}]: + if args == [{}] or args == [None]: d = defer.maybeDeferred(function) else: d = defer.maybeDeferred(function, *args) @@ -1356,23 +1356,38 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._render_response(self.fetcher.verbose, OK_CODE) def jsonrpc_check_for_new_version(self): + """ + Checks local version against versions in __init__.py and version.py in the lbrynet and lbryum repos + Returns true/false, true meaning that there is a new version available + """ + def _get_lbryum_version(): - r = urlopen("https://rawgit.com/lbryio/lbryum/master/lib/version.py").read().split('\n') + r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') version = next(line.split("=")[1].split("#")[0].replace(" ", "") for line in r if "ELECTRUM_VERSION" in line) version = version.replace("'", "") + log.info("remote lbryum " + str(version) + " > local lbryum " + str(lbryum_version) + " = " + str(version > lbryum_version)) return version def _get_lbrynet_version(): - r = urlopen("https://rawgit.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') + r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') vs = next(i for i in r if 'version =' in i).split("=")[1].replace(" ", "") vt = tuple(int(x) for x in vs[1:-1].split(',')) - return ".".join([str(x) for x in vt]) + vr = ".".join([str(x) for x in vt]) + log.info("remote lbrynet " + str(vr) + " > local lbrynet " + str(lbrynet_version) + " = " + str(vr > lbrynet_version)) + return vr - if (lbrynet_version >= _get_lbrynet_version()) and (lbryum_version >= _get_lbryum_version()): - return self._render_response(False, OK_CODE) - else: - return self._render_response(True, OK_CODE) + def _check_version(): + git_lbrynet = _get_lbrynet_version() + git_lbryum = _get_lbryum_version() + if (lbrynet_version >= git_lbrynet) and (lbryum_version >= git_lbryum): + log.info("[" + str(datetime.now()) + "] Up to date") + return self._render_response(False, OK_CODE) + else: + log.info("[" + str(datetime.now()) + "] Updates available") + return self._render_response(True, OK_CODE) + + return _check_version() def jsonrpc___dir__(self): return ['is_running', 'get_settings', 'set_settings', 'start_fetcher', 'stop_fetcher', 'fetcher_status', From cdd44af043e0108a90bf22d17b0ebabf776fb5c0 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 13 Apr 2016 02:39:50 -0400 Subject: [PATCH 122/462] fix links in LBRYDaemonControl https was failing --- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index ada6fa83f..86c2699e7 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -78,11 +78,11 @@ def start(): if args.branch == "HEAD": GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep %s | cut -f 1" % args.branch - DIST_URL = "https://rawgit.com/lbryio/lbry-web-ui/master/dist.zip" + DIST_URL = "http://rawgit.com/lbryio/lbry-web-ui/master/dist.zip" else: log.info("Using UI branch: " + args.branch) GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep refs/heads/%s | cut -f 1" % args.branch - DIST_URL = "https://rawgit.com/lbryio/lbry-web-ui/%s/dist.zip" % args.branch + DIST_URL = "http://rawgit.com/lbryio/lbry-web-ui/%s/dist.zip" % args.branch def getui(ui_dir=None): if ui_dir: From 2000a1b33474d9468e9ffd1339572d041a76584b Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 13 Apr 2016 02:52:41 -0400 Subject: [PATCH 123/462] replace rawgit with raw.githubusercontent --- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 86c2699e7..e509c7c81 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -78,11 +78,11 @@ def start(): if args.branch == "HEAD": GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep %s | cut -f 1" % args.branch - DIST_URL = "http://rawgit.com/lbryio/lbry-web-ui/master/dist.zip" + DIST_URL = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/master/dist.zip" else: log.info("Using UI branch: " + args.branch) GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep refs/heads/%s | cut -f 1" % args.branch - DIST_URL = "http://rawgit.com/lbryio/lbry-web-ui/%s/dist.zip" % args.branch + DIST_URL = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % args.branch def getui(ui_dir=None): if ui_dir: From 65a0583c85492b2c5118ad98f2d7a5eab1a7e887 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 13 Apr 2016 14:47:34 -0400 Subject: [PATCH 124/462] add help function and documentation for functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -calling help() will return the list of functions -setting the ‘function’ param in the help function will return the doc string for that function -setting the ‘callable_on_startup’ in the help function will return the list of functions callable during the startup sequence --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 294 +++++++++++++++++++++------ 1 file changed, 228 insertions(+), 66 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 56d790626..5914048cd 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -71,6 +71,10 @@ STARTUP_STAGES = [ ('started', 'Started lbrynet') ] +ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run', + 'get_time_behind_blockchain', 'stop', + 'daemon_status', 'get_start_notice', + 'version', 'check_for_new_version'] BAD_REQUEST = 400 NOT_FOUND = 404 @@ -79,11 +83,6 @@ OK_CODE = 200 # TODO alert if your copy of a lbry file is out of date with the name record -class Bunch: - def __init__(self, params): - self.__dict__.update(params) - - class LBRYDaemon(jsonrpc.JSONRPC): """ LBRYnet daemon, a jsonrpc interface to lbry functions @@ -218,10 +217,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): # versions... if not self.announced_startup: - if functionPath not in ['is_running', 'is_first_run', - 'get_time_behind_blockchain', 'stop', - 'daemon_status', 'get_start_notice', - 'version', 'check_for_new_version']: + if functionPath not in ALLOWED_DURING_STARTUP: return server.failure try: @@ -878,7 +874,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_is_running(self): """ - Returns true if daemon completed startup, otherwise returns false + Check if lbrynet daemon is running + + Args: + None + Returns: true if daemon completed startup, otherwise false """ log.info("[" + str(datetime.now()) + "] is_running: " + str(self.announced_startup)) @@ -890,7 +890,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_daemon_status(self): """ - Returns {'status_message': startup status message, 'status_code': status_code} + Get lbrynet daemon status information + + Args: + None + Returns: + 'status_message': startup status message + 'status_code': status_code + if status_code is 'loading_wallet', also contains key 'progress': blockchain catchup progress """ r = {'status_code': self.startup_status[0], 'status_message': self.startup_status[1]} @@ -905,7 +912,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_is_first_run(self): """ - Get True/False if can be determined, if wallet still is being set up returns None + Check if this is the first time lbrynet daemon has been run + + Args: + None + Returns: + True if first run, otherwise False """ log.info("[" + str(datetime.now()) + "] Check if is first run") @@ -920,9 +932,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_get_start_notice(self): """ - Get any special message to be displayed at startup, such as a first run notice - """ + Get special message to be displayed at startup + Args: + None + Returns: + Startup message, such as first run notification + """ log.info("[" + str(datetime.now()) + "] Get startup notice") @@ -936,11 +952,18 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_version(self): """ Get lbry version information + + Args: + None + Returns: + 'lbrynet version': lbrynet version + 'lbryum version': lbryum version + 'ui_version': commit hash of ui """ msg = { - "lbrynet version: ": lbrynet_version, - "lbryum version: ": lbryum_version, + "lbrynet_version: ": lbrynet_version, + "lbryum_version: ": lbryum_version, "ui_version": self.ui_version, } @@ -949,11 +972,26 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_get_settings(self): """ - Get LBRY payment settings + Get lbrynet daemon settings - @return {'data_rate': float, 'max_key_fee': float, 'max_upload': float (0.0 for unlimited), - 'default_download_directory': string, 'run_on_startup': bool, - 'max_download': float (0.0 for unlimited)} + Args: + None + Returns: + 'run_on_startup': bool, + 'data_rate': float, + 'max_key_fee': float, + 'default_download_directory': string, + 'max_upload': float, 0.0 for unlimited + 'max_download': float, 0.0 for unlimited + 'upload_log': bool, + 'search_timeout': float, + 'max_search_results': int, + 'wallet_type': string, + 'delete_blobs_on_remove': bool, + 'peer_port': int, + 'dht_node_port': int, + 'use_upnp': bool, + 'start_lbrycrdd': bool, """ log.info("[" + str(datetime.now()) + "] Get daemon settings") @@ -961,11 +999,26 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_set_settings(self, p): """ - Set LBRY payment settings + Set lbrynet daemon settings - @param settings: {'data_rate': float, 'max_key_fee': float, 'max_upload': float (0.0 for unlimited), - 'default_download_directory': string, 'run_on_startup': bool, - 'max_download': float (0.0 for unlimited)} + Args: + 'run_on_startup': bool, + 'data_rate': float, + 'max_key_fee': float, + 'default_download_directory': string, + 'max_upload': float, 0.0 for unlimited + 'max_download': float, 0.0 for unlimited + 'upload_log': bool, + 'search_timeout': float, + 'max_search_results': int, + 'wallet_type': string, + 'delete_blobs_on_remove': bool, + 'peer_port': int, + 'dht_node_port': int, + 'use_upnp': bool, + 'start_lbrycrdd': bool, + Returns: + settings dict """ def _log_settings_change(params): @@ -977,11 +1030,43 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d + def jsonrpc_help(self, p=None): + """ + Function to retrieve docstring for API function + + Args: + optional 'function': function to retrieve documentation for + optional 'callable_during_startup': + Returns: + if given a function, returns given documentation + if given callable_during_startup flag, returns list of functions callable during the startup sequence + if no params are given, returns the list of callable functions + """ + + if not p: + return self._render_response(['is_running', 'get_settings', 'set_settings', 'start_fetcher', 'stop_fetcher', + 'fetcher_status', 'get_balance', 'stop', 'get_lbry_files', 'resolve_name', + 'get', 'search_nametrie', 'delete_lbry_file', 'check', 'publish', + 'abandon_name', 'get_name_claims', 'get_time_behind_blockchain', + 'get_new_address', 'toggle_fetcher_verbose', 'check_for_new_version'], + OK_CODE) + elif 'callable_during_start' in p.keys(): + return self._render_response(ALLOWED_DURING_STARTUP, OK_CODE) + elif 'function' in p.keys(): + func_path = p['function'] + function = self._getFunction(func_path) + return self._render_response(function.__doc__, OK_CODE) + else: + return self._render_response(self.jsonrpc_help.__doc__, OK_CODE) + def jsonrpc_start_fetcher(self): """ - Start automatically downloading new name claims as they happen + Start automatically downloading new name claims as they occur (off by default) - @return: confirmation message + Args: + None + Returns: + confirmation message """ self.fetcher.start() @@ -991,9 +1076,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_stop_fetcher(self): """ - Stop automatically downloading new name claims as they happen + Stop automatically downloading new name claims as they occur - @return: confirmation message + Args: + None + Returns: + confirmation message """ self.fetcher.stop() @@ -1004,7 +1092,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Get fetcher status - @return: True/False + Args: + None + Returns: + True/False """ log.info("[" + str(datetime.now()) + "] Get fetcher status") @@ -1012,9 +1103,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_get_balance(self): """ - Get LBC balance + Get balance - @return: balance + Args: + None + Returns: + balance, float """ log.info("[" + str(datetime.now()) + "] Get balance") @@ -1024,7 +1118,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Stop lbrynet-daemon - @return: shutdown message + Args: + None + Returns: + shutdown message """ def _disp_shutdown(): @@ -1040,7 +1137,19 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Get LBRY files - @return: List of managed LBRY files + Args: + None + Returns: + List of lbry files: + 'completed': bool + 'file_name': string + 'key': hex string + 'points_paid': float + 'stopped': bool + 'stream_hash': base 58 string + 'stream_name': string + 'suggested_file_name': string + 'upload_allowed': bool """ r = [] @@ -1065,10 +1174,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Resolve stream info from a LBRY uri - @param: {'name': name to look up} - @return: info for name claim + Args: + 'name': name to look up, string, do not include lbry:// prefix + Returns: + metadata from name claim """ - params = Bunch(p) + + if 'name' in p.keys(): + name = p['name'] + else: + return self._render_response(None, BAD_REQUEST) def _disp(info): stream_hash = info['stream_hash'] @@ -1079,7 +1194,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return self._render_response(info, OK_CODE) - d = self._resolve_name(params.name) + d = self._resolve_name(name) d.addCallbacks(_disp, lambda _: server.failure) d.callback(None) return d @@ -1088,8 +1203,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Download stream from a LBRY uri - @param: name, optional: download_directory - @return: {'stream_hash': hex string, 'path': path of download} + Args: + 'name': name to download, string + optional 'download_directory': path to directory where file will be saved, string + Returns: + 'stream_hash': hex string + 'path': path of download """ if 'timeout' not in p.keys(): @@ -1145,13 +1264,18 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_search_nametrie(self, p): """ - Search the nametrie for claims beginning with search + Search the nametrie for claims beginning with search (yes, this is a dumb search, it'll be made better) - @param {'search': search string} - @return: List of search results + Args: + 'search': search query, string + Returns: + List of search results """ - params = Bunch(p) + if 'search' in p.keys(): + search = p['search'] + else: + return self._render_response(None, BAD_REQUEST) def _clean(n): t = [] @@ -1187,10 +1311,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): # log.info(str(t)) return consolidated_results - log.info('[' + str(datetime.now()) + '] Search nametrie: ' + params.search) + log.info('[' + str(datetime.now()) + '] Search nametrie: ' + search) d = self.session.wallet.get_nametrie() - d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(params.search) and 'txid' in claim]) + d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) d.addCallback(lambda claims: claims[:self.max_search_results]) d.addCallback(resolve_claims) d.addCallback(_clean) @@ -1203,8 +1327,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Delete a lbry file - @param {'file_name': string} - @return: confirmation message + Args: + 'file_name': downloaded file name, string + Returns: + confirmation message """ def _disp(file_name): @@ -1222,8 +1348,20 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Make a new name claim - @param: - @return: + Args: + 'name': name to be claimed, string + 'file_path': path to file to be associated with name, string + 'bid': amount of credits to commit in this claim, float + optional 'author': author, string + optional 'title': title, description + optional 'description': content description, string + optional 'thumbnail': thumbnail image url + optional 'key_fee': key fee to be paid to publisher, float (default 0.0) + optional 'key_fee_address': address for key fee to be sent to, string (defaults on new address) + optional 'content_license': content license string + optional 'sources': alternative sources dict, keys 'lbry_sd_hash', 'btih', 'url' + Returns: + Confirmation message """ metadata_fields = ["name", "file_path", "bid", "author", "title", @@ -1255,19 +1393,25 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_abandon_name(self, p): """ - Abandon and reclaim credits from a name claim + Abandon a name and reclaim credits from the claim - @param: {'txid': string} - @return: txid + Args: + 'txid': txid of claim, string + Return: + Confirmation message """ - params = Bunch(p) + + if 'txid' in p.keys(): + txid = p['txid'] + else: + return server.failure def _disp(x): log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + str(x)) return self._render_response(x, OK_CODE) d = defer.Deferred() - d.addCallback(lambda _: self.session.wallet.abandon_name(params.txid)) + d.addCallback(lambda _: self.session.wallet.abandon_name(txid)) d.addCallback(_disp) d.callback(None) @@ -1275,10 +1419,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_get_name_claims(self): """ - Get name claims + Get my name claims - @return: list of name claims + Args: + None + Returns + list of name claims """ + def _clean(claims): for c in claims: for k in c.keys(): @@ -1294,9 +1442,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_get_time_behind_blockchain(self): """ - Get time behind blockchain + Get number of blocks behind the blockchain - @return: time behind blockchain + Args: + None + Returns: + number of blocks behind blockchain, int """ def _get_time_behind(): @@ -1316,8 +1467,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ Generate a new wallet address - @return: new wallet address + Args: + None + Returns: + new wallet address, base 58 string """ + def _disp(address): log.info("[" + str(datetime.now()) + "] Got new wallet address: " + address) return defer.succeed(address) @@ -1348,6 +1503,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): # return d def jsonrpc_toggle_fetcher_verbose(self): + """ + Toggle fetcher verbose mode + + Args: + None + Returns: + Fetcher verbose status, bool + """ + if self.fetcher.verbose: self.fetcher.verbose = False else: @@ -1358,7 +1522,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_check_for_new_version(self): """ Checks local version against versions in __init__.py and version.py in the lbrynet and lbryum repos - Returns true/false, true meaning that there is a new version available + + Args: + None + Returns: + true/false, true meaning that there is a new version available """ def _get_lbryum_version(): @@ -1389,12 +1557,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): return _check_version() - def jsonrpc___dir__(self): - return ['is_running', 'get_settings', 'set_settings', 'start_fetcher', 'stop_fetcher', 'fetcher_status', - 'get_balance', 'stop', 'get_lbry_files', 'resolve_name', 'get', 'search_nametrie', - 'delete_lbry_file', 'check', 'publish', 'abandon_name', 'get_name_claims', - 'get_time_behind_blockchain', 'get_new_address', 'toggle_fetcher_verbose', 'check_for_new_version'] - class LBRYDaemonCommandHandler(object): def __init__(self, command): From 87e09efff57e22ded8ae6399510eaacf95b75428 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 13 Apr 2016 20:52:51 -0400 Subject: [PATCH 125/462] update lbryum version in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e4a9432f0..1863dad82 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', - 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] + 'leveldb', 'lbryum>=2.6.0.1', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf', 'lbry.png'] From 8b966c61d8f6b807d230bcec1ba42e3988fb12d0 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 13 Apr 2016 22:07:27 -0400 Subject: [PATCH 126/462] check version during startup --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 96 ++++++++++++++-------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 5914048cd..537f667de 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -99,6 +99,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.announced_startup = False self.query_handlers = {} self.ui_version = ui_version_info.replace('\n', '') + self.git_lbrynet_version = None + self.git_lbryum_version = None self.wallet_type = wallet_type self.session_settings = None self.first_run = None @@ -264,23 +266,31 @@ class LBRYDaemon(jsonrpc.JSONRPC): def setup(self): def _log_starting_vals(): def _get_lbry_files_json(): - r = [] - for f in self.lbry_file_manager.lbry_files: - if f.key: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), - 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, - 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, - 'upload_allowed': f.upload_allowed} - - else: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, - 'points_paid': f.points_paid, - 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, - 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed} - - r.append(t) + r = self._get_lbry_files() return json.dumps(r) + def _get_lbryum_version(): + r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') + version = next(line.split("=")[1].split("#")[0].replace(" ", "") + for line in r if "ELECTRUM_VERSION" in line) + version = version.replace("'", "") + log.info("remote lbryum " + str(version) + " > local lbryum " + str(lbryum_version) + " = " + str( + version > lbryum_version)) + return version + + def _get_lbrynet_version(): + r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split( + '\n') + vs = next(i for i in r if 'version =' in i).split("=")[1].replace(" ", "") + vt = tuple(int(x) for x in vs[1:-1].split(',')) + vr = ".".join([str(x) for x in vt]) + log.info("remote lbrynet " + str(vr) + " > local lbrynet " + str(lbrynet_version) + " = " + str( + vr > lbrynet_version)) + return vr + + self.git_lbrynet_version = _get_lbrynet_version() + self.git_lbryum_version = _get_lbryum_version() + log.info("LBRY Files: " + _get_lbry_files_json()) log.info("Starting balance: " + str(self.session.wallet.wallet_balance)) @@ -869,6 +879,24 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d + def _get_lbry_files(self): + r = [] + for f in self.lbry_file_manager.lbry_files: + if f.key: + t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), + 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed} + + else: + t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, + 'points_paid': f.points_paid, + 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, + 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed} + + r.append(t) + return r + def _render_response(self, result, code): return defer.succeed({'result': result, 'code': code}) @@ -959,12 +987,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'lbrynet version': lbrynet version 'lbryum version': lbryum version 'ui_version': commit hash of ui + "remote_lbrynet": most recent lbrynet version available from github + "remote_lbryum": most recent lbryum version available from github """ msg = { "lbrynet_version: ": lbrynet_version, "lbryum_version: ": lbryum_version, "ui_version": self.ui_version, + "remote_lbrynet": self.git_lbrynet_version, + "remote_lbryum": self.git_lbryum_version, } log.info("[" + str(datetime.now()) + "] Get version info: " + json.dumps(msg)) @@ -1152,21 +1184,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'upload_allowed': bool """ - r = [] - for f in self.lbry_file_manager.lbry_files: - if f.key: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), - 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, - 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, - 'upload_allowed': f.upload_allowed} - - else: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, 'points_paid': f.points_paid, - 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, - 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed} - - r.append(json.dumps(t)) - + r = self._get_lbry_files() log.info("[" + str(datetime.now()) + "] Get LBRY files") return self._render_response(r, OK_CODE) @@ -1529,26 +1547,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): true/false, true meaning that there is a new version available """ - def _get_lbryum_version(): - r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') - version = next(line.split("=")[1].split("#")[0].replace(" ", "") - for line in r if "ELECTRUM_VERSION" in line) - version = version.replace("'", "") - log.info("remote lbryum " + str(version) + " > local lbryum " + str(lbryum_version) + " = " + str(version > lbryum_version)) - return version - def _get_lbrynet_version(): - r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') - vs = next(i for i in r if 'version =' in i).split("=")[1].replace(" ", "") - vt = tuple(int(x) for x in vs[1:-1].split(',')) - vr = ".".join([str(x) for x in vt]) - log.info("remote lbrynet " + str(vr) + " > local lbrynet " + str(lbrynet_version) + " = " + str(vr > lbrynet_version)) - return vr def _check_version(): - git_lbrynet = _get_lbrynet_version() - git_lbryum = _get_lbryum_version() - if (lbrynet_version >= git_lbrynet) and (lbryum_version >= git_lbryum): + if (lbrynet_version >= self.git_lbrynet_version) and (lbryum_version >= self.git_lbryum_version): log.info("[" + str(datetime.now()) + "] Up to date") return self._render_response(False, OK_CODE) else: From 6b9f1d519e82fde3f3ae60b6efce979ccd001dbb Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 13 Apr 2016 23:10:17 -0400 Subject: [PATCH 127/462] remove null handling used to fix a now resolved bug, update daemon_status outputs per alex's suggestions --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 537f667de..38f504800 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -229,7 +229,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: request.setHeader("Access-Control-Allow-Origin", "*") request.setHeader("content-type", "text/json") - if args == [{}] or args == [None]: + if args == [{}]: d = defer.maybeDeferred(function) else: d = defer.maybeDeferred(function, *args) @@ -928,13 +928,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): if status_code is 'loading_wallet', also contains key 'progress': blockchain catchup progress """ - r = {'status_code': self.startup_status[0], 'status_message': self.startup_status[1]} - try: - if self.startup_status[0] == 'loading_wallet': - r['status_message'] = r['status_message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") - r['progress'] = self.session.wallet.catchup_progress - except: - pass + r = {'code': self.startup_status[0], 'message': self.startup_status[1], 'progress': None} + if self.startup_status[0] == 'loading_wallet': + r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") + r['progress'] = self.session.wallet.catchup_progress log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) return self._render_response(r, OK_CODE) From 47d897b48d26b1fd18101458b88f0c68e189063d Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 14 Apr 2016 00:29:40 -0400 Subject: [PATCH 128/462] is_lagging indicator and internet connection check -adds is_lagging indicator as a key from daemon_status, which is set to True during a phase of the startup sequence, presently only during loading_wallet if catching up with the blockchain takes longer than a minute. -checks if connected to internet before trying to start --- lbrynet/core/LBRYcrdWallet.py | 14 ++++--- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 +- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 45 ++++++++++++++------- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index c6cb8c4d9..895dbfa49 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -70,6 +70,8 @@ class LBRYWallet(object): self.max_expected_payment_time = datetime.timedelta(minutes=3) self.stopped = True + self.is_lagging = None + self.manage_running = False self._manage_count = 0 self._balance_refresh_time = 3 @@ -996,15 +998,17 @@ class LBRYumWallet(LBRYWallet): self._catch_up_check = None blockchain_caught_d.callback(True) elif remote_height != 0: + self.blocks_behind_alert = remote_height - local_height + if self.blocks_behind_alert > self.max_behind: + self.max_behind = self.blocks_behind_alert + self.catchup_progress = int(100 * (self.blocks_behind_alert / (5 + self.max_behind))) if self._caught_up_counter == 0: alert.info('Catching up to the blockchain...showing blocks left...') if self._caught_up_counter % 30 == 0: - self.blocks_behind_alert = remote_height - local_height - if self.blocks_behind_alert > self.max_behind: - self.max_behind = self.blocks_behind_alert - self.catchup_progress = int(100 * (self.blocks_behind_alert / (5 + self.max_behind))) alert.info('%d...', (remote_height - local_height)) - alert.info("Catching up: " + str(int(100 * (self.blocks_behind_alert / (5 + self.max_behind)))) + "%") + alert.info("Catching up: " + str(self.catchup_progress) + "%") + if self._caught_up_counter >= 600: + self.is_lagging = True self._caught_up_counter += 1 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 38f504800..e0b7c92bb 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -928,10 +928,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): if status_code is 'loading_wallet', also contains key 'progress': blockchain catchup progress """ - r = {'code': self.startup_status[0], 'message': self.startup_status[1], 'progress': None} + r = {'code': self.startup_status[0], 'message': self.startup_status[1], 'progress': None, 'is_lagging': None} if self.startup_status[0] == 'loading_wallet': r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") r['progress'] = self.session.wallet.catchup_progress + r['is_lagging'] = self.session.wallet.is_lagging + log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) return self._render_response(r, OK_CODE) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index e509c7c81..32fc8ec9c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -6,6 +6,8 @@ import os import shutil import webbrowser import sys +import socket + from StringIO import StringIO from zipfile import ZipFile @@ -34,6 +36,16 @@ handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, ba log.addHandler(handler) logging.basicConfig(level=logging.INFO) +REMOTE_SERVER = "www.google.com" + +def test_internet_connection(): + try: + host = socket.gethostbyname(REMOTE_SERVER) + s = socket.create_connection((host, 80), 2) + return True + except: + return False + def stop(): def _disp_shutdown(): @@ -104,6 +116,10 @@ def start(): version_dir = os.path.join(data_dir, "ui_version_history") git_version = subprocess.check_output(GIT_CMD_STRING, shell=True) + if not git_version: + log.info("You should have been notified to install xcode command line tools, once it's installed you can start LBRY") + sys.exit(0) + ui_version_info = git_version if not os.path.isdir(data_dir): @@ -113,15 +129,11 @@ def start(): os.mkdir(version_dir) if not os.path.isfile(os.path.join(version_dir, git_version)): - try: - f = open(os.path.join(version_dir, git_version), "w") - version_message = "[" + str(datetime.now()) + "] Updating UI --> " + git_version - f.write(version_message) - f.close() - log.info(version_message) - except: - log.info("You should have been notified to install xcode command line tools, once it's installed you can start LBRY") - sys.exit(0) + f = open(os.path.join(version_dir, git_version), "w") + version_message = "[" + str(datetime.now()) + "] Updating UI --> " + git_version + f.write(version_message) + f.close() + log.info(version_message) if os.path.isdir(os.path.join(data_dir, "lbry-web-ui")): shutil.rmtree(os.path.join(data_dir, "lbry-web-ui")) @@ -149,9 +161,12 @@ def start(): reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) return daemon.setup() - d = getui(args.ui) - d.addCallback(lambda r: setupserver(r[0], r[1])) - d.addCallback(lambda r: setupapi(r[0], args.wallet, r[1])) - d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) - - reactor.run() \ No newline at end of file + if test_internet_connection(): + d = getui(args.ui) + d.addCallback(lambda r: setupserver(r[0], r[1])) + d.addCallback(lambda r: setupapi(r[0], args.wallet, r[1])) + d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) + reactor.run() + else: + log.info("Not connected to internet, unable to start") + return From 57138d9629c10565322718afb88e0fc735dad403 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 14 Apr 2016 16:50:11 -0400 Subject: [PATCH 129/462] is_lagging message --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index e0b7c92bb..c62d065b2 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -65,7 +65,7 @@ log.addHandler(handler) STARTUP_STAGES = [ ('initializing', 'Initializing...'), ('loading_db', 'Loading databases...'), - ('loading_wallet', 'Catching up with blockchain... %s'), + ('loading_wallet', 'Catching up with the blockchain... %s'), ('loading_file_manager', 'Setting up file manager'), ('loading_server', 'Starting lbrynet'), ('started', 'Started lbrynet') @@ -930,9 +930,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): r = {'code': self.startup_status[0], 'message': self.startup_status[1], 'progress': None, 'is_lagging': None} if self.startup_status[0] == 'loading_wallet': - r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") - r['progress'] = self.session.wallet.catchup_progress r['is_lagging'] = self.session.wallet.is_lagging + if r['is_lagging'] == True: + r['message'] = "Synchronization with the blockchain is lagging... if this continues try restarting LBRY" + else: + r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") + r['progress'] = self.session.wallet.catchup_progress log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) return self._render_response(r, OK_CODE) From 9a26d869fe2c8e396e68147b9e35fd908f892c82 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 14 Apr 2016 17:01:05 -0400 Subject: [PATCH 130/462] better first run message --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index c62d065b2..937f4c6e3 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -683,7 +683,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _show_first_run_result(self, credits_received): if credits_received != 0.0: points_string = locale.format_string("%.2f LBC", (round(credits_received, 2),), grouping=True) - self.startup_message = "Thank you for testing the alpha version of LBRY! You have been given %s for free because we love you. Please give them a few minutes to show up while you catch up with our blockchain." % points_string + self.startup_message = "Thank you for testing the alpha version of LBRY! You have been given %s for free because we love you. Please hang on for a few minutes for the next block to be mined. When you refresh this page and see your credits you're ready to go!." % points_string else: self.startup_message = None From b3b5581f2d1cb7538d13dffd0802372c58f1a071 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 15 Apr 2016 19:37:27 -0400 Subject: [PATCH 131/462] don't log to console, add --no-launch flag, fix help() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit by default, the ui opens in the lbrynet-daemon startup sequence, the —no-launch flag stops the ui from launching --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 8 ++------ lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 22 +++++++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 937f4c6e3..9dff721d5 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -61,6 +61,7 @@ LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') log = logging.getLogger(__name__) handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) log.addHandler(handler) +log.setLevel(logging.INFO) STARTUP_STAGES = [ ('initializing', 'Initializing...'), @@ -1078,12 +1079,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ if not p: - return self._render_response(['is_running', 'get_settings', 'set_settings', 'start_fetcher', 'stop_fetcher', - 'fetcher_status', 'get_balance', 'stop', 'get_lbry_files', 'resolve_name', - 'get', 'search_nametrie', 'delete_lbry_file', 'check', 'publish', - 'abandon_name', 'get_name_claims', 'get_time_behind_blockchain', - 'get_new_address', 'toggle_fetcher_verbose', 'check_for_new_version'], - OK_CODE) + return self._render_response(self._listFunctions(), OK_CODE) elif 'callable_during_start' in p.keys(): return self._render_response(ALLOWED_DURING_STARTUP, OK_CODE) elif 'function' in p.keys(): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 32fc8ec9c..5fc6f98ff 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -8,7 +8,6 @@ import webbrowser import sys import socket - from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen @@ -30,14 +29,14 @@ if not os.path.isdir(log_dir): os.mkdir(log_dir) LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') - log = logging.getLogger(__name__) handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) log.addHandler(handler) -logging.basicConfig(level=logging.INFO) +log.setLevel(logging.INFO) REMOTE_SERVER = "www.google.com" + def test_internet_connection(): try: host = socket.gethostbyname(REMOTE_SERVER) @@ -66,16 +65,14 @@ def start(): help="lbrycrd or lbryum, default lbryum", type=str, default=DEFAULT_WALLET) - parser.add_argument("--update", - help="True or false, default true", - type=str, - default="True") parser.add_argument("--ui", help="path to custom UI folder", default="") parser.add_argument("--branch", help="Branch of lbry-web-ui repo to use, defaults on HEAD", default="HEAD") + parser.add_argument('--no-launch', dest='launchui', action="store_false") + parser.set_defaults(launchui=True) try: JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running() @@ -85,6 +82,11 @@ def start(): pass log.info("Starting lbrynet-daemon from command line") + print "Starting lbrynet-daemon from command line" + print "To view activity, view the log file here: " + LOG_FILENAME + print "Web UI is available at http://%s:%i" %(API_INTERFACE, API_PORT) + print "JSONRPC API is available at " + API_CONNECTION_STRING + print "To quit press ctrl-c or call 'stop' via the API" args = parser.parse_args() @@ -118,6 +120,7 @@ def start(): git_version = subprocess.check_output(GIT_CMD_STRING, shell=True) if not git_version: log.info("You should have been notified to install xcode command line tools, once it's installed you can start LBRY") + print "You should have been notified to install xcode command line tools, once it's installed you can start LBRY" sys.exit(0) ui_version_info = git_version @@ -165,8 +168,11 @@ def start(): d = getui(args.ui) d.addCallback(lambda r: setupserver(r[0], r[1])) d.addCallback(lambda r: setupapi(r[0], args.wallet, r[1])) - d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) + if args.launchui: + d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) reactor.run() + print "\nClosing lbrynet-daemon" else: log.info("Not connected to internet, unable to start") + print "Not connected to internet, unable to start" return From bac7ea5dc05fb79825c724424986458d0de2ce32 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 15 Apr 2016 22:31:06 -0400 Subject: [PATCH 132/462] include platform information in version() --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 47 +++++++++++++++------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 9dff721d5..95340be0d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -111,6 +111,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.run_server = True self.session = None self.known_dht_nodes = KNOWN_DHT_NODES + self.platform_info = { + "processor": platform.processor(), + "python_version: ": platform.python_version(), + "platform": platform.platform(), + "os_release": platform.release(), + "os_system": platform.system(), + "lbrynet_version: ": lbrynet_version, + "lbryum_version: ": lbryum_version, + "ui_version": self.ui_version, + } if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle @@ -328,20 +338,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _initial_setup(self): def _log_platform(): - msg = { - "processor": platform.processor(), - "python version: ": platform.python_version(), - "lbrynet version: ": lbrynet_version, - "lbryum version: ": lbryum_version, - "ui_version": self.ui_version, - # 'ip': json.load(urlopen('http://jsonip.com'))['ip'], - } - if sys.platform == "darwin": - msg['osx version'] = platform.mac_ver()[0] + " " + platform.mac_ver()[2] - else: - msg['platform'] = platform.platform() - - log.info("Platform: " + json.dumps(msg)) + log.info("Platform: " + json.dumps(self.platform_info)) return defer.succeed(None) def _load_daemon_conf(): @@ -987,19 +984,25 @@ class LBRYDaemon(jsonrpc.JSONRPC): Args: None Returns: - 'lbrynet version': lbrynet version - 'lbryum version': lbryum version - 'ui_version': commit hash of ui + "platform": platform string + "os_release": os release string + "os_system": os name + "lbrynet_version: ": lbrynet_version, + "lbryum_version: ": lbryum_version, + "ui_version": commit hash of ui version being used "remote_lbrynet": most recent lbrynet version available from github "remote_lbryum": most recent lbryum version available from github """ msg = { - "lbrynet_version: ": lbrynet_version, - "lbryum_version: ": lbryum_version, - "ui_version": self.ui_version, - "remote_lbrynet": self.git_lbrynet_version, - "remote_lbryum": self.git_lbryum_version, + 'platform': self.platform_info['platform'], + 'os_release': self.platform_info['os_release'], + 'os_system': self.platform_info['os_system'], + 'lbrynet_version': lbrynet_version, + 'lbryum_version': lbryum_version, + 'ui_version': self.ui_version, + 'remote_lbrynet': self.git_lbrynet_version, + 'remote_lbryum': self.git_lbryum_version } log.info("[" + str(datetime.now()) + "] Get version info: " + json.dumps(msg)) From cdab127b98422a3e712354df2d5920a080d72c8c Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 15 Apr 2016 23:57:52 -0400 Subject: [PATCH 133/462] have separate handlers for uris on linux and os x --- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index 18e5ae366..c748a1015 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -45,7 +45,7 @@ class LBRYURIHandler(object): else: raise Timeout("Timed out trying to start LBRY daemon") - def handle(self, lbry_name): + def handle_osx(self, lbry_name): lbry_process = [d for d in subprocess.Popen(['ps','aux'], stdout=subprocess.PIPE).stdout.readlines() if 'LBRY.app' in d and 'LBRYURIHandler' not in d] try: @@ -62,17 +62,33 @@ class LBRYURIHandler(object): started = True if lbry_name == "lbry" or lbry_name == "" and not started: - webbrowser.get('safari').open(UI_ADDRESS) + webbrowser.open(UI_ADDRESS) else: - webbrowser.get('safari').open(UI_ADDRESS + "/view?name=" + lbry_name) + webbrowser.open(UI_ADDRESS + "/view?name=" + lbry_name) + + def handle_linux(self, lbry_name): + try: + is_running = self.daemon.is_running() + if not is_running: + sys.exit(0) + except: + sys.exit(0) + + if lbry_name == "lbry": + webbrowser.open(UI_ADDRESS) + else: + webbrowser.open(UI_ADDRESS + "/view?name=" + lbry_name) + def main(args): if len(args) != 1: args = ['lbry://lbry'] name = args[0][7:] - LBRYURIHandler().handle(lbry_name=name) - + if sys.platform == "darwin": + LBRYURIHandler().handle_osx(lbry_name=name) + else: + LBRYURIHandler().handle_linux(lbry_name=name) if __name__ == "__main__": main(sys.argv[1:]) From 5f49827bd27eda5372a72d5796437c5526d0bb6f Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 17 Apr 2016 19:54:04 -0400 Subject: [PATCH 134/462] fix lagging indicator and improve diagnostic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -make is_lagging more meaningful - it is set to true after 90 seconds with no progress, this is to stop slow but steady catchups from triggering it -prevent situation where repeated shutdowns before wallet catchup results in never receiving first run credits -fix settings to write new defaults that aren’t already in the configuration file -report log of startup sequence if upload_log set to true -redirect /view?name=lbry to the main UI page. This is to make the ui accessible from a lbry:// link on linux --- lbrynet/__init__.py | 2 +- lbrynet/core/LBRYcrdWallet.py | 14 ++- lbrynet/lbrynet_daemon/LBRYDaemon.py | 149 +++++++++++++++++++-------- 3 files changed, 116 insertions(+), 49 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 993518cab..8b1a36331 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) -version = (0, 2, 1) +version = (0, 2, 2) __version__ = ".".join([str(x) for x in version]) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 895dbfa49..bdf35af6b 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -904,6 +904,7 @@ class LBRYumWallet(LBRYWallet): self._start_check = None self._catch_up_check = None self._caught_up_counter = 0 + self._lag_counter = 0 self.blocks_behind_alert = 0 self.catchup_progress = 0 self.max_behind = 0 @@ -997,8 +998,18 @@ class LBRYumWallet(LBRYWallet): self._catch_up_check.stop() self._catch_up_check = None blockchain_caught_d.callback(True) + elif remote_height != 0: + past_blocks_behind = self.blocks_behind_alert self.blocks_behind_alert = remote_height - local_height + if self.blocks_behind_alert < past_blocks_behind: + self._lag_counter = 0 + self.is_lagging = False + else: + self._lag_counter += 1 + if self._lag_counter >= 900: + self.is_lagging = True + if self.blocks_behind_alert > self.max_behind: self.max_behind = self.blocks_behind_alert self.catchup_progress = int(100 * (self.blocks_behind_alert / (5 + self.max_behind))) @@ -1006,9 +1017,6 @@ class LBRYumWallet(LBRYWallet): alert.info('Catching up to the blockchain...showing blocks left...') if self._caught_up_counter % 30 == 0: alert.info('%d...', (remote_height - local_height)) - alert.info("Catching up: " + str(self.catchup_progress) + "%") - if self._caught_up_counter >= 600: - self.is_lagging = True self._caught_up_counter += 1 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 95340be0d..ff0bfd87a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -58,6 +58,13 @@ if not os.path.isdir(log_dir): LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') +if os.path.isfile(LOG_FILENAME): + f = open(LOG_FILENAME, 'r') + PREVIOUS_LOG = len(f.read()) + f.close() +else: + PREVIOUS_LOG = 0 + log = logging.getLogger(__name__) handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) log.addHandler(handler) @@ -121,6 +128,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): "lbryum_version: ": lbryum_version, "ui_version": self.ui_version, } + try: + self.platform_info['ip'] = json.load(urlopen('http://jsonip.com'))['ip'] + except: + self.platform_info['ip'] = "Could not determine" if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle @@ -185,28 +196,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'dht_node_port': 4444, 'use_upnp': True, 'start_lbrycrdd': True, + 'requested_first_run_credits': False } - if os.path.isfile(self.daemon_conf): - #load given settings, set missing settings to defaults - temp_settings = self.default_settings - f = open(self.daemon_conf, "r") - s = json.loads(f.read()) - f.close() - for k in temp_settings.keys(): - if k in s.keys(): - if type(temp_settings[k]) == type(s[k]): - temp_settings[k] = s[k] - self.__dict__[k] = s[k] - f = open(self.daemon_conf, "w") - f.write(json.dumps(temp_settings)) - f.close() - else: - log.info("Writing default settings: " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) - f = open(self.daemon_conf, "w") - f.write(json.dumps(self.default_settings)) - f.close() - for k in self.default_settings.keys(): - self.__dict__[k] = self.default_settings[k] def render(self, request): request.content.seek(0, 0) @@ -290,8 +281,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return version def _get_lbrynet_version(): - r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split( - '\n') + r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') vs = next(i for i in r if 'version =' in i).split("=")[1].replace(" ", "") vt = tuple(int(x) for x in vs[1:-1].split(',')) vr = ".".join([str(x) for x in vt]) @@ -308,10 +298,22 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) def _announce_startup(): - self.announced_startup = True - self.startup_status = STARTUP_STAGES[5] - log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") - return defer.succeed(None) + def _announce(): + self.announced_startup = True + self.startup_status = STARTUP_STAGES[5] + log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") + + if self.first_run: + d = self._upload_log(name_prefix="fr") + else: + d = self._upload_log(exclude_previous=True, name_prefix="start") + + if float(self.session.wallet.wallet_balance) == 0.0: + d.addCallback(lambda _: self._check_first_run()) + d.addCallback(self._show_first_run_result) + + d.addCallback(lambda _: _announce()) + return d log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") @@ -343,13 +345,30 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _load_daemon_conf(): if os.path.isfile(self.daemon_conf): - return json.loads(open(self.daemon_conf, "r").read()) + f = open(self.daemon_conf, "r") + loaded_settings = json.loads(f.read()) + f.close() + missing_settings = {} + for k in self.default_settings.keys(): + if k not in loaded_settings.keys(): + missing_settings[k] = self.default_settings[k] + if missing_settings != {}: + for k in missing_settings.keys(): + log.info("Adding missing setting: " + k + " with default value: " + str(missing_settings[k])) + loaded_settings[k] = missing_settings[k] + f = open(self.daemon_conf, "w") + f.write(json.dumps(loaded_settings)) + f.close() + rsettings = loaded_settings else: log.info("Writing default settings : " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) f = open(self.daemon_conf, "w") f.write(json.dumps(self.default_settings)) f.close() - return self.default_settings + rsettings = self.default_settings + for k in rsettings.keys(): + self.__dict__[k] = rsettings[k] + return rsettings d = _log_platform() d.addCallback(lambda _: _load_daemon_conf()) @@ -434,21 +453,36 @@ class LBRYDaemon(jsonrpc.JSONRPC): dl.addCallback(_set_query_handlers) return dl - def _upload_log(self): + def _upload_log(self, name_prefix=None, exclude_previous=False): + if name_prefix: + name_prefix = name_prefix + "-" + platform.system() + else: + name_prefix = platform.system() + if self.session_settings['upload_log']: LOG_URL = "https://lbry.io/log-upload" - f = open(self.log_file, "r") + if exclude_previous: + f = open(self.log_file, "r") + f.seek(PREVIOUS_LOG) + log_contents = f.read() + f.close() + else: + f = open(self.log_file, "r") + log_contents = f.read() + f.close() t = datetime.now() - log_name = base58.b58encode(self.lbryid)[:20] + "-" + str(t.month) + "-" + str(t.day) + "-" + str(t.year) + "-" + str(t.hour) + "-" + str(t.minute) - params = {'name': log_name, 'log': f.read()} - f.close() + log_name = name_prefix + "-" + base58.b58encode(self.lbryid)[:20] + "-" + str(t.month) + "-" + str(t.day) + "-" + str(t.year) + "-" + str(t.hour) + "-" + str(t.minute) + params = {'name': log_name, 'log': log_contents} + requests.post(LOG_URL, params) return defer.succeed(None) def _shutdown(self): log.info("Closing lbrynet session") - d = self._upload_log() + log.info("Status at time of shutdown: " + self.startup_status[0]) + + d = self._upload_log(name_prefix="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) d.addErrback(lambda err: log.info("Bad server shutdown: " + err.getTraceback())) if self.session is not None: @@ -638,8 +672,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): dl.addCallback(combine_results) dl.addCallback(create_session) dl.addCallback(lambda _: self.session.setup()) - dl.addCallback(lambda _: self._check_first_run()) - dl.addCallback(self._show_first_run_result) + return dl def _check_first_run(self): @@ -649,18 +682,20 @@ class LBRYDaemon(jsonrpc.JSONRPC): return 0.0 d = self.session.wallet.is_first_run() - - d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else _set_first_run_false()) + d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run or not self.requested_first_run_credits + else _set_first_run_false()) return d def _do_first_run(self): - self.first_run = True - d = self.session.wallet.get_new_address() - def send_request(url, data): log.info("Requesting first run credits") r = requests.post(url, json=data) if r.status_code == 200: + self.requested_first_run_credits = True + self.session_settings['requested_first_run_credits'] = True + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() return r.json()['credits_sent'] return 0.0 @@ -675,7 +710,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addErrback(log_error) return d + self.first_run = True + d = self.session.wallet.get_new_address() d.addCallback(request_credits) + return d def _show_first_run_result(self, credits_received): @@ -1560,6 +1598,24 @@ class LBRYDaemon(jsonrpc.JSONRPC): return _check_version() + def jsonrpc_upload_log(self, p=None): + if p: + if 'name_prefix' in p.keys(): + prefix = p['name_prefix'] + '_api' + else: + prefix = None + if 'exclude_previous' in p.keys: + exclude_previous = p['exclude_previous'] + else: + exclude_previous = True + else: + prefix = "api" + exclude_previous = True + + d = self._upload_log(name_prefix=prefix, exclude_previous=exclude_previous) + d.addCallback(lambda _: self._render_response(True, OK_CODE)) + return d + class LBRYDaemonCommandHandler(object): def __init__(self, command): @@ -1600,9 +1656,12 @@ class LBRYFileRender(resource.Resource): def render_GET(self, request): if 'name' in request.args.keys(): api = jsonrpc.Proxy(API_CONNECTION_STRING) - d = api.callRemote("get", {'name': request.args['name'][0]}) - d.addCallback(lambda results: static.File(results['path']).render_GET(request)) - + if request.args['name'][0] != 'lbry': + d = api.callRemote("get", {'name': request.args['name'][0]}) + d.addCallback(lambda results: static.File(results['path']).render_GET(request)) + else: + request.redirect(UI_ADDRESS) + request.finish() return server.NOT_DONE_YET else: return server.failure From 7b9d84f528f63a4c6d012f70b0c13430dafd6a5b Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 17 Apr 2016 20:11:10 -0400 Subject: [PATCH 135/462] use master branch for ui in .deb --- packaging/ubuntu/lbry | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index 0c6edeeeb..a4aed0ff8 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -17,7 +17,7 @@ urlencode() { DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ -z "$(pgrep lbrynet-daemon)" ]; then echo "running lbrynet-daemon..." - $DIR/lbrynet-daemon --branch=settings-page & + $DIR/lbrynet-daemon & sleep 3 # let the daemon load before connecting fi From 99b3cdd45ddf88cede391c01497ec6d5aee3c5e1 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 17 Apr 2016 20:16:35 -0400 Subject: [PATCH 136/462] default on launching ui if daemon already running when command is called -also update deb version --- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 6 ++++-- packaging/ubuntu/lbry.desktop | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 5fc6f98ff..d04133144 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -74,9 +74,13 @@ def start(): parser.add_argument('--no-launch', dest='launchui', action="store_false") parser.set_defaults(launchui=True) + args = parser.parse_args() + try: JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running() log.info("lbrynet-daemon is already running") + if args.launchui: + webbrowser.open(UI_ADDRESS) return except: pass @@ -88,8 +92,6 @@ def start(): print "JSONRPC API is available at " + API_CONNECTION_STRING print "To quit press ctrl-c or call 'stop' via the API" - args = parser.parse_args() - if args.branch == "HEAD": GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep %s | cut -f 1" % args.branch DIST_URL = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/master/dist.zip" diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index f034b97c6..d5c029ec6 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.2.1 +Version=0.2.2 Name=LBRY # Only KDE 4 seems to use GenericName, so we reuse the KDE strings. # From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413. From 7a622986d669002293086e73c8c84c9c282927d7 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 17 Apr 2016 22:56:51 -0400 Subject: [PATCH 137/462] remove lbryum version from setup.py version number made dependency link not work --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1863dad82..e4a9432f0 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', - 'leveldb', 'lbryum>=2.6.0.1', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] + 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf', 'lbry.png'] From 4385b90cca09397e8b37d882dc7e9fe389ff8270 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 17 Apr 2016 23:23:20 -0400 Subject: [PATCH 138/462] add get transaction and history functions to daemon --- lbrynet/core/LBRYcrdWallet.py | 35 +++++++++++++++- lbrynet/lbrynet_daemon/LBRYDaemon.py | 44 +++++++++++++++++++++ lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 23 +++++++---- 3 files changed, 93 insertions(+), 9 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 895dbfa49..3ff1008fb 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -1003,7 +1003,7 @@ class LBRYumWallet(LBRYWallet): self.max_behind = self.blocks_behind_alert self.catchup_progress = int(100 * (self.blocks_behind_alert / (5 + self.max_behind))) if self._caught_up_counter == 0: - alert.info('Catching up to the blockchain...showing blocks left...') + alert.info('Catching up with the blockchain...showing blocks left...') if self._caught_up_counter % 30 == 0: alert.info('%d...', (remote_height - local_height)) alert.info("Catching up: " + str(self.catchup_progress) + "%") @@ -1128,6 +1128,39 @@ class LBRYumWallet(LBRYWallet): func = getattr(self.cmd_runner, cmd.name) return threads.deferToThread(func) + def get_history(self): + cmd = known_commands['history'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func) + + def get_tx_json(self, txid): + def _decode(raw_tx): + tx = Transaction(raw_tx).deserialize() + decoded_tx = {} + for txkey in tx.keys(): + if isinstance(tx[txkey], list): + decoded_tx[txkey] = [] + for i in tx[txkey]: + tmp = {} + for k in i.keys(): + if isinstance(i[k], Decimal): + tmp[k] = float(i[k] / 1e8) + else: + tmp[k] = i[k] + decoded_tx[txkey].append(tmp) + else: + decoded_tx[txkey] = tx[txkey] + return decoded_tx + + d = self._get_raw_tx(txid) + d.addCallback(_decode) + return d + + def get_pub_keys(self, wallet): + cmd = known_commands['getpubkeys'] + func = getattr(self.cmd_runner, cmd.name) + return threads.deferToThread(func, wallet) + def _save_wallet(self, val): d = threads.deferToThread(self.wallet.storage.write) d.addCallback(lambda _: val) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 95340be0d..3b658c880 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1459,6 +1459,50 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d + def jsonrpc_get_transaction_history(self): + """ + Get transaction history + + Args: + None + Returns: + list of transactions + """ + + d = self.session.wallet.get_history() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_transaction(self, p): + """ + Get a decoded transaction from a txid + + Args: + txid: txid hex string + Returns: + JSON formatted transaction + """ + + + txid = p['txid'] + d = self.session.wallet.get_tx_json(txid) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_public_key_from_wallet(self, p): + """ + Get public key from wallet address + + Args: + wallet: wallet address, base58 + Returns: + public key + """ + + wallet = p['wallet'] + d = self.session.wallet.get_pub_keys(wallet) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + def jsonrpc_get_time_behind_blockchain(self): """ Get number of blocks behind the blockchain diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 5fc6f98ff..2f0f00154 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -72,7 +72,13 @@ def start(): help="Branch of lbry-web-ui repo to use, defaults on HEAD", default="HEAD") parser.add_argument('--no-launch', dest='launchui', action="store_false") - parser.set_defaults(launchui=True) + parser.add_argument('--log-to-console', dest='logtoconsole', action="store_true") + parser.add_argument('--quiet', dest='quiet', action="store_true") + parser.set_defaults(launchui=True, logtoconsole=False, quiet=False) + args = parser.parse_args() + + if args.logtoconsole: + logging.basicConfig(level=logging.INFO) try: JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running() @@ -82,13 +88,13 @@ def start(): pass log.info("Starting lbrynet-daemon from command line") - print "Starting lbrynet-daemon from command line" - print "To view activity, view the log file here: " + LOG_FILENAME - print "Web UI is available at http://%s:%i" %(API_INTERFACE, API_PORT) - print "JSONRPC API is available at " + API_CONNECTION_STRING - print "To quit press ctrl-c or call 'stop' via the API" - args = parser.parse_args() + if not args.logtoconsole and not args.quiet: + print "Starting lbrynet-daemon from command line" + print "To view activity, view the log file here: " + LOG_FILENAME + print "Web UI is available at http://%s:%i" %(API_INTERFACE, API_PORT) + print "JSONRPC API is available at " + API_CONNECTION_STRING + print "To quit press ctrl-c or call 'stop' via the API" if args.branch == "HEAD": GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep %s | cut -f 1" % args.branch @@ -171,7 +177,8 @@ def start(): if args.launchui: d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) reactor.run() - print "\nClosing lbrynet-daemon" + if not args.logtoconsole and not args.quiet: + print "\nClosing lbrynet-daemon" else: log.info("Not connected to internet, unable to start") print "Not connected to internet, unable to start" From 24eed71959e6e9188dadd903190cf1f89d13fa72 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 18 Apr 2016 03:41:16 -0400 Subject: [PATCH 139/462] upload log function doc string --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 5547b1367..5e3296c4c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1643,6 +1643,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): return _check_version() def jsonrpc_upload_log(self, p=None): + """ + Upload log + + Args, optional: + 'name_prefix': prefix to denote what is requesting the log upload + 'exclude_previous': true/false, whether or not to exclude previous sessions from upload, defaults on true + Returns + True + """ + if p: if 'name_prefix' in p.keys(): prefix = p['name_prefix'] + '_api' From 29f7307f9d5bea59d3c0df436a183b1de0ddb51a Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 18 Apr 2016 13:21:53 -0400 Subject: [PATCH 140/462] periodically check internet connection and for new versions --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 147 +++++++++++++++++++-------- 1 file changed, 105 insertions(+), 42 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 5e3296c4c..f185e1de2 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -11,9 +11,11 @@ import base64 import base58 import platform import json +import socket from twisted.web import server, resource, static from twisted.internet import defer, threads, error, reactor +from twisted.internet.task import LoopingCall from txjsonrpc import jsonrpclib from txjsonrpc.web import jsonrpc from txjsonrpc.web.jsonrpc import Handler @@ -70,15 +72,29 @@ handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, ba log.addHandler(handler) log.setLevel(logging.INFO) + +INITIALIZING_CODE = 'initializing' +LOADING_DB_CODE = 'loading_db' +LOADING_WALLET_CODE = 'loading_wallet' +LOADING_FILE_MANAGER_CODE = 'loading_file_manager' +LOADING_SERVER_CODE = 'loading_server' +STARTED_CODE = 'started' STARTUP_STAGES = [ - ('initializing', 'Initializing...'), - ('loading_db', 'Loading databases...'), - ('loading_wallet', 'Catching up with the blockchain... %s'), - ('loading_file_manager', 'Setting up file manager'), - ('loading_server', 'Starting lbrynet'), - ('started', 'Started lbrynet') + (INITIALIZING_CODE, 'Initializing...'), + (LOADING_DB_CODE, 'Loading databases...'), + (LOADING_WALLET_CODE, 'Catching up with the blockchain... %s'), + (LOADING_FILE_MANAGER_CODE, 'Setting up file manager'), + (LOADING_SERVER_CODE, 'Starting lbrynet'), + (STARTED_CODE, 'Started lbrynet') ] +CONNECT_CODE_VERSION_CHECK = 'version_check' +CONNECT_CODE_NETWORK = 'network_connection' +CONNECT_CODE_WALLET = 'wallet_catchup_lag' +CONNECTION_PROBLEM_CODES = [(CONNECT_CODE_VERSION_CHECK, "There was a problem checking for updates on github"), + (CONNECT_CODE_NETWORK, "Your internet connection appears to have been interrupted"), + (CONNECT_CODE_WALLET, "Synchronization with the blockchain is lagging... if this continues try restarting LBRY")] + ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run', 'get_time_behind_blockchain', 'stop', 'daemon_status', 'get_start_notice', @@ -91,6 +107,9 @@ OK_CODE = 200 # TODO alert if your copy of a lbry file is out of date with the name record +REMOTE_SERVER = "www.google.com" + + class LBRYDaemon(jsonrpc.JSONRPC): """ LBRYnet daemon, a jsonrpc interface to lbry functions @@ -105,6 +124,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.startup_status = STARTUP_STAGES[0] self.startup_message = None self.announced_startup = False + self.connected_to_internet = True + self.connection_problem = None self.query_handlers = {} self.ui_version = ui_version_info.replace('\n', '') self.git_lbrynet_version = None @@ -172,6 +193,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.wallet_user = None self.wallet_password = None + self.internet_connection_checker = LoopingCall(self._check_network_connection) + self.version_checker = LoopingCall(self._check_remote_versions) + self.connection_problem_checker = LoopingCall(self._check_connection_problems) + self.sd_identifier = StreamDescriptorIdentifier() self.stream_info_manager = TempLBRYFileMetadataManager() self.settings = LBRYSettings(self.db_dir) @@ -267,32 +292,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): def setup(self): def _log_starting_vals(): - def _get_lbry_files_json(): - r = self._get_lbry_files() - return json.dumps(r) + r = json.dumps(self._get_lbry_files()) - def _get_lbryum_version(): - r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') - version = next(line.split("=")[1].split("#")[0].replace(" ", "") - for line in r if "ELECTRUM_VERSION" in line) - version = version.replace("'", "") - log.info("remote lbryum " + str(version) + " > local lbryum " + str(lbryum_version) + " = " + str( - version > lbryum_version)) - return version - - def _get_lbrynet_version(): - r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') - vs = next(i for i in r if 'version =' in i).split("=")[1].replace(" ", "") - vt = tuple(int(x) for x in vs[1:-1].split(',')) - vr = ".".join([str(x) for x in vt]) - log.info("remote lbrynet " + str(vr) + " > local lbrynet " + str(lbrynet_version) + " = " + str( - vr > lbrynet_version)) - return vr - - self.git_lbrynet_version = _get_lbrynet_version() - self.git_lbryum_version = _get_lbryum_version() - - log.info("LBRY Files: " + _get_lbry_files_json()) + log.info("LBRY Files: " + r) log.info("Starting balance: " + str(self.session.wallet.wallet_balance)) return defer.succeed(None) @@ -317,6 +319,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") + self.internet_connection_checker.start(60) + self.version_checker.start(3600) + self.connection_problem_checker.start(1) + d = defer.Deferred() d.addCallback(lambda _: self._initial_setup()) d.addCallback(self._set_daemon_settings) @@ -379,8 +385,63 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.session_settings = settings return defer.succeed(None) - def _start_server(self): + def _check_network_connection(self): + try: + host = socket.gethostbyname(REMOTE_SERVER) + s = socket.create_connection((host, 80), 2) + self.connected_to_internet = True + except: + log.info("[" + str(datetime.now()) + "] Internet connection not working") + self.connected_to_internet = False + def _check_remote_versions(self): + def _get_lbryum_version(): + try: + r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') + version = next(line.split("=")[1].split("#")[0].replace(" ", "") + for line in r if "ELECTRUM_VERSION" in line) + version = version.replace("'", "") + log.info("remote lbryum " + str(version) + " > local lbryum " + str(lbryum_version) + " = " + str( + version > lbryum_version)) + self.git_lbryum_version = version + return defer.succeed(None) + except: + log.info("[" + str(datetime.now()) + "] Failed to get lbryum version from git") + self.git_lbryum_version = None + return defer.fail(None) + + def _get_lbrynet_version(): + try: + r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') + vs = next(i for i in r if 'version =' in i).split("=")[1].replace(" ", "") + vt = tuple(int(x) for x in vs[1:-1].split(',')) + vr = ".".join([str(x) for x in vt]) + log.info("remote lbrynet " + str(vr) + " > local lbrynet " + str(lbrynet_version) + " = " + str( + vr > lbrynet_version)) + self.git_lbrynet_version = vr + return defer.succeed(None) + except: + log.info("[" + str(datetime.now()) + "] Failed to get lbrynet version from git") + self.git_lbrynet_version = None + return defer.fail(None) + + d = _get_lbrynet_version() + d.addCallback(lambda _: _get_lbryum_version()) + + def _check_connection_problems(self): + if not self.git_lbrynet_version or not self.git_lbryum_version: + self.connection_problem = CONNECTION_PROBLEM_CODES[0] + + elif self.startup_status[0] == 'loading_wallet': + if self.session.wallet.is_lagging: + self.connection_problem = CONNECTION_PROBLEM_CODES[2] + else: + self.connection_problem = None + + if not self.connected_to_internet: + self.connection_problem = CONNECTION_PROBLEM_CODES[1] + + def _start_server(self): if self.peer_port is not None: server_factory = ServerProtocolFactory(self.session.rate_limiter, @@ -402,7 +463,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _setup_server(self): - def restore_running_status(running): if running is True: return self._start_server() @@ -439,7 +499,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): return dl def _add_query_handlers(self, query_handlers): - def _set_query_handlers(statuses): from future_builtins import zip for handler, (success, status) in zip(query_handlers, statuses): @@ -482,6 +541,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("Closing lbrynet session") log.info("Status at time of shutdown: " + self.startup_status[0]) + self.internet_connection_checker.stop() + self.version_checker.stop() + self.connection_problem_checker.stop() + d = self._upload_log(name_prefix="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) d.addErrback(lambda err: log.info("Bad server shutdown: " + err.getTraceback())) @@ -965,12 +1028,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ r = {'code': self.startup_status[0], 'message': self.startup_status[1], 'progress': None, 'is_lagging': None} - if self.startup_status[0] == 'loading_wallet': - r['is_lagging'] = self.session.wallet.is_lagging - if r['is_lagging'] == True: - r['message'] = "Synchronization with the blockchain is lagging... if this continues try restarting LBRY" - else: - r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") + + if self.connection_problem: + r['message'] = self.connection_problem[1] + r['is_lagging'] = True + elif self.startup_status[0] == LOADING_WALLET_CODE: + r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") r['progress'] = self.session.wallet.catchup_progress log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) @@ -1647,8 +1710,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): Upload log Args, optional: - 'name_prefix': prefix to denote what is requesting the log upload - 'exclude_previous': true/false, whether or not to exclude previous sessions from upload, defaults on true + 'name_prefix': prefix to indicate what is requesting the log upload + 'exclude_previous': true/false, whether or not to exclude previous sessions from upload, defaults on true Returns True """ From a82f85f0586ed484da6734d8f55611f37fc6c5ef Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Apr 2016 02:56:29 -0400 Subject: [PATCH 141/462] bump version, increase log size, fix shutdown problem --- lbrynet/__init__.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 15 ++++++--------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 8b1a36331..8f010329d 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) -version = (0, 2, 2) +version = (0, 2, 3) __version__ = ".".join([str(x) for x in version]) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index f185e1de2..3afff6eaf 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -68,7 +68,7 @@ else: PREVIOUS_LOG = 0 log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) log.addHandler(handler) log.setLevel(logging.INFO) @@ -541,10 +541,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("Closing lbrynet session") log.info("Status at time of shutdown: " + self.startup_status[0]) - self.internet_connection_checker.stop() - self.version_checker.stop() - self.connection_problem_checker.stop() - d = self._upload_log(name_prefix="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) d.addErrback(lambda err: log.info("Bad server shutdown: " + err.getTraceback())) @@ -1022,9 +1018,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): Args: None Returns: - 'status_message': startup status message - 'status_code': status_code - if status_code is 'loading_wallet', also contains key 'progress': blockchain catchup progress + 'message': startup status message + 'code': status_code + 'progress': progress, only used in loading_wallet + 'is_lagging': flag set to indicate lag, if set message will contain relevant message """ r = {'code': self.startup_status[0], 'message': self.startup_status[1], 'progress': None, 'is_lagging': None} @@ -1263,7 +1260,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self._shutdown() d.addCallback(lambda _: _disp_shutdown()) - d.addCallback(lambda _: reactor.callLater(1.0, reactor.stop)) + d.addCallback(lambda _: reactor.callLater(0.0, reactor.stop)) return self._render_response("Shutting down", OK_CODE) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 3ecdab03c..13772a386 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -30,7 +30,7 @@ if not os.path.isdir(log_dir): LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=262144, backupCount=5) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) log.addHandler(handler) log.setLevel(logging.INFO) From c4620a0b0cc689b48a0e4aee8b1fc94fdb93e55d Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Apr 2016 04:24:32 -0400 Subject: [PATCH 142/462] add problem_code to daemon_status --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 3afff6eaf..05a089afd 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1024,9 +1024,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'is_lagging': flag set to indicate lag, if set message will contain relevant message """ - r = {'code': self.startup_status[0], 'message': self.startup_status[1], 'progress': None, 'is_lagging': None} + r = {'code': self.startup_status[0], 'message': self.startup_status[1], + 'progress': None, 'is_lagging': None, 'problem_code': None} if self.connection_problem: + r['problem_code'] = self.connection_problem[0] r['message'] = self.connection_problem[1] r['is_lagging'] = True elif self.startup_status[0] == LOADING_WALLET_CODE: From c5d653a51aaf2d616edc577d4c6cefd2e7086a06 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Apr 2016 14:44:26 -0400 Subject: [PATCH 143/462] add bug report page adds a page to submit a message and upload log at /report --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 42 ++++++++++++++++++--- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 3 +- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 05a089afd..7d0645404 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -512,13 +512,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): dl.addCallback(_set_query_handlers) return dl - def _upload_log(self, name_prefix=None, exclude_previous=False): + def _upload_log(self, name_prefix=None, exclude_previous=False, force=False): if name_prefix: name_prefix = name_prefix + "-" + platform.system() else: name_prefix = platform.system() - if self.session_settings['upload_log']: + if self.session_settings['upload_log'] or force: LOG_URL = "https://lbry.io/log-upload" if exclude_previous: f = open(self.log_file, "r") @@ -534,8 +534,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): params = {'name': log_name, 'log': log_contents} requests.post(LOG_URL, params) - - return defer.succeed(None) + return defer.succeed(None) + else: + return defer.succeed(None) def _shutdown(self): log.info("Closing lbrynet session") @@ -1720,15 +1721,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): prefix = p['name_prefix'] + '_api' else: prefix = None - if 'exclude_previous' in p.keys: + + if 'exclude_previous' in p.keys(): exclude_previous = p['exclude_previous'] else: exclude_previous = True + + if 'force' in p.keys(): + force = p['force'] + else: + force = False else: prefix = "api" exclude_previous = True - d = self._upload_log(name_prefix=prefix, exclude_previous=exclude_previous) + d = self._upload_log(name_prefix=prefix, exclude_previous=exclude_previous, force=force) d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d @@ -1781,3 +1788,26 @@ class LBRYFileRender(resource.Resource): return server.NOT_DONE_YET else: return server.failure + +class LBRYBugReport(resource.Resource): + isLeaf = False + + def _delayed_render(self, request, results): + request.write(results) + request.finish() + + def render_GET(self, request): + return '
' \ + '
Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!
' \ + '' \ + '' \ + '' + + def render_POST(self, request): + msg = request.args["message"][0] + log.info("User submitted error report: " + str(msg)) + api = jsonrpc.Proxy(API_CONNECTION_STRING) + d = api.callRemote("upload_log", {'name_prefix': 'report', 'exclude_previous': False, 'force': True}) + d.addCallback(lambda _: self._delayed_render(request, "Your bug report is greatly appreciated! Click here to return to LBRY")) + + return server.NOT_DONE_YET \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 13772a386..3f7b7e411 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -17,7 +17,7 @@ from twisted.web import server, static from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy -from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYFileRender +from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYFileRender, LBRYBugReport from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET, UI_ADDRESS if sys.platform != "darwin": @@ -166,6 +166,7 @@ def start(): root.putChild("img", static.File(os.path.join(ui_dir, "img"))) root.putChild("js", static.File(os.path.join(ui_dir, "js"))) root.putChild("view", LBRYFileRender()) + root.putChild("report", LBRYBugReport()) return defer.succeed([root, ui_version]) def setupapi(root, wallet, ui_version): From e5e25012ffc7ce2c86940b955208342a7e5a55ff Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Apr 2016 22:02:52 -0400 Subject: [PATCH 144/462] move daemon server stuff into its own class --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 87 +------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 125 ++++-------- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 210 ++++++++++++++++++++ 3 files changed, 248 insertions(+), 174 deletions(-) create mode 100644 lbrynet/lbrynet_daemon/LBRYDaemonServer.py diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 7d0645404..c30b5b594 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -3,17 +3,13 @@ import os import sys import simplejson as json import binascii -import subprocess -import logging import logging.handlers import requests -import base64 import base58 import platform -import json import socket -from twisted.web import server, resource, static +from twisted.web import server from twisted.internet import defer, threads, error, reactor from twisted.internet.task import LoopingCall from txjsonrpc import jsonrpclib @@ -31,7 +27,6 @@ from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory -from lbrynet.lbrynet_console.ControlHandlers import get_time_behind_blockchain from lbrynet.core.Error import UnknownNameError from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory @@ -40,7 +35,8 @@ from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream, FetcherDaemon from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE +from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ + DEFAULT_WALLET, API_INTERFACE from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS, DEFAULT_TIMEOUT, UI_ADDRESS from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -117,7 +113,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): isLeaf = True - def __init__(self, ui_version_info, wallet_type="lbryum"): + def __init__(self, ui_version_info, wallet_type=DEFAULT_WALLET): jsonrpc.JSONRPC.__init__(self) reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) @@ -1693,8 +1689,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): true/false, true meaning that there is a new version available """ - - def _check_version(): if (lbrynet_version >= self.git_lbrynet_version) and (lbryum_version >= self.git_lbryum_version): log.info("[" + str(datetime.now()) + "] Up to date") @@ -1738,76 +1732,3 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self._upload_log(name_prefix=prefix, exclude_previous=exclude_previous, force=force) d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d - - -class LBRYDaemonCommandHandler(object): - def __init__(self, command): - self._api = jsonrpc.Proxy(API_CONNECTION_STRING) - self.command = command - - def run(self, params=None): - if params: - d = self._api.callRemote(self.command, params) - else: - d = self._api.callRemote(self.command) - return d - - -class LBRYindex(resource.Resource): - def __init__(self, ui_dir): - resource.Resource.__init__(self) - self.ui_dir = ui_dir - - isLeaf = False - - def _delayed_render(self, request, results): - request.write(str(results)) - request.finish() - - def getChild(self, name, request): - if name == '': - return self - return resource.Resource.getChild(self, name, request) - - def render_GET(self, request): - return static.File(os.path.join(self.ui_dir, "index.html")).render_GET(request) - - -class LBRYFileRender(resource.Resource): - isLeaf = False - - def render_GET(self, request): - if 'name' in request.args.keys(): - api = jsonrpc.Proxy(API_CONNECTION_STRING) - if request.args['name'][0] != 'lbry': - d = api.callRemote("get", {'name': request.args['name'][0]}) - d.addCallback(lambda results: static.File(results['path']).render_GET(request)) - else: - request.redirect(UI_ADDRESS) - request.finish() - return server.NOT_DONE_YET - else: - return server.failure - -class LBRYBugReport(resource.Resource): - isLeaf = False - - def _delayed_render(self, request, results): - request.write(results) - request.finish() - - def render_GET(self, request): - return '
' \ - '
Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!
' \ - '' \ - '' \ - '' - - def render_POST(self, request): - msg = request.args["message"][0] - log.info("User submitted error report: " + str(msg)) - api = jsonrpc.Proxy(API_CONNECTION_STRING) - d = api.callRemote("upload_log", {'name_prefix': 'report', 'exclude_previous': False, 'force': True}) - d.addCallback(lambda _: self._delayed_render(request, "Your bug report is greatly appreciated! Click here to return to LBRY")) - - return server.NOT_DONE_YET \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 3f7b7e411..0321de4df 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -1,25 +1,22 @@ import argparse import logging import logging.handlers -import subprocess import os -import shutil import webbrowser import sys import socket +import subprocess +import platform -from StringIO import StringIO -from zipfile import ZipFile -from urllib import urlopen -from datetime import datetime from appdirs import user_data_dir -from twisted.web import server, static +from twisted.web import server from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy -from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon, LBRYindex, LBRYFileRender, LBRYBugReport +from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET, UI_ADDRESS + if sys.platform != "darwin": log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: @@ -34,6 +31,7 @@ handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, b log.addHandler(handler) log.setLevel(logging.INFO) + REMOTE_SERVER = "www.google.com" @@ -46,11 +44,24 @@ def test_internet_connection(): return False +def prompt_for_xcode_if_needed(): + t = subprocess.check_output("git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep HEAD | cut -f 1", shell=True) + if not t: + if platform.system().lower() != "darwin": + print "Please install git" + sys.exit(0) + else: + print "You should have been alerted to install xcode command line tools, please do so and then start lbry" + sys.exit(0) + + def stop(): def _disp_shutdown(): + print "Shutting down lbrynet-daemon from command line" log.info("Shutting down lbrynet-daemon from command line") def _disp_not_running(): + print "Attempt to shut down lbrynet-daemon from command line when daemon isn't running" log.info("Attempt to shut down lbrynet-daemon from command line when daemon isn't running") d = defer.Deferred(None) @@ -67,7 +78,7 @@ def start(): default=DEFAULT_WALLET) parser.add_argument("--ui", help="path to custom UI folder", - default="") + default=None) parser.add_argument("--branch", help="Branch of lbry-web-ui repo to use, defaults on HEAD", default="HEAD") @@ -85,106 +96,38 @@ def start(): try: JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running() log.info("lbrynet-daemon is already running") + if not args.logtoconsole: + print "lbrynet-daemon is already running" if args.launchui: webbrowser.open(UI_ADDRESS) return except: pass + prompt_for_xcode_if_needed() + log.info("Starting lbrynet-daemon from command line") if not args.logtoconsole and not args.quiet: print "Starting lbrynet-daemon from command line" print "To view activity, view the log file here: " + LOG_FILENAME - print "Web UI is available at http://%s:%i" %(API_INTERFACE, API_PORT) + print "Web UI is available at http://%s:%i" % (API_INTERFACE, API_PORT) print "JSONRPC API is available at " + API_CONNECTION_STRING print "To quit press ctrl-c or call 'stop' via the API" - if args.branch == "HEAD": - GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep %s | cut -f 1" % args.branch - DIST_URL = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/master/dist.zip" - else: - log.info("Using UI branch: " + args.branch) - GIT_CMD_STRING = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep refs/heads/%s | cut -f 1" % args.branch - DIST_URL = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % args.branch - - def getui(ui_dir=None): - if ui_dir: - if os.path.isdir(ui_dir): - log.info("Using user specified UI directory: " + str(ui_dir)) - ui_version_info = "user-specified" - return defer.succeed([ui_dir, ui_version_info]) - else: - log.info("User specified UI directory doesn't exist: " + str(ui_dir)) - - def download_ui(dest_dir, ui_version): - url = urlopen(DIST_URL) - z = ZipFile(StringIO(url.read())) - names = [i for i in z.namelist() if '.DS_Store' not in i and '__MACOSX' not in i] - z.extractall(dest_dir, members=names) - return defer.succeed([dest_dir, ui_version]) - - data_dir = user_data_dir("LBRY") - version_dir = os.path.join(data_dir, "ui_version_history") - - git_version = subprocess.check_output(GIT_CMD_STRING, shell=True) - if not git_version: - log.info("You should have been notified to install xcode command line tools, once it's installed you can start LBRY") - print "You should have been notified to install xcode command line tools, once it's installed you can start LBRY" - sys.exit(0) - - ui_version_info = git_version - - if not os.path.isdir(data_dir): - os.mkdir(data_dir) - - if not os.path.isdir(os.path.join(data_dir, "ui_version_history")): - os.mkdir(version_dir) - - if not os.path.isfile(os.path.join(version_dir, git_version)): - f = open(os.path.join(version_dir, git_version), "w") - version_message = "[" + str(datetime.now()) + "] Updating UI --> " + git_version - f.write(version_message) - f.close() - log.info(version_message) - - if os.path.isdir(os.path.join(data_dir, "lbry-web-ui")): - shutil.rmtree(os.path.join(data_dir, "lbry-web-ui")) - else: - version_message = "[" + str(datetime.now()) + "] UI version " + git_version + " up to date" - log.info(version_message) - - if os.path.isdir(os.path.join(data_dir, "lbry-web-ui")): - return defer.succeed([os.path.join(data_dir, "lbry-web-ui"), ui_version_info]) - else: - return download_ui(os.path.join(data_dir, "lbry-web-ui"), ui_version_info) - - def setupserver(ui_dir, ui_version): - root = LBRYindex(ui_dir) - root.putChild("css", static.File(os.path.join(ui_dir, "css"))) - root.putChild("font", static.File(os.path.join(ui_dir, "font"))) - root.putChild("img", static.File(os.path.join(ui_dir, "img"))) - root.putChild("js", static.File(os.path.join(ui_dir, "js"))) - root.putChild("view", LBRYFileRender()) - root.putChild("report", LBRYBugReport()) - return defer.succeed([root, ui_version]) - - def setupapi(root, wallet, ui_version): - daemon = LBRYDaemon(ui_version, wallet_type=wallet) - root.putChild(API_ADDRESS, daemon) - reactor.listenTCP(API_PORT, server.Site(root), interface=API_INTERFACE) - return daemon.setup() - if test_internet_connection(): - d = getui(args.ui) - d.addCallback(lambda r: setupserver(r[0], r[1])) - d.addCallback(lambda r: setupapi(r[0], args.wallet, r[1])) - if args.launchui: - d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) + lbry = LBRYDaemonServer() + + d = lbry.start(branch=args.branch, user_specified=args.ui) + d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) + + reactor.listenTCP(API_PORT, server.Site(lbry.root), interface=API_INTERFACE) reactor.run() + if not args.logtoconsole and not args.quiet: print "\nClosing lbrynet-daemon" else: log.info("Not connected to internet, unable to start") - print "Not connected to internet, unable to start" + if not args.logtoconsole: + print "Not connected to internet, unable to start" return diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py new file mode 100644 index 000000000..deb23e51d --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -0,0 +1,210 @@ +import logging +import subprocess +import os +import shutil +import jsonrpc +import json + +from StringIO import StringIO +from zipfile import ZipFile +from urllib import urlopen +from datetime import datetime +from appdirs import user_data_dir +from twisted.web import server, static, resource +from twisted.internet import defer + +from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon +from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS + +log = logging.getLogger(__name__) + +data_dir = user_data_dir("LBRY") +if not os.path.isdir(data_dir): + os.mkdir(data_dir) +version_dir = os.path.join(data_dir, "ui_version_history") +if not os.path.isdir(version_dir): + os.mkdir(version_dir) + +version_log = logging.getLogger("lbry_version") +version_log.addHandler(logging.FileHandler(os.path.join(version_dir, "lbry_version.log"))) +version_log.setLevel(logging.INFO) + + +class LBRYindex(resource.Resource): + def __init__(self, ui_dir): + resource.Resource.__init__(self) + self.ui_dir = ui_dir + + isLeaf = False + + def _delayed_render(self, request, results): + request.write(str(results)) + request.finish() + + def getChild(self, name, request): + if name == '': + return self + return resource.Resource.getChild(self, name, request) + + def render_GET(self, request): + return static.File(os.path.join(self.ui_dir, "index.html")).render_GET(request) + + +class LBRYFileRender(resource.Resource): + isLeaf = False + + def render_GET(self, request): + if 'name' in request.args.keys(): + api = jsonrpc.Proxy(API_CONNECTION_STRING) + if request.args['name'][0] != 'lbry': + d = api.callRemote("get", {'name': request.args['name'][0]}) + d.addCallback(lambda results: static.File(results['path']).render_GET(request)) + else: + request.redirect(UI_ADDRESS) + request.finish() + return server.NOT_DONE_YET + else: + return server.failure + + +class LBRYBugReport(resource.Resource): + isLeaf = False + + def _delayed_render(self, request, results): + request.write(results) + request.finish() + + def render_GET(self, request): + return '
' \ + '
Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!
' \ + '' \ + '' \ + '' + + def render_POST(self, request): + msg = request.args["message"][0] + log.info("User submitted error report: " + str(msg)) + api = jsonrpc.Proxy(API_CONNECTION_STRING) + d = api.callRemote("upload_log", {'name_prefix': 'report', 'exclude_previous': False, 'force': True}) + d.addCallback(lambda _: self._delayed_render(request, "Your bug report is greatly appreciated! Click here to return to LBRY")) + + return server.NOT_DONE_YET + + +class LBRYDaemonServer(object): + def __init__(self): + self.data_dir = user_data_dir("LBRY") + if not os.path.isdir(self.data_dir): + os.mkdir(self.data_dir) + self.version_dir = os.path.join(self.data_dir, "ui_version_history") + if not os.path.isdir(self.version_dir): + os.mkdir(self.version_dir) + self.config = os.path.join(self.version_dir, "active.json") + self.ui_dir = os.path.join(self.data_dir, "lbry-web-ui") + self.git_version = None + self._api = None + self.root = None + + if not os.path.isfile(os.path.join(self.config)): + self.loaded_git_version = None + else: + try: + f = open(self.config, "r") + loaded_ui = json.loads(f.read()) + f.close() + self.loaded_git_version = loaded_ui['commit'] + except: + self.loaded_git_version = None + + def setup(self, branch="HEAD", user_specified=None): + self.branch = branch + if user_specified: + if os.path.isdir(user_specified): + log.info("Using user specified UI directory: " + str(user_specified)) + self.branch = "user-specified" + self.loaded_git_version = "user-specified" + self.ui_dir = user_specified + return defer.succeed("user-specified") + else: + log.info("User specified UI directory doesn't exist, using " + branch) + elif branch == "HEAD": + log.info("Using UI branch: " + branch) + self._gitcmd = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep %s | cut -f 1" % branch + self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/master/dist.zip" + else: + log.info("Using UI branch: " + branch) + self._gitcmd = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep refs/heads/%s | cut -f 1" % branch + self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % branch + + d = self._up_to_date() + d.addCallback(lambda r: self._download_ui() if not r else self.branch) + return d + + def _up_to_date(self): + def _get_git_info(): + r = subprocess.check_output(self._gitcmd, shell=True) + return defer.succeed(r) + + def _set_git(version): + self.git_version = version + log.info("UI version from git: " + str(self.git_version).replace("\n", "")) + version_log.info("UI version from git: " + str(self.git_version).replace("\n", "")) + + if self.git_version == self.loaded_git_version and os.path.isdir(self.ui_dir): + log.info("UI is up to date") + version_log.info("UI is up to date") + return defer.succeed(True) + else: + log.info("Downloading UI") + version_log.info("Downloading UI") + f = open(self.config, "w") + f.write(json.dumps({'commit': self.git_version, + 'time': str(datetime.now())})) + f.close() + return defer.succeed(False) + + d = _get_git_info() + d.addCallback(_set_git) + return d + + def _download_ui(self): + def _delete_ui_dir(): + if os.path.isdir(self.ui_dir): + if self.loaded_git_version: + version_log.info("Removed ui files for commit " + str(self.loaded_git_version)) + log.info("Removing out of date ui files") + shutil.rmtree(self.ui_dir) + return defer.succeed(None) + + def _dl_ui(): + url = urlopen(self._dist_url) + z = ZipFile(StringIO(url.read())) + names = [i for i in z.namelist() if '.DS_exStore' not in i and '__MACOSX' not in i] + z.extractall(self.ui_dir, members=names) + version_log.info("[" + str(datetime.now()) + "] Updated branch " + self.branch + " commit: " + str(self.loaded_git_version) + " to " + self.git_version) + log.info("Downloaded files for UI commit " + self.git_version) + self.loaded_git_version = self.git_version + return self.branch + + d = _delete_ui_dir() + d.addCallback(lambda _: _dl_ui()) + return d + + def _setup_server(self, ui_ver): + self._api = LBRYDaemon(ui_ver, wallet_type=DEFAULT_WALLET) + self.root = LBRYindex(self.ui_dir) + self.root.putChild("css", static.File(os.path.join(self.ui_dir, "css"))) + self.root.putChild("font", static.File(os.path.join(self.ui_dir, "font"))) + self.root.putChild("img", static.File(os.path.join(self.ui_dir, "img"))) + self.root.putChild("js", static.File(os.path.join(self.ui_dir, "js"))) + self.root.putChild("view", LBRYFileRender()) + self.root.putChild("report", LBRYBugReport()) + self.root.putChild(API_ADDRESS, self._api) + return defer.succeed(True) + + def start(self, branch="HEAD", user_specified=False): + d = self.setup(branch=branch, user_specified=user_specified) + d.addCallback(lambda v: self._setup_server(v)) + d.addCallback(lambda _: self._api.setup()) + + return d From 561cdba7c9c873402c96f478b9ea835c444d2592 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Apr 2016 22:59:46 -0400 Subject: [PATCH 145/462] fix jsonrpc import --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index deb23e51d..5cb76072f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -2,7 +2,7 @@ import logging import subprocess import os import shutil -import jsonrpc +from txjsonrpc.web import jsonrpc import json from StringIO import StringIO From 2c1ba623a45928075c520f3d9eb56f2036d7a553 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Apr 2016 23:50:13 -0400 Subject: [PATCH 146/462] add message key to upload_log log the message which is then uploaded with a special file prefix, and also send the message to slack --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 11 +++++++++++ lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index c30b5b594..b9613c93e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -989,6 +989,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): r.append(t) return r + def _log_to_slack(self, msg): + URL = "https://hooks.slack.com/services/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" + msg = platform.platform() + ": " + base58.b58encode(self.lbryid)[:20] + ", " + msg + requests.post(URL, json.dumps({"text": msg})) + return defer.succeed(None) + def _render_response(self, result, code): return defer.succeed({'result': result, 'code': code}) @@ -1721,6 +1727,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: exclude_previous = True + if 'message' in p.keys(): + log.info("[" + str(datetime.now()) + "] Upload log message: " + str(p['message'])) + if 'force' in p.keys(): force = p['force'] else: @@ -1730,5 +1739,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): exclude_previous = True d = self._upload_log(name_prefix=prefix, exclude_previous=exclude_previous, force=force) + if 'message' in p.keys(): + d.addCallback(lambda _: self._log_to_slack(p['message'])) d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 5cb76072f..14310dfcb 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -83,9 +83,8 @@ class LBRYBugReport(resource.Resource): def render_POST(self, request): msg = request.args["message"][0] - log.info("User submitted error report: " + str(msg)) api = jsonrpc.Proxy(API_CONNECTION_STRING) - d = api.callRemote("upload_log", {'name_prefix': 'report', 'exclude_previous': False, 'force': True}) + d = api.callRemote("upload_log", {'name_prefix': 'report', 'exclude_previous': False, 'force': True, 'message': str(msg)}) d.addCallback(lambda _: self._delayed_render(request, "Your bug report is greatly appreciated! Click here to return to LBRY")) return server.NOT_DONE_YET From c22eed9109b24b1fa0842cf99a3f60e3f0e2dc23 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Thu, 21 Apr 2016 17:18:51 -0400 Subject: [PATCH 147/462] improved unity description --- packaging/ubuntu/lbry.desktop | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index d5c029ec6..832d673c3 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,15 +1,19 @@ [Desktop Entry] Version=0.2.2 Name=LBRY -# Only KDE 4 seems to use GenericName, so we reuse the KDE strings. -# From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413. -GenericName=Filesharing -# Gnome and KDE 3 uses Comment. -Comment=Stream. Share. Earn. -#Exec=/usr/bin/xdg-open http://localhost:5279/view?name=%U -Exec=/usr/share/python/lbrynet/bin/lbry %U -Terminal=false +Comment=The world's first user-owned content marketplace Icon=/usr/share/python/lbrynet/lbrynet/lbrynet_gui/lbry.png -Type=Application +GenericName=Content Marketplace Categories=Network;Internet;Filesharing +Terminal=false +Type=Application + MimeType=x-scheme-handler/lbry; + +Exec=/usr/share/python/lbrynet/bin/lbry %U + +Actions=StopDaemon; + +[Desktop Action StopDaemon] +Name=Stop Daemon +Exec=/usr/share/python/lbrynet/bin/stop-lbrynet-daemon From fecd29a9676e3993ad93b835d33a126d4a03eb7e Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 22 Apr 2016 02:45:05 -0400 Subject: [PATCH 148/462] only load /view page when file isn't empty --- lbrynet/conf.py | 12 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 181 ++++++++++++-------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 3 +- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 28 +-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 2 +- 5 files changed, 131 insertions(+), 95 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index f3903ae6a..a4a387991 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -18,8 +18,6 @@ MIN_BLOB_INFO_PAYMENT_RATE = .02 # points/1000 infos MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .05 # points/1000 infos MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .05 # points/1000 infos MAX_CONNECTIONS_PER_STREAM = 5 -DEFAULT_MAX_SEARCH_RESULTS = 25 -DEFAULT_MAX_KEY_FEE = 100.0 KNOWN_DHT_NODES = [('104.236.42.182', 4000)] @@ -33,11 +31,13 @@ API_ADDRESS = "lbryapi" API_PORT = 5279 ICON_PATH = "app.icns" APP_NAME = "LBRY" -DEFAULT_WALLET = "lbryum" - API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRESS) UI_ADDRESS = "http://" + API_INTERFACE + ":" + str(API_PORT) - PROTOCOL_PREFIX = "lbry" -DEFAULT_TIMEOUT = 30 \ No newline at end of file +DEFAULT_WALLET = "lbryum" +DEFAULT_TIMEOUT = 30 +DEFAULT_MAX_SEARCH_RESULTS = 25 +DEFAULT_MAX_KEY_FEE = 100.0 +DEFAULT_SEARCH_TIMEOUT = 3.0 + diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index b9613c93e..36a1546d9 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -36,7 +36,7 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ - DEFAULT_WALLET, API_INTERFACE + DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS, DEFAULT_TIMEOUT, UI_ADDRESS from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -99,6 +99,7 @@ ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run', BAD_REQUEST = 400 NOT_FOUND = 404 OK_CODE = 200 + # TODO add login credentials in a conf file # TODO alert if your copy of a lbry file is out of date with the name record @@ -127,7 +128,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.git_lbrynet_version = None self.git_lbryum_version = None self.wallet_type = wallet_type - self.session_settings = None self.first_run = None self.log_file = LOG_FILENAME self.fetcher = None @@ -152,7 +152,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle - self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) + default_download_directory = get_path(FOLDERID.Downloads, UserHandle.current) self.db_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrynet") self.lbrycrdd_path = "lbrycrdd.exe" if wallet_type == "lbrycrd": @@ -160,7 +160,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbryum") elif sys.platform == "darwin": - self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + default_download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') self.db_dir = user_data_dir("LBRY") self.lbrycrdd_path = "./lbrycrdd" if wallet_type == "lbrycrd": @@ -168,7 +168,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self.wallet_dir = user_data_dir("LBRY") else: - self.download_directory = os.getcwd() + default_download_directory = os.getcwd() self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") self.lbrycrdd_path = "./lbrycrdd" if wallet_type == "lbrycrd": @@ -192,6 +192,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.internet_connection_checker = LoopingCall(self._check_network_connection) self.version_checker = LoopingCall(self._check_remote_versions) self.connection_problem_checker = LoopingCall(self._check_connection_problems) + # self.lbrynet_connection_checker = LoopingCall(self._check_lbrynet_connection) self.sd_identifier = StreamDescriptorIdentifier() self.stream_info_manager = TempLBRYFileMetadataManager() @@ -200,16 +201,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.lbry_file_metadata_manager = None self.lbry_file_manager = None - #defaults for settings otherwise loaded from daemon_settings.json self.default_settings = { 'run_on_startup': False, 'data_rate': MIN_BLOB_DATA_PAYMENT_RATE, 'max_key_fee': DEFAULT_MAX_KEY_FEE, - 'default_download_directory': self.download_directory, + 'download_directory': default_download_directory, 'max_upload': 0.0, 'max_download': 0.0, 'upload_log': True, - 'search_timeout': 3.0, + 'search_timeout': DEFAULT_SEARCH_TIMEOUT, + 'download_timeout': DEFAULT_TIMEOUT, 'max_search_results': DEFAULT_MAX_SEARCH_RESULTS, 'wallet_type': wallet_type, 'delete_blobs_on_remove': True, @@ -220,6 +221,58 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'requested_first_run_credits': False } + if os.path.isfile(self.daemon_conf): + f = open(self.daemon_conf, "r") + loaded_settings = json.loads(f.read()) + f.close() + missing_settings = {} + removed_settings = {} + for k in self.default_settings.keys(): + if k not in loaded_settings.keys(): + missing_settings[k] = self.default_settings[k] + for k in loaded_settings.keys(): + if not k in self.default_settings.keys(): + log.info("Removing unused setting: " + k + " with value: " + str(loaded_settings[k])) + removed_settings[k] = loaded_settings[k] + del loaded_settings[k] + for k in missing_settings.keys(): + log.info("Adding missing setting: " + k + " with default value: " + str(missing_settings[k])) + loaded_settings[k] = missing_settings[k] + if missing_settings or removed_settings: + f = open(self.daemon_conf, "w") + f.write(json.dumps(loaded_settings)) + f.close() + else: + log.info("Loaded lbrynet-daemon configuration") + settings_dict = loaded_settings + else: + log.info( + "Writing default settings : " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.default_settings)) + f.close() + settings_dict = self.default_settings + + self.session_settings = settings_dict + + self.run_on_startup = self.session_settings['run_on_startup'] + self.data_rate = self.session_settings['data_rate'] + self.max_key_fee = self.session_settings['max_key_fee'] + self.download_directory = self.session_settings['download_directory'] + self.max_upload = self.session_settings['max_upload'] + self.max_download = self.session_settings['max_download'] + self.upload_log = self.session_settings['upload_log'] + self.search_timeout = self.session_settings['search_timeout'] + self.download_timeout = self.session_settings['download_timeout'] + self.max_search_results = self.session_settings['max_search_results'] + self.wallet_type = self.session_settings['wallet_type'] + self.delete_blobs_on_remove = self.session_settings['delete_blobs_on_remove'] + self.peer_port = self.session_settings['peer_port'] + self.dht_node_port = self.session_settings['dht_node_port'] + self.use_upnp = self.session_settings['use_upnp'] + self.start_lbrycrdd = self.session_settings['start_lbrycrdd'] + self.requested_first_run_credits = self.session_settings['requested_first_run_credits'] + def render(self, request): request.content.seek(0, 0) # Unmarshal the JSON-RPC data. @@ -300,6 +353,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.announced_startup = True self.startup_status = STARTUP_STAGES[5] log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") + # self.lbrynet_connection_checker.start(3600) if self.first_run: d = self._upload_log(name_prefix="fr") @@ -321,7 +375,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = defer.Deferred() d.addCallback(lambda _: self._initial_setup()) - d.addCallback(self._set_daemon_settings) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) d.addCallback(lambda _: self._get_settings()) @@ -345,42 +398,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("Platform: " + json.dumps(self.platform_info)) return defer.succeed(None) - def _load_daemon_conf(): - if os.path.isfile(self.daemon_conf): - f = open(self.daemon_conf, "r") - loaded_settings = json.loads(f.read()) - f.close() - missing_settings = {} - for k in self.default_settings.keys(): - if k not in loaded_settings.keys(): - missing_settings[k] = self.default_settings[k] - if missing_settings != {}: - for k in missing_settings.keys(): - log.info("Adding missing setting: " + k + " with default value: " + str(missing_settings[k])) - loaded_settings[k] = missing_settings[k] - f = open(self.daemon_conf, "w") - f.write(json.dumps(loaded_settings)) - f.close() - rsettings = loaded_settings - else: - log.info("Writing default settings : " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) - f = open(self.daemon_conf, "w") - f.write(json.dumps(self.default_settings)) - f.close() - rsettings = self.default_settings - for k in rsettings.keys(): - self.__dict__[k] = rsettings[k] - return rsettings - d = _log_platform() - d.addCallback(lambda _: _load_daemon_conf()) return d - def _set_daemon_settings(self, settings): - self.session_settings = settings - return defer.succeed(None) - def _check_network_connection(self): try: host = socket.gethostbyname(REMOTE_SERVER) @@ -390,6 +411,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("[" + str(datetime.now()) + "] Internet connection not working") self.connected_to_internet = False + def _check_lbrynet_connection(self): + def _log_success(): + log.info("[" + str(datetime.now()) + "] lbrynet connectivity test passed") + def _log_failure(): + log.info("[" + str(datetime.now()) + "] lbrynet connectivity test failed") + + wonderfullife_sh = "6f3af0fa3924be98a54766aa2715d22c6c1509c3f7fa32566df4899a41f3530a9f97b2ecb817fa1dcbf1b30553aefaa7" + d = download_sd_blob(self.session, wonderfullife_sh, self.session.base_payment_rate_manager) + d.addCallbacks(lambda _: _log_success, lambda _: _log_failure) + def _check_remote_versions(self): def _get_lbryum_version(): try: @@ -514,7 +545,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: name_prefix = platform.system() - if self.session_settings['upload_log'] or force: + if self.upload_log or force: LOG_URL = "https://lbry.io/log-upload" if exclude_previous: f = open(self.log_file, "r") @@ -567,10 +598,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.session_settings['max_key_fee'] = float(settings['max_key_fee']) else: return defer.fail() - elif k == 'default_download_directory': - if type(settings['default_download_directory']) is unicode: - if os.path.isdir(settings['default_download_directory']): - self.session_settings['default_download_directory'] = settings['default_download_directory'] + elif k == 'download_directory': + if type(settings['download_directory']) is unicode: + if os.path.isdir(settings['download_directory']): + self.session_settings['download_directory'] = settings['download_directory'] else: pass else: @@ -595,6 +626,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: return defer.fail() + self.run_on_startup = self.session_settings['run_on_startup'] + self.data_rate = self.session_settings['data_rate'] + self.max_key_fee = self.session_settings['max_key_fee'] + self.download_directory = self.session_settings['download_directory'] + self.max_upload = self.session_settings['max_upload'] + self.max_download = self.session_settings['max_download'] + self.upload_log = self.session_settings['upload_log'] + f = open(self.daemon_conf, "w") f.write(json.dumps(self.session_settings)) f.close() @@ -748,7 +787,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): r = requests.post(url, json=data) if r.status_code == 200: self.requested_first_run_credits = True - self.session_settings['requested_first_run_credits'] = True f = open(self.daemon_conf, "w") f.write(json.dumps(self.session_settings)) f.close() @@ -801,7 +839,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _setup_stream_identifier(self): file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter, self.session.blob_manager, self.stream_info_manager, - self.session.wallet, self.session_settings['default_download_directory']) + self.session.wallet, self.download_directory) self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_saver_factory) file_opener_factory = LBRYFileOpenerFactory(self.session.peer_finder, self.session.rate_limiter, self.session.blob_manager, self.stream_info_manager, @@ -819,12 +857,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None): if not download_directory: - download_directory = self.session_settings['default_download_directory'] + download_directory = self.download_directory elif not os.path.isdir(download_directory): - download_directory = self.session_settings['default_download_directory'] + download_directory = self.download_directory def _disp_file(f): - file_path = os.path.join(self.session_settings['default_download_directory'], f.file_name) + file_path = os.path.join(self.download_directory, f.file_name) log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) return defer.succeed(f) @@ -850,7 +888,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self._check_history(name) d.addCallback(lambda lbry_file: _get_stream(name) if not lbry_file else _disp_file(lbry_file)) d.addCallback(lambda _: self._path_from_name(name)) - d.addErrback(lambda err: defer.fail(NOT_FOUND)) return d @@ -880,7 +917,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): for lbry_file in self.lbry_file_manager.lbry_files: if lbry_file.stream_name == file_name: if sys.platform == "darwin": - if os.path.isfile(os.path.join(self.session_settings['default_download_directory'], lbry_file.stream_name)): + if os.path.isfile(os.path.join(self.download_directory, lbry_file.stream_name)): return lbry_file else: return False @@ -921,8 +958,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.lbry_file_manager.get_count_for_stream_hash(s_h) # TODO: could possibly be a timing issue here d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) - d.addCallback(lambda _: os.remove(os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)) if - os.path.isfile(os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)) else defer.succeed(None)) + d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if + os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) else defer.succeed(None)) return d d.addCallback(lambda _: finish_deletion(lbry_file)) @@ -931,21 +968,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _path_from_name(self, name): d = self._check_history(name) d.addCallback(lambda lbry_file: {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)} + 'path': os.path.join(self.download_directory, lbry_file.file_name)} if lbry_file else defer.fail(UnknownNameError)) return d def _path_from_lbry_file(self, lbry_file): if lbry_file: r = {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.session_settings['default_download_directory'], lbry_file.file_name)} + 'path': os.path.join(self.download_directory, lbry_file.file_name)} return defer.succeed(r) else: return defer.fail(UnknownNameError) def _get_est_cost(self, name): def _check_est(d, name): - if type(d.result) is float: + if isinstance(d.result, float): log.info("[" + str(datetime.now()) + "] Cost est for lbry://" + name + ": " + str(d.result) + "LBC") else: log.info("[" + str(datetime.now()) + "] Timeout estimating cost for lbry://" + name + ", using key fee") @@ -1121,11 +1158,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'run_on_startup': bool, 'data_rate': float, 'max_key_fee': float, - 'default_download_directory': string, + 'download_directory': string, 'max_upload': float, 0.0 for unlimited 'max_download': float, 0.0 for unlimited 'upload_log': bool, 'search_timeout': float, + 'download_timeout': int 'max_search_results': int, 'wallet_type': string, 'delete_blobs_on_remove': bool, @@ -1146,27 +1184,19 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'run_on_startup': bool, 'data_rate': float, 'max_key_fee': float, - 'default_download_directory': string, + 'download_directory': string, 'max_upload': float, 0.0 for unlimited 'max_download': float, 0.0 for unlimited 'upload_log': bool, - 'search_timeout': float, - 'max_search_results': int, - 'wallet_type': string, - 'delete_blobs_on_remove': bool, - 'peer_port': int, - 'dht_node_port': int, - 'use_upnp': bool, - 'start_lbrycrdd': bool, Returns: settings dict """ - def _log_settings_change(params): - log.info("[" + str(datetime.now()) + "] Set daemon settings to " + str(params)) + def _log_settings_change(): + log.info("[" + str(datetime.now()) + "] Set daemon settings to " + json.dumps(self.session_settings)) d = self._update_settings(p) - d.addCallback(lambda _: _log_settings_change(p)) + d.addCallback(lambda _: _log_settings_change()) d.addCallback(lambda _: self._render_response(self.session_settings, OK_CODE)) return d @@ -1334,22 +1364,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ if 'timeout' not in p.keys(): - timeout = DEFAULT_TIMEOUT + timeout = self.download_timeout else: timeout = p['timeout'] if 'download_directory' not in p.keys(): - download_directory = self.session_settings['default_download_directory'] + download_directory = self.download_directory else: download_directory = p['download_directory'] if 'name' in p.keys(): name = p['name'] d = self._download_name(name=name, timeout=timeout, download_directory=download_directory) - d.addCallbacks(lambda message: self._render_response(message, OK_CODE), - lambda err: self._render_response('error', NOT_FOUND)) + d.addCallback(lambda message: self._render_response(message, OK_CODE)) else: - d = self._render_response('error', BAD_REQUEST) + d = server.failure return d diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 0321de4df..9cf3f4143 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -119,7 +119,8 @@ def start(): lbry = LBRYDaemonServer() d = lbry.start(branch=args.branch, user_specified=args.ui) - d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) + if args.launchui: + d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) reactor.listenTCP(API_PORT, server.Site(lbry.root), interface=API_INTERFACE) reactor.run() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 14310dfcb..12dc9d0b2 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -58,7 +58,9 @@ class LBRYFileRender(resource.Resource): api = jsonrpc.Proxy(API_CONNECTION_STRING) if request.args['name'][0] != 'lbry': d = api.callRemote("get", {'name': request.args['name'][0]}) - d.addCallback(lambda results: static.File(results['path']).render_GET(request)) + d.addCallback(lambda results: static.File(results['path'])) + d.addCallback(lambda static_file: static_file.render_GET(request) if static_file.getFileSize() > 0 + else server.failure) else: request.redirect(UI_ADDRESS) request.finish() @@ -112,8 +114,11 @@ class LBRYDaemonServer(object): loaded_ui = json.loads(f.read()) f.close() self.loaded_git_version = loaded_ui['commit'] + self.loaded_branch = loaded_ui['branch'] + version_log.info("[" + str(datetime.now()) + "] Last used " + self.loaded_branch + " commit " + str(self.loaded_git_version).replace("\n", "")) except: self.loaded_git_version = None + self.loaded_branch = None def setup(self, branch="HEAD", user_specified=None): self.branch = branch @@ -146,19 +151,20 @@ class LBRYDaemonServer(object): def _set_git(version): self.git_version = version - log.info("UI version from git: " + str(self.git_version).replace("\n", "")) - version_log.info("UI version from git: " + str(self.git_version).replace("\n", "")) + version_log.info("[" + str(datetime.now()) + "] UI branch " + self.branch + " has a most recent commit of: " + str(self.git_version).replace("\n", "")) if self.git_version == self.loaded_git_version and os.path.isdir(self.ui_dir): - log.info("UI is up to date") - version_log.info("UI is up to date") + version_log.info("[" + str(datetime.now()) + "] local copy of " + self.branch + " is up to date") return defer.succeed(True) else: - log.info("Downloading UI") - version_log.info("Downloading UI") + if self.git_version == self.loaded_git_version: + version_log.info("[" + str(datetime.now()) + "] Can't find ui files, downloading them again") + else: + version_log.info("[" + str(datetime.now()) + "] local copy of " + self.branch + " branch is out of date, updating") f = open(self.config, "w") f.write(json.dumps({'commit': self.git_version, - 'time': str(datetime.now())})) + 'time': str(datetime.now()), + 'branch': self.branch})) f.close() return defer.succeed(False) @@ -170,7 +176,7 @@ class LBRYDaemonServer(object): def _delete_ui_dir(): if os.path.isdir(self.ui_dir): if self.loaded_git_version: - version_log.info("Removed ui files for commit " + str(self.loaded_git_version)) + version_log.info("[" + str(datetime.now()) + "] Removed ui files for commit " + str(self.loaded_git_version).replace("\n", "")) log.info("Removing out of date ui files") shutil.rmtree(self.ui_dir) return defer.succeed(None) @@ -180,8 +186,8 @@ class LBRYDaemonServer(object): z = ZipFile(StringIO(url.read())) names = [i for i in z.namelist() if '.DS_exStore' not in i and '__MACOSX' not in i] z.extractall(self.ui_dir, members=names) - version_log.info("[" + str(datetime.now()) + "] Updated branch " + self.branch + " commit: " + str(self.loaded_git_version) + " to " + self.git_version) - log.info("Downloaded files for UI commit " + self.git_version) + version_log.info("[" + str(datetime.now()) + "] Updated branch " + self.branch + ": " + str(self.loaded_git_version).replace("\n", "") + " --> " + self.git_version.replace("\n", "")) + log.info("Downloaded files for UI commit " + str(self.git_version).replace("\n", "")) self.loaded_git_version = self.git_version return self.branch diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 5ebc4b59e..f0c157e42 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -118,7 +118,7 @@ class GetStream(object): downloader.start() self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d.addCallback(lambda _: log.info("Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) + log.info("Downloading " + str(self.stream_hash) + " --> " + str(self.download_path)) return d From bac7f097a99379769a1622e4d3f8926504992feb Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 22 Apr 2016 02:45:41 -0400 Subject: [PATCH 149/462] add -x to filename before extension for duplicate downloads --- lbrynet/lbryfile/client/LBRYFileDownloader.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbryfile/client/LBRYFileDownloader.py b/lbrynet/lbryfile/client/LBRYFileDownloader.py index a65f49484..16ee9425d 100644 --- a/lbrynet/lbryfile/client/LBRYFileDownloader.py +++ b/lbrynet/lbryfile/client/LBRYFileDownloader.py @@ -211,10 +211,20 @@ class LBRYFileSaver(LBRYFileDownloader): file_name = "_" if os.path.exists(os.path.join(self.download_directory, file_name)): ext_num = 1 + + def _get_file_name(ext): + if len(file_name.split(".")): + fn = ''.join(file_name.split(".")[:-1]) + file_ext = ''.join(file_name.split(".")[-1]) + return fn + "-" + str(ext) + "." + file_ext + else: + return file_name + "_" + str(ext) + while os.path.exists(os.path.join(self.download_directory, - file_name + "_" + str(ext_num))): + _get_file_name(ext_num))): ext_num += 1 - file_name = file_name + "_" + str(ext_num) + + file_name = _get_file_name(ext_num) try: self.file_handle = open(os.path.join(self.download_directory, file_name), 'wb') self.file_written_to = os.path.join(self.download_directory, file_name) From 28f66e30dd19f769d93b2de5cc5d3520f350a94f Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 22 Apr 2016 18:18:17 -0400 Subject: [PATCH 150/462] improve timeouts --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 17 +++++++--- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 7 ++++- lbrynet/lbrynet_daemon/LBRYDownloader.py | 36 ++++++++++++---------- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 36a1546d9..a44607505 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -625,6 +625,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.session_settings['upload_log'] = settings['upload_log'] else: return defer.fail() + elif k == 'download_timeout': + if type(settings['download_timeout']) is int: + self.session_settings['download_timeout'] = settings['download_timeout'] + else: + return defer.fail() self.run_on_startup = self.session_settings['run_on_startup'] self.data_rate = self.session_settings['data_rate'] @@ -633,6 +638,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.max_upload = self.session_settings['max_upload'] self.max_download = self.session_settings['max_download'] self.upload_log = self.session_settings['upload_log'] + self.download_timeout = self.session_settings['download_timeout'] f = open(self.daemon_conf, "w") f.write(json.dumps(self.session_settings)) @@ -864,7 +870,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _disp_file(f): file_path = os.path.join(self.download_directory, f.file_name) log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) - return defer.succeed(f) + return {'stream_hash': f.stream_hash, 'path': file_path} def _get_stream(name): def _disp(stream): @@ -880,14 +886,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, download_directory=download_directory) d.addCallback(_disp) - d.addCallback(lambda stream_info: stream.start(stream_info)) - d.addCallback(lambda _: self._path_from_name(name)) + d.addCallback(lambda stream_info: stream.start(stream_info, name)) + d.addCallback(lambda r: {'stream_hash': r[0], 'path': r[1]} if r else server.failure) return d d = self._check_history(name) d.addCallback(lambda lbry_file: _get_stream(name) if not lbry_file else _disp_file(lbry_file)) - d.addCallback(lambda _: self._path_from_name(name)) return d @@ -933,7 +938,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): path = os.path.join(self.blobfile_dir, stream_hash) if os.path.isfile(path): - log.info("[" + str(datetime.now()) + "] Search for lbry_file, returning: " + stream_hash) + file_size = os.stat(path).st_size + log.info("[" + str(datetime.now()) + "] Search for lbry_file, found " + str(file_size) + " bytes written from stream hash: " + stream_hash) return defer.succeed(_get_lbry_file(path)) else: log.info("[" + str(datetime.now()) + "] Search for lbry_file didn't return anything") @@ -1188,6 +1194,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'max_upload': float, 0.0 for unlimited 'max_download': float, 0.0 for unlimited 'upload_log': bool, + 'download_timeout': int Returns: settings dict """ diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 12dc9d0b2..04ad5f561 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -50,6 +50,11 @@ class LBRYindex(resource.Resource): return static.File(os.path.join(self.ui_dir, "index.html")).render_GET(request) +class HostedLBRYFile(static.File): + def __init__(self, path): + static.File.__init__(self, path=path) + + class LBRYFileRender(resource.Resource): isLeaf = False @@ -58,7 +63,7 @@ class LBRYFileRender(resource.Resource): api = jsonrpc.Proxy(API_CONNECTION_STRING) if request.args['name'][0] != 'lbry': d = api.callRemote("get", {'name': request.args['name'][0]}) - d.addCallback(lambda results: static.File(results['path'])) + d.addCallback(lambda results: HostedLBRYFile(results['path'])) d.addCallback(lambda static_file: static_file.render_GET(request) if static_file.getFileSize() > 0 else server.failure) else: diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index f0c157e42..32622534c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -13,6 +13,7 @@ from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader from lbrynet.conf import DEFAULT_TIMEOUT log = logging.getLogger(__name__) +log.setLevel(logging.INFO) class GetStream(object): @@ -39,22 +40,24 @@ class GetStream(object): self.timeout_counter = 0 self.download_directory = download_directory self.download_path = None + self.finished = defer.Deferred() self.checker = LoopingCall(self.check_status) - def check_status(self): self.timeout_counter += 1 - if self.download_path and os.path.isfile(self.download_path): + if self.download_path: self.checker.stop() - return defer.succeed(True) + self.finished.callback((self.stream_hash, self.download_path)) elif self.timeout_counter >= self.timeout: - log.info("Timeout downloading " + str(self.stream_info)) + log.info("Timeout downloading lbry://" + self.resolved_name + ", " + str(self.stream_info)) self.checker.stop() self.d.cancel() + self.finished.callback(False) - def start(self, stream_info): + def start(self, stream_info, name): + self.resolved_name = name self.stream_info = stream_info if 'stream_hash' in self.stream_info.keys(): self.description = self.stream_info['description'] @@ -84,21 +87,24 @@ class GetStream(object): else: pass + def _cause_timeout(): + self.timeout_counter = self.timeout * 2 + self.checker.start(1) self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) - self.d.addCallback(lambda metadata: (next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), metadata)) + self.d.addCallback(lambda metadata: ( + next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), + metadata)) self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager, download_directory=self.download_directory)) - self.d.addErrback(lambda err: err.trap(defer.CancelledError)) - self.d.addErrback(lambda err: log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback())) - self.d.addCallback(self._start_download) + self.d.addCallbacks(self._start_download, lambda _: _cause_timeout()) self.d.callback(None) - return self.d + return self.finished def _start_download(self, downloader): def _pay_key_fee(): @@ -114,14 +120,10 @@ class GetStream(object): d = _pay_key_fee() else: d = defer.Deferred() - - downloader.start() - self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - log.info("Downloading " + str(self.stream_hash) + " --> " + str(self.download_path)) - - return d - + d.addCallback(lambda _: log.info("Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) + d.addCallback(lambda _: downloader.start()) + d.callback() class FetcherDaemon(object): def __init__(self, session, lbry_file_manager, lbry_file_metadata_manager, wallet, sd_identifier, autofetcher_conf, From 3e7c09bb44d81c6291609fd1812661574dc169c5 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 22 Apr 2016 18:48:44 -0400 Subject: [PATCH 151/462] fix duplicate/buffering problem fix problem where get request takes time to respond, in this time it can receive more get requests that each manage to start downloading --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 14 ++++++++++++-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 1 - 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index a44607505..555e1deff 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -134,6 +134,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.current_db_revision = 1 self.run_server = True self.session = None + self.waiting_on = {} self.known_dht_nodes = KNOWN_DHT_NODES self.platform_info = { "processor": platform.processor(), @@ -867,6 +868,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): elif not os.path.isdir(download_directory): download_directory = self.download_directory + def _remove_from_wait(r): + del self.waiting_on[name] + return r + def _disp_file(f): file_path = os.path.join(self.download_directory, f.file_name) log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) @@ -891,8 +896,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d + self.waiting_on[name] = True d = self._check_history(name) d.addCallback(lambda lbry_file: _get_stream(name) if not lbry_file else _disp_file(lbry_file)) + d.addCallback(_remove_from_wait) return d @@ -1382,8 +1389,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): if 'name' in p.keys(): name = p['name'] - d = self._download_name(name=name, timeout=timeout, download_directory=download_directory) - d.addCallback(lambda message: self._render_response(message, OK_CODE)) + if p['name'] not in self.waiting_on.keys(): + d = self._download_name(name=name, timeout=timeout, download_directory=download_directory) + d.addCallback(lambda message: self._render_response(message, OK_CODE)) + else: + d = server.failure else: d = server.failure diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 32622534c..c8cdc7fbb 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -123,7 +123,6 @@ class GetStream(object): self.download_path = os.path.join(downloader.download_directory, downloader.file_name) d.addCallback(lambda _: log.info("Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) d.addCallback(lambda _: downloader.start()) - d.callback() class FetcherDaemon(object): def __init__(self, session, lbry_file_manager, lbry_file_metadata_manager, wallet, sd_identifier, autofetcher_conf, From c2ec066c851ecb8e13070f3a3483cc4b81c04039 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 24 Apr 2016 04:42:42 -0400 Subject: [PATCH 152/462] add LBRYFileProducer -Add LBRYFileProducer, to host the contents of a download without having to keep re-opening it as it is added to -included sd hash in ManagedLBRYFileDownloader, to make comparing the contents of the file manager against name claims easier -add get_lbry_file function, which returns information about a LBRY file found by sd_hash, file name, or lbry uri --- lbrynet/lbryfilemanager/LBRYFileDownloader.py | 19 +++- lbrynet/lbrynet_daemon/LBRYDaemon.py | 88 +++++++++++++-- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 106 +++++++++++++----- lbrynet/lbrynet_daemon/LBRYDownloader.py | 5 +- 4 files changed, 180 insertions(+), 38 deletions(-) diff --git a/lbrynet/lbryfilemanager/LBRYFileDownloader.py b/lbrynet/lbryfilemanager/LBRYFileDownloader.py index 2afc17588..8bb1ada42 100644 --- a/lbrynet/lbryfilemanager/LBRYFileDownloader.py +++ b/lbrynet/lbryfilemanager/LBRYFileDownloader.py @@ -24,12 +24,21 @@ class ManagedLBRYFileDownloader(LBRYFileSaver): LBRYFileSaver.__init__(self, stream_hash, peer_finder, rate_limiter, blob_manager, stream_info_manager, payment_rate_manager, wallet, download_directory, upload_allowed, file_name) + self.sd_hash = None self.rowid = rowid self.lbry_file_manager = lbry_file_manager self.saving_status = False def restore(self): - d = self.lbry_file_manager.get_lbry_file_status(self) + d = self.stream_info_manager._get_sd_blob_hashes_for_stream(self.stream_hash) + + def _save_sd_hash(sd_hash): + self.sd_hash = sd_hash[0] + return defer.succeed(None) + + d.addCallback(_save_sd_hash) + + d.addCallback(lambda _: self.lbry_file_manager.get_lbry_file_status(self)) def restore_status(status): if status == ManagedLBRYFileDownloader.STATUS_RUNNING: @@ -87,6 +96,14 @@ class ManagedLBRYFileDownloader(LBRYFileSaver): d = LBRYFileSaver._start(self) + d.addCallback(lambda _: self.stream_info_manager._get_sd_blob_hashes_for_stream(self.stream_hash)) + + def _save_sd_hash(sd_hash): + self.sd_hash = sd_hash[0] + return defer.succeed(None) + + d.addCallback(_save_sd_hash) + d.addCallback(lambda _: self._save_status()) return d diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 555e1deff..c0c487b42 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -68,7 +68,6 @@ handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, b log.addHandler(handler) log.setLevel(logging.INFO) - INITIALIZING_CODE = 'initializing' LOADING_DB_CODE = 'loading_db' LOADING_WALLET_CODE = 'loading_wallet' @@ -875,7 +874,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _disp_file(f): file_path = os.path.join(self.download_directory, f.file_name) log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) - return {'stream_hash': f.stream_hash, 'path': file_path} + return f def _get_stream(name): def _disp(stream): @@ -886,13 +885,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("[" + str(datetime.now()) + "] Start stream: " + stream_hash) return stream - d = self.session.wallet.get_stream_info_for_name(name) - stream = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, + stream = GetStream(self.sd_identifier, self.session, self.session.wallet, + self.lbry_file_manager, max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, download_directory=download_directory) + d = self.session.wallet.get_stream_info_for_name(name) d.addCallback(_disp) d.addCallback(lambda stream_info: stream.start(stream_info, name)) - d.addCallback(lambda r: {'stream_hash': r[0], 'path': r[1]} if r else server.failure) + d.addCallback(lambda _: stream.downloader) return d @@ -1028,17 +1028,60 @@ class LBRYDaemon(jsonrpc.JSONRPC): t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, - 'upload_allowed': f.upload_allowed} + 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash} else: t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, - 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed} + 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, + 'sd_hash': f.sd_hash} r.append(t) return r + def _get_lbry_file_by_uri(self, name): + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallback(lambda info: info['stream_hash']) + d.addCallback(lambda sd_hash: next(l for l in self.lbry_file_manager.lbry_files if l.sd_hash == sd_hash)) + return d + + def _get_lbry_file_by_sd_hash(self, sd_hash): + r = next(l for l in self.lbry_file_manager.lbry_files if l.sd_hash == sd_hash) + return defer.succeed(r) + + def _get_lbry_file_by_file_name(self, file_name): + r = next(l for l in self.lbry_file_manager.lbry_files if l.file_name == file_name) + return defer.succeed(r) + + def _get_lbry_file(self, search_by, val): + def _show_file(f): + if f: + if f.key: + t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), + 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash} + + else: + t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, + 'points_paid': f.points_paid, + 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, + 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, + 'sd_hash': f.sd_hash} + return t + else: + return False + + if search_by == "name": + d = self._get_lbry_file_by_uri(val) + elif search_by == "sd_hash": + d = self._get_lbry_file_by_sd_hash(val) + elif search_by == "file_name": + d = self._get_lbry_file_by_file_name(val) + d.addCallback(_show_file) + return d + def _log_to_slack(self, msg): URL = "https://hooks.slack.com/services/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" msg = platform.platform() + ": " + base58.b58encode(self.lbryid)[:20] + ", " + msg @@ -1330,12 +1373,42 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'stream_name': string 'suggested_file_name': string 'upload_allowed': bool + 'sd_hash': string """ r = self._get_lbry_files() log.info("[" + str(datetime.now()) + "] Get LBRY files") return self._render_response(r, OK_CODE) + def jsonrpc_get_lbry_file(self, p): + """ + Get lbry file + + Args: + 'name': get file by lbry uri, + 'sd_hash': get file by the hash in the name claim, + 'file_name': get file by its name in the downloads folder, + Returns: + 'completed': bool + 'file_name': string + 'key': hex string + 'points_paid': float + 'stopped': bool + 'stream_hash': base 58 string + 'stream_name': string + 'suggested_file_name': string + 'upload_allowed': bool + 'sd_hash': string + """ + + if p.keys()[0] in ['name', 'sd_hash', 'file_name']: + search_type = p.keys()[0] + d = self._get_lbry_file(search_type, p[search_type]) + else: + d = defer.fail() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + def jsonrpc_resolve_name(self, p): """ Resolve stream info from a LBRY uri @@ -1391,6 +1464,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): name = p['name'] if p['name'] not in self.waiting_on.keys(): d = self._download_name(name=name, timeout=timeout, download_directory=download_directory) + d.addCallback(lambda l: {'stream_hash': l.sd_hash, 'path': os.path.join(self.download_directory, l.file_name)}) d.addCallback(lambda message: self._render_response(message, OK_CODE)) else: d = server.failure diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 04ad5f561..e68479bbe 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -2,6 +2,8 @@ import logging import subprocess import os import shutil + +from twisted.internet.task import LoopingCall from txjsonrpc.web import jsonrpc import json @@ -12,6 +14,7 @@ from datetime import datetime from appdirs import user_data_dir from twisted.web import server, static, resource from twisted.internet import defer +from twisted.web.static import StaticProducer from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS @@ -50,9 +53,77 @@ class LBRYindex(resource.Resource): return static.File(os.path.join(self.ui_dir, "index.html")).render_GET(request) -class HostedLBRYFile(static.File): - def __init__(self, path): - static.File.__init__(self, path=path) +class LBRYFileProducer(StaticProducer): + def __init__(self, request, lbry_stream): + self.stream = lbry_stream + self.updater = LoopingCall(self._check_for_data) + StaticProducer.__init__(self, request, fileObject=file(lbry_stream.file_written_to)) + + def start(self): + d = self._set_size() + self.updater.start(5) + + def _set_size(self): + def _set(size): + self.request.setHeader('content-length', str(size)) + self.request.setHeader('content-type', ' application/octet-stream') + return defer.succeed(None) + + d = self.stream.get_total_bytes() + d.addCallback(_set) + return d + + def _check_for_data(self): + self.fileObject.seek(self.fileObject.tell()) + data = self.fileObject.read() + if data: + self.request.write(data) + + def _check_status(stream_status): + if stream_status.running_status == "completed": + self.stopProducing() + return defer.succeed(None) + + d = self.stream.status() + d.addCallback(_check_status) + + def resumeProducing(self): + self.updater.start(1) + + def stopProducing(self): + self.updater.stop() + self.fileObject.close() + self.stream.stop() + self.request.finish() + + +class HostedLBRYFile(resource.Resource): + def __init__(self, api): + self._api = api + self.stream = None + self.streaming_file = None + resource.Resource.__init__(self) + + def _set_stream(self, stream): + self.stream = stream + + def makeProducer(self, request, stream): + return LBRYFileProducer(request, stream) + + def render_GET(self, request): + if 'name' in request.args.keys(): + if request.args['name'][0] != 'lbry': + if request.args['name'][0] != self.streaming_file: + self.streaming_file = request.args['name'][0] + d = self._api._download_name(request.args['name'][0]) + d.addCallback(self._set_stream) + else: + d = defer.succeed(None) + d.addCallback(lambda _: self.makeProducer(request, self.stream).start()) + else: + request.redirect(UI_ADDRESS) + request.finish() + return server.NOT_DONE_YET class LBRYFileRender(resource.Resource): @@ -63,7 +134,7 @@ class LBRYFileRender(resource.Resource): api = jsonrpc.Proxy(API_CONNECTION_STRING) if request.args['name'][0] != 'lbry': d = api.callRemote("get", {'name': request.args['name'][0]}) - d.addCallback(lambda results: HostedLBRYFile(results['path'])) + d.addCallback(lambda results: static.File(results['path'], defaultType='video/octet-stream')) d.addCallback(lambda static_file: static_file.render_GET(request) if static_file.getFileSize() > 0 else server.failure) else: @@ -74,29 +145,6 @@ class LBRYFileRender(resource.Resource): return server.failure -class LBRYBugReport(resource.Resource): - isLeaf = False - - def _delayed_render(self, request, results): - request.write(results) - request.finish() - - def render_GET(self, request): - return '
' \ - '
Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!
' \ - '' \ - '' \ - '' - - def render_POST(self, request): - msg = request.args["message"][0] - api = jsonrpc.Proxy(API_CONNECTION_STRING) - d = api.callRemote("upload_log", {'name_prefix': 'report', 'exclude_previous': False, 'force': True, 'message': str(msg)}) - d.addCallback(lambda _: self._delayed_render(request, "Your bug report is greatly appreciated! Click here to return to LBRY")) - - return server.NOT_DONE_YET - - class LBRYDaemonServer(object): def __init__(self): self.data_dir = user_data_dir("LBRY") @@ -207,8 +255,8 @@ class LBRYDaemonServer(object): self.root.putChild("font", static.File(os.path.join(self.ui_dir, "font"))) self.root.putChild("img", static.File(os.path.join(self.ui_dir, "img"))) self.root.putChild("js", static.File(os.path.join(self.ui_dir, "js"))) - self.root.putChild("view", LBRYFileRender()) - self.root.putChild("report", LBRYBugReport()) + # self.root.putChild("view", LBRYFileRender()) + self.root.putChild("view", HostedLBRYFile(self._api)) self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index c8cdc7fbb..b52c8c92a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -40,6 +40,7 @@ class GetStream(object): self.timeout_counter = 0 self.download_directory = download_directory self.download_path = None + self.downloader = None self.finished = defer.Deferred() self.checker = LoopingCall(self.check_status) @@ -120,9 +121,11 @@ class GetStream(object): d = _pay_key_fee() else: d = defer.Deferred() + self.downloader = downloader self.download_path = os.path.join(downloader.download_directory, downloader.file_name) d.addCallback(lambda _: log.info("Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) - d.addCallback(lambda _: downloader.start()) + d.addCallback(lambda _: self.downloader.start()) + class FetcherDaemon(object): def __init__(self, session, lbry_file_manager, lbry_file_metadata_manager, wallet, sd_identifier, autofetcher_conf, From dfaf51a432e6293600849915ac47754541f69c21 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 24 Apr 2016 17:51:24 -0400 Subject: [PATCH 153/462] lbry file improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -fix daemon functions to start/stop lbry files -remove unused stuff in LBRYFileManager -improve and use new get_lbry_file function instead of _check_history, which didn’t use the lbry file manager -use said function to let delete_lbry_file use the same search keys (sd_hash, name, and file_name) -logging in LBRYDownloader --- lbrynet/lbryfilemanager/LBRYFileManager.py | 32 +-- lbrynet/lbrynet_daemon/LBRYDaemon.py | 234 ++++++++++----------- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 48 +++-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 15 +- 4 files changed, 159 insertions(+), 170 deletions(-) diff --git a/lbrynet/lbryfilemanager/LBRYFileManager.py b/lbrynet/lbryfilemanager/LBRYFileManager.py index d0dbca1ae..144425c6f 100644 --- a/lbrynet/lbryfilemanager/LBRYFileManager.py +++ b/lbrynet/lbryfilemanager/LBRYFileManager.py @@ -4,10 +4,7 @@ Keep track of which LBRY Files are downloading and store their LBRY File specifi import logging import os -import sys -from datetime import datetime -from twisted.internet.task import LoopingCall from twisted.enterprise import adbapi from twisted.internet import defer, task, reactor from twisted.python.failure import Failure @@ -28,14 +25,12 @@ class LBRYFileManager(object): Keeps track of currently opened LBRY Files, their options, and their LBRY File specific metadata. """ - def __init__(self, session, stream_info_manager, sd_identifier, delete_data=False, download_directory=None): + def __init__(self, session, stream_info_manager, sd_identifier, download_directory=None): self.session = session self.stream_info_manager = stream_info_manager self.sd_identifier = sd_identifier self.lbry_files = [] self.sql_db = None - # self.delete_data = delete_data - # self.check_exists_loop = LoopingCall(self.check_files_exist) if download_directory: self.download_directory = download_directory else: @@ -43,35 +38,11 @@ class LBRYFileManager(object): log.debug("Download directory for LBRYFileManager: %s", str(self.download_directory)) def setup(self): - # self.check_exists_loop.start(10) - d = self._open_db() d.addCallback(lambda _: self._add_to_sd_identifier()) d.addCallback(lambda _: self._start_lbry_files()) return d - # def check_files_exist(self): - # def _disp(deleted_files): - # if deleted_files[0][0]: - # for file in bad_files: - # log.info("[" + str(datetime.now()) + "] Detected " + file.file_name + " was deleted, removing from file manager") - # - # def _delete_stream_data(lbry_file): - # s_h = lbry_file.stream_hash - # d = self.get_count_for_stream_hash(s_h) - # # TODO: could possibly be a timing issue here - # d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) - # return d - # - # bad_files = [lbry_file for lbry_file in self.lbry_files - # if lbry_file.completed == True and - # os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) == False] - # d = defer.DeferredList([self.delete_lbry_file(lbry_file) for lbry_file in bad_files], consumeErrors=True) - # d.addCallback(lambda files: _disp(files) if len(files) else defer.succeed(None)) - # - # if self.delete_data: - # d2 = defer.DeferredList([_delete_stream_data(lbry_file) for lbry_file in bad_files], consumeErrors=True) - def get_lbry_file_status(self, lbry_file): return self._get_lbry_file_status(lbry_file.rowid) @@ -183,7 +154,6 @@ class LBRYFileManager(object): return defer.fail(Failure(ValueError("Could not find that LBRY file"))) def stop(self): - # self.check_exists_loop.stop() ds = [] diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index c0c487b42..961d6bdca 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -716,7 +716,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.lbry_file_manager = LBRYFileManager(self.session, self.lbry_file_metadata_manager, self.sd_identifier, - delete_data=True) + download_directory=self.download_directory) return self.lbry_file_manager.setup() d.addCallback(lambda _: set_lbry_file_manager()) @@ -862,6 +862,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None): + """ + Add a lbry file to the file manager, start the download, and return the new lbry file + if it already exists in the file manager, return the existing lbry file + """ if not download_directory: download_directory = self.download_directory elif not os.path.isdir(download_directory): @@ -871,34 +875,36 @@ class LBRYDaemon(jsonrpc.JSONRPC): del self.waiting_on[name] return r + def _setup_stream(stream_info): + stream_hash = stream_info['stream_hash'] + if isinstance(stream_hash, dict): + stream_hash = stream_hash['sd_hash'] + log.info("[" + str(datetime.now()) + "] Resolved lbry://" + name + " to sd hash: " + stream_hash) + d = self._get_lbry_file_by_sd_hash(stream_hash) + def _add_results(l): + return defer.succeed((stream_info, l)) + d.addCallback(_add_results) + return d + def _disp_file(f): file_path = os.path.join(self.download_directory, f.file_name) - log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.stream_hash) + " --> " + file_path) + log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.sd_hash) + " --> " + file_path) return f - def _get_stream(name): - def _disp(stream): - stream_hash = stream['stream_hash'] - if isinstance(stream_hash, dict): - stream_hash = stream_hash['sd_hash'] - - log.info("[" + str(datetime.now()) + "] Start stream: " + stream_hash) - return stream - + def _get_stream(stream_info): stream = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, download_directory=download_directory) - d = self.session.wallet.get_stream_info_for_name(name) - d.addCallback(_disp) - d.addCallback(lambda stream_info: stream.start(stream_info, name)) + d = stream.start(stream_info, name) d.addCallback(lambda _: stream.downloader) return d self.waiting_on[name] = True - d = self._check_history(name) - d.addCallback(lambda lbry_file: _get_stream(name) if not lbry_file else _disp_file(lbry_file)) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallback(_setup_stream) + d.addCallback(lambda (stream_info, lbry_file): _get_stream(stream_info) if not lbry_file else _disp_file(lbry_file)) d.addCallback(_remove_from_wait) return d @@ -918,46 +924,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d - def _check_history(self, name): - def _get_lbry_file(path): - f = open(path, 'r') - l = json.loads(f.read()) - f.close() - - file_name = l['stream_name'].decode('hex') - - for lbry_file in self.lbry_file_manager.lbry_files: - if lbry_file.stream_name == file_name: - if sys.platform == "darwin": - if os.path.isfile(os.path.join(self.download_directory, lbry_file.stream_name)): - return lbry_file - else: - return False - else: - return lbry_file - else: - return False - - def _check(info): - stream_hash = info['stream_hash'] - if isinstance(stream_hash, dict): - stream_hash = stream_hash['sd_hash'] - - path = os.path.join(self.blobfile_dir, stream_hash) - if os.path.isfile(path): - file_size = os.stat(path).st_size - log.info("[" + str(datetime.now()) + "] Search for lbry_file, found " + str(file_size) + " bytes written from stream hash: " + stream_hash) - return defer.succeed(_get_lbry_file(path)) - else: - log.info("[" + str(datetime.now()) + "] Search for lbry_file didn't return anything") - return defer.succeed(False) - - d = self._resolve_name(name) - d.addCallback(_check) - d.callback(None) - - return d - def _delete_lbry_file(self, lbry_file): d = self.lbry_file_manager.delete_lbry_file(lbry_file) @@ -978,21 +944,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: finish_deletion(lbry_file)) return d - def _path_from_name(self, name): - d = self._check_history(name) - d.addCallback(lambda lbry_file: {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, lbry_file.file_name)} - if lbry_file else defer.fail(UnknownNameError)) - return d - - def _path_from_lbry_file(self, lbry_file): - if lbry_file: - r = {'stream_hash': lbry_file.stream_hash, - 'path': os.path.join(self.download_directory, lbry_file.file_name)} - return defer.succeed(r) - else: - return defer.fail(UnknownNameError) - def _get_est_cost(self, name): def _check_est(d, name): if isinstance(d.result, float): @@ -1041,21 +992,43 @@ class LBRYDaemon(jsonrpc.JSONRPC): return r def _get_lbry_file_by_uri(self, name): + def _get_file(stream_info): + if isinstance(stream_info['stream_hash'], str) or isinstance(stream_info['stream_hash'], unicode): + sd = stream_info['stream_hash'] + elif isinstance(stream_info['stream_hash'], dict): + sd = stream_info['stream_hash']['sd_hash'] + + for l in self.lbry_file_manager.lbry_files: + if l.sd_hash == sd: + return defer.succeed(l) + return defer.succeed(None) + d = self.session.wallet.get_stream_info_for_name(name) - d.addCallback(lambda info: info['stream_hash']) - d.addCallback(lambda sd_hash: next(l for l in self.lbry_file_manager.lbry_files if l.sd_hash == sd_hash)) + d.addCallback(_get_file) + return d def _get_lbry_file_by_sd_hash(self, sd_hash): - r = next(l for l in self.lbry_file_manager.lbry_files if l.sd_hash == sd_hash) - return defer.succeed(r) + for l in self.lbry_file_manager.lbry_files: + if l.sd_hash == sd_hash: + return defer.succeed(l) + return defer.succeed(None) def _get_lbry_file_by_file_name(self, file_name): - r = next(l for l in self.lbry_file_manager.lbry_files if l.file_name == file_name) - return defer.succeed(r) + for l in self.lbry_file_manager.lbry_files: + if l.file_name == file_name: + return defer.succeed(l) + return defer.succeed(None) - def _get_lbry_file(self, search_by, val): - def _show_file(f): + def _get_lbry_file(self, search_by, val, return_json=True): + def _log_get_lbry_file(f): + if f: + log.info("Found LBRY file for " + search_by + ": " + val) + else: + log.info("Did not find LBRY file for " + search_by + ": " + val) + return f + + def _get_json_for_return(f): if f: if f.key: t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), @@ -1079,7 +1052,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self._get_lbry_file_by_sd_hash(val) elif search_by == "file_name": d = self._get_lbry_file_by_file_name(val) - d.addCallback(_show_file) + d.addCallback(_log_get_lbry_file) + if return_json: + d.addCallback(_get_json_for_return) return d def _log_to_slack(self, msg): @@ -1473,36 +1448,54 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d - # def jsonrpc_stop_lbry_file(self, p): - # params = Bunch(p) - # - # try: - # lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] - # except IndexError: - # return defer.fail(UnknownNameError) - # - # if not lbry_file.stopped: - # d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - # d.addCallback(lambda _: self._render_response("Stream has been stopped", OK_CODE)) - # d.addErrback(lambda err: self._render_response(err.getTraceback(), )) - # return d - # else: - # return json.dumps({'result': 'Stream was already stopped'}) - # - # def jsonrpc_start_lbry_file(self, p): - # params = Bunch(p) - # - # try: - # lbry_file = [f for f in self.lbry_file_manager.lbry_files if f.stream_hash == params.stream_hash][0] - # except IndexError: - # return defer.fail(UnknownNameError) - # - # if lbry_file.stopped: - # d = self.lbry_file_manager.toggle_lbry_file_running(lbry_file) - # d.callback(None) - # return json.dumps({'result': 'Stream started'}) - # else: - # return json.dumps({'result': 'Stream was already running'}) + def jsonrpc_stop_lbry_file(self, p): + """ + Stop lbry file + + Args: + 'name': stop file by lbry uri, + 'sd_hash': stop file by the hash in the name claim, + 'file_name': stop file by its name in the downloads folder, + Returns: + confirmation message + """ + + def _stop_file(f): + d = self.lbry_file_manager.toggle_lbry_file_running(f) + d.addCallback(lambda _: "Stopped LBRY file") + return d + + if p.keys()[0] in ['name', 'sd_hash', 'file_name']: + search_type = p.keys()[0] + d = self._get_lbry_file(search_type, p[search_type], return_json=False) + d.addCallback(lambda l: _stop_file(l) if not l.stopped else "LBRY file wasn't running") + + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_start_lbry_file(self, p): + """ + Stop lbry file + + Args: + 'name': stop file by lbry uri, + 'sd_hash': stop file by the hash in the name claim, + 'file_name': stop file by its name in the downloads folder, + Returns: + confirmation message + """ + + def _start_file(f): + d = self.lbry_file_manager.toggle_lbry_file_running(f) + return defer.succeed("Started LBRY file") + + if p.keys()[0] in ['name', 'sd_hash', 'file_name']: + search_type = p.keys()[0] + d = self._get_lbry_file(search_type, p[search_type], return_json=False) + d.addCallback(lambda l: _start_file(l) if l.stopped else "LBRY file was already running") + + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d def jsonrpc_search_nametrie(self, p): """ @@ -1575,17 +1568,20 @@ class LBRYDaemon(jsonrpc.JSONRPC): confirmation message """ - def _disp(file_name): - log.info("[" + str(datetime.now()) + "] Deleted: " + file_name) - return self._render_response("Deleted: " + file_name, OK_CODE) - - if "file_name" in p.keys(): - lbry_files = [self._delete_lbry_file(f) for f in self.lbry_file_manager.lbry_files - if p['file_name'] == f.file_name] - d = defer.DeferredList(lbry_files) - d.addCallback(lambda _: _disp(p['file_name'])) + def _delete_file(f): + file_name = f.file_name + d = self._delete_lbry_file(f) + d.addCallback(lambda _: "Deleted LBRY file" + file_name) return d + if p.keys()[0] in ['name', 'sd_hash', 'file_name']: + search_type = p.keys()[0] + d = self._get_lbry_file(search_type, p[search_type], return_json=False) + d.addCallback(lambda l: _delete_file(l) if l else "Couldn't find LBRY file to delete") + + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + def jsonrpc_publish(self, p): """ Make a new name claim diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index e68479bbe..167c11e92 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -54,13 +54,20 @@ class LBRYindex(resource.Resource): class LBRYFileProducer(StaticProducer): - def __init__(self, request, lbry_stream): + def __init__(self, request, lbry_stream, api): + self._api = api self.stream = lbry_stream self.updater = LoopingCall(self._check_for_data) - StaticProducer.__init__(self, request, fileObject=file(lbry_stream.file_written_to)) + self.total_bytes = 0 + if lbry_stream.file_written_to: + file_name = lbry_stream.file_written_to + else: + file_name = os.path.join(self._api.download_directory, lbry_stream.file_name) + StaticProducer.__init__(self, request, fileObject=file(file_name)) def start(self): d = self._set_size() + self.fileObject.seek(0) self.updater.start(5) def _set_size(self): @@ -74,17 +81,22 @@ class LBRYFileProducer(StaticProducer): return d def _check_for_data(self): - self.fileObject.seek(self.fileObject.tell()) - data = self.fileObject.read() - if data: - self.request.write(data) + def _write_new_data_to_request(): + self.fileObject.seek(self.fileObject.tell()) + data = self.fileObject.read() + self.total_bytes += len(data) + + if data: + self.request.write(data) + return defer.succeed(None) def _check_status(stream_status): if stream_status.running_status == "completed": self.stopProducing() return defer.succeed(None) - d = self.stream.status() + d = _write_new_data_to_request() + d.addCallback(lambda _: self.stream.status()) d.addCallback(_check_status) def resumeProducing(self): @@ -102,24 +114,22 @@ class HostedLBRYFile(resource.Resource): self._api = api self.stream = None self.streaming_file = None + self.producer = None resource.Resource.__init__(self) - def _set_stream(self, stream): - self.stream = stream - def makeProducer(self, request, stream): - return LBRYFileProducer(request, stream) + self.producer = LBRYFileProducer(request, stream, self._api) + return self.producer def render_GET(self, request): if 'name' in request.args.keys(): - if request.args['name'][0] != 'lbry': - if request.args['name'][0] != self.streaming_file: - self.streaming_file = request.args['name'][0] - d = self._api._download_name(request.args['name'][0]) - d.addCallback(self._set_stream) - else: - d = defer.succeed(None) - d.addCallback(lambda _: self.makeProducer(request, self.stream).start()) + if request.args['name'][0] != 'lbry' and request.args['name'][0] not in self._api.waiting_on.keys(): + d = self._api._download_name(request.args['name'][0]) + d.addCallback(lambda stream: self.makeProducer(request, stream)) + d.addCallback(lambda producer: producer.start()) + elif request.args['name'][0] in self._api.waiting_on.keys(): + request.redirect(UI_ADDRESS + "/?watch=" + request.args['name'][0]) + request.finish() else: request.redirect(UI_ADDRESS) request.finish() diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index b52c8c92a..048eb3e5b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -1,7 +1,9 @@ import json import logging import os +import sys +from appdirs import user_data_dir from datetime import datetime from twisted.internet import defer from twisted.internet.task import LoopingCall @@ -12,7 +14,18 @@ from lbrynet.core.StreamDescriptor import download_sd_blob from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory from lbrynet.conf import DEFAULT_TIMEOUT +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) log.setLevel(logging.INFO) @@ -123,7 +136,7 @@ class GetStream(object): d = defer.Deferred() self.downloader = downloader self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d.addCallback(lambda _: log.info("Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) + d.addCallback(lambda _: log.info("[" + str(datetime.now()) + "] Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) d.addCallback(lambda _: self.downloader.start()) From 591634f175a8338c24fb4df022a12370b07135bc Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Apr 2016 22:35:21 -0400 Subject: [PATCH 154/462] cache name claim info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -cache name claim info for an hour rather than looking it up each time it’s required -add default thumbnail to search results --- lbrynet/conf.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 170 ++++++++++++--------- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 6 +- 3 files changed, 102 insertions(+), 76 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index a4a387991..dfae3d274 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -40,4 +40,4 @@ DEFAULT_TIMEOUT = 30 DEFAULT_MAX_SEARCH_RESULTS = 25 DEFAULT_MAX_KEY_FEE = 100.0 DEFAULT_SEARCH_TIMEOUT = 3.0 - +DEFAULT_CACHE_TIME = 3600 \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 961d6bdca..3fd60fe1f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -36,7 +36,7 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ - DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT + DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS, DEFAULT_TIMEOUT, UI_ADDRESS from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -83,12 +83,26 @@ STARTUP_STAGES = [ (STARTED_CODE, 'Started lbrynet') ] +DOWNLOAD_METADATA_CODE = 'downloading_metadata' +DOWNLOAD_TIMEOUT_CODE = 'timeout' +DOWNLOAD_RUNNING_CODE = 'running' +DOWNLOAD_STOPPED_CODE = 'stopped' +STREAM_STAGES = [ + (INITIALIZING_CODE, 'Initializing...'), + (DOWNLOAD_METADATA_CODE, 'Downloading metadata'), + (DOWNLOAD_RUNNING_CODE, 'Started stream'), + (DOWNLOAD_STOPPED_CODE, 'Paused stream'), + (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') + ] + CONNECT_CODE_VERSION_CHECK = 'version_check' CONNECT_CODE_NETWORK = 'network_connection' CONNECT_CODE_WALLET = 'wallet_catchup_lag' -CONNECTION_PROBLEM_CODES = [(CONNECT_CODE_VERSION_CHECK, "There was a problem checking for updates on github"), - (CONNECT_CODE_NETWORK, "Your internet connection appears to have been interrupted"), - (CONNECT_CODE_WALLET, "Synchronization with the blockchain is lagging... if this continues try restarting LBRY")] +CONNECTION_PROBLEM_CODES = [ + (CONNECT_CODE_VERSION_CHECK, "There was a problem checking for updates on github"), + (CONNECT_CODE_NETWORK, "Your internet connection appears to have been interrupted"), + (CONNECT_CODE_WALLET, "Synchronization with the blockchain is lagging... if this continues try restarting LBRY") + ] ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run', 'get_time_behind_blockchain', 'stop', @@ -218,7 +232,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'dht_node_port': 4444, 'use_upnp': True, 'start_lbrycrdd': True, - 'requested_first_run_credits': False + 'requested_first_run_credits': False, + 'cache_time': DEFAULT_CACHE_TIME } if os.path.isfile(self.daemon_conf): @@ -272,6 +287,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.use_upnp = self.session_settings['use_upnp'] self.start_lbrycrdd = self.session_settings['start_lbrycrdd'] self.requested_first_run_credits = self.session_settings['requested_first_run_credits'] + self.cache_time = self.session_settings['cache_time'] + + if os.path.isfile(os.path.join(self.db_dir, "stream_info_cache.json")): + f = open(os.path.join(self.db_dir, "stream_info_cache.json"), "r") + self.name_cache = json.loads(f.read()) + f.close() + log.info("Loaded claim info cache") + else: + self.name_cache = {} def render(self, request): request.content.seek(0, 0) @@ -341,12 +365,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): def setup(self): def _log_starting_vals(): - r = json.dumps(self._get_lbry_files()) - - log.info("LBRY Files: " + r) - log.info("Starting balance: " + str(self.session.wallet.wallet_balance)) - - return defer.succeed(None) + d = self._get_lbry_files() + d.addCallback(lambda r: json.dumps([d[1] for d in r])) + d.addCallback(lambda r: log.info("LBRY Files: " + r)) + d.addCallback(lambda _: log.info("Starting balance: " + str(self.session.wallet.wallet_balance))) + return d def _announce_startup(): def _announce(): @@ -630,7 +653,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.session_settings['download_timeout'] = settings['download_timeout'] else: return defer.fail() - + elif k == 'search_timeout': + if type(settings['search_timeout']) is float: + self.session_settings['search_timeout'] = settings['search_timeout'] + else: + return defer.fail() + elif k == 'cache_time': + if type(settings['cache_time']) is int: + self.session_settings['cache_time'] = settings['cache_time'] + else: + return defer.fail() self.run_on_startup = self.session_settings['run_on_startup'] self.data_rate = self.session_settings['data_rate'] self.max_key_fee = self.session_settings['max_key_fee'] @@ -639,6 +671,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.max_download = self.session_settings['max_download'] self.upload_log = self.session_settings['upload_log'] self.download_timeout = self.session_settings['download_timeout'] + self.search_timeout = self.session_settings['search_timeout'] + self.cache_time = self.session_settings['cache_time'] f = open(self.daemon_conf, "w") f.write(json.dumps(self.session_settings)) @@ -879,7 +913,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): stream_hash = stream_info['stream_hash'] if isinstance(stream_hash, dict): stream_hash = stream_hash['sd_hash'] - log.info("[" + str(datetime.now()) + "] Resolved lbry://" + name + " to sd hash: " + stream_hash) d = self._get_lbry_file_by_sd_hash(stream_hash) def _add_results(l): return defer.succeed((stream_info, l)) @@ -902,25 +935,41 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d self.waiting_on[name] = True - d = self.session.wallet.get_stream_info_for_name(name) + d = self._resolve_name(name) d.addCallback(_setup_stream) d.addCallback(lambda (stream_info, lbry_file): _get_stream(stream_info) if not lbry_file else _disp_file(lbry_file)) d.addCallback(_remove_from_wait) return d + def _get_long_count_timestamp(self): + return int((datetime.utcnow() - (datetime(year=2012, month=12, day=21))).total_seconds()) + + def _update_claim_cache(self): + f = open(os.path.join(self.db_dir, "stream_info_cache.json"), "w") + f.write(json.dumps(self.name_cache)) + f.close() + return defer.succeed(True) + def _resolve_name(self, name): - d = defer.Deferred() - d.addCallback(lambda _: self.session.wallet.get_stream_info_for_name(name)) - d.addErrback(lambda _: defer.fail(UnknownNameError)) + def _cache_stream_info(stream_info): + self.name_cache[name] = {'claim_metadata': stream_info, 'timestamp': self._get_long_count_timestamp()} + d = self._update_claim_cache() + d.addCallback(lambda _: self.name_cache[name]['claim_metadata']) + return d - return d - - def _resolve_name_wc(self, name): - d = defer.Deferred() - d.addCallback(lambda _: self.session.wallet.get_stream_info_for_name(name)) - d.addErrback(lambda _: defer.fail(UnknownNameError)) - d.callback(None) + if name in self.name_cache.keys(): + if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: + log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) + d = defer.succeed(self.name_cache[name]['claim_metadata']) + else: + log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) + else: + log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) return d @@ -954,11 +1003,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) def _add_key_fee(data_cost): - d = self.session.wallet.get_stream_info_for_name(name) + d = self._resolve_name(name) d.addCallback(lambda info: data_cost + info['key_fee'] if 'key_fee' in info.keys() else data_cost) return d - d = self.session.wallet.get_stream_info_for_name(name) + d = self._resolve_name(name) d.addCallback(lambda info: info['stream_hash'] if isinstance(info['stream_hash'], str) else info['stream_hash']['sd_hash']) d.addCallback(lambda sd_hash: download_sd_blob(self.session, sd_hash, @@ -972,25 +1021,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d - def _get_lbry_files(self): - r = [] - for f in self.lbry_file_manager.lbry_files: - if f.key: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), - 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, - 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, - 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash} - - else: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, - 'points_paid': f.points_paid, - 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, - 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, - 'sd_hash': f.sd_hash} - - r.append(t) - return r - def _get_lbry_file_by_uri(self, name): def _get_file(stream_info): if isinstance(stream_info['stream_hash'], str) or isinstance(stream_info['stream_hash'], unicode): @@ -1003,7 +1033,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(l) return defer.succeed(None) - d = self.session.wallet.get_stream_info_for_name(name) + d = self._resolve_name(name) d.addCallback(_get_file) return d @@ -1022,26 +1052,22 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _get_lbry_file(self, search_by, val, return_json=True): def _log_get_lbry_file(f): - if f: + if f and val: log.info("Found LBRY file for " + search_by + ": " + val) - else: + elif val: log.info("Did not find LBRY file for " + search_by + ": " + val) return f def _get_json_for_return(f): if f: if f.key: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': binascii.b2a_hex(f.key), - 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, - 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, - 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash} - + key = binascii.b2a_hex(f.key) else: - t = {'completed': f.completed, 'file_name': f.file_name, 'key': None, - 'points_paid': f.points_paid, - 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, - 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, - 'sd_hash': f.sd_hash} + key = None + t = {'completed': f.completed, 'file_name': f.file_name, 'key': key, + 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash} return t else: return False @@ -1057,6 +1083,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(_get_json_for_return) return d + def _get_lbry_files(self): + d = defer.DeferredList([self._get_lbry_file('sd_hash', l.sd_hash) for l in self.lbry_file_manager.lbry_files]) + return d + def _log_to_slack(self, msg): URL = "https://hooks.slack.com/services/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" msg = platform.platform() + ": " + base58.b58encode(self.lbryid)[:20] + ", " + msg @@ -1351,9 +1381,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'sd_hash': string """ - r = self._get_lbry_files() - log.info("[" + str(datetime.now()) + "] Get LBRY files") - return self._render_response(r, OK_CODE) + d = self._get_lbry_files() + d.addCallback(lambda r: [d[1] for d in r]) + d.addCallback(lambda r: self._render_response(r, OK_CODE) if len(r) else self._render_response(False, OK_CODE)) + + return d def jsonrpc_get_lbry_file(self, p): """ @@ -1399,18 +1431,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: return self._render_response(None, BAD_REQUEST) - def _disp(info): - stream_hash = info['stream_hash'] - if isinstance(stream_hash, dict): - stream_hash = stream_hash['sd_hash'] - - log.info("[" + str(datetime.now()) + "] Resolved info: " + stream_hash) - - return self._render_response(info, OK_CODE) - d = self._resolve_name(name) - d.addCallbacks(_disp, lambda _: server.failure) - d.callback(None) + d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure) return d def jsonrpc_get(self, p): @@ -1525,7 +1547,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): ds = [] for claim in claims: d1 = defer.succeed(claim) - d2 = self._resolve_name_wc(claim['name']) + d2 = self._resolve_name(claim['name']) d3 = self._get_est_cost(claim['name']) dl = defer.DeferredList([d1, d2, d3], consumeErrors=True) ds.append(dl) @@ -1542,6 +1564,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): del r[1]['name'] t.update(r[1]) t['cost_est'] = r[2] + if not 'thumbnail' in t.keys(): + t['thumbnail'] = "img/Free-speech-flag.svg" consolidated_results.append(t) # log.info(str(t)) return consolidated_results diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 167c11e92..740a1bd42 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -68,12 +68,12 @@ class LBRYFileProducer(StaticProducer): def start(self): d = self._set_size() self.fileObject.seek(0) - self.updater.start(5) + self.updater.start(1) def _set_size(self): def _set(size): self.request.setHeader('content-length', str(size)) - self.request.setHeader('content-type', ' application/octet-stream') + self.request.setHeader('content-type', 'application/octet-stream') return defer.succeed(None) d = self.stream.get_total_bytes() @@ -85,6 +85,7 @@ class LBRYFileProducer(StaticProducer): self.fileObject.seek(self.fileObject.tell()) data = self.fileObject.read() self.total_bytes += len(data) + log.info(str(self.total_bytes)) if data: self.request.write(data) @@ -93,6 +94,7 @@ class LBRYFileProducer(StaticProducer): def _check_status(stream_status): if stream_status.running_status == "completed": self.stopProducing() + return defer.succeed(None) d = _write_new_data_to_request() From caf5d2f7880fdfcc0e54111c98a91a534bf8150f Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Apr 2016 19:48:43 -0400 Subject: [PATCH 155/462] update uri handler to use /watch --- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 4 ++-- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index c748a1015..ed63c6f03 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -64,7 +64,7 @@ class LBRYURIHandler(object): if lbry_name == "lbry" or lbry_name == "" and not started: webbrowser.open(UI_ADDRESS) else: - webbrowser.open(UI_ADDRESS + "/view?name=" + lbry_name) + webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) def handle_linux(self, lbry_name): try: @@ -77,7 +77,7 @@ class LBRYURIHandler(object): if lbry_name == "lbry": webbrowser.open(UI_ADDRESS) else: - webbrowser.open(UI_ADDRESS + "/view?name=" + lbry_name) + webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) def main(args): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 3fd60fe1f..12f17ca18 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -392,8 +392,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") - self.internet_connection_checker.start(60) - self.version_checker.start(3600) + self.internet_connection_checker.start(3600) + self.version_checker.start(3600 * 12) self.connection_problem_checker.start(1) d = defer.Deferred() From 226e9084c934fee83c18c5fd3bbc284d77b6710d Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 30 Apr 2016 23:43:02 -0400 Subject: [PATCH 156/462] return false from get_lbry_file if the file doesnt exist --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 12f17ca18..bbc1f1384 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1601,7 +1601,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): if p.keys()[0] in ['name', 'sd_hash', 'file_name']: search_type = p.keys()[0] d = self._get_lbry_file(search_type, p[search_type], return_json=False) - d.addCallback(lambda l: _delete_file(l) if l else "Couldn't find LBRY file to delete") + d.addCallback(lambda l: _delete_file(l) if l else False) d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d From 46368f52f5fd2f4496f71cdf0675321b5a9be79a Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 1 May 2016 05:17:59 -0400 Subject: [PATCH 157/462] streaming files still needs work, still has problems in safari and is otherwise slow --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 205 +++++++++++++-------- 1 file changed, 132 insertions(+), 73 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 740a1bd42..b6d4942cf 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -2,10 +2,8 @@ import logging import subprocess import os import shutil - -from twisted.internet.task import LoopingCall -from txjsonrpc.web import jsonrpc import json +import sys from StringIO import StringIO from zipfile import ZipFile @@ -13,15 +11,21 @@ from urllib import urlopen from datetime import datetime from appdirs import user_data_dir from twisted.web import server, static, resource -from twisted.internet import defer -from twisted.web.static import StaticProducer +from twisted.internet import defer, interfaces, error, reactor, task +from twisted.python.failure import Failure +from txjsonrpc.web import jsonrpc + +from zope.interface import implements from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS -log = logging.getLogger(__name__) -data_dir = user_data_dir("LBRY") +if sys.platform != "darwin": + data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + data_dir = user_data_dir("LBRY") + if not os.path.isdir(data_dir): os.mkdir(data_dir) version_dir = os.path.join(data_dir, "ui_version_history") @@ -31,6 +35,9 @@ if not os.path.isdir(version_dir): version_log = logging.getLogger("lbry_version") version_log.addHandler(logging.FileHandler(os.path.join(version_dir, "lbry_version.log"))) version_log.setLevel(logging.INFO) +log = logging.getLogger(__name__) +log.addHandler(logging.FileHandler(os.path.join(data_dir, 'lbrynet-daemon.log'))) +log.setLevel(logging.INFO) class LBRYindex(resource.Resource): @@ -53,82 +60,103 @@ class LBRYindex(resource.Resource): return static.File(os.path.join(self.ui_dir, "index.html")).render_GET(request) -class LBRYFileProducer(StaticProducer): - def __init__(self, request, lbry_stream, api): - self._api = api - self.stream = lbry_stream - self.updater = LoopingCall(self._check_for_data) - self.total_bytes = 0 - if lbry_stream.file_written_to: - file_name = lbry_stream.file_written_to - else: - file_name = os.path.join(self._api.download_directory, lbry_stream.file_name) - StaticProducer.__init__(self, request, fileObject=file(file_name)) +class LBRYFileStreamer(object): + """ + Writes downloaded LBRY file to request as the download comes in, pausing and resuming as requested + used for Chrome + """ - def start(self): - d = self._set_size() - self.fileObject.seek(0) - self.updater.start(1) + implements(interfaces.IPushProducer) - def _set_size(self): - def _set(size): - self.request.setHeader('content-length', str(size)) - self.request.setHeader('content-type', 'application/octet-stream') - return defer.succeed(None) + def __init__(self, request, path, start, stop, size): + self._request = request + self._fileObject = file(path) + self._stop_pos = size if stop == '' else int(stop) #chrome and firefox send range requests for "0-" + self._cursor = self._start_pos = int(start) + self._file_size = size - d = self.stream.get_total_bytes() - d.addCallback(_set) - return d + self._paused = self._sent_bytes = False + self._delay = 0.1 - def _check_for_data(self): - def _write_new_data_to_request(): - self.fileObject.seek(self.fileObject.tell()) - data = self.fileObject.read() - self.total_bytes += len(data) - log.info(str(self.total_bytes)) + self._request.setResponseCode(206) + self._request.setHeader('accept-ranges', 'bytes') + self._request.setHeader('content-type', 'application/octet-stream') + self._request.setHeader('content-range', 'bytes %s-%s/%s' % (self._start_pos, self._stop_pos, self._file_size)) - if data: - self.request.write(data) - return defer.succeed(None) + self.resumeProducing() - def _check_status(stream_status): - if stream_status.running_status == "completed": - self.stopProducing() - - return defer.succeed(None) - - d = _write_new_data_to_request() - d.addCallback(lambda _: self.stream.status()) - d.addCallback(_check_status) + def pauseProducing(self): + self._paused = True + log.info("[" + str(datetime.now()) + "] Pausing producer") + return defer.succeed(None) def resumeProducing(self): - self.updater.start(1) + def _check_for_new_data(): + self._fileObject.seek(self._start_pos, os.SEEK_END) + readable_bytes = self._fileObject.tell() + self._fileObject.seek(self._cursor) + + self._sent_bytes = False + + if readable_bytes > self._cursor: + read_length = min(readable_bytes, self._stop_pos) - self._cursor + log.info('Writing range %s-%s/%s' % (self._cursor, self._cursor + read_length, self._file_size)) + self._request.setHeader('content-range', 'bytes %s-%s/%s' % (self._cursor, self._cursor + read_length, self._file_size)) + for i in range(read_length + 1): + if self._paused: + break + else: + data = self._fileObject.read(1) + self._request.write(data) + self._cursor += 1 + self._sent_bytes = True + return defer.succeed(None) + + def _write_reply(): + if self._cursor == self._stop_pos + 1: + self.stopProducing() + return defer.succeed(None) + elif self._paused: + return defer.succeed(None) + else: + d = task.deferLater(reactor, self._delay, _check_for_new_data) + d.addCallback(lambda _: _write_reply()) + return d + + log.info("[" + str(datetime.now()) + "] Resuming producer") + + self._paused = False + _write_reply() + return defer.succeed(None) def stopProducing(self): - self.updater.stop() - self.fileObject.close() - self.stream.stop() - self.request.finish() + log.info("Stopping producer") + # self._fileObject.close() + self._request.finish() + self._request.unregisterProducer() class HostedLBRYFile(resource.Resource): def __init__(self, api): self._api = api - self.stream = None - self.streaming_file = None - self.producer = None resource.Resource.__init__(self) def makeProducer(self, request, stream): - self.producer = LBRYFileProducer(request, stream, self._api) - return self.producer + range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-') + start, stop = int(range_header[0]), range_header[1] + path = os.path.join(self._api.download_directory, stream.file_name) + + d = stream.get_total_bytes() + d.addCallback(lambda size: request.registerProducer(LBRYFileStreamer(request, path, start, stop, size), streaming=True)) + + return d def render_GET(self, request): if 'name' in request.args.keys(): if request.args['name'][0] != 'lbry' and request.args['name'][0] not in self._api.waiting_on.keys(): d = self._api._download_name(request.args['name'][0]) d.addCallback(lambda stream: self.makeProducer(request, stream)) - d.addCallback(lambda producer: producer.start()) + request.notifyFinish().addErrback(self._responseFailed, d) elif request.args['name'][0] in self._api.waiting_on.keys(): request.redirect(UI_ADDRESS + "/?watch=" + request.args['name'][0]) request.finish() @@ -137,24 +165,55 @@ class HostedLBRYFile(resource.Resource): request.finish() return server.NOT_DONE_YET + def _responseFailed(self, err, call): + call.addErrback(lambda err: err.trap(error.ConnectionDone)) + call.addErrback(lambda err: err.trap(defer.CancelledError)) + call.addErrback(lambda err: log.info("Error: " + str(err))) + call.cancel() -class LBRYFileRender(resource.Resource): + +class MyLBRYFiles(resource.Resource): isLeaf = False + def __init__(self): + resource.Resource.__init__(self) + self.files_table = None + + def delayed_render(self, request, result): + request.write(result.encode('utf-8')) + request.finish() + def render_GET(self, request): - if 'name' in request.args.keys(): - api = jsonrpc.Proxy(API_CONNECTION_STRING) - if request.args['name'][0] != 'lbry': - d = api.callRemote("get", {'name': request.args['name'][0]}) - d.addCallback(lambda results: static.File(results['path'], defaultType='video/octet-stream')) - d.addCallback(lambda static_file: static_file.render_GET(request) if static_file.getFileSize() > 0 - else server.failure) - else: - request.redirect(UI_ADDRESS) - request.finish() - return server.NOT_DONE_YET + self.files_table = None + api = jsonrpc.Proxy(API_CONNECTION_STRING) + d = api.callRemote("get_lbry_files", {}) + d.addCallback(self._get_table) + d.addCallback(lambda results: self.delayed_render(request, results)) + + return server.NOT_DONE_YET + + def _get_table(self, files): + if not self.files_table: + self.files_table = r'My LBRY files
' + self.files_table += r'' + self.files_table += r'' + self.files_table += r'' + self.files_table += r'' + self.files_table += r'' + self.files_table += r'' + return self._get_table(files) + if not len(files): + self.files_table += r'
Stream nameCompletedToggleRemove
' + return self.files_table else: - return server.failure + f = files.pop() + self.files_table += r'' + self.files_table += r'%s' % (f['stream_name']) + self.files_table += r'%s' % (f['completed']) + self.files_table += r'Start' if f['stopped'] else r'Stop' + self.files_table += r'Delete' + self.files_table += r'' + return self._get_table(files) class LBRYDaemonServer(object): @@ -267,8 +326,8 @@ class LBRYDaemonServer(object): self.root.putChild("font", static.File(os.path.join(self.ui_dir, "font"))) self.root.putChild("img", static.File(os.path.join(self.ui_dir, "img"))) self.root.putChild("js", static.File(os.path.join(self.ui_dir, "js"))) - # self.root.putChild("view", LBRYFileRender()) self.root.putChild("view", HostedLBRYFile(self._api)) + self.root.putChild("files", MyLBRYFiles()) self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) From 04ee9894c99d8d3bc689a58da28044a73a37249c Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 2 May 2016 04:10:50 -0400 Subject: [PATCH 158/462] speed up streaming, off by one errors --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 1 + lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 3 + lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 63 +++++++++++++-------- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index bbc1f1384..17be5449e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -991,6 +991,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d d.addCallback(lambda _: finish_deletion(lbry_file)) + d.addCallback(lambda _: log.info("[" + str(datetime.now()) + "] Delete lbry file")) return d def _get_est_cost(self, name): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 9cf3f4143..506dc23e1 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -132,3 +132,6 @@ def start(): if not args.logtoconsole: print "Not connected to internet, unable to start" return + +if __name__ == "__main__": + start() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index b6d4942cf..8a9aa0a05 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -4,6 +4,7 @@ import os import shutil import json import sys +import mimetypes from StringIO import StringIO from zipfile import ZipFile @@ -71,18 +72,21 @@ class LBRYFileStreamer(object): def __init__(self, request, path, start, stop, size): self._request = request self._fileObject = file(path) - self._stop_pos = size if stop == '' else int(stop) #chrome and firefox send range requests for "0-" + self._content_type = mimetypes.guess_type(path)[0] + self._stop_pos = size - 1 if stop == '' else int(stop) #chrome and firefox send range requests for "0-" self._cursor = self._start_pos = int(start) self._file_size = size - self._paused = self._sent_bytes = False + self._paused = self._sent_bytes = self._stopped = False self._delay = 0.1 + self._deferred = defer.succeed(None) self._request.setResponseCode(206) self._request.setHeader('accept-ranges', 'bytes') - self._request.setHeader('content-type', 'application/octet-stream') - self._request.setHeader('content-range', 'bytes %s-%s/%s' % (self._start_pos, self._stop_pos, self._file_size)) + # self._request.setHeader('content-type', 'application/octet-stream') + self._request.setHeader('content-type', self._content_type) + # self._request.setHeader('content-range', 'bytes %s-%s/%s' % (self._start_pos, self._stop_pos, self._file_size)) self.resumeProducing() def pauseProducing(self): @@ -98,57 +102,68 @@ class LBRYFileStreamer(object): self._sent_bytes = False - if readable_bytes > self._cursor: - read_length = min(readable_bytes, self._stop_pos) - self._cursor - log.info('Writing range %s-%s/%s' % (self._cursor, self._cursor + read_length, self._file_size)) - self._request.setHeader('content-range', 'bytes %s-%s/%s' % (self._cursor, self._cursor + read_length, self._file_size)) - for i in range(read_length + 1): - if self._paused: + if (readable_bytes > self._cursor) and not (self._stopped or self._paused): + read_length = min(readable_bytes, self._stop_pos) - self._cursor + 1 + self._request.setHeader('content-range', 'bytes %s-%s/%s' % (self._cursor, self._cursor + read_length - 1, self._file_size)) + self._request.setHeader('content-length', str(read_length)) + start_cur = self._cursor + for i in range(read_length): + if self._paused or self._stopped: break else: data = self._fileObject.read(1) self._request.write(data) self._cursor += 1 - self._sent_bytes = True - return defer.succeed(None) - def _write_reply(): + log.info("[" + str(datetime.now()) + "] Wrote range %s-%s/%s, length: %s" % (start_cur, self._cursor - 1, self._file_size, self._cursor - start_cur)) + self._sent_bytes = True + if self._cursor == self._stop_pos + 1: self.stopProducing() return defer.succeed(None) - elif self._paused: + elif self._paused or self._stopped: return defer.succeed(None) else: - d = task.deferLater(reactor, self._delay, _check_for_new_data) - d.addCallback(lambda _: _write_reply()) - return d + self._deferred.addCallback(lambda _: task.deferLater(reactor, self._delay, _check_for_new_data)) + return defer.succeed(None) log.info("[" + str(datetime.now()) + "] Resuming producer") - self._paused = False - _write_reply() + self._deferred.addCallback(lambda _: _check_for_new_data()) return defer.succeed(None) def stopProducing(self): - log.info("Stopping producer") + log.info("[" + str(datetime.now()) + "] Stopping producer") + self._stopped = True # self._fileObject.close() - self._request.finish() + self._deferred.addErrback(lambda err: err.trap(defer.CancelledError)) + self._deferred.addErrback(lambda err: err.trap(error.ConnectionDone)) + self._deferred.cancel() + # self._request.finish() self._request.unregisterProducer() class HostedLBRYFile(resource.Resource): def __init__(self, api): self._api = api + self._producer = None resource.Resource.__init__(self) def makeProducer(self, request, stream): + def _save_producer(producer): + self._producer = producer + return defer.succeed(None) + range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-') start, stop = int(range_header[0]), range_header[1] + log.info("[" + str(datetime.now()) + "] GET range %s-%s" % (start, stop)) path = os.path.join(self._api.download_directory, stream.file_name) d = stream.get_total_bytes() - d.addCallback(lambda size: request.registerProducer(LBRYFileStreamer(request, path, start, stop, size), streaming=True)) - + d.addCallback(lambda size: _save_producer(LBRYFileStreamer(request, path, start, stop, size))) + d.addCallback(lambda _: request.registerProducer(self._producer, streaming=True)) + # request.notifyFinish().addCallback(lambda _: self._producer.stopProducing()) + request.notifyFinish().addErrback(self._responseFailed, d) return d def render_GET(self, request): @@ -156,7 +171,7 @@ class HostedLBRYFile(resource.Resource): if request.args['name'][0] != 'lbry' and request.args['name'][0] not in self._api.waiting_on.keys(): d = self._api._download_name(request.args['name'][0]) d.addCallback(lambda stream: self.makeProducer(request, stream)) - request.notifyFinish().addErrback(self._responseFailed, d) + elif request.args['name'][0] in self._api.waiting_on.keys(): request.redirect(UI_ADDRESS + "/?watch=" + request.args['name'][0]) request.finish() From 30f88f99b7d825b08b455446f4a3e788f9573e44 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 2 May 2016 15:58:40 -0400 Subject: [PATCH 159/462] use static.File --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 8a9aa0a05..152e9c353 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -12,7 +12,7 @@ from urllib import urlopen from datetime import datetime from appdirs import user_data_dir from twisted.web import server, static, resource -from twisted.internet import defer, interfaces, error, reactor, task +from twisted.internet import defer, interfaces, error, reactor, task, threads from twisted.python.failure import Failure from txjsonrpc.web import jsonrpc @@ -76,17 +76,16 @@ class LBRYFileStreamer(object): self._stop_pos = size - 1 if stop == '' else int(stop) #chrome and firefox send range requests for "0-" self._cursor = self._start_pos = int(start) self._file_size = size + self._depth = 0 self._paused = self._sent_bytes = self._stopped = False - self._delay = 0.1 + self._delay = 0.25 self._deferred = defer.succeed(None) self._request.setResponseCode(206) self._request.setHeader('accept-ranges', 'bytes') - # self._request.setHeader('content-type', 'application/octet-stream') self._request.setHeader('content-type', self._content_type) - # self._request.setHeader('content-range', 'bytes %s-%s/%s' % (self._start_pos, self._stop_pos, self._file_size)) self.resumeProducing() def pauseProducing(self): @@ -96,6 +95,7 @@ class LBRYFileStreamer(object): def resumeProducing(self): def _check_for_new_data(): + self._depth += 1 self._fileObject.seek(self._start_pos, os.SEEK_END) readable_bytes = self._fileObject.tell() self._fileObject.seek(self._cursor) @@ -115,7 +115,8 @@ class LBRYFileStreamer(object): self._request.write(data) self._cursor += 1 - log.info("[" + str(datetime.now()) + "] Wrote range %s-%s/%s, length: %s" % (start_cur, self._cursor - 1, self._file_size, self._cursor - start_cur)) + log.info("[" + str(datetime.now()) + "] Wrote range %s-%s/%s, length: %s, readable: %s, depth: %s" % + (start_cur, self._cursor, self._file_size, self._cursor - start_cur, readable_bytes, self._depth)) self._sent_bytes = True if self._cursor == self._stop_pos + 1: @@ -124,13 +125,12 @@ class LBRYFileStreamer(object): elif self._paused or self._stopped: return defer.succeed(None) else: - self._deferred.addCallback(lambda _: task.deferLater(reactor, self._delay, _check_for_new_data)) + self._deferred.addCallback(lambda _: threads.deferToThread(reactor.callLater, self._delay, _check_for_new_data)) return defer.succeed(None) log.info("[" + str(datetime.now()) + "] Resuming producer") self._paused = False self._deferred.addCallback(lambda _: _check_for_new_data()) - return defer.succeed(None) def stopProducing(self): log.info("[" + str(datetime.now()) + "] Stopping producer") @@ -141,6 +141,7 @@ class LBRYFileStreamer(object): self._deferred.cancel() # self._request.finish() self._request.unregisterProducer() + return defer.succeed(None) class HostedLBRYFile(resource.Resource): @@ -170,7 +171,9 @@ class HostedLBRYFile(resource.Resource): if 'name' in request.args.keys(): if request.args['name'][0] != 'lbry' and request.args['name'][0] not in self._api.waiting_on.keys(): d = self._api._download_name(request.args['name'][0]) - d.addCallback(lambda stream: self.makeProducer(request, stream)) + # d.addCallback(lambda stream: self.makeProducer(request, stream)) + d.addCallback(lambda stream: static.File(os.path.join(self._api.download_directory, + stream.file_name)).render_GET(request)) elif request.args['name'][0] in self._api.waiting_on.keys(): request.redirect(UI_ADDRESS + "/?watch=" + request.args['name'][0]) From 019de08c64dcef6a8cc20f423200869f4612aa16 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 3 May 2016 23:13:31 -0400 Subject: [PATCH 160/462] add more transaction functions to daemon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit also fix —wallet command line option --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 94 +++++++++++++++++++++- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 8 +- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 17be5449e..0731dcb71 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -27,7 +27,7 @@ from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory -from lbrynet.core.Error import UnknownNameError +from lbrynet.core.Error import UnknownNameError, InsufficientFundsError from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier @@ -1789,6 +1789,98 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda address: self._render_response(address, OK_CODE)) return d + def jsonrpc_send_amount_to_address(self, p): + """ + Send credits to an address + + Args: + amount: the amount to send + address: the address of the recipient + Returns: + True if payment successfully scheduled + """ + + if 'amount' in p.keys() and 'address' in p.keys(): + amount = p['amount'] + address = p['address'] + else: + return server.failure + + reserved_points = self.session.wallet.reserve_points(address, amount) + if reserved_points is None: + return defer.fail(InsufficientFundsError()) + d = self.session.wallet.send_points_to_address(reserved_points, amount) + d.addCallback(lambda _: self._render_response(True, OK_CODE)) + return d + + def jsonrpc_get_best_blockhash(self): + """ + Get hash of most recent block + + Args: + None + Returns: + Hash of most recent block + """ + + d = self.session.wallet.get_best_blockhash() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_block(self, p): + """ + Get contents of a block + + Args: + blockhash: hash of the block to look up + Returns: + requested block + """ + + if 'blockhash' in p.keys(): + blockhash = p['blockhash'] + else: + return server.failure + + d = self.session.wallet.get_block(blockhash) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_claims_for_tx(self, p): + """ + Get claims for tx + + Args: + txid: txid of a name claim transaction + Returns: + any claims contained in the requested tx + """ + + if 'txid' in p.keys(): + txid = p['txid'] + else: + return server.failure + + d = self.session.wallet.get_claims_from_tx(txid) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + + def jsonrpc_get_nametrie(self): + """ + Get the nametrie + + Args: + None + Returns: + Name claim trie + """ + + d = self.session.wallet.get_nametrie() + d.addCallback(lambda r: [i for i in r if 'txid' in i.keys()]) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + # def jsonrpc_update_name(self, metadata): # def _disp(x): # print x diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 152e9c353..86c28c557 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -337,8 +337,8 @@ class LBRYDaemonServer(object): d.addCallback(lambda _: _dl_ui()) return d - def _setup_server(self, ui_ver): - self._api = LBRYDaemon(ui_ver, wallet_type=DEFAULT_WALLET) + def _setup_server(self, ui_ver, wallet): + self._api = LBRYDaemon(ui_ver, wallet_type=wallet) self.root = LBRYindex(self.ui_dir) self.root.putChild("css", static.File(os.path.join(self.ui_dir, "css"))) self.root.putChild("font", static.File(os.path.join(self.ui_dir, "font"))) @@ -349,9 +349,9 @@ class LBRYDaemonServer(object): self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) - def start(self, branch="HEAD", user_specified=False): + def start(self, branch="HEAD", user_specified=False, wallet=DEFAULT_WALLET): d = self.setup(branch=branch, user_specified=user_specified) - d.addCallback(lambda v: self._setup_server(v)) + d.addCallback(lambda v: self._setup_server(v, wallet)) d.addCallback(lambda _: self._api.setup()) return d From c1d0f9cf1baed82ea13c6496c1a369d3b27328c9 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 4 May 2016 04:12:11 -0400 Subject: [PATCH 161/462] get rid of autofetcher in daemon the purpose of the autofetcher was to automatically back up and host published content, it is simpler to do this in a separate script that uses existing daemon functions than to have it be built in --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 66 ------------- lbrynet/lbrynet_daemon/LBRYDownloader.py | 116 ----------------------- 2 files changed, 182 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0731dcb71..ba58d333c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -143,7 +143,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.wallet_type = wallet_type self.first_run = None self.log_file = LOG_FILENAME - self.fetcher = None self.current_db_revision = 1 self.run_server = True self.session = None @@ -680,11 +679,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) - def _setup_fetcher(self): - self.fetcher = FetcherDaemon(self.session, self.lbry_file_manager, self.lbry_file_metadata_manager, - self.session.wallet, self.sd_identifier, self.autofetcher_conf) - return defer.succeed(None) - def _setup_data_directory(self): self.startup_status = STARTUP_STAGES[1] log.info("Loading databases...") @@ -1288,48 +1282,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: return self._render_response(self.jsonrpc_help.__doc__, OK_CODE) - def jsonrpc_start_fetcher(self): - """ - Start automatically downloading new name claims as they occur (off by default) - - Args: - None - Returns: - confirmation message - """ - - self.fetcher.start() - log.info('[' + str(datetime.now()) + '] Start autofetcher') - # self._log_to_slack('[' + str(datetime.now()) + '] Start autofetcher') - return self._render_response("Started autofetching claims", OK_CODE) - - def jsonrpc_stop_fetcher(self): - """ - Stop automatically downloading new name claims as they occur - - Args: - None - Returns: - confirmation message - """ - - self.fetcher.stop() - log.info('[' + str(datetime.now()) + '] Stop autofetcher') - return self._render_response("Stopped autofetching claims", OK_CODE) - - def jsonrpc_fetcher_status(self): - """ - Get fetcher status - - Args: - None - Returns: - True/False - """ - - log.info("[" + str(datetime.now()) + "] Get fetcher status") - return self._render_response(self.fetcher.check_if_running(), OK_CODE) - def jsonrpc_get_balance(self): """ Get balance @@ -1865,7 +1817,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - def jsonrpc_get_nametrie(self): """ Get the nametrie @@ -1901,23 +1852,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): # # return d - def jsonrpc_toggle_fetcher_verbose(self): - """ - Toggle fetcher verbose mode - - Args: - None - Returns: - Fetcher verbose status, bool - """ - - if self.fetcher.verbose: - self.fetcher.verbose = False - else: - self.fetcher.verbose = True - - return self._render_response(self.fetcher.verbose, OK_CODE) - def jsonrpc_check_for_new_version(self): """ Checks local version against versions in __init__.py and version.py in the lbrynet and lbryum repos diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 048eb3e5b..8504c4202 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -138,119 +138,3 @@ class GetStream(object): self.download_path = os.path.join(downloader.download_directory, downloader.file_name) d.addCallback(lambda _: log.info("[" + str(datetime.now()) + "] Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) d.addCallback(lambda _: self.downloader.start()) - - -class FetcherDaemon(object): - def __init__(self, session, lbry_file_manager, lbry_file_metadata_manager, wallet, sd_identifier, autofetcher_conf, - verbose=False): - self.autofetcher_conf = autofetcher_conf - self.max_key_fee = 0.0 - self.sd_identifier = sd_identifier - self.wallet = wallet - self.session = session - self.lbry_file_manager = lbry_file_manager - self.lbry_metadata_manager = lbry_file_metadata_manager - self.seen = [] - self.lastbestblock = None - self.search = None - self.first_run = True - self.is_running = False - self.verbose = verbose - self._get_autofetcher_conf() - - def start(self): - if not self.is_running: - self.is_running = True - self.search = LoopingCall(self._looped_search) - self.search.start(1) - log.info("Starting autofetcher") - else: - log.info("Autofetcher is already running") - - def stop(self): - if self.is_running: - self.search.stop() - self.is_running = False - else: - log.info("Autofetcher isn't running, there's nothing to stop") - - def check_if_running(self): - if self.is_running: - msg = "Autofetcher is running\n" - msg += "Last block hash: " + str(self.lastbestblock) - else: - msg = "Autofetcher is not running" - return msg - - def _get_names(self): - d = self.wallet.get_best_blockhash() - d.addCallback(lambda blockhash: get_new_streams(blockhash) if blockhash != self.lastbestblock else []) - - def get_new_streams(blockhash): - self.lastbestblock = blockhash - d = self.wallet.get_block(blockhash) - d.addCallback(lambda block: get_new_streams_in_txes(block['tx'], blockhash)) - return d - - def get_new_streams_in_txes(txids, blockhash): - ds = [] - for t in txids: - d = self.wallet.get_claims_from_tx(t) - d.addCallback(get_new_streams_in_tx, t, blockhash) - ds.append(d) - d = defer.DeferredList(ds, consumeErrors=True) - d.addCallback(lambda result: [r[1] for r in result if r[0]]) - d.addCallback(lambda stream_lists: [stream for streams in stream_lists for stream in streams]) - return d - - def get_new_streams_in_tx(claims, t, blockhash): - rtn = [] - if claims: - for claim in claims: - if claim not in self.seen: - msg = "[" + str(datetime.now()) + "] New claim | lbry://" + str(claim['name']) + \ - " | stream hash: " + str(json.loads(claim['value'])['stream_hash']) - log.info(msg) - if self.verbose: - print msg - rtn.append((claim['name'], t)) - self.seen.append(claim) - else: - if self.verbose: - print "[" + str(datetime.now()) + "] No claims in block", blockhash - return rtn - - d.addCallback(lambda streams: defer.DeferredList( - [self.wallet.get_stream_info_from_txid(name, t) for name, t in streams])) - return d - - def _download_claims(self, claims): - if claims: - for claim in claims: - stream = GetStream(self.sd_identifier, self.session, self.wallet, self.lbry_file_manager, - self.max_key_fee, pay_key=False) - stream.start(claim[1]) - - return defer.succeed(None) - - def _looped_search(self): - d = self._get_names() - d.addCallback(self._download_claims) - return d - - def _get_autofetcher_conf(self): - settings = {"maxkey": "0.0"} - if os.path.exists(self.autofetcher_conf): - conf = open(self.autofetcher_conf) - for l in conf: - if l.startswith("maxkey="): - settings["maxkey"] = float(l[7:].rstrip('\n')) - conf.close() - else: - conf = open(self.autofetcher_conf, "w") - conf.write("maxkey=10.0") - conf.close() - settings["maxkey"] = 10.0 - log.info("No autofetcher conf file found, making one with max key fee of 10.0") - - self.max_key_fee = settings["maxkey"] From 461c2f9055e0551547d7a4cb9a09acfeb1892511 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 4 May 2016 05:20:38 -0400 Subject: [PATCH 162/462] add file download statuses for get_lbry_file also clean up remaining fetcher stuff --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 52 +++++++++++++++++------- lbrynet/lbrynet_daemon/LBRYDownloader.py | 21 ++++++++++ 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index ba58d333c..d87f6282a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -31,7 +31,7 @@ from lbrynet.core.Error import UnknownNameError, InsufficientFundsError from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier -from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream, FetcherDaemon +from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher from lbrynet.core.utils import generate_id from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings @@ -147,6 +147,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.run_server = True self.session = None self.waiting_on = {} + self.streams = {} self.known_dht_nodes = KNOWN_DHT_NODES self.platform_info = { "processor": platform.processor(), @@ -408,7 +409,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._setup_lbry_file_opener()) d.addCallback(lambda _: self._setup_query_handlers()) d.addCallback(lambda _: self._setup_server()) - d.addCallback(lambda _: self._setup_fetcher()) d.addCallback(lambda _: _log_starting_vals()) d.addCallback(lambda _: _announce_startup()) d.callback(None) @@ -891,9 +891,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None): """ - Add a lbry file to the file manager, start the download, and return the new lbry file - if it already exists in the file manager, return the existing lbry file + Add a lbry file to the file manager, start the download, and return the new lbry file. + If it already exists in the file manager, return the existing lbry file """ + if not download_directory: download_directory = self.download_directory elif not os.path.isdir(download_directory): @@ -919,12 +920,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): return f def _get_stream(stream_info): - stream = GetStream(self.sd_identifier, self.session, self.session.wallet, - self.lbry_file_manager, - max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, - download_directory=download_directory) - d = stream.start(stream_info, name) - d.addCallback(lambda _: stream.downloader) + self.streams[name] = GetStream(self.sd_identifier, self.session, self.session.wallet, + self.lbry_file_manager, max_key_fee=self.max_key_fee, + data_rate=self.data_rate, timeout=timeout, + download_directory=download_directory) + d = self.streams[name].start(stream_info, name) + d.addCallback(lambda _: self.streams[name].downloader) return d @@ -1054,16 +1055,39 @@ class LBRYDaemon(jsonrpc.JSONRPC): return f def _get_json_for_return(f): - if f: + def _generate_reply(size): if f.key: key = binascii.b2a_hex(f.key) else: key = None + + if os.path.isfile(os.path.join(self.download_directory, f.file_name)): + written_file = file(os.path.join(self.download_directory, f.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + + if search_by == "name": + if val in self.streams.keys(): + status = self.streams[val].code + else: + status = [False, False] + else: + status = [False, False] + t = {'completed': f.completed, 'file_name': f.file_name, 'key': key, - 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, - 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, - 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash} + 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, + 'written_bytes': written_bytes, 'code': status[0], 'message': status[1]} return t + + if f: + d = f.get_total_bytes() + d.addCallback(_generate_reply) + return d else: return False diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 8504c4202..9ace4c9be 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -14,6 +14,19 @@ from lbrynet.core.StreamDescriptor import download_sd_blob from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory from lbrynet.conf import DEFAULT_TIMEOUT +INITIALIZING_CODE = 'initializing' +DOWNLOAD_METADATA_CODE = 'downloading_metadata' +DOWNLOAD_TIMEOUT_CODE = 'timeout' +DOWNLOAD_RUNNING_CODE = 'running' +DOWNLOAD_STOPPED_CODE = 'stopped' +STREAM_STAGES = [ + (INITIALIZING_CODE, 'Initializing...'), + (DOWNLOAD_METADATA_CODE, 'Downloading metadata'), + (DOWNLOAD_RUNNING_CODE, 'Started stream'), + (DOWNLOAD_STOPPED_CODE, 'Paused stream'), + (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') + ] + if sys.platform != "darwin": log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: @@ -56,6 +69,7 @@ class GetStream(object): self.downloader = None self.finished = defer.Deferred() self.checker = LoopingCall(self.check_status) + self.code = STREAM_STAGES[0] def check_status(self): self.timeout_counter += 1 @@ -68,6 +82,7 @@ class GetStream(object): log.info("Timeout downloading lbry://" + self.resolved_name + ", " + str(self.stream_info)) self.checker.stop() self.d.cancel() + self.code = STREAM_STAGES[4] self.finished.callback(False) def start(self, stream_info, name): @@ -104,10 +119,16 @@ class GetStream(object): def _cause_timeout(): self.timeout_counter = self.timeout * 2 + def _set_status(x, status): + self.code = next(s for s in STREAM_STAGES if s[0] == status) + return x + self.checker.start(1) + self.d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE)) self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) + self.d.addCallback(lambda r: _set_status(r, DOWNLOAD_RUNNING_CODE)) self.d.addCallback(lambda metadata: ( next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), metadata)) From 509b8f3a29f38d6c7e8e143a395f6535b46443cf Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 4 May 2016 21:25:46 -0400 Subject: [PATCH 163/462] add file_name and stream_info fields to get() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -file_name is the name of the file in the downloads folder -stream_info is a dict of the metadata in a name claim, it can be used to download streams where the claim hasn’t yet been added to the nametrie --- lbrynet/lbryfilemanager/LBRYFileDownloader.py | 5 +-- lbrynet/lbryfilemanager/LBRYFileManager.py | 10 +++--- lbrynet/lbrynet_daemon/LBRYDaemon.py | 31 ++++++++++++++----- lbrynet/lbrynet_daemon/LBRYDownloader.py | 6 ++-- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/lbrynet/lbryfilemanager/LBRYFileDownloader.py b/lbrynet/lbryfilemanager/LBRYFileDownloader.py index 8bb1ada42..39f1f4899 100644 --- a/lbrynet/lbryfilemanager/LBRYFileDownloader.py +++ b/lbrynet/lbryfilemanager/LBRYFileDownloader.py @@ -136,7 +136,7 @@ class ManagedLBRYFileDownloaderFactory(object): def can_download(self, sd_validator): return True - def make_downloader(self, metadata, options, payment_rate_manager, download_directory=None): + def make_downloader(self, metadata, options, payment_rate_manager, download_directory=None, file_name=None): data_rate = options[0] upload_allowed = options[1] @@ -155,7 +155,8 @@ class ManagedLBRYFileDownloaderFactory(object): payment_rate_manager, data_rate, upload_allowed, - download_directory=download_directory)) + download_directory=download_directory, + file_name=file_name)) return d @staticmethod diff --git a/lbrynet/lbryfilemanager/LBRYFileManager.py b/lbrynet/lbryfilemanager/LBRYFileManager.py index 144425c6f..d805fb38d 100644 --- a/lbrynet/lbryfilemanager/LBRYFileManager.py +++ b/lbrynet/lbryfilemanager/LBRYFileManager.py @@ -94,7 +94,7 @@ class LBRYFileManager(object): return d def start_lbry_file(self, rowid, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True, - download_directory=None): + download_directory=None, file_name=None): if not download_directory: download_directory = self.download_directory payment_rate_manager.min_blob_data_payment_rate = blob_data_rate @@ -105,16 +105,18 @@ class LBRYFileManager(object): self.stream_info_manager, self, payment_rate_manager, self.session.wallet, download_directory, - upload_allowed) + upload_allowed, + file_name=file_name) self.lbry_files.append(lbry_file_downloader) d = lbry_file_downloader.set_stream_info() d.addCallback(lambda _: lbry_file_downloader) return d - def add_lbry_file(self, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True, download_directory=None): + def add_lbry_file(self, stream_hash, payment_rate_manager, blob_data_rate=None, upload_allowed=True, + download_directory=None, file_name=None): d = self._save_lbry_file(stream_hash, blob_data_rate) d.addCallback(lambda rowid: self.start_lbry_file(rowid, stream_hash, payment_rate_manager, - blob_data_rate, upload_allowed, download_directory)) + blob_data_rate, upload_allowed, download_directory, file_name)) return d def delete_lbry_file(self, lbry_file): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index d87f6282a..c30a1a539 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -889,7 +889,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory) return defer.succeed(True) - def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None): + def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None, stream_info=None): """ Add a lbry file to the file manager, start the download, and return the new lbry file. If it already exists in the file manager, return the existing lbry file @@ -923,17 +923,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.streams[name] = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, - download_directory=download_directory) + download_directory=download_directory, file_name=file_name) d = self.streams[name].start(stream_info, name) d.addCallback(lambda _: self.streams[name].downloader) return d - self.waiting_on[name] = True - d = self._resolve_name(name) + if not stream_info: + self.waiting_on[name] = True + d = self._resolve_name(name) + else: + d = defer.succeed(stream_info) d.addCallback(_setup_stream) d.addCallback(lambda (stream_info, lbry_file): _get_stream(stream_info) if not lbry_file else _disp_file(lbry_file)) - d.addCallback(_remove_from_wait) + if not stream_info: + d.addCallback(_remove_from_wait) return d @@ -1418,7 +1422,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): Args: 'name': name to download, string - optional 'download_directory': path to directory where file will be saved, string + 'download_directory': optional, path to directory where file will be saved, string + 'file_name': optional, a user specified name for the downloaded file + 'stream_info': optional, specified stream info overrides name Returns: 'stream_hash': hex string 'path': path of download @@ -1434,10 +1440,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: download_directory = p['download_directory'] + if 'file_name' in p.keys(): + file_name = p['file_name'] + else: + file_name = None + + if 'stream_info' in p.keys(): + stream_info = p['stream_info'] + else: + stream_info = None + if 'name' in p.keys(): name = p['name'] if p['name'] not in self.waiting_on.keys(): - d = self._download_name(name=name, timeout=timeout, download_directory=download_directory) + d = self._download_name(name=name, timeout=timeout, download_directory=download_directory, + stream_info=stream_info, file_name=file_name) d.addCallback(lambda l: {'stream_hash': l.sd_hash, 'path': os.path.join(self.download_directory, l.file_name)}) d.addCallback(lambda message: self._render_response(message, OK_CODE)) else: diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 9ace4c9be..002bb179e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -44,7 +44,7 @@ log.setLevel(logging.INFO) class GetStream(object): def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5, - timeout=DEFAULT_TIMEOUT, download_directory=None): + timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None): self.wallet = wallet self.resolved_name = None self.description = None @@ -53,6 +53,7 @@ class GetStream(object): self.data_rate = data_rate self.pay_key = pay_key self.name = None + self.file_name = file_name self.session = session self.payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager) self.lbry_file_manager = lbry_file_manager @@ -135,7 +136,8 @@ class GetStream(object): self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager, - download_directory=self.download_directory)) + download_directory=self.download_directory, + file_name=self.file_name)) self.d.addCallbacks(self._start_download, lambda _: _cause_timeout()) self.d.callback(None) From 4cab6726b07401cc182d5a32a1b0a0148f295691 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 4 May 2016 23:27:40 -0400 Subject: [PATCH 164/462] log to file from publisher also fix sd_hash exception that could happen --- lbrynet/lbryfilemanager/LBRYFileDownloader.py | 3 ++- lbrynet/lbrynet_daemon/LBRYPublisher.py | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbryfilemanager/LBRYFileDownloader.py b/lbrynet/lbryfilemanager/LBRYFileDownloader.py index 39f1f4899..fa3ce530e 100644 --- a/lbrynet/lbryfilemanager/LBRYFileDownloader.py +++ b/lbrynet/lbryfilemanager/LBRYFileDownloader.py @@ -33,7 +33,8 @@ class ManagedLBRYFileDownloader(LBRYFileSaver): d = self.stream_info_manager._get_sd_blob_hashes_for_stream(self.stream_hash) def _save_sd_hash(sd_hash): - self.sd_hash = sd_hash[0] + if len(sd_hash): + self.sd_hash = sd_hash[0] return defer.succeed(None) d.addCallback(_save_sd_hash) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index eeb8bbed3..e68a06498 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -1,15 +1,30 @@ +import logging +import os +import sys + +from appdirs import user_data_dir +from datetime import datetime + 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.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader from twisted.internet import threads, defer -import os -import logging -from datetime import datetime +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) class Publisher(object): From d6983a25b01d8bb43c725de56f25314532381541 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 4 May 2016 23:40:05 -0400 Subject: [PATCH 165/462] fix metadata problem in downloader and publisher --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 8 ++++--- lbrynet/lbrynet_daemon/LBRYDownloader.py | 29 +++++++++++------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index c30a1a539..a66a1ee25 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -905,9 +905,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): return r def _setup_stream(stream_info): - stream_hash = stream_info['stream_hash'] - if isinstance(stream_hash, dict): - stream_hash = stream_hash['sd_hash'] + if 'sources' in stream_info.keys(): + stream_hash = stream_info['sources']['lbry_sd_hash'] + else: + stream_hash = stream_info['stream_hash'] + d = self._get_lbry_file_by_sd_hash(stream_hash) def _add_results(l): return defer.succeed((stream_info, l)) diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 002bb179e..d62bdc3b1 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -90,25 +90,22 @@ class GetStream(object): self.resolved_name = name self.stream_info = stream_info if 'stream_hash' in self.stream_info.keys(): - self.description = self.stream_info['description'] - if 'key_fee' in self.stream_info.keys(): - self.key_fee = float(self.stream_info['key_fee']) - if 'key_fee_address' in self.stream_info.keys(): - self.key_fee_address = self.stream_info['key_fee_address'] - else: - self.key_fee_address = None - else: - self.key_fee = None - self.key_fee_address = None - self.stream_hash = self.stream_info['stream_hash'] - if isinstance(self.stream_hash, dict): - self.stream_hash = self.stream_hash['sd_hash'] - + elif 'sources' in self.stream_info.keys(): + self.stream_hash = self.stream_info['sources']['lbry_sd_hash'] else: - log.error("InvalidStreamInfoError in autofetcher: ", stream_info) raise InvalidStreamInfoError(self.stream_info) - + if 'description' in self.stream_info.keys(): + self.description = self.stream_info['description'] + if 'key_fee' in self.stream_info.keys(): + self.key_fee = float(self.stream_info['key_fee']) + if 'key_fee_address' in self.stream_info.keys(): + self.key_fee_address = self.stream_info['key_fee_address'] + else: + self.key_fee_address = None + else: + self.key_fee = None + self.key_fee_address = None if self.key_fee > self.max_key_fee: if self.pay_key: log.info("Key fee (" + str(self.key_fee) + ") above limit of " + str( From 29d6d824fc38c3ed686c73de1a9e7b37df3f5f7c Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 5 May 2016 19:16:36 -0400 Subject: [PATCH 166/462] write cryptsd files to data dir instead of cwd --- lbrynet/lbryfilemanager/LBRYFileCreator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbryfilemanager/LBRYFileCreator.py b/lbrynet/lbryfilemanager/LBRYFileCreator.py index 1b0fcb5b0..770d1ba6b 100644 --- a/lbrynet/lbryfilemanager/LBRYFileCreator.py +++ b/lbrynet/lbryfilemanager/LBRYFileCreator.py @@ -131,7 +131,8 @@ def create_lbry_file(session, lbry_file_manager, file_name, file_handle, key=Non def make_stream_desc_file(stream_hash): log.debug("creating the stream descriptor file") - descriptor_writer = PlainStreamDescriptorWriter(file_name + conf.CRYPTSD_FILE_EXTENSION) + descriptor_file_path = os.path.join(session.db_dir, file_name + conf.CRYPTSD_FILE_EXTENSION) + descriptor_writer = PlainStreamDescriptorWriter(descriptor_file_path) d = get_sd_info(lbry_file_manager.stream_info_manager, stream_hash, True) From b017b6374521307b3f734a1a968b97f997e8d451 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 5 May 2016 21:45:25 -0400 Subject: [PATCH 167/462] status code for previously downloaded file also fix return for files downloaded manually with stream_info --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index a66a1ee25..aacc4a70c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1078,6 +1078,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): if search_by == "name": if val in self.streams.keys(): status = self.streams[val].code + elif f in self.lbry_file_manager.lbry_files: + # if f.stopped: + # status = STREAM_STAGES[3] + # else: + status = STREAM_STAGES[2] else: status = [False, False] else: @@ -1449,6 +1454,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): if 'stream_info' in p.keys(): stream_info = p['stream_info'] + if 'sources' in stream_info.keys(): + sd_hash = stream_info['sources']['lbry_sd_hash'] + else: + sd_hash = stream_info['stream_hash'] else: stream_info = None @@ -1457,7 +1466,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): if p['name'] not in self.waiting_on.keys(): d = self._download_name(name=name, timeout=timeout, download_directory=download_directory, stream_info=stream_info, file_name=file_name) - d.addCallback(lambda l: {'stream_hash': l.sd_hash, 'path': os.path.join(self.download_directory, l.file_name)}) + d.addCallback(lambda l: {'stream_hash': sd_hash, + 'path': os.path.join(self.download_directory, l.file_name)} + if stream_info else + {'stream_hash': l.sd_hash, + 'path': os.path.join(self.download_directory, l.file_name)}) d.addCallback(lambda message: self._render_response(message, OK_CODE)) else: d = server.failure From e32853744a6b35a97cde822ec917e047554752a5 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 5 May 2016 22:26:25 -0400 Subject: [PATCH 168/462] better loading message --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 32 ++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index aacc4a70c..caa1a6873 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -90,7 +90,7 @@ DOWNLOAD_STOPPED_CODE = 'stopped' STREAM_STAGES = [ (INITIALIZING_CODE, 'Initializing...'), (DOWNLOAD_METADATA_CODE, 'Downloading metadata'), - (DOWNLOAD_RUNNING_CODE, 'Started stream'), + (DOWNLOAD_RUNNING_CODE, 'Started %s, got %s/%s blobs, stream status: %s'), (DOWNLOAD_STOPPED_CODE, 'Paused stream'), (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') ] @@ -1061,6 +1061,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): return f def _get_json_for_return(f): + def _get_file_status(file_status): + message = STREAM_STAGES[2][1] % (file_status.name, file_status.num_completed, file_status.num_known, file_status.running_status) + return defer.succeed(message) + def _generate_reply(size): if f.key: key = binascii.b2a_hex(f.key) @@ -1088,12 +1092,26 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: status = [False, False] - t = {'completed': f.completed, 'file_name': f.file_name, 'key': key, - 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, - 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, - 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, - 'written_bytes': written_bytes, 'code': status[0], 'message': status[1]} - return t + if status[0] == DOWNLOAD_RUNNING_CODE: + d = f.status() + d.addCallback(_get_file_status) + d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, 'key': key, + 'points_paid': f.points_paid, 'stopped': f.stopped, + 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, + 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, + 'total_bytes': size, + 'written_bytes': written_bytes, 'code': status[0], + 'message': message}) + else: + d = defer.succeed({'completed': f.completed, 'file_name': f.file_name, 'key': key, + 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, + 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, + 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, + 'written_bytes': written_bytes, 'code': status[0], 'message': status[1]}) + + return d if f: d = f.get_total_bytes() From 9a655988dd382dd99c92507f7cd14ecafb326871 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Fri, 6 May 2016 14:30:00 -0400 Subject: [PATCH 169/462] update script to build against diff web ui branches --- packaging/ubuntu/lbry | 4 +++- packaging/ubuntu/ubuntu_package_setup.sh | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index a4aed0ff8..15cf9d171 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -2,6 +2,8 @@ set -euo pipefail +WEB_UI_BRANCH='master' + urlencode() { local LANG=C local length="${#1}" @@ -17,7 +19,7 @@ urlencode() { DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ -z "$(pgrep lbrynet-daemon)" ]; then echo "running lbrynet-daemon..." - $DIR/lbrynet-daemon & + $DIR/lbrynet-daemon --branch="$WEB_UI_BRANCH" & sleep 3 # let the daemon load before connecting fi diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 4be10c8a7..09483bb8e 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -3,11 +3,12 @@ # Tested on fresh Ubuntu 14.04 install. # wget https://raw.githubusercontent.com/lbryio/lbry/master/packaging/ubuntu/ubuntu_package_setup.sh -# bash ubuntu_package_setup.sh master +# bash ubuntu_package_setup.sh [BRANCH] [WEB-UI-BRANCH] set -euo pipefail BRANCH=${1:-master} +WEB_UI_BRANCH=${2:-} BUILD_DIR="lbry-build-$(date +%Y%m%d-%H%M%S)" mkdir "$BUILD_DIR" @@ -44,6 +45,13 @@ mkdir control data tar -xvzf control.tar.gz --directory control tar -xvJf data.tar.xz --directory data +PACKAGING_DIR='lbry/packaging/ubuntu' + +# set web ui branch +if [ -z "$WEB_UI_BRANCH" ]; then + sed -i "s/^WEB_UI_BRANCH='[^']\+'/WEB_UI_BRANCH='$WEB_UI_BRANCH'/" "$PACKAGING_DIR/lbry" +fi + # add files function addfile() { FILE="$1" @@ -52,7 +60,6 @@ function addfile() { cp "$FILE" "data/$TARGET" echo "$(md5sum "data/$TARGET" | cut -d' ' -f1) $TARGET" >> control/md5sums } -PACKAGING_DIR='lbry/packaging/ubuntu' addfile "$PACKAGING_DIR/lbry" usr/share/python/lbrynet/bin/lbry addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop #addfile lbry/packaging/ubuntu/lbry-init.conf etc/init/lbry.conf From f3d3a0e57f0e98bb1ec08752a58bc1732fd907c2 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 6 May 2016 14:31:44 -0400 Subject: [PATCH 170/462] Don't show 0 while downloading headers from amazon, fix uri handler --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 9 ++++++--- packaging/ubuntu/lbry | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index caa1a6873..f16f5562a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1181,9 +1181,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): r['message'] = self.connection_problem[1] r['is_lagging'] = True elif self.startup_status[0] == LOADING_WALLET_CODE: - r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") - r['progress'] = self.session.wallet.catchup_progress - + if self.session.wallet.blocks_behind_alert != 0: + r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") + r['progress'] = self.session.wallet.catchup_progress + else: + r['message'] = "Catching up with the blockchain" + r['progress'] = 0 log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) return self._render_response(r, OK_CODE) diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index a4aed0ff8..37859de4d 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -26,7 +26,7 @@ ARG=${1:-} if [ -z "$ARG" ]; then URL="" else - URL="view?name=$(urlencode "$(echo "$ARG" | cut -c 8-)")" + URL="?watch=$(urlencode "$(echo "$ARG" | cut -c 8-)")" fi /usr/bin/xdg-open "http://localhost:5279/$URL" From da315204658947c46e2f2436957e8ce9779d06e8 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Fri, 6 May 2016 15:34:41 -0400 Subject: [PATCH 171/462] use github api instead of git --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 86c28c557..92161aaa9 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -273,13 +273,11 @@ class LBRYDaemonServer(object): return defer.succeed("user-specified") else: log.info("User specified UI directory doesn't exist, using " + branch) - elif branch == "HEAD": - log.info("Using UI branch: " + branch) - self._gitcmd = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep %s | cut -f 1" % branch - self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/master/dist.zip" else: log.info("Using UI branch: " + branch) - self._gitcmd = "git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep refs/heads/%s | cut -f 1" % branch + if branch == "HEAD": + branch = "master" + self._git_url = "https://api.github.com/repos/lbryio/lbry.io/git/refs/heads/%s" % branch self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % branch d = self._up_to_date() @@ -288,8 +286,9 @@ class LBRYDaemonServer(object): def _up_to_date(self): def _get_git_info(): - r = subprocess.check_output(self._gitcmd, shell=True) - return defer.succeed(r) + response = urlopen(self._git_url) + data = json.loads(response.read()) + return defer.succeed(data['object']['sha']) def _set_git(version): self.git_version = version From da9b1b3f5873e30b8934b3aeaa08da80c305fead Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 6 May 2016 15:42:21 -0400 Subject: [PATCH 172/462] merge conflicts --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 92161aaa9..086786d83 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -1,5 +1,4 @@ import logging -import subprocess import os import shutil import json @@ -273,11 +272,13 @@ class LBRYDaemonServer(object): return defer.succeed("user-specified") else: log.info("User specified UI directory doesn't exist, using " + branch) + elif branch == "HEAD": + log.info("Using UI branch: " + branch) + self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/master" + self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/master/dist.zip" else: log.info("Using UI branch: " + branch) - if branch == "HEAD": - branch = "master" - self._git_url = "https://api.github.com/repos/lbryio/lbry.io/git/refs/heads/%s" % branch + self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/%s" % branch self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % branch d = self._up_to_date() From b4500a29149e8a66242c3d183f5aeb9ac4c214da Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 6 May 2016 15:51:59 -0400 Subject: [PATCH 173/462] get rid of git HEAD stuff --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 086786d83..85494d21c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -261,7 +261,7 @@ class LBRYDaemonServer(object): self.loaded_git_version = None self.loaded_branch = None - def setup(self, branch="HEAD", user_specified=None): + def setup(self, branch="master", user_specified=None): self.branch = branch if user_specified: if os.path.isdir(user_specified): @@ -272,10 +272,6 @@ class LBRYDaemonServer(object): return defer.succeed("user-specified") else: log.info("User specified UI directory doesn't exist, using " + branch) - elif branch == "HEAD": - log.info("Using UI branch: " + branch) - self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/master" - self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/master/dist.zip" else: log.info("Using UI branch: " + branch) self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/%s" % branch @@ -349,7 +345,7 @@ class LBRYDaemonServer(object): self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) - def start(self, branch="HEAD", user_specified=False, wallet=DEFAULT_WALLET): + def start(self, branch="master", user_specified=False, wallet=DEFAULT_WALLET): d = self.setup(branch=branch, user_specified=user_specified) d.addCallback(lambda v: self._setup_server(v, wallet)) d.addCallback(lambda _: self._api.setup()) From 10b016312dae7915ee87fb3b6b1834533e534c0d Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 6 May 2016 16:16:09 -0400 Subject: [PATCH 174/462] get rid of Xcode tools and git requirement --- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 506dc23e1..c9e9acf75 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -5,7 +5,6 @@ import os import webbrowser import sys import socket -import subprocess import platform from appdirs import user_data_dir @@ -44,17 +43,6 @@ def test_internet_connection(): return False -def prompt_for_xcode_if_needed(): - t = subprocess.check_output("git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep HEAD | cut -f 1", shell=True) - if not t: - if platform.system().lower() != "darwin": - print "Please install git" - sys.exit(0) - else: - print "You should have been alerted to install xcode command line tools, please do so and then start lbry" - sys.exit(0) - - def stop(): def _disp_shutdown(): print "Shutting down lbrynet-daemon from command line" @@ -80,8 +68,8 @@ def start(): help="path to custom UI folder", default=None) parser.add_argument("--branch", - help="Branch of lbry-web-ui repo to use, defaults on HEAD", - default="HEAD") + help="Branch of lbry-web-ui repo to use, defaults on master", + default="master") parser.add_argument('--no-launch', dest='launchui', action="store_false") parser.add_argument('--log-to-console', dest='logtoconsole', action="store_true") parser.add_argument('--quiet', dest='quiet', action="store_true") @@ -104,8 +92,6 @@ def start(): except: pass - prompt_for_xcode_if_needed() - log.info("Starting lbrynet-daemon from command line") if not args.logtoconsole and not args.quiet: From bb2fb92b0eb8cc31baa2fde20042e3c9690c1ddb Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 6 May 2016 16:59:23 -0400 Subject: [PATCH 175/462] have uri handler fail silently to prevent misleading error otherwise first run produced an error while it was downloading the headers if the app was started by going to lbry://lbry --- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index ed63c6f03..9bdc20ec8 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -36,14 +36,16 @@ class LBRYURIHandler(object): elif status: return True else: - raise Timeout("LBRY daemon is running, but connection timed out") + sys.exit(0) + # raise Timeout("LBRY daemon is running, but connection timed out") except: if self.start_timeout < 30: sleep(1) self.start_timeout += 1 self.check_status() else: - raise Timeout("Timed out trying to start LBRY daemon") + sys.exit(0) + # raise Timeout("Timed out trying to start LBRY daemon") def handle_osx(self, lbry_name): lbry_process = [d for d in subprocess.Popen(['ps','aux'], stdout=subprocess.PIPE).stdout.readlines() @@ -72,6 +74,7 @@ class LBRYURIHandler(object): if not is_running: sys.exit(0) except: + #start lbrynet-daemon sys.exit(0) if lbry_name == "lbry": From 8bd6fb5b4e30f61def0b51aeb2d88e8cfa3df3ee Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 6 May 2016 17:20:03 -0400 Subject: [PATCH 176/462] clean up uri handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clean up work arounds from when ui didn’t do loading nicely --- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 59 ++++--------------- 1 file changed, 11 insertions(+), 48 deletions(-) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py index 9bdc20ec8..f6990cfea 100644 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py @@ -11,73 +11,36 @@ API_CONNECTION_STRING = "http://localhost:5279/lbryapi" UI_ADDRESS = "http://localhost:5279" -class Timeout(Exception): - def __init__(self, value): - self.parameter = value - - def __str__(self): - return repr(self.parameter) - - class LBRYURIHandler(object): def __init__(self): self.started_daemon = False - self.start_timeout = 0 self.daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING) - def check_status(self): - status = None - try: - status = self.daemon.is_running() - if self.start_timeout < 30 and not status: - sleep(1) - self.start_timeout += 1 - self.check_status() - elif status: - return True - else: - sys.exit(0) - # raise Timeout("LBRY daemon is running, but connection timed out") - except: - if self.start_timeout < 30: - sleep(1) - self.start_timeout += 1 - self.check_status() - else: - sys.exit(0) - # raise Timeout("Timed out trying to start LBRY daemon") - def handle_osx(self, lbry_name): - lbry_process = [d for d in subprocess.Popen(['ps','aux'], stdout=subprocess.PIPE).stdout.readlines() - if 'LBRY.app' in d and 'LBRYURIHandler' not in d] try: status = self.daemon.is_running() except: - status = None - - if lbry_process or status: - self.check_status() - started = False - else: os.system("open /Applications/LBRY.app") - self.check_status() - started = True + sleep(3) - if lbry_name == "lbry" or lbry_name == "" and not started: + if lbry_name == "lbry" or lbry_name == "": webbrowser.open(UI_ADDRESS) else: webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) def handle_linux(self, lbry_name): try: - is_running = self.daemon.is_running() - if not is_running: - sys.exit(0) + status = self.daemon.is_running() except: - #start lbrynet-daemon - sys.exit(0) + cmd = r'DIR = "$( cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )"' \ + r'if [-z "$(pgrep lbrynet-daemon)"]; then' \ + r'echo "running lbrynet-daemon..."' \ + r'$DIR / lbrynet - daemon &' \ + r'sleep 3 # let the daemon load before connecting' \ + r'fi' + subprocess.Popen(cmd, shell=True) - if lbry_name == "lbry": + if lbry_name == "lbry" or lbry_name == "": webbrowser.open(UI_ADDRESS) else: webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) From 74250982d141156d3d80994a57fa5740cd4ce144 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Fri, 6 May 2016 17:38:57 -0400 Subject: [PATCH 177/462] update linux uri handler --- packaging/ubuntu/lbry | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index 37859de4d..3735a253a 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -26,7 +26,12 @@ ARG=${1:-} if [ -z "$ARG" ]; then URL="" else - URL="?watch=$(urlencode "$(echo "$ARG" | cut -c 8-)")" + NAME=$(echo "$ARG" | cut -c 8-) + if [ -z "$NAME" -o "$NAME" == "lbry" ]; then + URL="" + else + URL="/?watch=$(urlencode "$NAME")" + fi fi -/usr/bin/xdg-open "http://localhost:5279/$URL" +/usr/bin/xdg-open "http://localhost:5279$URL" From 42a3006f6e74468932b08944c00f5e3ae787f6a7 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 10 May 2016 19:03:14 -0400 Subject: [PATCH 178/462] add claim txid and uri to lbry_file --- lbrynet/core/LBRYcrdWallet.py | 6 ++++-- lbrynet/lbryfilemanager/LBRYFileDownloader.py | 13 ++++++++++++- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 +++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 5691d291f..8ec200611 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -530,8 +530,10 @@ class LBRYWallet(object): " sd_hash text)") def _save_name_metadata(self, name, sd_hash, txid): - d = self.db.runQuery("insert into name_metadata values (?, ?, ?)", - (name, txid, sd_hash)) + d = self.db.runQuery("select * from name_metadata where txid=?", (txid,)) + d.addCallback(lambda r: self.db.runQuery("insert into name_metadata values (?, ?, ?)", (name, txid, sd_hash)) + if not len(r) else None) + return d def _get_claim_metadata_for_sd_hash(self, sd_hash): diff --git a/lbrynet/lbryfilemanager/LBRYFileDownloader.py b/lbrynet/lbryfilemanager/LBRYFileDownloader.py index fa3ce530e..aad9d08ea 100644 --- a/lbrynet/lbryfilemanager/LBRYFileDownloader.py +++ b/lbrynet/lbryfilemanager/LBRYFileDownloader.py @@ -25,6 +25,8 @@ class ManagedLBRYFileDownloader(LBRYFileSaver): stream_info_manager, payment_rate_manager, wallet, download_directory, upload_allowed, file_name) self.sd_hash = None + self.txid = None + self.uri = None self.rowid = rowid self.lbry_file_manager = lbry_file_manager self.saving_status = False @@ -35,10 +37,19 @@ class ManagedLBRYFileDownloader(LBRYFileSaver): def _save_sd_hash(sd_hash): if len(sd_hash): self.sd_hash = sd_hash[0] + d = self.wallet._get_claim_metadata_for_sd_hash(self.sd_hash) + else: + d = defer.succeed(None) + + return d + + def _save_claim(name, txid): + self.uri = name + self.txid = txid return defer.succeed(None) d.addCallback(_save_sd_hash) - + d.addCallback(lambda r: _save_claim(r[0], r[1]) if r else None) d.addCallback(lambda _: self.lbry_file_manager.get_lbry_file_status(self)) def restore_status(status): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index f16f5562a..9e92387d7 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1101,6 +1101,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, + 'lbry_uri': f.uri, 'txid': f.txid, 'total_bytes': size, 'written_bytes': written_bytes, 'code': status[0], 'message': message}) @@ -1109,7 +1110,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, - 'written_bytes': written_bytes, 'code': status[0], 'message': status[1]}) + 'written_bytes': written_bytes, 'lbry_uri': f.uri, 'txid': f.txid, + 'code': status[0], 'message': status[1]}) return d From b04cfdc09118f12ba82192933e6ecac72d8c59a9 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 10 May 2016 20:47:35 -0400 Subject: [PATCH 179/462] load uri and txid when stream is started --- lbrynet/lbryfilemanager/LBRYFileDownloader.py | 14 ++++++++++++-- lbrynet/lbrynet_daemon/LBRYDaemon.py | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbryfilemanager/LBRYFileDownloader.py b/lbrynet/lbryfilemanager/LBRYFileDownloader.py index aad9d08ea..b01a84708 100644 --- a/lbrynet/lbryfilemanager/LBRYFileDownloader.py +++ b/lbrynet/lbryfilemanager/LBRYFileDownloader.py @@ -111,11 +111,21 @@ class ManagedLBRYFileDownloader(LBRYFileSaver): d.addCallback(lambda _: self.stream_info_manager._get_sd_blob_hashes_for_stream(self.stream_hash)) def _save_sd_hash(sd_hash): - self.sd_hash = sd_hash[0] + if len(sd_hash): + self.sd_hash = sd_hash[0] + d = self.wallet._get_claim_metadata_for_sd_hash(self.sd_hash) + else: + d = defer.succeed(None) + + return d + + def _save_claim(name, txid): + self.uri = name + self.txid = txid return defer.succeed(None) d.addCallback(_save_sd_hash) - + d.addCallback(lambda r: _save_claim(r[0], r[1]) if r else None) d.addCallback(lambda _: self._save_status()) return d diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 9e92387d7..d0ecc8fb6 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -593,6 +593,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self._upload_log(name_prefix="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) + d.addCallback(lambda _: self.lbry_file_manager.stop()) d.addErrback(lambda err: log.info("Bad server shutdown: " + err.getTraceback())) if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) From 1c507393a95647921e1c50d4733816715bd7c2dc Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 10 May 2016 21:01:53 -0400 Subject: [PATCH 180/462] fix problem with getting a stream where the file has been deleted --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index d0ecc8fb6..75179f88e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -913,7 +913,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self._get_lbry_file_by_sd_hash(stream_hash) def _add_results(l): - return defer.succeed((stream_info, l)) + if l: + if os.path.isfile(os.path.join(self.download_directory, l.file_name)): + return defer.succeed((stream_info, l)) + return defer.succeed((stream_info, None)) d.addCallback(_add_results) return d From 210c1215d636c9f40b703a5576e87e68b6e27b94 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 11 May 2016 01:48:23 -0400 Subject: [PATCH 181/462] add metadata to lbry_file --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 75179f88e..5d21bf76a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1119,9 +1119,22 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d + def _add_metadata(message): + def _add_to_dict(metadata): + message['metadata'] = metadata + return defer.succeed(message) + + if f.txid: + d = self._resolve_name(f.uri) + d.addCallback(_add_to_dict) + else: + d = defer.succeed(None) + return d + if f: d = f.get_total_bytes() d.addCallback(_generate_reply) + d.addCallback(_add_metadata) return d else: return False From 20f423a2fcc519708e30d23ac268459759d7b9c6 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 11 May 2016 02:08:13 -0400 Subject: [PATCH 182/462] bump version --- lbrynet/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 8f010329d..26dafe45d 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) -version = (0, 2, 3) +version = (0, 2, 4) __version__ = ".".join([str(x) for x in version]) From f985a2a8ad9bb977ed0ad0e89f90267226ca2897 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 11 May 2016 02:47:33 -0400 Subject: [PATCH 183/462] add waiting_for_credits startup code --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 5d21bf76a..ff4c1e7f0 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -74,13 +74,15 @@ LOADING_WALLET_CODE = 'loading_wallet' LOADING_FILE_MANAGER_CODE = 'loading_file_manager' LOADING_SERVER_CODE = 'loading_server' STARTED_CODE = 'started' +WAITING_FOR_FIRST_RUN_CREDITS = 'waiting_for_credits' STARTUP_STAGES = [ (INITIALIZING_CODE, 'Initializing...'), (LOADING_DB_CODE, 'Loading databases...'), (LOADING_WALLET_CODE, 'Catching up with the blockchain... %s'), (LOADING_FILE_MANAGER_CODE, 'Setting up file manager'), (LOADING_SERVER_CODE, 'Starting lbrynet'), - (STARTED_CODE, 'Started lbrynet') + (STARTED_CODE, 'Started lbrynet'), + (WAITING_FOR_FIRST_RUN_CREDITS, 'Waiting for first run credits...') ] DOWNLOAD_METADATA_CODE = 'downloading_metadata' @@ -372,6 +374,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def _announce_startup(): + def _wait_for_credits(): + if float(self.session.wallet.wallet_balance) == 0.0: + self.startup_status = STARTUP_STAGES[6] + return reactor.callLater(1, _wait_for_credits) + else: + return _announce() + def _announce(): self.announced_startup = True self.startup_status = STARTUP_STAGES[5] @@ -387,7 +396,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._check_first_run()) d.addCallback(self._show_first_run_result) - d.addCallback(lambda _: _announce()) + d.addCallback(lambda _: _wait_for_credits() if self.requested_first_run_credits else _announce()) return d log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") From 1425ad87ab5e0d2bcb06bf592718b1c82416f19f Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 11 May 2016 04:44:23 -0400 Subject: [PATCH 184/462] fix get_lbry_file problem --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index ff4c1e7f0..c5d019456 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1137,7 +1137,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self._resolve_name(f.uri) d.addCallback(_add_to_dict) else: - d = defer.succeed(None) + d = defer.succeed(message) return d if f: From 278c7dc836b5f8469436f04299986e410cadc1a5 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 13 May 2016 19:35:38 -0400 Subject: [PATCH 185/462] have get() wait for file to have written data before returning by default controlled by wait_for_write parameter --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 47 +++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index c5d019456..954c55100 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -899,7 +899,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory) return defer.succeed(True) - def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None, stream_info=None): + def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None, + file_name=None, stream_info=None, wait_for_write=True): """ Add a lbry file to the file manager, start the download, and return the new lbry file. If it already exists in the file manager, return the existing lbry file @@ -929,17 +930,51 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(_add_results) return d + def _wait_on_lbry_file(f): + if os.path.isfile(os.path.join(self.download_directory, f.file_name)): + written_file = file(os.path.join(self.download_directory, f.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + + if not written_bytes: + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, _wait_on_lbry_file, f)) + return d + else: + return defer.succeed(_disp_file(f)) + def _disp_file(f): file_path = os.path.join(self.download_directory, f.file_name) log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.sd_hash) + " --> " + file_path) return f def _get_stream(stream_info): + def _wait_for_write(): + if os.path.isfile(os.path.join(self.download_directory, self.streams[name].downloader.file_name)): + written_file = file(os.path.join(self.download_directory, self.streams[name].downloader.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + + if not written_bytes: + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, _wait_for_write)) + return d + else: + return defer.succeed(None) + self.streams[name] = GetStream(self.sd_identifier, self.session, self.session.wallet, self.lbry_file_manager, max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, download_directory=download_directory, file_name=file_name) d = self.streams[name].start(stream_info, name) + if wait_for_write: + d.addCallback(lambda _: _wait_for_write()) d.addCallback(lambda _: self.streams[name].downloader) return d @@ -950,10 +985,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: d = defer.succeed(stream_info) d.addCallback(_setup_stream) - d.addCallback(lambda (stream_info, lbry_file): _get_stream(stream_info) if not lbry_file else _disp_file(lbry_file)) + d.addCallback(lambda (stream_info, lbry_file): _get_stream(stream_info) if not lbry_file else _wait_on_lbry_file(lbry_file)) if not stream_info: d.addCallback(_remove_from_wait) - return d def _get_long_count_timestamp(self): @@ -1510,11 +1544,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: stream_info = None + if 'wait_for_write' in p.keys(): + wait_for_write = p['wait_for_write'] + else: + wait_for_write = True + if 'name' in p.keys(): name = p['name'] if p['name'] not in self.waiting_on.keys(): d = self._download_name(name=name, timeout=timeout, download_directory=download_directory, - stream_info=stream_info, file_name=file_name) + stream_info=stream_info, file_name=file_name, wait_for_write=wait_for_write) d.addCallback(lambda l: {'stream_hash': sd_hash, 'path': os.path.join(self.download_directory, l.file_name)} if stream_info else From b5e8aec54839596f5fc3c1d0b98c4d4eef26e8a6 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 14 May 2016 17:36:30 -0400 Subject: [PATCH 186/462] fix --wallet=lbrycrd --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 111 ++++++++++++-------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 2 +- 2 files changed, 66 insertions(+), 47 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 954c55100..2de7caa98 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -170,52 +170,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle default_download_directory = get_path(FOLDERID.Downloads, UserHandle.current) self.db_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrynet") - self.lbrycrdd_path = "lbrycrdd.exe" - if wallet_type == "lbrycrd": - self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") - else: - self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbryum") elif sys.platform == "darwin": default_download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') self.db_dir = user_data_dir("LBRY") - self.lbrycrdd_path = "./lbrycrdd" - if wallet_type == "lbrycrd": - self.wallet_dir = user_data_dir("lbrycrd") - else: - self.wallet_dir = user_data_dir("LBRY") else: default_download_directory = os.getcwd() self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") - self.lbrycrdd_path = "./lbrycrdd" - if wallet_type == "lbrycrd": - self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") - else: - self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") - self.created_data_dir = False - if not os.path.exists(self.db_dir): - os.mkdir(self.db_dir) - self.created_data_dir = True - - self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") - self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') - self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.wallet_user = None - self.wallet_password = None - - self.internet_connection_checker = LoopingCall(self._check_network_connection) - self.version_checker = LoopingCall(self._check_remote_versions) - self.connection_problem_checker = LoopingCall(self._check_connection_problems) - # self.lbrynet_connection_checker = LoopingCall(self._check_lbrynet_connection) - - self.sd_identifier = StreamDescriptorIdentifier() - self.stream_info_manager = TempLBRYFileMetadataManager() - self.settings = LBRYSettings(self.db_dir) - self.blob_request_payment_rate_manager = None - self.lbry_file_metadata_manager = None - self.lbry_file_manager = None self.default_settings = { 'run_on_startup': False, @@ -228,7 +190,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'search_timeout': DEFAULT_SEARCH_TIMEOUT, 'download_timeout': DEFAULT_TIMEOUT, 'max_search_results': DEFAULT_MAX_SEARCH_RESULTS, - 'wallet_type': wallet_type, + 'wallet_type': DEFAULT_WALLET, 'delete_blobs_on_remove': True, 'peer_port': 3333, 'dht_node_port': 4444, @@ -282,7 +244,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.search_timeout = self.session_settings['search_timeout'] self.download_timeout = self.session_settings['download_timeout'] self.max_search_results = self.session_settings['max_search_results'] - self.wallet_type = self.session_settings['wallet_type'] + self.wallet_type = self.session_settings['wallet_type'] if self.session_settings['wallet_type'] == wallet_type else wallet_type self.delete_blobs_on_remove = self.session_settings['delete_blobs_on_remove'] self.peer_port = self.session_settings['peer_port'] self.dht_node_port = self.session_settings['dht_node_port'] @@ -299,6 +261,50 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self.name_cache = {} + if os.name == "nt": + from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle + self.lbrycrdd_path = "lbrycrdd.exe" + if self.wallet_type == "lbrycrd": + self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") + else: + self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbryum") + elif sys.platform == "darwin": + self.lbrycrdd_path = "./lbrycrdd" + if self.wallet_type == "lbrycrd": + self.wallet_dir = user_data_dir("lbrycrd") + else: + self.wallet_dir = user_data_dir("LBRY") + else: + self.lbrycrdd_path = "./lbrycrdd" + if self.wallet_type == "lbrycrd": + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") + else: + self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") + + self.created_data_dir = False + if not os.path.exists(self.db_dir): + os.mkdir(self.db_dir) + self.created_data_dir = True + + self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") + self.lbrycrd_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.autofetcher_conf = os.path.join(self.wallet_dir, "autofetcher.conf") + self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") + self.wallet_user = None + self.wallet_password = None + + self.internet_connection_checker = LoopingCall(self._check_network_connection) + self.version_checker = LoopingCall(self._check_remote_versions) + self.connection_problem_checker = LoopingCall(self._check_connection_problems) + # self.lbrynet_connection_checker = LoopingCall(self._check_lbrynet_connection) + + self.sd_identifier = StreamDescriptorIdentifier() + self.stream_info_manager = TempLBRYFileMetadataManager() + self.settings = LBRYSettings(self.db_dir) + self.blob_request_payment_rate_manager = None + self.lbry_file_metadata_manager = None + self.lbry_file_manager = None + def render(self, request): request.content.seek(0, 0) # Unmarshal the JSON-RPC data. @@ -818,11 +824,19 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _set_first_run_false(): log.info("Not first run") self.first_run = False + self.session_settings['requested_first_run_credits'] = True + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() return 0.0 - d = self.session.wallet.is_first_run() - d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run or not self.requested_first_run_credits - else _set_first_run_false()) + if self.wallet_type == 'lbryum': + d = self.session.wallet.is_first_run() + d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run or not self.requested_first_run_credits + else _set_first_run_false()) + else: + d = defer.succeed(None) + d.addCallback(lambda _: _set_first_run_false()) return d def _do_first_run(self): @@ -831,6 +845,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): r = requests.post(url, json=data) if r.status_code == 200: self.requested_first_run_credits = True + self.session_settings['requested_first_run_credits'] = True f = open(self.daemon_conf, "w") f.write(json.dumps(self.session_settings)) f.close() @@ -1243,9 +1258,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): r['message'] = self.connection_problem[1] r['is_lagging'] = True elif self.startup_status[0] == LOADING_WALLET_CODE: - if self.session.wallet.blocks_behind_alert != 0: - r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") - r['progress'] = self.session.wallet.catchup_progress + if self.wallet_type == 'lbryum': + if self.session.wallet.blocks_behind_alert != 0: + r['message'] = r['message'] % (str(self.session.wallet.blocks_behind_alert) + " blocks behind") + r['progress'] = self.session.wallet.catchup_progress + else: + r['message'] = "Catching up with the blockchain" + r['progress'] = 0 else: r['message'] = "Catching up with the blockchain" r['progress'] = 0 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index c9e9acf75..1d7531331 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -104,7 +104,7 @@ def start(): if test_internet_connection(): lbry = LBRYDaemonServer() - d = lbry.start(branch=args.branch, user_specified=args.ui) + d = lbry.start(branch=args.branch, user_specified=args.ui, wallet=args.wallet) if args.launchui: d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) From be96c77467fae2202a1df4013e7b8bdd77e0d83f Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 14 May 2016 17:57:34 -0400 Subject: [PATCH 187/462] add set_miner and get_miner_status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set_miner takes parameter key ‘run’, which is True or False --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 2de7caa98..0a10dff9d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -330,6 +330,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): if functionPath not in ALLOWED_DURING_STARTUP: return server.failure + if self.wallet_type == "lbryum" and functionPath in ['set_miner', 'get_miner_status']: + return server.failure + try: function = self._getFunction(functionPath) except jsonrpclib.Fault, f: @@ -1995,6 +1998,39 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d + def jsonrpc_set_miner(self, p): + """ + Start of stop the miner, function only available when lbrycrd is set as the wallet + + Args: + run: True/False + Returns: + miner status, True/False + """ + + stat = p['run'] + if stat: + d = self.session.wallet.start_miner() + else: + d = self.session.wallet.stop_miner() + d.addCallback(lambda _: self.session.wallet.get_miner_status()) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_get_miner_status(self): + """ + Get status of miner + + Args: + None + Returns: + True/False + """ + + d = self.session.wallet.get_miner_status() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + # def jsonrpc_update_name(self, metadata): # def _disp(x): # print x From 098b5b28a8a1638595a3fe3831bb8b63154ae397 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Fri, 20 May 2016 12:13:58 -0400 Subject: [PATCH 188/462] fix web ui branch in install script --- packaging/ubuntu/ubuntu_package_setup.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 09483bb8e..95c0d4694 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -8,7 +8,7 @@ set -euo pipefail BRANCH=${1:-master} -WEB_UI_BRANCH=${2:-} +WEB_UI_BRANCH=${2:-master} BUILD_DIR="lbry-build-$(date +%Y%m%d-%H%M%S)" mkdir "$BUILD_DIR" @@ -48,9 +48,7 @@ tar -xvJf data.tar.xz --directory data PACKAGING_DIR='lbry/packaging/ubuntu' # set web ui branch -if [ -z "$WEB_UI_BRANCH" ]; then - sed -i "s/^WEB_UI_BRANCH='[^']\+'/WEB_UI_BRANCH='$WEB_UI_BRANCH'/" "$PACKAGING_DIR/lbry" -fi +sed -i "s/^WEB_UI_BRANCH='[^']\+'/WEB_UI_BRANCH='$WEB_UI_BRANCH'/" "$PACKAGING_DIR/lbry" # add files function addfile() { From f48c1562f3efa8a95bcee6acaae14aea72bfc697 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Fri, 20 May 2016 19:59:49 -0400 Subject: [PATCH 189/462] better log names --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 31 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0a10dff9d..58e8fbdde 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -397,9 +397,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): # self.lbrynet_connection_checker.start(3600) if self.first_run: - d = self._upload_log(name_prefix="fr") + d = self._upload_log(log_type="first_run") else: - d = self._upload_log(exclude_previous=True, name_prefix="start") + d = self._upload_log(exclude_previous=True, log_type="start") if float(self.session.wallet.wallet_balance) == 0.0: d.addCallback(lambda _: self._check_first_run()) @@ -579,12 +579,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): dl.addCallback(_set_query_handlers) return dl - def _upload_log(self, name_prefix=None, exclude_previous=False, force=False): - if name_prefix: - name_prefix = name_prefix + "-" + platform.system() - else: - name_prefix = platform.system() - + def _upload_log(self, log_type=None, exclude_previous=False, force=False): if self.upload_log or force: LOG_URL = "https://lbry.io/log-upload" if exclude_previous: @@ -596,9 +591,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): f = open(self.log_file, "r") log_contents = f.read() f.close() - t = datetime.now() - log_name = name_prefix + "-" + base58.b58encode(self.lbryid)[:20] + "-" + str(t.month) + "-" + str(t.day) + "-" + str(t.year) + "-" + str(t.hour) + "-" + str(t.minute) - params = {'name': log_name, 'log': log_contents} + params = { + 'date': datetime.utcnow().strftime('%Y%m%d-%H%M%S'), + 'hash': base58.b58encode(self.lbryid)[:20], + 'sys': platform.system(), + 'type': log_type, + 'log': log_contents + } requests.post(LOG_URL, params) return defer.succeed(None) @@ -609,7 +608,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("Closing lbrynet session") log.info("Status at time of shutdown: " + self.startup_status[0]) - d = self._upload_log(name_prefix="close", exclude_previous=False if self.first_run else True) + d = self._upload_log(log_type="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) d.addCallback(lambda _: self.lbry_file_manager.stop()) d.addErrback(lambda err: log.info("Bad server shutdown: " + err.getTraceback())) @@ -2084,9 +2083,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): if p: if 'name_prefix' in p.keys(): - prefix = p['name_prefix'] + '_api' + log_type = p['name_prefix'] + '_api' else: - prefix = None + log_type = None if 'exclude_previous' in p.keys(): exclude_previous = p['exclude_previous'] @@ -2101,10 +2100,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: force = False else: - prefix = "api" + log_type = "api" exclude_previous = True - d = self._upload_log(name_prefix=prefix, exclude_previous=exclude_previous, force=force) + d = self._upload_log(log_type=log_type, exclude_previous=exclude_previous, force=force) if 'message' in p.keys(): d.addCallback(lambda _: self._log_to_slack(p['message'])) d.addCallback(lambda _: self._render_response(True, OK_CODE)) From efb5157027078b45dc6b3af9b236090f8486bdb7 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Sat, 7 May 2016 14:15:42 -0500 Subject: [PATCH 190/462] Enabling builds on travis-ci - adds a travis.yml config file - modifies the ubuntu build script to work on travis - adds start of osx builds - waiting for certs to be added --- .gitmodules | 3 + .travis.yml | 32 +++++ packaging/osx/add-key.sh | 25 ++++ packaging/osx/lbry-osx-app | 1 + packaging/ubuntu/ubuntu_package_setup.sh | 150 ++++++++++++++++++----- 5 files changed, 182 insertions(+), 29 deletions(-) create mode 100644 .gitmodules create mode 100644 .travis.yml create mode 100755 packaging/osx/add-key.sh create mode 160000 packaging/osx/lbry-osx-app diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..a8c2e8de6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "packaging/osx/lbry-osx-app"] + path = packaging/osx/lbry-osx-app + url = https://github.com/jobevers/lbry-osx-app.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..d21cb628d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ + +matrix: + include: + - os: linux + sudo: required + dist: trust + # dh-virtualenv requires that we use the same python interpreter + # that comes with the system, so we don't want to use anything that + # travis would try to set-up for us in python + language: generic + +before_install: + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; brew install python; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo pip install --upgrade pip virtualenv; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then virtualenv $HOME/venv; source $HOME/venv/bin/activate; fi + +install: true + +before_script: +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.cer.enc -d -a -out packaging/osx/certs/dist.cer; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.p12.enc -d -a -out packaging/osx/certs/dist.p12; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/osx/add-key.sh; fi + +script: +- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade gmp; fi +# the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi +# py2app fails to find jsonrpc unless json-rpc is installed. why? I don't know. +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi + diff --git a/packaging/osx/add-key.sh b/packaging/osx/add-key.sh new file mode 100755 index 000000000..ac06aa373 --- /dev/null +++ b/packaging/osx/add-key.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# http://stackoverflow.com/a/246128 +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# adapted from https://www.objc.io/issues/6-build-tools/travis-ci/#add-scripts + +KEYCHAIN_PASSWORD=travis + +# Create a custom keychain +security create-keychain -p ${KEYCHAIN_PASSWORD} osx-build.keychain + +# Make the custom keychain default, so xcodebuild will use it for signing +security default-keychain -s osx-build.keychain + +# Unlock the keychain +security unlock-keychain -p ${KEYCHAIN_PASSWORD} osx-build.keychain + +# Set keychain timeout to 1 hour for long builds +# see http://www.egeek.me/2013/02/23/jenkins-and-xcode-user-interaction-is-not-allowed/ +security set-keychain-settings -t 3600 -l ~/Library/Keychains/osx-build.keychain + +# Add certificates to keychain and allow codesign to access them +security import ${DIR}/certs/dist.cer -k ~/Library/Keychains/osx-build.keychain -T /usr/bin/codesign +security import ${DIR}/certs/dist.p12 -k ~/Library/Keychains/osx-build.keychain -P $KEY_PASSWORD -T /usr/bin/codesign + diff --git a/packaging/osx/lbry-osx-app b/packaging/osx/lbry-osx-app new file mode 160000 index 000000000..e5b465205 --- /dev/null +++ b/packaging/osx/lbry-osx-app @@ -0,0 +1 @@ +Subproject commit e5b465205469e8f14999b56138b3bea6666e006d diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 95c0d4694..5e1437e18 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -1,38 +1,124 @@ #!/bin/bash -# Tested on fresh Ubuntu 14.04 install. - -# wget https://raw.githubusercontent.com/lbryio/lbry/master/packaging/ubuntu/ubuntu_package_setup.sh -# bash ubuntu_package_setup.sh [BRANCH] [WEB-UI-BRANCH] - set -euo pipefail -BRANCH=${1:-master} -WEB_UI_BRANCH=${2:-master} +function HELP { + echo "Build a debian package for lbry" + echo "-----" + echo "When run without any arguments, this script expects the current directory" + echo "to be the main lbry repo and it builds what is in that directory" + echo + echo "Optional arguments:" + echo + echo "-c: clone a fresh copy of the repo" + echo "-b : use the specified branch of the lbry repo" + echo "-w : set the webui branch" + echo "-d : specifiy the build directory" + echo "-h: show help" + echo "-t: turn trace on" + exit 1 +} -BUILD_DIR="lbry-build-$(date +%Y%m%d-%H%M%S)" -mkdir "$BUILD_DIR" +CLONE=false +BUILD_DIR="" +BRANCH="" +WEB_UI_BRANCH="master" + +while getopts :hctb:w:d: FLAG; do + case $FLAG in + c) + CLONE=true + ;; + b) + BRANCH=${OPTARG} + ;; + w) + WEB_UI_BRANCH=${OPTARG} + ;; + d) + BUILD_DIR=${OPTARG} + ;; + t) + set -o xtrace + ;; + h) + HELP + ;; + \?) #unrecognized option - show help + echo "Option -$OPTARG not allowed." + HELP + ;; + :) + echo "Option -$OPTARG requires an argument." + HELP + ;; + esac +done + +shift $((OPTIND-1)) + + +SUDO='' +if (( $EUID != 0 )); then + SUDO='sudo' +fi + +if [ "$CLONE" = false ]; then + if [ `basename $PWD` != "lbry" ]; then + echo "Not currently in the lbry directory. Cowardly refusing to go forward" + exit 1 + fi + SOURCE_DIR=$PWD +fi + +if [ -z "${BUILD_DIR}" ]; then + if [ "$CLONE" = true ]; then + # build in the current directory + BUILD_DIR="lbry-build-$(date +%Y%m%d-%H%M%S)" + else + BUILD_DIR="../lbry-build-$(date +%Y%m%d-%H%M%S)" + fi +fi + +mkdir -p "$BUILD_DIR" cd "$BUILD_DIR" +if [ -z ${TRAVIS+x} ]; then + # if not on travis, its nice to see progress + QUIET="" +else + QUIET="-qq" +fi + # get the required OS packages -sudo add-apt-repository -y ppa:spotify-jyrki/dh-virtualenv -sudo apt-get update -sudo apt-get install -y build-essential git python-dev libffi-dev libssl-dev libgmp3-dev dh-virtualenv debhelper +$SUDO apt-get ${QUIET} update +$SUDO apt-get ${QUIET} install -y --no-install-recommends software-properties-common +$SUDO add-apt-repository -y ppa:spotify-jyrki/dh-virtualenv +$SUDO apt-get ${QUIET} update +$SUDO apt-get ${QUIET} install -y --no-install-recommends \ + build-essential git python-dev libffi-dev libssl-dev \ + libgmp3-dev dh-virtualenv debhelper wget python-pip # need a modern version of pip (more modern than ubuntu default) -wget https://bootstrap.pypa.io/get-pip.py -sudo python get-pip.py -rm get-pip.py -sudo pip install make-deb - -# check out LBRY -git clone https://github.com/lbryio/lbry.git --branch "$BRANCH" +$SUDO pip install --upgrade pip +$SUDO pip install make-deb # build packages +# +# dpkg-buildpackage outputs its results into '..' so +# we need to move/clone lbry into the build directory +if [ "$CLONE" == true]; then + cp -a $SOURCE_DIR lbry +else + git clone https://github.com/lbryio/lbry.git +fi ( - cd lbry - make-deb - dpkg-buildpackage -us -uc + cd lbry + if [ -n "${BRANCH}" ]; then + git checkout "${BRANCH}" + fi + make-deb + dpkg-buildpackage -us -uc ) @@ -42,8 +128,15 @@ git clone https://github.com/lbryio/lbry.git --branch "$BRANCH" PACKAGE="$(ls | grep '.deb')" ar vx "$PACKAGE" mkdir control data -tar -xvzf control.tar.gz --directory control -tar -xvJf data.tar.xz --directory data +tar -xzf control.tar.gz --directory control + +# The output of the travis build is a +# tar.gz and the output locally is tar.xz. +# Instead of having tar detect the compression used, we +# could update the config to output the same in either spot. +# Unfortunately, doing so requires editting some auto-generated +# files: http://linux.spiney.org/forcing_gzip_compression_when_building_debian_packages +tar -xf data.tar.?z --directory data PACKAGING_DIR='lbry/packaging/ubuntu' @@ -60,13 +153,12 @@ function addfile() { } addfile "$PACKAGING_DIR/lbry" usr/share/python/lbrynet/bin/lbry addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop -#addfile lbry/packaging/ubuntu/lbry-init.conf etc/init/lbry.conf # repackage .deb -sudo chown -R root:root control data -tar -cvzf control.tar.gz -C control . -tar -cvJf data.tar.xz -C data . -sudo chown root:root debian-binary control.tar.gz data.tar.xz +$SUDO chown -R root:root control data +tar -czf control.tar.gz -C control . +tar -cJf data.tar.xz -C data . +$SUDO chown root:root debian-binary control.tar.gz data.tar.xz ar r "$PACKAGE" debian-binary control.tar.gz data.tar.xz # TODO: we can append to data.tar instead of extracting it all and recompressing From 44aba93d37d333f6138144f90aa8cfe1b551759d Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Sun, 22 May 2016 22:36:56 -0400 Subject: [PATCH 191/462] need fakeroot too --- packaging/ubuntu/ubuntu_package_setup.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 5e1437e18..7255f2fd0 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -26,13 +26,13 @@ WEB_UI_BRANCH="master" while getopts :hctb:w:d: FLAG; do case $FLAG in - c) + c) CLONE=true ;; - b) + b) BRANCH=${OPTARG} ;; - w) + w) WEB_UI_BRANCH=${OPTARG} ;; d) @@ -41,7 +41,7 @@ while getopts :hctb:w:d: FLAG; do t) set -o xtrace ;; - h) + h) HELP ;; \?) #unrecognized option - show help @@ -97,7 +97,7 @@ $SUDO add-apt-repository -y ppa:spotify-jyrki/dh-virtualenv $SUDO apt-get ${QUIET} update $SUDO apt-get ${QUIET} install -y --no-install-recommends \ build-essential git python-dev libffi-dev libssl-dev \ - libgmp3-dev dh-virtualenv debhelper wget python-pip + libgmp3-dev dh-virtualenv debhelper wget python-pip fakeroot # need a modern version of pip (more modern than ubuntu default) $SUDO pip install --upgrade pip From dda8f281a15e71816a9a81c68e518981dd3a7012 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Sun, 22 May 2016 22:45:08 -0400 Subject: [PATCH 192/462] :shipit: travis build badge :shipit: --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index dc8b5c81f..7e5d60ed5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/lbryio/lbry.svg?branch=master)](https://travis-ci.org/lbryio/lbry) + # LBRYnet LBRYnet is a fully decentralized network for distributing data. It consists of peers uploading From 7a97a28e0023434d2a36eddea03cc8760ff5e6be Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Sun, 22 May 2016 23:48:51 -0400 Subject: [PATCH 193/462] fix lbry icon --- lbrynet/lbrynet_gui/lbry.png | Bin 10651 -> 18944 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lbrynet/lbrynet_gui/lbry.png b/lbrynet/lbrynet_gui/lbry.png index 16c7c4683affa3e73c5bcf4fbd0b6bbb0778f275..6599574065ccfcc176cdd5e8726ca783f85f7097 100644 GIT binary patch literal 18944 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

U2Ho%8h$Z1A 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 O6N9I#pUXO@geCykjvm1P literal 10651 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 lz#oNlK}QRNn5{lM7K Date: Mon, 23 May 2016 00:19:55 -0400 Subject: [PATCH 194/462] let user know how to run lbry after install --- packaging/ubuntu/postinst_append | 22 ++++++++++++++++++++++ packaging/ubuntu/ubuntu_package_setup.sh | 2 ++ 2 files changed, 24 insertions(+) create mode 100755 packaging/ubuntu/postinst_append diff --git a/packaging/ubuntu/postinst_append b/packaging/ubuntu/postinst_append new file mode 100755 index 000000000..802b4ba5e --- /dev/null +++ b/packaging/ubuntu/postinst_append @@ -0,0 +1,22 @@ + +zenity --question --text="LBRY Installed\n\nRun LBRY now?" --title="LBRY Installed" --ok-label=" Yes" \ + --cancel-label=" No" --icon-name="system-software-install" + +case $? in + 0) RUN=1 # yes + ;; + 1) RUN=0 # no + ;; + *) RUN=0 # timeout or escape or whatever + ;; +esac + +if [ $RUN = 1 ]; then + xdg-open "lbry://lbry" +else + zenity --info --text="LBRY Installed\n\nNo problem. You can run LBRY later by going to + +lbry://lbry + +in your browser." --title="LBRY Installed" --icon-name="system-software-install" +fi diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 7255f2fd0..f35a78b72 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -154,6 +154,8 @@ function addfile() { addfile "$PACKAGING_DIR/lbry" usr/share/python/lbrynet/bin/lbry addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop +cat "$PACKAGING_DIR/postinst_append" >> control/postinst + # repackage .deb $SUDO chown -R root:root control data tar -czf control.tar.gz -C control . From 4c81906b502cb4ee5ae842ea33eafd134cb39c82 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Mon, 23 May 2016 00:35:44 -0400 Subject: [PATCH 195/462] run lbry instructions in bg --- packaging/ubuntu/postinst_append | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packaging/ubuntu/postinst_append b/packaging/ubuntu/postinst_append index 802b4ba5e..43b10214d 100755 --- a/packaging/ubuntu/postinst_append +++ b/packaging/ubuntu/postinst_append @@ -1,4 +1,8 @@ +( + +sleep 10 + zenity --question --text="LBRY Installed\n\nRun LBRY now?" --title="LBRY Installed" --ok-label=" Yes" \ --cancel-label=" No" --icon-name="system-software-install" @@ -20,3 +24,5 @@ lbry://lbry in your browser." --title="LBRY Installed" --icon-name="system-software-install" fi + +) & From 0a89fb1928217d68c3e88adc60b00afe8d81638a Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Mon, 23 May 2016 00:40:18 -0400 Subject: [PATCH 196/462] 10 is too long --- packaging/ubuntu/postinst_append | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/ubuntu/postinst_append b/packaging/ubuntu/postinst_append index 43b10214d..2e040b492 100755 --- a/packaging/ubuntu/postinst_append +++ b/packaging/ubuntu/postinst_append @@ -1,7 +1,7 @@ ( -sleep 10 +sleep 4 zenity --question --text="LBRY Installed\n\nRun LBRY now?" --title="LBRY Installed" --ok-label=" Yes" \ --cancel-label=" No" --icon-name="system-software-install" From b605630a9c9221b332322cc52dd0e450a9efe2ff Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Mon, 23 May 2016 15:21:47 -0400 Subject: [PATCH 197/462] update install instructions --- INSTALL.md | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 6dc552bcf..c6e2b0f11 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,19 +1,33 @@ -#### Installing lbrynet on Linux +#### Installing the LBRY app -------------------------- -The following packages are necessary (the following are their names on Ubuntu): -libgmp3-dev build-essential python2.7 python2.7-dev python-pip +Installing LBRY is simple. You can get a dmg installer for OS X (Mavericks and up) or a .deb for linux [here](https://lbry.io/get). -To install them on Ubuntu: -sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip +##### OS X +Just drag and drop LBRY.app into your applications folder (replacing any older versions). When it's running you'll have a LBRY icon in your status bar. -python setup.py build bdist_egg -sudo python setup.py install -``` +##### Linux +Double click the .deb file and follow the prompts. The app can be started by searching "LBRY", and it can be turned off by clicking the red 'x' in the browser interface. -this will install all of the libraries and a few applications +On both systems you can also open the UI while the app is running by going to lbry://lbry in Firefox or Safari, or localhost:5279 in Chrome. -For running the file sharing application, see [RUNNING](RUNNING.md) + +#### Installing LBRY command line + +##### OS X +You can install LBRY command line by running `curl -sL https://rawgit.com/lbryio/lbry-setup/master/lbry_setup_osx.sh | sudo bash` in a terminal. This script will install lbrynet and its dependancies, as well as the app. You can start LBRY by either starting the app or by running `lbrynet-daemon` from a terminal. + +##### Linux +On Ubuntu or Mint you can install the prerequisites and lbrynet by running + + ``` + sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip + git clone https://github.com/lbryio/lbry.git + cd lbry + sudo python setup.py install + ``` + +To start LBRY, run `lbrynet-daemon` in a terminal. #### On windows: From 838207514777a91968823a31fd4cfee58e38f2eb Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Mon, 23 May 2016 15:22:42 -0400 Subject: [PATCH 198/462] add divider line --- INSTALL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index c6e2b0f11..23491e60c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -12,7 +12,9 @@ Double click the .deb file and follow the prompts. The app can be started by sea On both systems you can also open the UI while the app is running by going to lbry://lbry in Firefox or Safari, or localhost:5279 in Chrome. + #### Installing LBRY command line +-------------------------- ##### OS X You can install LBRY command line by running `curl -sL https://rawgit.com/lbryio/lbry-setup/master/lbry_setup_osx.sh | sudo bash` in a terminal. This script will install lbrynet and its dependancies, as well as the app. You can start LBRY by either starting the app or by running `lbrynet-daemon` from a terminal. From de614b293ce082b662e650993959cdb87d0f0376 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Mon, 23 May 2016 09:55:20 -0400 Subject: [PATCH 199/462] change description --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e4a9432f0..baf069fa7 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'l gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] setup(name='lbrynet', - description='A fully decentralized network for distributing data', + description='A fully-decentralized content marketplace', version=__version__, maintainer='Jimmy Kiselak', maintainer_email='jimmy@lbry.io', From f9e8bf92bd1ea239eab870b756c6b4932db6bef8 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Mon, 23 May 2016 16:13:46 -0400 Subject: [PATCH 200/462] readme rewrite --- README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7e5d60ed5..c1608e550 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ LBRYnet is a fully decentralized network for distributing data. It consists of p and downloading data from other peers, possibly in exchange for payments, and a distributed hash table, used by peers to discover other peers. +## Installation + +Download the [latest release](https://github.com/lbryio/lbry/releases/latest) or see [INSTALL.md](INSTALL.md) for manual installation. + ## Overview On LBRYnet, data is broken into chunks, and each chunk is specified by its sha384 hash sum. This @@ -20,9 +24,9 @@ help peers find each other. For example, an application for which clients don't necessary chunks may use some identifier, chosen by the application, to find clients which do know all of the necessary chunks. -## Running +## For Developers -LBRYnet comes with an file sharing application, called 'lbrynet-console', which breaks +LBRY comes with an file sharing application, called 'lbrynet-console', which breaks files into chunks, encrypts them with a symmetric key, computes their sha384 hash sum, generates a special file called a 'stream descriptor' containing the hash sums and some other file metadata, and makes the chunks available for download by other peers. A peer wishing to download the file @@ -30,22 +34,19 @@ must first obtain the 'stream descriptor' and then may open it with his 'lbrynet download all of the chunks by locating peers with the chunks via the DHT, and then combine the chunks into the original file, according to the metadata included in the 'stream descriptor'. -To install and use this client, see [INSTALL](INSTALL.md) and [RUNNING](RUNNING.md) +For detailed instructions, see [INSTALL.md](INSTALL.md) and [RUNNING.md](RUNNING.md). -## Installation +Documentation: doc.lbry.io (may be out of date) -See [INSTALL](INSTALL.md) - -## Developers - -Documentation: doc.lbry.io Source code: https://github.com/lbryio/lbry -To contribute to the development of LBRYnet or lbrynet-console, contact jimmy@lbry.io +To contribute, [join us on Slack](https://lbry-slackin.herokuapp.com/) or contact josh@lbry.io. Pull requests are also welcome. ## Support -Send all support requests to jimmy@lbry.io +Please open an issue and describe your situation in detail. We will respond as soon as we can. + +If private issues, contact josh@lbry.io. ## License From 45ecdafd521c05d965e87efde414ec0109d1162b Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Mon, 23 May 2016 16:25:14 -0400 Subject: [PATCH 201/462] i have the best words --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1608e550..cd1693811 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ To contribute, [join us on Slack](https://lbry-slackin.herokuapp.com/) or contac Please open an issue and describe your situation in detail. We will respond as soon as we can. -If private issues, contact josh@lbry.io. +For private issues, contact josh@lbry.io. ## License From c5dc06962d12599879257d937771908debbe0d3e Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 24 May 2016 17:44:26 -0400 Subject: [PATCH 202/462] travis code signing --- .travis.yml | 32 +++++++++++++++++++++++++++++++ packaging/osx/certs/cert.cer.enc | 30 +++++++++++++++++++++++++++++ packaging/osx/certs/cert.p12.enc | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 .travis.yml create mode 100644 packaging/osx/certs/cert.cer.enc create mode 100644 packaging/osx/certs/cert.p12.enc diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..1f5f36cd7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +matrix: + include: + - os: linux + sudo: required + dist: trust + language: generic +before_install: +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; brew install python; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo pip install --upgrade pip virtualenv; + fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then virtualenv $HOME/venv; source $HOME/venv/bin/activate; + fi +install: true +before_script: +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" + -in packaging/osx/certs/dist.cer.enc -d -a -out packaging/osx/certs/dist.cer; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" + -in packaging/osx/certs/dist.p12.enc -d -a -out packaging/osx/certs/dist.p12; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/osx/add-key.sh; fi +script: +- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh; + fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade gmp; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; + fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; + cd $TRAVIS_BUILD_DIR; fi +env: + global: + - secure: d3glJxXC0goiFETAP0JxMDEQoSNlh1fRDR76D3tjYY4Brxh2UgvUvpNJOAkFApynciA3n8kva4uBsv52jwnKG0VmCy/meaWowouhyi8ChdPpg7HZL84oC7rz3bZ2OA2iuYFPpAQrd6p7OMhmiCkeqhRKtW0YmOKn1F45kaIMnmq+bD0QK3IdP3QBdGz0rf6TQlNSQLSJtDAP0+HO+NaawJ1TQJXHUHA8mKnbOTRal4bH007uxfhvthXysm1QRQOfthGud2q/DN+f6RfCqF2Fv9l7NwR5BwVKluQYgqJjdhk9IOU7D+zW4Ne2fSt6V1PRAASAfyhtDiOA7B3ZUw2igimQ6rWyWao1csilq0RW9EmycOT8S7x5YgXltk0kVdNizdQeHCDII0mOxkIFBF0bs2ZgTgKasZrU5jGnEhV42eACl9CGeoT5/ots7an+zgCBoQ8c4HGkMQyKV4uNnvKD+gq1FPDuwlHEUHhbjy5uRI4kAVMzjsCJX7XBSumFELuwiOpE/XguiJAV/LvjYbN6OFemJ6HUOhep6kgq7Zcoxh+UnaMixiq6NQTSoLpffatSxCM9EG8uBoXZJBH45cS9aD0eP9zjV2COCC0Iom5BQFhkArgMVodYtcrNevPUA3AuvJOjdJpSwKB4fqaU/CDqThw/ieZQlzaZqWIM5RnHf6Y= + - secure: n08IQBOLaipKzAwS5aQM+JfFtvLSCrWqFFztTMeElmck6IJOVnPlDvPYRCrRmrXtgFmJ1G6hEU5Vri086MkTaISq3V04ndquhz7nv93Pp1do42vWPmvmFHdMrhaOGPVtYZ4m4XfgNcA+NC79V9X2VQ+MoQgCRhcGlgGomxJBHc924bGxppkIrDMljvSipkR5v9g/UFGRPYJ6BIF0imWrdsHUfqhbZMdAxAmdlBAjx/ZUOmUyPWYttHGYEW4HpLwvkU2K+sJrt1emogvR7GT94CadtqjmRsiKh0Y2lHHhHkqV1J941T08p9hyKU6S4fHQqPHfpkHRiPeJQY8JiipCbuERn0YeGqsp5q0jRE6PFUxCWcvlWqc7XNihOMQAb5JQD8vF2Lvs3YYycFBgZkIOaZQFbemqkx2pnQMjVF7GCZp4u+p5yo8iBeImYFYdkqIBHqPqsKxM9aBfe05XhAmi/6jUP0L2xikVFvJZDNxKP2uyqXVbSMR8KF8v1eaYnsSL3vzBolwkn96zaj2E/lItHaNeNIXvutAsGuy2ybTXeabm5rTaUr+5cmdqx4JqmyGM3DinGFik/fzpLUJCd4GaC1n1taLy6aUUW2oH46QRqLLAAjZ4AWao92Eb4cnFiVxxBqVG3efWaoEt+uW7/hzqG27iieoVqCXs3Hd5g4bCYyo= diff --git a/packaging/osx/certs/cert.cer.enc b/packaging/osx/certs/cert.cer.enc new file mode 100644 index 000000000..4dc96d9ba --- /dev/null +++ b/packaging/osx/certs/cert.cer.enc @@ -0,0 +1,30 @@ +U2FsdGVkX1/oLoj7zhPd0imh2T8RhLpKzZLk4EHQV0GUJ1g8nwGvxWov+CUMnmjh +Y+LNdomGBYWoUilhe4JaWDUYepwIXn6+TvuWBdVEMGpJhXGbmIf+ncMXo6AP8Fh/ +g9x79SE4RJxFj3utc02B2ivVehoQno5sEvNSZMVml5n9skJoJUBbAsbp1p+7Hm5j +p2Z7UI7/qiih6TmszX5KQvOl/DPezVNksn1c1mUShuaBTjxbprGlr/LvtboR3mAd +8DN4yTGLLJAQ2+FNftM4rAedrr6db2AhQ8WxRmiwTfdubnEoC6zYySFq2kmdjj3S +gPnK0atx+ZihMp+S+GqMnvfAHEtb0vqxoq6nFcSIvcQVxKPyzu5E1kMriY4Oq3xr +K6ebc1NKJHjh7niaUmR3NImBx2h1LAAf/hcKRH2+ATEVczGtI1AsSGgGhUM34eGH +7G+m7+bIkgb8AtlaIGS/VVHsIZCNSgzwZJoNd3hD6ZV65Hb2yeT6Hhos88/05iFT +ewYasa73TqFm5SJHRwt4d1W9WVIJKJPDJ910p+V+NZVUsKOx34+vMNrjCrqW9p9x +gQnza2V/F6blIHTbSzIGc+MFbeHYBO80d+v5jVxheL8z6ollDVts1SyJ5rKJBY6c +quvSgmc/ltE0dqRxLOQJ9mAFbayuMIUP6CbRkPXp8GfE55UtUJkDilalzcpCPrUC +YJpuAI61INOQZZPEVKWW8L68/tLY+oEwWpexQX7xs4FUCblIFf20T3XE2lVuBHf9 +Bp9k7cD2m4mNrbzWOJuqrVt1pr176l9+VSP/ESdDFbmPch2FHl8HK8kgfJvkV+iB +kudmAmzI9DTUpWd5lJp6Fr/rLCMjslFDs37zMg4/E5ikKFSDNeYMtgPZhCwM83kh +OAktow4QAzh3RdbVZMFxaKk9nbiGPuBEsgvraPjb2gY8U34RC9R2FINIuTnJttLK +q7CKFTdbJIf+TIIgzfNu/c978adsK/qS68iltyyx8WFflcybnlqVgja192Ptqw1M +PXBQkH4mUrAeWDfmCPPh/mhO67Bau5u9Wzv/qZ2RXcX0dgXOoMa2sO6ZpR2SzxCJ +/XZwXnElMl+pvojLURDOV16fMPpjMCbzCN+hQabiTASqFNCsz4C9hmOquNh2t+V9 +8xvU/bnOM+/SMhahjYnvdhmRMcY+5Wv32ZnKATq88dq4T7/OZI7q3IsROZ7MnucT +x4vADvcFOfOdtPK35IFfMTfl+Ri3q7REIHMts2WEwXddf8CUiVeIaf8NgrWYW0hP +f9DQbMGKFcqqCHlKrQkv2dBKX/qEbIzN7T7535Ly68zyFuBS252gsLO7nrf+CLEZ +AROOfmt2jv0BvQ4MI5dslzsXFAU11tS36gOZ303R+NJVVqySkza964h2rH5M1F7i +A5p7w/l0OVV7r6aXkmsrIcsUZuY7QnZJORQ1MxNtK20weKfrqs90nMTklUVPc4V8 +LnAW6AYem0ZaeDHn2kx947sglMYxf0h/mFECGhif9hfDTErw7TkSJ26t9ByuEyEf +vGpp3P4iTXHUx7HSh7L4KDva6CP6slGjFMAFUEETn7N5uX3VEYeztMBdHLz0XHZc +PcgVZ8kytXVTEg95upvWmliEbQqWRsy6sr9PanaN1QY6re6RLlYj4pOWVm8qgCXU +IJVTWkROMlYZTWCibCsTsY8fk8aNObZamHjzZGvnU8nEGTx7xQJS8i0r3NM1j2Ka +ehBA+WfXbTplI/Fq8Z2Nrb/O39hQpGbXp4ERsEmVbK0twwsqVNehI0CdobxmGsd5 +E9Afb9iL97DTXsna1Il6FXnHvj3iAQsxxaNLIaj0sN1GaQd9N1mbxThlFNOM3wry +jI8TKCWEfLRQzykkcR3sMg== diff --git a/packaging/osx/certs/cert.p12.enc b/packaging/osx/certs/cert.p12.enc new file mode 100644 index 000000000..40828aff6 --- /dev/null +++ b/packaging/osx/certs/cert.p12.enc @@ -0,0 +1,33 @@ +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 +vOtCHu46xXuBJbDmz2mslw== From 7fca1f865d4de45de0221f6451ed8bc58623b5a5 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 24 May 2016 17:50:01 -0400 Subject: [PATCH 203/462] update from master --- .gitmodules | 3 + INSTALL.md | 36 ++++-- README.md | 25 ++-- lbrynet/lbrynet_gui/lbry.png | Bin 10651 -> 18944 bytes packaging/osx/add-key.sh | 25 ++++ packaging/osx/lbry-osx-app | 1 + packaging/ubuntu/lbry | 4 +- packaging/ubuntu/postinst_append | 28 ++++ packaging/ubuntu/ubuntu_package_setup.sh | 157 ++++++++++++++++++----- setup.py | 2 +- 10 files changed, 229 insertions(+), 52 deletions(-) create mode 100644 .gitmodules create mode 100755 packaging/osx/add-key.sh create mode 160000 packaging/osx/lbry-osx-app create mode 100755 packaging/ubuntu/postinst_append diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..a8c2e8de6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "packaging/osx/lbry-osx-app"] + path = packaging/osx/lbry-osx-app + url = https://github.com/jobevers/lbry-osx-app.git diff --git a/INSTALL.md b/INSTALL.md index 6dc552bcf..23491e60c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,19 +1,35 @@ -#### Installing lbrynet on Linux +#### Installing the LBRY app -------------------------- -The following packages are necessary (the following are their names on Ubuntu): -libgmp3-dev build-essential python2.7 python2.7-dev python-pip +Installing LBRY is simple. You can get a dmg installer for OS X (Mavericks and up) or a .deb for linux [here](https://lbry.io/get). -To install them on Ubuntu: -sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip +##### OS X +Just drag and drop LBRY.app into your applications folder (replacing any older versions). When it's running you'll have a LBRY icon in your status bar. -python setup.py build bdist_egg -sudo python setup.py install -``` +##### Linux +Double click the .deb file and follow the prompts. The app can be started by searching "LBRY", and it can be turned off by clicking the red 'x' in the browser interface. -this will install all of the libraries and a few applications +On both systems you can also open the UI while the app is running by going to lbry://lbry in Firefox or Safari, or localhost:5279 in Chrome. -For running the file sharing application, see [RUNNING](RUNNING.md) + + +#### Installing LBRY command line +-------------------------- + +##### OS X +You can install LBRY command line by running `curl -sL https://rawgit.com/lbryio/lbry-setup/master/lbry_setup_osx.sh | sudo bash` in a terminal. This script will install lbrynet and its dependancies, as well as the app. You can start LBRY by either starting the app or by running `lbrynet-daemon` from a terminal. + +##### Linux +On Ubuntu or Mint you can install the prerequisites and lbrynet by running + + ``` + sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip + git clone https://github.com/lbryio/lbry.git + cd lbry + sudo python setup.py install + ``` + +To start LBRY, run `lbrynet-daemon` in a terminal. #### On windows: diff --git a/README.md b/README.md index dc8b5c81f..cd1693811 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,15 @@ +[![Build Status](https://travis-ci.org/lbryio/lbry.svg?branch=master)](https://travis-ci.org/lbryio/lbry) + # LBRYnet LBRYnet is a fully decentralized network for distributing data. It consists of peers uploading and downloading data from other peers, possibly in exchange for payments, and a distributed hash table, used by peers to discover other peers. +## Installation + +Download the [latest release](https://github.com/lbryio/lbry/releases/latest) or see [INSTALL.md](INSTALL.md) for manual installation. + ## Overview On LBRYnet, data is broken into chunks, and each chunk is specified by its sha384 hash sum. This @@ -18,9 +24,9 @@ help peers find each other. For example, an application for which clients don't necessary chunks may use some identifier, chosen by the application, to find clients which do know all of the necessary chunks. -## Running +## For Developers -LBRYnet comes with an file sharing application, called 'lbrynet-console', which breaks +LBRY comes with an file sharing application, called 'lbrynet-console', which breaks files into chunks, encrypts them with a symmetric key, computes their sha384 hash sum, generates a special file called a 'stream descriptor' containing the hash sums and some other file metadata, and makes the chunks available for download by other peers. A peer wishing to download the file @@ -28,22 +34,19 @@ must first obtain the 'stream descriptor' and then may open it with his 'lbrynet download all of the chunks by locating peers with the chunks via the DHT, and then combine the chunks into the original file, according to the metadata included in the 'stream descriptor'. -To install and use this client, see [INSTALL](INSTALL.md) and [RUNNING](RUNNING.md) +For detailed instructions, see [INSTALL.md](INSTALL.md) and [RUNNING.md](RUNNING.md). -## Installation +Documentation: doc.lbry.io (may be out of date) -See [INSTALL](INSTALL.md) - -## Developers - -Documentation: doc.lbry.io Source code: https://github.com/lbryio/lbry -To contribute to the development of LBRYnet or lbrynet-console, contact jimmy@lbry.io +To contribute, [join us on Slack](https://lbry-slackin.herokuapp.com/) or contact josh@lbry.io. Pull requests are also welcome. ## Support -Send all support requests to jimmy@lbry.io +Please open an issue and describe your situation in detail. We will respond as soon as we can. + +For private issues, contact josh@lbry.io. ## License diff --git a/lbrynet/lbrynet_gui/lbry.png b/lbrynet/lbrynet_gui/lbry.png index 16c7c4683affa3e73c5bcf4fbd0b6bbb0778f275..6599574065ccfcc176cdd5e8726ca783f85f7097 100644 GIT binary patch literal 18944 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

U2Ho%8h$Z1A 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 O6N9I#pUXO@geCykjvm1P literal 10651 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 lz#oNlK}QRNn5{lM7KLBRY Installed\n\nRun LBRY now?" --title="LBRY Installed" --ok-label=" Yes" \ + --cancel-label=" No" --icon-name="system-software-install" + +case $? in + 0) RUN=1 # yes + ;; + 1) RUN=0 # no + ;; + *) RUN=0 # timeout or escape or whatever + ;; +esac + +if [ $RUN = 1 ]; then + xdg-open "lbry://lbry" +else + zenity --info --text="LBRY Installed\n\nNo problem. You can run LBRY later by going to + +lbry://lbry + +in your browser." --title="LBRY Installed" --icon-name="system-software-install" +fi + +) & diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 4be10c8a7..f35a78b72 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -1,37 +1,124 @@ #!/bin/bash -# Tested on fresh Ubuntu 14.04 install. - -# wget https://raw.githubusercontent.com/lbryio/lbry/master/packaging/ubuntu/ubuntu_package_setup.sh -# bash ubuntu_package_setup.sh master - set -euo pipefail -BRANCH=${1:-master} +function HELP { + echo "Build a debian package for lbry" + echo "-----" + echo "When run without any arguments, this script expects the current directory" + echo "to be the main lbry repo and it builds what is in that directory" + echo + echo "Optional arguments:" + echo + echo "-c: clone a fresh copy of the repo" + echo "-b : use the specified branch of the lbry repo" + echo "-w : set the webui branch" + echo "-d : specifiy the build directory" + echo "-h: show help" + echo "-t: turn trace on" + exit 1 +} -BUILD_DIR="lbry-build-$(date +%Y%m%d-%H%M%S)" -mkdir "$BUILD_DIR" +CLONE=false +BUILD_DIR="" +BRANCH="" +WEB_UI_BRANCH="master" + +while getopts :hctb:w:d: FLAG; do + case $FLAG in + c) + CLONE=true + ;; + b) + BRANCH=${OPTARG} + ;; + w) + WEB_UI_BRANCH=${OPTARG} + ;; + d) + BUILD_DIR=${OPTARG} + ;; + t) + set -o xtrace + ;; + h) + HELP + ;; + \?) #unrecognized option - show help + echo "Option -$OPTARG not allowed." + HELP + ;; + :) + echo "Option -$OPTARG requires an argument." + HELP + ;; + esac +done + +shift $((OPTIND-1)) + + +SUDO='' +if (( $EUID != 0 )); then + SUDO='sudo' +fi + +if [ "$CLONE" = false ]; then + if [ `basename $PWD` != "lbry" ]; then + echo "Not currently in the lbry directory. Cowardly refusing to go forward" + exit 1 + fi + SOURCE_DIR=$PWD +fi + +if [ -z "${BUILD_DIR}" ]; then + if [ "$CLONE" = true ]; then + # build in the current directory + BUILD_DIR="lbry-build-$(date +%Y%m%d-%H%M%S)" + else + BUILD_DIR="../lbry-build-$(date +%Y%m%d-%H%M%S)" + fi +fi + +mkdir -p "$BUILD_DIR" cd "$BUILD_DIR" +if [ -z ${TRAVIS+x} ]; then + # if not on travis, its nice to see progress + QUIET="" +else + QUIET="-qq" +fi + # get the required OS packages -sudo add-apt-repository -y ppa:spotify-jyrki/dh-virtualenv -sudo apt-get update -sudo apt-get install -y build-essential git python-dev libffi-dev libssl-dev libgmp3-dev dh-virtualenv debhelper +$SUDO apt-get ${QUIET} update +$SUDO apt-get ${QUIET} install -y --no-install-recommends software-properties-common +$SUDO add-apt-repository -y ppa:spotify-jyrki/dh-virtualenv +$SUDO apt-get ${QUIET} update +$SUDO apt-get ${QUIET} install -y --no-install-recommends \ + build-essential git python-dev libffi-dev libssl-dev \ + libgmp3-dev dh-virtualenv debhelper wget python-pip fakeroot # need a modern version of pip (more modern than ubuntu default) -wget https://bootstrap.pypa.io/get-pip.py -sudo python get-pip.py -rm get-pip.py -sudo pip install make-deb - -# check out LBRY -git clone https://github.com/lbryio/lbry.git --branch "$BRANCH" +$SUDO pip install --upgrade pip +$SUDO pip install make-deb # build packages +# +# dpkg-buildpackage outputs its results into '..' so +# we need to move/clone lbry into the build directory +if [ "$CLONE" == true]; then + cp -a $SOURCE_DIR lbry +else + git clone https://github.com/lbryio/lbry.git +fi ( - cd lbry - make-deb - dpkg-buildpackage -us -uc + cd lbry + if [ -n "${BRANCH}" ]; then + git checkout "${BRANCH}" + fi + make-deb + dpkg-buildpackage -us -uc ) @@ -41,8 +128,20 @@ git clone https://github.com/lbryio/lbry.git --branch "$BRANCH" PACKAGE="$(ls | grep '.deb')" ar vx "$PACKAGE" mkdir control data -tar -xvzf control.tar.gz --directory control -tar -xvJf data.tar.xz --directory data +tar -xzf control.tar.gz --directory control + +# The output of the travis build is a +# tar.gz and the output locally is tar.xz. +# Instead of having tar detect the compression used, we +# could update the config to output the same in either spot. +# Unfortunately, doing so requires editting some auto-generated +# files: http://linux.spiney.org/forcing_gzip_compression_when_building_debian_packages +tar -xf data.tar.?z --directory data + +PACKAGING_DIR='lbry/packaging/ubuntu' + +# set web ui branch +sed -i "s/^WEB_UI_BRANCH='[^']\+'/WEB_UI_BRANCH='$WEB_UI_BRANCH'/" "$PACKAGING_DIR/lbry" # add files function addfile() { @@ -52,16 +151,16 @@ function addfile() { cp "$FILE" "data/$TARGET" echo "$(md5sum "data/$TARGET" | cut -d' ' -f1) $TARGET" >> control/md5sums } -PACKAGING_DIR='lbry/packaging/ubuntu' addfile "$PACKAGING_DIR/lbry" usr/share/python/lbrynet/bin/lbry addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop -#addfile lbry/packaging/ubuntu/lbry-init.conf etc/init/lbry.conf + +cat "$PACKAGING_DIR/postinst_append" >> control/postinst # repackage .deb -sudo chown -R root:root control data -tar -cvzf control.tar.gz -C control . -tar -cvJf data.tar.xz -C data . -sudo chown root:root debian-binary control.tar.gz data.tar.xz +$SUDO chown -R root:root control data +tar -czf control.tar.gz -C control . +tar -cJf data.tar.xz -C data . +$SUDO chown root:root debian-binary control.tar.gz data.tar.xz ar r "$PACKAGE" debian-binary control.tar.gz data.tar.xz # TODO: we can append to data.tar instead of extracting it all and recompressing diff --git a/setup.py b/setup.py index e4a9432f0..baf069fa7 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'l gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] setup(name='lbrynet', - description='A fully decentralized network for distributing data', + description='A fully-decentralized content marketplace', version=__version__, maintainer='Jimmy Kiselak', maintainer_email='jimmy@lbry.io', From eb0dd827b1f46a66f0038fe7ed8124f2d23412bf Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 24 May 2016 17:54:44 -0400 Subject: [PATCH 204/462] delete old unused app and move uri handler to lbry-osx-app --- .../lbrynet_daemon/Apps/LBRYOSXStatusBar.py | 108 ------------------ lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 60 ---------- .../{Apps => daemon_scripts}/__init__.py | 0 setup_uri_handler.py | 25 ---- 4 files changed, 193 deletions(-) delete mode 100644 lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py delete mode 100644 lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py rename lbrynet/lbrynet_daemon/{Apps => daemon_scripts}/__init__.py (100%) delete mode 100644 setup_uri_handler.py diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py deleted file mode 100644 index 5ca433f72..000000000 --- a/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py +++ /dev/null @@ -1,108 +0,0 @@ -import rumps -import xmlrpclib -import os -import webbrowser -import subprocess -import argparse - - -class DaemonStatusBarApp(rumps.App): - def __init__(self): - icon_path = 'app.icns' - if os.path.isfile(icon_path): - rumps.App.__init__(self, name="LBRY", icon=icon_path, quit_button=None, - menu=["Open", "Preferences", "View balance", "Quit"]) - else: - rumps.App.__init__(self, name="LBRY", title="LBRY", quit_button=None, - menu=["Open", "Preferences", "View balance", "Quit"]) - - @rumps.timer(1) - def alert_daemon_start(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - start_msg = daemon.is_running() - if isinstance(start_msg, str): - rumps.notification(title='LBRY', subtitle='', message=str(start_msg), sound=True) - update_info = daemon.check_for_new_version() - update_msg = "" - for p in update_info: - if not p[0]: - update_msg += p[1] + "\n" - if update_msg: - update_msg += "\n Try running the installer again to fix this" - rumps.notification(title='LBRY', subtitle='', message=update_msg, sound=True) - except: - pass - - @rumps.clicked('Open') - def get_ui(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - daemon.is_running() - webbrowser.get('safari').open("lbry://lbry") - except: - try: - rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) - except: - rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") - - @rumps.clicked("Preferences") - def prefs(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - daemon.is_running() - webbrowser.get('safari').open("lbry://settings") - except: - rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) - - @rumps.clicked("View balance") - def disp_balance(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - balance = daemon.get_balance() - r = round(float(balance), 2) - try: - rumps.notification(title='LBRY', subtitle='', message=str("Your balance is %.2f LBC" % r), sound=False) - except: - rumps.alert(title='LBRY', message=str("Your balance is %.2f LBC" % r)) - - except: - try: - rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) - except: - rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") - - @rumps.clicked('Quit') - def clean_quit(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - daemon.stop() - except: - pass - rumps.quit_application() - - -def main(): - parser = argparse.ArgumentParser(description="Launch lbrynet status bar application") - parser.add_argument("--startdaemon", - help="true or false, default true", - type=str, - default="true") - args = parser.parse_args() - - if str(args.startdaemon).lower() == "true": - daemon = xmlrpclib.ServerProxy('http://localhost:7080') - try: - daemon.is_running() - except: - subprocess.Popen("screen -dmS lbrynet bash -c " - "'PYTHONPATH=$PYTHONPATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.python_path`; " - "PATH=$PATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.lbry_bin_path`; " - "lbrynet-daemon --update=False'", shell=True) - - status_app = DaemonStatusBarApp() - status_app.run() - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py deleted file mode 100644 index f6990cfea..000000000 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ /dev/null @@ -1,60 +0,0 @@ -import os -import json -import webbrowser -import subprocess -import sys - -from time import sleep -from jsonrpc.proxy import JSONRPCProxy - -API_CONNECTION_STRING = "http://localhost:5279/lbryapi" -UI_ADDRESS = "http://localhost:5279" - - -class LBRYURIHandler(object): - def __init__(self): - self.started_daemon = False - self.daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING) - - def handle_osx(self, lbry_name): - try: - status = self.daemon.is_running() - except: - os.system("open /Applications/LBRY.app") - sleep(3) - - if lbry_name == "lbry" or lbry_name == "": - webbrowser.open(UI_ADDRESS) - else: - webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) - - def handle_linux(self, lbry_name): - try: - status = self.daemon.is_running() - except: - cmd = r'DIR = "$( cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )"' \ - r'if [-z "$(pgrep lbrynet-daemon)"]; then' \ - r'echo "running lbrynet-daemon..."' \ - r'$DIR / lbrynet - daemon &' \ - r'sleep 3 # let the daemon load before connecting' \ - r'fi' - subprocess.Popen(cmd, shell=True) - - if lbry_name == "lbry" or lbry_name == "": - webbrowser.open(UI_ADDRESS) - else: - webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) - - -def main(args): - if len(args) != 1: - args = ['lbry://lbry'] - - name = args[0][7:] - if sys.platform == "darwin": - LBRYURIHandler().handle_osx(lbry_name=name) - else: - LBRYURIHandler().handle_linux(lbry_name=name) - -if __name__ == "__main__": - main(sys.argv[1:]) diff --git a/lbrynet/lbrynet_daemon/Apps/__init__.py b/lbrynet/lbrynet_daemon/daemon_scripts/__init__.py similarity index 100% rename from lbrynet/lbrynet_daemon/Apps/__init__.py rename to lbrynet/lbrynet_daemon/daemon_scripts/__init__.py diff --git a/setup_uri_handler.py b/setup_uri_handler.py deleted file mode 100644 index e9ba6749c..000000000 --- a/setup_uri_handler.py +++ /dev/null @@ -1,25 +0,0 @@ -from setuptools import setup -import os - -APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'Apps', 'LBRYURIHandler.py')] -DATA_FILES = [] -OPTIONS = {'argv_emulation': True, - 'packages': ['jsonrpc'], - 'plist': { - 'LSUIElement': True, - 'CFBundleIdentifier': 'io.lbry.LBRYURIHandler', - 'CFBundleURLTypes': [ - { - 'CFBundleURLTypes': 'LBRYURIHandler', - 'CFBundleURLSchemes': ['lbry'] - } - ] - } - } - -setup( - app=APP, - data_files=DATA_FILES, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], -) \ No newline at end of file From 6b39e549f7ca44dc5f6b5b455ded0ea4695fc985 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 25 May 2016 21:02:30 -0400 Subject: [PATCH 205/462] add developer id to travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1f5f36cd7..5215f0df5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,3 +30,4 @@ env: global: - secure: d3glJxXC0goiFETAP0JxMDEQoSNlh1fRDR76D3tjYY4Brxh2UgvUvpNJOAkFApynciA3n8kva4uBsv52jwnKG0VmCy/meaWowouhyi8ChdPpg7HZL84oC7rz3bZ2OA2iuYFPpAQrd6p7OMhmiCkeqhRKtW0YmOKn1F45kaIMnmq+bD0QK3IdP3QBdGz0rf6TQlNSQLSJtDAP0+HO+NaawJ1TQJXHUHA8mKnbOTRal4bH007uxfhvthXysm1QRQOfthGud2q/DN+f6RfCqF2Fv9l7NwR5BwVKluQYgqJjdhk9IOU7D+zW4Ne2fSt6V1PRAASAfyhtDiOA7B3ZUw2igimQ6rWyWao1csilq0RW9EmycOT8S7x5YgXltk0kVdNizdQeHCDII0mOxkIFBF0bs2ZgTgKasZrU5jGnEhV42eACl9CGeoT5/ots7an+zgCBoQ8c4HGkMQyKV4uNnvKD+gq1FPDuwlHEUHhbjy5uRI4kAVMzjsCJX7XBSumFELuwiOpE/XguiJAV/LvjYbN6OFemJ6HUOhep6kgq7Zcoxh+UnaMixiq6NQTSoLpffatSxCM9EG8uBoXZJBH45cS9aD0eP9zjV2COCC0Iom5BQFhkArgMVodYtcrNevPUA3AuvJOjdJpSwKB4fqaU/CDqThw/ieZQlzaZqWIM5RnHf6Y= - secure: n08IQBOLaipKzAwS5aQM+JfFtvLSCrWqFFztTMeElmck6IJOVnPlDvPYRCrRmrXtgFmJ1G6hEU5Vri086MkTaISq3V04ndquhz7nv93Pp1do42vWPmvmFHdMrhaOGPVtYZ4m4XfgNcA+NC79V9X2VQ+MoQgCRhcGlgGomxJBHc924bGxppkIrDMljvSipkR5v9g/UFGRPYJ6BIF0imWrdsHUfqhbZMdAxAmdlBAjx/ZUOmUyPWYttHGYEW4HpLwvkU2K+sJrt1emogvR7GT94CadtqjmRsiKh0Y2lHHhHkqV1J941T08p9hyKU6S4fHQqPHfpkHRiPeJQY8JiipCbuERn0YeGqsp5q0jRE6PFUxCWcvlWqc7XNihOMQAb5JQD8vF2Lvs3YYycFBgZkIOaZQFbemqkx2pnQMjVF7GCZp4u+p5yo8iBeImYFYdkqIBHqPqsKxM9aBfe05XhAmi/6jUP0L2xikVFvJZDNxKP2uyqXVbSMR8KF8v1eaYnsSL3vzBolwkn96zaj2E/lItHaNeNIXvutAsGuy2ybTXeabm5rTaUr+5cmdqx4JqmyGM3DinGFik/fzpLUJCd4GaC1n1taLy6aUUW2oH46QRqLLAAjZ4AWao92Eb4cnFiVxxBqVG3efWaoEt+uW7/hzqG27iieoVqCXs3Hd5g4bCYyo= + - secure: Unv3cSrgeT6fdHbWWXqBAH2WRpudiX1aPG90HFT2zwfdvXBTS8zUSTmsQJu0UJjsYUjErqjjwMNrzh0HFn1MaIc31aJQurC2CuKXZvzViBXh1abr5WBYzXEvkXuwgzhpcyNcg9SU2Qmhmxtdb1r1WV+HNnTofQahrWVCmUJh8mCunrXnFRMaTwW0qBfUpW9UvMaZ4N9RWdkkEAMV0at/kuP2MnYgW0EWA/42BV7N92HtWxAyvmiHyk2EjQ3cQ9avb4/6C2vFIhiFaYzU3ZR5VtT/XQxiuRRtVYj9aG4RpXhcXMg1LTTqCJ9JJTvcpQX75gpKz3pLr7rA3hVoV5nueHLpyGwoZU6iy+8auCA/6WnP+xGAgL/JRq9ozisr8NVD5BX+BrQED3FAxH9uARM1LVR24PphErctqzdDTvjkvdj9rHROdlD5DfSRuroNjwsRrrRTENruekWyYp0W3pORQQTO/A0wE+/YWfKR941j9w+Q1GdYWcvDnkPzTnL2o7D76N9REsy2jpIIT9dqsOcgp1fvCYaTN1k5GXlmgRydsRWBnelr0vD1uQxEyGhBzCrPwrCJz2U5VZDMmoCBPEMwZOLaRNkLbGX0Qn3gQqhKLeM/SYeJSuuxTO0WVyQvheNJIaE+crhZrk8sB5SrdN9hGGD4NYYUXrdcgEt91K78/BE= From 8922fd6dde76dc4c413f566ebe5142fa52aa1fd3 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 25 May 2016 22:28:45 -0400 Subject: [PATCH 206/462] add startup scripts -populate blockchainname.db on first run from older version --- lbrynet/__init__.py | 2 +- lbrynet/core/LBRYcrdWallet.py | 5 ++ lbrynet/lbrynet_daemon/LBRYDaemon.py | 78 +++++++++++++++++-- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 51 +----------- .../daemon_scripts/Autofetcher.py | 68 ++++++++++++++++ .../daemon_scripts/migrateto025.py | 33 ++++++++ 6 files changed, 179 insertions(+), 58 deletions(-) create mode 100644 lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py create mode 100644 lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 26dafe45d..3d80f11a2 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) -version = (0, 2, 4) +version = (0, 2, 5) __version__ = ".".join([str(x) for x in version]) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 8ec200611..17fd0e229 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -295,6 +295,11 @@ class LBRYWallet(object): d.addCallback(self._get_stream_info_from_value, name) return d + def get_txid_for_name(self, name): + d = self._get_value_for_name(name) + d.addCallback(lambda r: None if 'txid' not in r else r['txid']) + return d + def get_stream_info_from_txid(self, name, txid): d = self.get_claims_from_tx(txid) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0a10dff9d..320ab6a51 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -151,6 +151,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.waiting_on = {} self.streams = {} self.known_dht_nodes = KNOWN_DHT_NODES + self.first_run_after_update = False self.platform_info = { "processor": platform.processor(), "python_version: ": platform.python_version(), @@ -197,7 +198,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'use_upnp': True, 'start_lbrycrdd': True, 'requested_first_run_credits': False, - 'cache_time': DEFAULT_CACHE_TIME + 'cache_time': DEFAULT_CACHE_TIME, + 'startup_scripts': [], + 'last_version': {'lbrynet': lbrynet_version, 'lbryum': lbryum_version} } if os.path.isfile(self.daemon_conf): @@ -234,6 +237,20 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.session_settings = settings_dict + if 'last_version' in missing_settings.keys(): + self.session_settings['last_version'] = None + + if self.session_settings['last_version'] != self.default_settings['last_version']: + self.session_settings['last_version'] = self.default_settings['last_version'] + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() + + self.first_run_after_update = True + log.info("First run after update") + if lbrynet_version == '0.2.5': + self.session_settings['startup_scripts'].append({'script_name': 'migrateto025', 'run_once': True}) + self.run_on_startup = self.session_settings['run_on_startup'] self.data_rate = self.session_settings['data_rate'] self.max_key_fee = self.session_settings['max_key_fee'] @@ -252,6 +269,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.start_lbrycrdd = self.session_settings['start_lbrycrdd'] self.requested_first_run_credits = self.session_settings['requested_first_run_credits'] self.cache_time = self.session_settings['cache_time'] + self.startup_scripts = self.session_settings['startup_scripts'] if os.path.isfile(os.path.join(self.db_dir, "stream_info_cache.json")): f = open(os.path.join(self.db_dir, "stream_info_cache.json"), "r") @@ -394,6 +412,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.announced_startup = True self.startup_status = STARTUP_STAGES[5] log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") + if len(self.startup_scripts): + log.info("Scheduling scripts") + reactor.callLater(3, self._run_scripts) + # self.lbrynet_connection_checker.start(3600) if self.first_run: @@ -608,6 +630,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _shutdown(self): log.info("Closing lbrynet session") log.info("Status at time of shutdown: " + self.startup_status[0]) + self.internet_connection_checker.stop() + self.version_checker.stop() + self.connection_problem_checker.stop() d = self._upload_log(name_prefix="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) @@ -1017,19 +1042,31 @@ class LBRYDaemon(jsonrpc.JSONRPC): f.close() return defer.succeed(True) - def _resolve_name(self, name): + def _resolve_name(self, name, force_refresh=False): def _cache_stream_info(stream_info): + def _add_txid(txid): + self.name_cache[name]['txid'] = txid + return defer.succeed(None) + self.name_cache[name] = {'claim_metadata': stream_info, 'timestamp': self._get_long_count_timestamp()} - d = self._update_claim_cache() + d = self.session.wallet.get_txid_for_name(name) + d.addCallback(_add_txid) + d.addCallback(lambda _: self._update_claim_cache()) d.addCallback(lambda _: self.name_cache[name]['claim_metadata']) + return d - if name in self.name_cache.keys(): - if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: - log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) - d = defer.succeed(self.name_cache[name]['claim_metadata']) + if not force_refresh: + if name in self.name_cache.keys(): + if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: + log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) + d = defer.succeed(self.name_cache[name]['claim_metadata']) + else: + log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) else: - log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) + log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) d = self.session.wallet.get_stream_info_for_name(name) d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) else: @@ -1221,6 +1258,31 @@ class LBRYDaemon(jsonrpc.JSONRPC): requests.post(URL, json.dumps({"text": msg})) return defer.succeed(None) + def _run_scripts(self): + if len([k for k in self.startup_scripts if 'run_once' in k.keys()]): + log.info("Removing one time startup scripts") + f = open(self.daemon_conf, "r") + initialsettings = json.loads(f.read()) + f.close() + t = [s for s in self.startup_scripts if 'run_once' not in s.keys()] + initialsettings['startup_scripts'] = t + f = open(self.daemon_conf, "w") + f.write(json.dumps(initialsettings)) + f.close() + + for script in self.startup_scripts: + if script['script_name'] == 'migrateto025': + log.info("Running migrator to 0.2.5") + from lbrynet.lbrynet_daemon.daemon_scripts.migrateto025 import run as run_migrate + run_migrate(self) + + if script['script_name'] == 'Autofetcher': + log.info("Starting autofetcher script") + from lbrynet.lbrynet_daemon.daemon_scripts.Autofetcher import run as run_autofetcher + run_autofetcher(self) + + return defer.succeed(None) + def _render_response(self, result, code): return defer.succeed({'result': result, 'code': code}) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 85494d21c..5843bca06 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -189,50 +189,6 @@ class HostedLBRYFile(resource.Resource): call.cancel() -class MyLBRYFiles(resource.Resource): - isLeaf = False - - def __init__(self): - resource.Resource.__init__(self) - self.files_table = None - - def delayed_render(self, request, result): - request.write(result.encode('utf-8')) - request.finish() - - def render_GET(self, request): - self.files_table = None - api = jsonrpc.Proxy(API_CONNECTION_STRING) - d = api.callRemote("get_lbry_files", {}) - d.addCallback(self._get_table) - d.addCallback(lambda results: self.delayed_render(request, results)) - - return server.NOT_DONE_YET - - def _get_table(self, files): - if not self.files_table: - self.files_table = r'My LBRY files' - self.files_table += r'' - self.files_table += r'' - self.files_table += r'' - self.files_table += r'' - self.files_table += r'' - self.files_table += r'' - return self._get_table(files) - if not len(files): - self.files_table += r'
Stream nameCompletedToggleRemove
' - return self.files_table - else: - f = files.pop() - self.files_table += r'' - self.files_table += r'%s' % (f['stream_name']) - self.files_table += r'%s' % (f['completed']) - self.files_table += r'Start' if f['stopped'] else r'Stop' - self.files_table += r'Delete' - self.files_table += r'' - return self._get_table(files) - - class LBRYDaemonServer(object): def __init__(self): self.data_dir = user_data_dir("LBRY") @@ -336,12 +292,9 @@ class LBRYDaemonServer(object): def _setup_server(self, ui_ver, wallet): self._api = LBRYDaemon(ui_ver, wallet_type=wallet) self.root = LBRYindex(self.ui_dir) - self.root.putChild("css", static.File(os.path.join(self.ui_dir, "css"))) - self.root.putChild("font", static.File(os.path.join(self.ui_dir, "font"))) - self.root.putChild("img", static.File(os.path.join(self.ui_dir, "img"))) - self.root.putChild("js", static.File(os.path.join(self.ui_dir, "js"))) + for d in [i[0] for i in os.walk(self.ui_dir) if os.path.dirname(i[0]) == self.ui_dir]: + self.root.putChild(os.path.basename(d), static.File(d)) self.root.putChild("view", HostedLBRYFile(self._api)) - self.root.putChild("files", MyLBRYFiles()) self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py new file mode 100644 index 000000000..cd7ed02cb --- /dev/null +++ b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py @@ -0,0 +1,68 @@ +import json +import logging.handlers +import sys +import os + +from appdirs import user_data_dir +from twisted.internet.task import LoopingCall +from twisted.internet import reactor + + +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') + +if os.path.isfile(LOG_FILENAME): + f = open(LOG_FILENAME, 'r') + PREVIOUS_LOG = len(f.read()) + f.close() +else: + PREVIOUS_LOG = 0 + +log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) + + +class Autofetcher(object): + """ + Download name claims as they occur + """ + + def __init__(self, api): + self._api = api + self._checker = LoopingCall(self._check_for_new_claims) + self.best_block = None + + def start(self): + reactor.addSystemEventTrigger('before', 'shutdown', self.stop) + self._checker.start(5) + + def stop(self): + log.info("Stopping autofetcher") + self._checker.stop() + + def _check_for_new_claims(self): + block = self._api.get_best_blockhash() + if block != self.best_block: + log.info("Checking new block for name claims, block hash: %s" % block) + self.best_block = block + transactions = self._api.get_block({'blockhash': block})['tx'] + for t in transactions: + c = self._api.get_claims_for_tx({'txid': t}) + if len(c): + for i in c: + log.info("Downloading stream for claim txid: %s" % t) + self._api.get({'name': t, 'stream_info': json.loads(i['value'])}) + + +def run(api): + fetcher = Autofetcher(api) + fetcher.start() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py b/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py new file mode 100644 index 000000000..9283b48aa --- /dev/null +++ b/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py @@ -0,0 +1,33 @@ +from twisted.internet import defer + + +class migrator(object): + """ + Re-resolve lbry names to write missing data to blockchain.db and to cache the nametrie + """ + + def __init__(self, api): + self._api = api + + def start(self): + def _resolve_claims(claimtrie): + claims = [i for i in claimtrie if 'txid' in i.keys()] + r = defer.DeferredList([self._api._resolve_name(claim['name'], force_refresh=True) for claim in claims], consumeErrors=True) + return r + + def _restart_lbry_files(): + def _restart_lbry_file(lbry_file): + return lbry_file.restore() + + r = defer.DeferredList([_restart_lbry_file(lbry_file) for lbry_file in self._api.lbry_file_manager.lbry_files if not lbry_file.txid], consumeErrors=True) + r.callback(None) + return r + + d = self._api.session.wallet.get_nametrie() + d.addCallback(_resolve_claims) + d.addCallback(lambda _: _restart_lbry_files()) + + +def run(api): + refresher = migrator(api) + refresher.start() From b557a14134385a4f625bf7e2d649871166fb71c5 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 23 May 2016 12:19:52 -0500 Subject: [PATCH 207/462] fix whitespace --- .travis.yml | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index d21cb628d..5257823d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ - matrix: include: - os: linux @@ -17,16 +16,15 @@ before_install: install: true before_script: -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.cer.enc -d -a -out packaging/osx/certs/dist.cer; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.p12.enc -d -a -out packaging/osx/certs/dist.p12; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/osx/add-key.sh; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.cer.enc -d -a -out packaging/osx/certs/dist.cer; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.p12.enc -d -a -out packaging/osx/certs/dist.p12; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/osx/add-key.sh; fi script: -- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade gmp; fi -# the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi -# py2app fails to find jsonrpc unless json-rpc is installed. why? I don't know. -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi - + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade gmp; fi + # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi + # py2app fails to find jsonrpc unless json-rpc is installed. why? I don't know. + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi From 53af9db28585feb76d3c416d15f7951494b9ece5 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Fri, 27 May 2016 15:40:30 -0500 Subject: [PATCH 208/462] add tests and linting --- .gitignore | 2 + .pylintrc | 379 ++++++++++++++++++ .travis.yml | 3 +- .../install_dependencies_and_run_tests.sh | 46 +++ 4 files changed, 429 insertions(+), 1 deletion(-) create mode 100644 .pylintrc create mode 100755 packaging/travis/install_dependencies_and_run_tests.sh diff --git a/.gitignore b/.gitignore index 819306a87..1bd0eea94 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ lbrynet.egg-info/PKG-INFO *.pem *.decTest + +.coverage diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 000000000..5e5aab35c --- /dev/null +++ b/.pylintrc @@ -0,0 +1,379 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_$|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,input + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules=twisted.internet.reactor,leveldb + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). This supports can work +# with qualified names. +ignored-classes=twisted.internet,RequestMessage + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/.travis.yml b/.travis.yml index 5257823d5..232e23219 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,8 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo pip install --upgrade pip virtualenv; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then virtualenv $HOME/venv; source $HOME/venv/bin/activate; fi -install: true +install: + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./packaging/travis/install_dependencies_and_run_tests.sh; fi before_script: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.cer.enc -d -a -out packaging/osx/certs/dist.cer; fi diff --git a/packaging/travis/install_dependencies_and_run_tests.sh b/packaging/travis/install_dependencies_and_run_tests.sh new file mode 100755 index 000000000..9bef0afc4 --- /dev/null +++ b/packaging/travis/install_dependencies_and_run_tests.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# +# This script is used by travis to install lbry from source +# + +set -euo pipefail +set -o xtrace + +SUDO='' +if (( $EUID != 0 )); then + SUDO='sudo' +fi + +if [ -z ${TRAVIS+x} ]; then + # if not on travis, its nice to see progress + QUIET="" +else + QUIET="-qq" +fi + +# get the required OS packages +$SUDO apt-get ${QUIET} update +$SUDO apt-get ${QUIET} install -y --no-install-recommends \ + build-essential python-dev libffi-dev libssl-dev git \ + libgmp3-dev wget ca-certificates python-virtualenv + +# create a virtualenv so we don't muck with anything on the system +virtualenv venv +# need to unset these or else we can't activate +set +eu +source venv/bin/activate +set -eu + +# need a modern version of pip (more modern than ubuntu default) +wget https://bootstrap.pypa.io/get-pip.py +python get-pip.py +rm get-pip.py + +pip install -r requirements.txt + +pip install nose coverage coveralls pylint +nosetests --with-coverage --cover-package=lbrynet -v -I lbrynet_test_bot.py -I functional_tests.py tests/ +# TODO: submit coverage report to coveralls + +# TODO: as code quality improves, make pylint be more strict +pylint -E --disable=inherit-non-class --disable=no-member lbrynet From cb30514cbc1d3ff027b98409b1fd024850d3bde8 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 23 May 2016 12:19:52 -0500 Subject: [PATCH 209/462] fix whitespace --- .travis.yml | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index d21cb628d..5257823d5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ - matrix: include: - os: linux @@ -17,16 +16,15 @@ before_install: install: true before_script: -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.cer.enc -d -a -out packaging/osx/certs/dist.cer; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.p12.enc -d -a -out packaging/osx/certs/dist.p12; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/osx/add-key.sh; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.cer.enc -d -a -out packaging/osx/certs/dist.cer; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then openssl aes-256-cbc -k "$ENCRYPTION_SECRET" -in packaging/osx/certs/dist.p12.enc -d -a -out packaging/osx/certs/dist.p12; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/osx/add-key.sh; fi script: -- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade gmp; fi -# the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi -# py2app fails to find jsonrpc unless json-rpc is installed. why? I don't know. -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi -- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi - + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade gmp; fi + # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi + # py2app fails to find jsonrpc unless json-rpc is installed. why? I don't know. + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi From ad7d508a3ccbbd819215d311a8f6cc4087d564ee Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 23 May 2016 12:33:47 -0500 Subject: [PATCH 210/462] fail build if version doens't match tag --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 5257823d5..8f44cc492 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,3 +28,5 @@ script: # py2app fails to find jsonrpc unless json-rpc is installed. why? I don't know. - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi + # fail the build if this is a build for a tag and we don't have the versions matching + - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi From dab4427c18e93036cc18f26b9920a101a5588e21 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 23 May 2016 12:57:22 -0500 Subject: [PATCH 211/462] add deployment section to .travis.yml - not sure if the file name entry will work. --- .travis.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8f44cc492..39b1d072d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,3 +30,13 @@ script: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi # fail the build if this is a build for a tag and we don't have the versions matching - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi + +deploy: + provider: releases + file: "`python setup.py --name`_`python setup.py -V`_amd64.deb" + skip_cleanup: true + on: + tags: true + # this is the oauth token for the lbry-ci user + api_key: + secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc= From a22052e85b8686ae05b72ed142bc34d88c08b4d4 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Sat, 28 May 2016 09:31:15 -0500 Subject: [PATCH 212/462] comment out version check --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 39b1d072d..f8b0682d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ script: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi # fail the build if this is a build for a tag and we don't have the versions matching - - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi + # - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi deploy: provider: releases From 7385cb0c89f412378afec7a4e8f96a48cdd144e8 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Sat, 28 May 2016 09:54:08 -0500 Subject: [PATCH 213/462] try new filename for travis deployment --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f8b0682d8..28730d068 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ script: deploy: provider: releases - file: "`python setup.py --name`_`python setup.py -V`_amd64.deb" + file: "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" skip_cleanup: true on: tags: true From f3e6b2ab57e949201b261953133cbd77a56bc17b Mon Sep 17 00:00:00 2001 From: Job Evers Date: Sat, 28 May 2016 10:55:15 -0500 Subject: [PATCH 214/462] on travis, move built deb file for easier access --- packaging/ubuntu/ubuntu_package_setup.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index f35a78b72..70cd064e4 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -164,3 +164,7 @@ $SUDO chown root:root debian-binary control.tar.gz data.tar.xz ar r "$PACKAGE" debian-binary control.tar.gz data.tar.xz # TODO: we can append to data.tar instead of extracting it all and recompressing + +if [[ -n "${TRAVIS_BUILD_DIR}" ]]; then + mv "${PACKAGE}" "${TRAVIS_BUILD_DIR}/${PACKAGE}" +fi From c967230969305365cb39c3771ae1634b84433a74 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Sat, 28 May 2016 11:04:10 -0500 Subject: [PATCH 215/462] restore version consistency check --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 28730d068..64dbd86cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ script: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi # fail the build if this is a build for a tag and we don't have the versions matching - # - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi + - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi deploy: provider: releases From e7a580fd3a3be55025abe386c638690b363c58f2 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 29 May 2016 23:18:30 -0400 Subject: [PATCH 216/462] add lbry_ui_manager --- lbrynet/conf.py | 4 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 127 +++++++----- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 160 +++------------ lbrynet/lbrynet_daemon/LBRYUIManager.py | 219 +++++++++++++++++++++ 4 files changed, 330 insertions(+), 180 deletions(-) create mode 100644 lbrynet/lbrynet_daemon/LBRYUIManager.py diff --git a/lbrynet/conf.py b/lbrynet/conf.py index dfae3d274..526dc711d 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -36,8 +36,10 @@ UI_ADDRESS = "http://" + API_INTERFACE + ":" + str(API_PORT) PROTOCOL_PREFIX = "lbry" DEFAULT_WALLET = "lbryum" +WALLET_TYPES = ["lbryum", "lbrycrd"] DEFAULT_TIMEOUT = 30 DEFAULT_MAX_SEARCH_RESULTS = 25 DEFAULT_MAX_KEY_FEE = 100.0 DEFAULT_SEARCH_TIMEOUT = 3.0 -DEFAULT_CACHE_TIME = 3600 \ No newline at end of file +DEFAULT_CACHE_TIME = 3600 +DEFAULT_UI_BRANCH = "master" diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 320ab6a51..0d3535012 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -31,13 +31,14 @@ from lbrynet.core.Error import UnknownNameError, InsufficientFundsError from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier +from lbrynet.lbrynet_daemon.LBRYUIManager import LBRYUIManager from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ - DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME -from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS, DEFAULT_TIMEOUT, UI_ADDRESS + DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH +from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet @@ -129,7 +130,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): isLeaf = True - def __init__(self, ui_version_info, wallet_type=DEFAULT_WALLET): + def __init__(self, root, wallet_type=DEFAULT_WALLET): jsonrpc.JSONRPC.__init__(self) reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) @@ -139,9 +140,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connected_to_internet = True self.connection_problem = None self.query_handlers = {} - self.ui_version = ui_version_info.replace('\n', '') self.git_lbrynet_version = None self.git_lbryum_version = None + self.ui_version = None + self.ip = None self.wallet_type = wallet_type self.first_run = None self.log_file = LOG_FILENAME @@ -152,20 +154,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.streams = {} self.known_dht_nodes = KNOWN_DHT_NODES self.first_run_after_update = False - self.platform_info = { - "processor": platform.processor(), - "python_version: ": platform.python_version(), - "platform": platform.platform(), - "os_release": platform.release(), - "os_system": platform.system(), - "lbrynet_version: ": lbrynet_version, - "lbryum_version: ": lbryum_version, - "ui_version": self.ui_version, - } - try: - self.platform_info['ip'] = json.load(urlopen('http://jsonip.com'))['ip'] - except: - self.platform_info['ip'] = "Could not determine" if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle @@ -261,7 +249,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.search_timeout = self.session_settings['search_timeout'] self.download_timeout = self.session_settings['download_timeout'] self.max_search_results = self.session_settings['max_search_results'] - self.wallet_type = self.session_settings['wallet_type'] if self.session_settings['wallet_type'] == wallet_type else wallet_type + self.wallet_type = self.session_settings['wallet_type'] if self.session_settings['wallet_type'] in WALLET_TYPES else wallet_type self.delete_blobs_on_remove = self.session_settings['delete_blobs_on_remove'] self.peer_port = self.session_settings['peer_port'] self.dht_node_port = self.session_settings['dht_node_port'] @@ -319,6 +307,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.sd_identifier = StreamDescriptorIdentifier() self.stream_info_manager = TempLBRYFileMetadataManager() self.settings = LBRYSettings(self.db_dir) + self.lbry_ui_manager = LBRYUIManager(root) self.blob_request_payment_rate_manager = None self.lbry_file_metadata_manager = None self.lbry_file_manager = None @@ -392,7 +381,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.error(failure) return jsonrpclib.Fault(self.FAILURE, "error") - def setup(self): + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False): def _log_starting_vals(): d = self._get_lbry_files() d.addCallback(lambda r: json.dumps([d[1] for d in r])) @@ -437,6 +426,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connection_problem_checker.start(1) d = defer.Deferred() + d.addCallback(lambda _: self.lbry_ui_manager.setup(branch=branch, user_specified=user_specified)) d.addCallback(lambda _: self._initial_setup()) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) @@ -455,9 +445,29 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) + def _get_platform(self): + r = { + "processor": platform.processor(), + "python_version: ": platform.python_version(), + "platform": platform.platform(), + "os_release": platform.release(), + "os_system": platform.system(), + "lbrynet_version: ": lbrynet_version, + "lbryum_version: ": lbryum_version, + "ui_version": self.lbry_ui_manager.loaded_git_version, + } + if not self.ip: + try: + r['ip'] = json.load(urlopen('http://jsonip.com'))['ip'] + self.ip = r['ip'] + except: + r['ip'] = "Could not determine" + + return r + def _initial_setup(self): def _log_platform(): - log.info("Platform: " + json.dumps(self.platform_info)) + log.info("Platform: " + json.dumps(self._get_platform())) return defer.succeed(None) d = _log_platform() @@ -545,10 +555,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _stop_server(self): - if self.lbry_server_port is not None: - self.lbry_server_port, p = None, self.lbry_server_port - return defer.maybeDeferred(p.stopListening) - else: + try: + if self.lbry_server_port is not None: + self.lbry_server_port, p = None, self.lbry_server_port + return defer.maybeDeferred(p.stopListening) + else: + return defer.succeed(True) + except AttributeError: return defer.succeed(True) def _setup_server(self): @@ -630,17 +643,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _shutdown(self): log.info("Closing lbrynet session") log.info("Status at time of shutdown: " + self.startup_status[0]) - self.internet_connection_checker.stop() - self.version_checker.stop() - self.connection_problem_checker.stop() + if self.internet_connection_checker.running: + self.internet_connection_checker.stop() + if self.version_checker.running: + self.version_checker.stop() + if self.connection_problem_checker.running: + self.connection_problem_checker.stop() d = self._upload_log(name_prefix="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) + d.addErrback(lambda err: True) d.addCallback(lambda _: self.lbry_file_manager.stop()) - d.addErrback(lambda err: log.info("Bad server shutdown: " + err.getTraceback())) + d.addErrback(lambda err: True) if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) - d.addErrback(lambda err: log.info("Bad session shutdown: " + err.getTraceback())) + d.addErrback(lambda err: True) return d def _update_settings(self, settings): @@ -819,7 +836,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("Using PTC wallet") d = defer.succeed(PTCWallet(self.db_dir)) else: - d = defer.fail() + log.info("Requested unknown wallet '%s', using default lbryum" % self.wallet_type) + d = defer.succeed(LBRYumWallet(self.db_dir)) d.addCallback(lambda wallet: {"wallet": wallet}) return d @@ -1392,10 +1410,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): "remote_lbryum": most recent lbryum version available from github """ + platform_info = self._get_platform() msg = { - 'platform': self.platform_info['platform'], - 'os_release': self.platform_info['os_release'], - 'os_system': self.platform_info['os_system'], + 'platform': platform_info['platform'], + 'os_release': platform_info['os_release'], + 'os_system': platform_info['os_system'], 'lbrynet_version': lbrynet_version, 'lbryum_version': lbryum_version, 'ui_version': self.ui_version, @@ -2113,25 +2132,18 @@ class LBRYDaemon(jsonrpc.JSONRPC): # # return d - def jsonrpc_check_for_new_version(self): + def jsonrpc_log(self, message): """ - Checks local version against versions in __init__.py and version.py in the lbrynet and lbryum repos + Log message Args: - None + message: message to be logged Returns: - true/false, true meaning that there is a new version available + True """ - def _check_version(): - if (lbrynet_version >= self.git_lbrynet_version) and (lbryum_version >= self.git_lbryum_version): - log.info("[" + str(datetime.now()) + "] Up to date") - return self._render_response(False, OK_CODE) - else: - log.info("[" + str(datetime.now()) + "] Updates available") - return self._render_response(True, OK_CODE) - - return _check_version() + log.info(message) + return self._render_response(True, OK_CODE) def jsonrpc_upload_log(self, p=None): """ @@ -2140,7 +2152,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): Args, optional: 'name_prefix': prefix to indicate what is requesting the log upload 'exclude_previous': true/false, whether or not to exclude previous sessions from upload, defaults on true - Returns + Returns: True """ @@ -2171,3 +2183,22 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._log_to_slack(p['message'])) d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d + + def jsonrpc_configure_ui(self, p): + """ + Configure the UI being hosted + + Args, optional: + 'branch': a branch name on lbryio/lbry-web-ui + 'path': path to a ui folder + """ + + if 'path' in p.keys(): + d = self.lbry_ui_manager.setup(user_specified=p['path']) + elif 'branch' in p.keys(): + d = self.lbry_ui_manager.setup(branch=p['branch']) + else: + d = self.lbry_ui_manager.setup() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + + return d \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 5843bca06..948c3ea2e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -18,7 +18,7 @@ from txjsonrpc.web import jsonrpc from zope.interface import implements from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon -from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS +from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH if sys.platform != "darwin": @@ -149,22 +149,23 @@ class HostedLBRYFile(resource.Resource): self._producer = None resource.Resource.__init__(self) - def makeProducer(self, request, stream): - def _save_producer(producer): - self._producer = producer - return defer.succeed(None) - - range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-') - start, stop = int(range_header[0]), range_header[1] - log.info("[" + str(datetime.now()) + "] GET range %s-%s" % (start, stop)) - path = os.path.join(self._api.download_directory, stream.file_name) - - d = stream.get_total_bytes() - d.addCallback(lambda size: _save_producer(LBRYFileStreamer(request, path, start, stop, size))) - d.addCallback(lambda _: request.registerProducer(self._producer, streaming=True)) - # request.notifyFinish().addCallback(lambda _: self._producer.stopProducing()) - request.notifyFinish().addErrback(self._responseFailed, d) - return d + # todo: fix LBRYFileStreamer and use it instead of static.File + # def makeProducer(self, request, stream): + # def _save_producer(producer): + # self._producer = producer + # return defer.succeed(None) + # + # range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-') + # start, stop = int(range_header[0]), range_header[1] + # log.info("[" + str(datetime.now()) + "] GET range %s-%s" % (start, stop)) + # path = os.path.join(self._api.download_directory, stream.file_name) + # + # d = stream.get_total_bytes() + # d.addCallback(lambda size: _save_producer(LBRYFileStreamer(request, path, start, stop, size))) + # d.addCallback(lambda _: request.registerProducer(self._producer, streaming=True)) + # # request.notifyFinish().addCallback(lambda _: self._producer.stopProducing()) + # request.notifyFinish().addErrback(self._responseFailed, d) + # return d def render_GET(self, request): if 'name' in request.args.keys(): @@ -182,125 +183,22 @@ class HostedLBRYFile(resource.Resource): request.finish() return server.NOT_DONE_YET - def _responseFailed(self, err, call): - call.addErrback(lambda err: err.trap(error.ConnectionDone)) - call.addErrback(lambda err: err.trap(defer.CancelledError)) - call.addErrback(lambda err: log.info("Error: " + str(err))) - call.cancel() + # def _responseFailed(self, err, call): + # call.addErrback(lambda err: err.trap(error.ConnectionDone)) + # call.addErrback(lambda err: err.trap(defer.CancelledError)) + # call.addErrback(lambda err: log.info("Error: " + str(err))) + # call.cancel() class LBRYDaemonServer(object): - def __init__(self): - self.data_dir = user_data_dir("LBRY") - if not os.path.isdir(self.data_dir): - os.mkdir(self.data_dir) - self.version_dir = os.path.join(self.data_dir, "ui_version_history") - if not os.path.isdir(self.version_dir): - os.mkdir(self.version_dir) - self.config = os.path.join(self.version_dir, "active.json") - self.ui_dir = os.path.join(self.data_dir, "lbry-web-ui") - self.git_version = None - self._api = None - self.root = None - - if not os.path.isfile(os.path.join(self.config)): - self.loaded_git_version = None - else: - try: - f = open(self.config, "r") - loaded_ui = json.loads(f.read()) - f.close() - self.loaded_git_version = loaded_ui['commit'] - self.loaded_branch = loaded_ui['branch'] - version_log.info("[" + str(datetime.now()) + "] Last used " + self.loaded_branch + " commit " + str(self.loaded_git_version).replace("\n", "")) - except: - self.loaded_git_version = None - self.loaded_branch = None - - def setup(self, branch="master", user_specified=None): - self.branch = branch - if user_specified: - if os.path.isdir(user_specified): - log.info("Using user specified UI directory: " + str(user_specified)) - self.branch = "user-specified" - self.loaded_git_version = "user-specified" - self.ui_dir = user_specified - return defer.succeed("user-specified") - else: - log.info("User specified UI directory doesn't exist, using " + branch) - else: - log.info("Using UI branch: " + branch) - self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/%s" % branch - self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % branch - - d = self._up_to_date() - d.addCallback(lambda r: self._download_ui() if not r else self.branch) - return d - - def _up_to_date(self): - def _get_git_info(): - response = urlopen(self._git_url) - data = json.loads(response.read()) - return defer.succeed(data['object']['sha']) - - def _set_git(version): - self.git_version = version - version_log.info("[" + str(datetime.now()) + "] UI branch " + self.branch + " has a most recent commit of: " + str(self.git_version).replace("\n", "")) - - if self.git_version == self.loaded_git_version and os.path.isdir(self.ui_dir): - version_log.info("[" + str(datetime.now()) + "] local copy of " + self.branch + " is up to date") - return defer.succeed(True) - else: - if self.git_version == self.loaded_git_version: - version_log.info("[" + str(datetime.now()) + "] Can't find ui files, downloading them again") - else: - version_log.info("[" + str(datetime.now()) + "] local copy of " + self.branch + " branch is out of date, updating") - f = open(self.config, "w") - f.write(json.dumps({'commit': self.git_version, - 'time': str(datetime.now()), - 'branch': self.branch})) - f.close() - return defer.succeed(False) - - d = _get_git_info() - d.addCallback(_set_git) - return d - - def _download_ui(self): - def _delete_ui_dir(): - if os.path.isdir(self.ui_dir): - if self.loaded_git_version: - version_log.info("[" + str(datetime.now()) + "] Removed ui files for commit " + str(self.loaded_git_version).replace("\n", "")) - log.info("Removing out of date ui files") - shutil.rmtree(self.ui_dir) - return defer.succeed(None) - - def _dl_ui(): - url = urlopen(self._dist_url) - z = ZipFile(StringIO(url.read())) - names = [i for i in z.namelist() if '.DS_exStore' not in i and '__MACOSX' not in i] - z.extractall(self.ui_dir, members=names) - version_log.info("[" + str(datetime.now()) + "] Updated branch " + self.branch + ": " + str(self.loaded_git_version).replace("\n", "") + " --> " + self.git_version.replace("\n", "")) - log.info("Downloaded files for UI commit " + str(self.git_version).replace("\n", "")) - self.loaded_git_version = self.git_version - return self.branch - - d = _delete_ui_dir() - d.addCallback(lambda _: _dl_ui()) - return d - - def _setup_server(self, ui_ver, wallet): - self._api = LBRYDaemon(ui_ver, wallet_type=wallet) - self.root = LBRYindex(self.ui_dir) - for d in [i[0] for i in os.walk(self.ui_dir) if os.path.dirname(i[0]) == self.ui_dir]: - self.root.putChild(os.path.basename(d), static.File(d)) + def _setup_server(self, wallet): + self.root = LBRYindex(os.path.join(os.path.join(data_dir, "lbry-ui"), "active")) + self._api = LBRYDaemon(self.root, wallet_type=wallet) self.root.putChild("view", HostedLBRYFile(self._api)) self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) - def start(self, branch="master", user_specified=False, wallet=DEFAULT_WALLET): - d = self.setup(branch=branch, user_specified=user_specified) - d.addCallback(lambda v: self._setup_server(v, wallet)) - d.addCallback(lambda _: self._api.setup()) - + def start(self, branch=DEFAULT_UI_BRANCH, user_specified=False, wallet=DEFAULT_WALLET): + d = self._setup_server(self._setup_server(wallet)) + d.addCallback(lambda _: self._api.setup(branch, user_specified)) return d diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py new file mode 100644 index 000000000..6c48d018e --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -0,0 +1,219 @@ +import os +import logging +import shutil +import sys +import json + +from urllib2 import urlopen +from StringIO import StringIO +from twisted.web import static +from twisted.internet import defer +from lbrynet.conf import DEFAULT_UI_BRANCH +from lbrynet import __version__ as lbrynet_version +from lbryum.version import ELECTRUM_VERSION as lbryum_version +from zipfile import ZipFile +from appdirs import user_data_dir + +if sys.platform != "darwin": + data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + data_dir = user_data_dir("LBRY") + +if not os.path.isdir(data_dir): + os.mkdir(data_dir) +version_dir = os.path.join(data_dir, "ui_version_history") +if not os.path.isdir(version_dir): + os.mkdir(version_dir) + +log = logging.getLogger(__name__) +log.addHandler(logging.FileHandler(os.path.join(data_dir, 'lbrynet-daemon.log'))) +log.setLevel(logging.INFO) + + +class LBRYUIManager(object): + def __init__(self, root): + self.data_dir = user_data_dir("LBRY") + self.ui_root = os.path.join(self.data_dir, "lbry-ui") + self.active_dir = os.path.join(self.ui_root, "active") + self.update_dir = os.path.join(self.ui_root, "update") + + if not os.path.isdir(self.data_dir): + os.mkdir(self.data_dir) + if not os.path.isdir(self.ui_root): + os.mkdir(self.ui_root) + if not os.path.isdir(self.ui_root): + os.mkdir(self.ui_root) + if not os.path.isdir(self.ui_root): + os.mkdir(self.ui_root) + + self.config = os.path.join(self.ui_root, "active.json") + self.update_requires = os.path.join(self.update_dir, "requirements.txt") + self.requirements = {} + self.ui_dir = self.active_dir + self.git_version = None + self.root = root + + if not os.path.isfile(os.path.join(self.config)): + self.loaded_git_version = None + else: + try: + f = open(self.config, "r") + loaded_ui = json.loads(f.read()) + f.close() + self.loaded_git_version = loaded_ui['commit'] + self.loaded_branch = loaded_ui['branch'] + self.loaded_requirements = loaded_ui['requirements'] + except: + self.loaded_git_version = None + self.loaded_branch = None + self.loaded_requirements = None + + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=None): + self.branch = branch + if user_specified: + if os.path.isdir(user_specified): + log.info("Checking user specified UI directory: " + str(user_specified)) + self.branch = "user-specified" + self.loaded_git_version = "user-specified" + self.ui_dir = user_specified + d = self.migrate_ui(source=user_specified) + d.addCallback(lambda _: self._load_ui()) + return d + else: + log.info("User specified UI directory doesn't exist, using " + branch) + else: + log.info("Checking for updates for UI branch: " + branch) + self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/%s" % branch + self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % branch + + d = self._up_to_date() + d.addCallback(lambda r: self._download_ui() if not r else self.branch) + return d + + def _up_to_date(self): + def _get_git_info(): + response = urlopen(self._git_url) + data = json.loads(response.read()) + return defer.succeed(data['object']['sha']) + + def _set_git(version): + self.git_version = version.replace('\n', '') + if self.git_version == self.loaded_git_version: + log.info("UI is up to date") + return defer.succeed(True) + else: + log.info("UI updates available, checking if installation meets requirements") + return defer.succeed(False) + + d = _get_git_info() + d.addCallback(_set_git) + return d + + def migrate_ui(self, source=None): + if not source: + requires_file = self.update_requires + source_dir = self.update_dir + delete_source = True + else: + requires_file = os.path.join(source, "requirements.txt") + source_dir = source + delete_source = False + + def _check_requirements(): + if not os.path.isfile(requires_file): + log.info("No requirements.txt file, rejecting request to migrate this UI") + return defer.succeed(False) + + f = open(requires_file, "r") + for requirement in [line for line in f.read().split('\n') if line]: + t = requirement.split('=') + if len(t) == 3: + self.requirements[t[0]] = {'version': t[1], 'operator': '=='} + elif t[0][-1] == ">": + self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '>='} + elif t[0][-1] == "<": + self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '<='} + f.close() + passed_requirements = True + for r in self.requirements: + if r == 'lbrynet': + c = lbrynet_version + elif r == 'lbryum': + c = lbryum_version + else: + c = None + if c: + if self.requirements[r]['operator'] == '==': + if not self.requirements[r]['version'] == c: + passed_requirements = False + log.info("Local version %s of %s does not meet UI requirement for version %s" % ( + c, r, self.requirements[r]['version'])) + else: + log.info("Local version of %s meets ui requirement" % r) + if self.requirements[r]['operator'] == '>=': + if not self.requirements[r]['version'] <= c: + passed_requirements = False + log.info("Local version %s of %s does not meet UI requirement for version %s" % ( + c, r, self.requirements[r]['version'])) + else: + log.info("Local version of %s meets ui requirement" % r) + if self.requirements[r]['operator'] == '<=': + if not self.requirements[r]['version'] >= c: + passed_requirements = False + log.info("Local version %s of %s does not meet UI requirement for version %s" % ( + c, r, self.requirements[r]['version'])) + else: + log.info("Local version of %s meets ui requirement" % r) + return defer.succeed(passed_requirements) + + def _disp_failure(): + log.info("Failed to satisfy requirements for branch '%s', update was not loaded" % self.branch) + return defer.succeed(False) + + def _do_migrate(): + if os.path.isdir(self.active_dir): + shutil.rmtree(self.active_dir) + shutil.copytree(source_dir, self.active_dir) + if delete_source: + shutil.rmtree(source_dir) + + log.info("Loaded UI update") + + f = open(self.config, "w") + loaded_ui = {'commit': self.git_version, 'branch': self.branch, 'requirements': self.requirements} + f.write(json.dumps(loaded_ui)) + f.close() + + self.loaded_git_version = loaded_ui['commit'] + self.loaded_branch = loaded_ui['branch'] + self.loaded_requirements = loaded_ui['requirements'] + return defer.succeed(True) + + d = _check_requirements() + d.addCallback(lambda r: _do_migrate() if r else _disp_failure()) + return d + + def _download_ui(self): + def _delete_update_dir(): + if os.path.isdir(self.update_dir): + shutil.rmtree(self.update_dir) + return defer.succeed(None) + + def _dl_ui(): + url = urlopen(self._dist_url) + z = ZipFile(StringIO(url.read())) + names = [i for i in z.namelist() if '.DS_exStore' not in i and '__MACOSX' not in i] + z.extractall(self.update_dir, members=names) + log.info("Downloaded files for UI commit " + str(self.git_version).replace("\n", "")) + return self.branch + + d = _delete_update_dir() + d.addCallback(lambda _: _dl_ui()) + d.addCallback(lambda _: self.migrate_ui()) + d.addCallback(lambda _: self._load_ui()) + return d + + def _load_ui(self): + for d in [i[0] for i in os.walk(self.active_dir) if os.path.dirname(i[0]) == self.active_dir]: + self.root.putChild(os.path.basename(d), static.File(d)) + return defer.succeed(True) \ No newline at end of file From 75052fc7739b276a06be04a5fba9fdcf9dc44c47 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 00:10:43 -0400 Subject: [PATCH 217/462] remove check_for_new_version vestige --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0d3535012..928f98f2a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -110,7 +110,7 @@ CONNECTION_PROBLEM_CODES = [ ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run', 'get_time_behind_blockchain', 'stop', 'daemon_status', 'get_start_notice', - 'version', 'check_for_new_version'] + 'version'] BAD_REQUEST = 400 NOT_FOUND = 404 From 41f8b5aee25ac0e8073b6c15f2f89795ded2dd77 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 02:05:16 -0400 Subject: [PATCH 218/462] add reveal() function and delete_target_file param for delete_lbry_file --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 36 ++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 928f98f2a..bd8c75f62 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,5 +1,6 @@ import locale import os +import subprocess import sys import simplejson as json import binascii @@ -1094,7 +1095,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d - def _delete_lbry_file(self, lbry_file): + def _delete_lbry_file(self, lbry_file, delete_file=True): d = self.lbry_file_manager.delete_lbry_file(lbry_file) def finish_deletion(lbry_file): @@ -1107,7 +1108,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.lbry_file_manager.get_count_for_stream_hash(s_h) # TODO: could possibly be a timing issue here d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) - d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if + if delete_file: + d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) else defer.succeed(None)) return d @@ -1792,14 +1794,19 @@ class LBRYDaemon(jsonrpc.JSONRPC): confirmation message """ + if 'delete_target_file' in p.keys(): + delete_file = p['delete_target_file'] + else: + delete_file = True + def _delete_file(f): file_name = f.file_name - d = self._delete_lbry_file(f) + d = self._delete_lbry_file(f, delete_file=delete_file) d.addCallback(lambda _: "Deleted LBRY file" + file_name) return d - if p.keys()[0] in ['name', 'sd_hash', 'file_name']: - search_type = p.keys()[0] + if 'name' in p.keys() or 'sd_hash' in p.keys() or 'file_name' in p.keys(): + search_type = [k for k in p.keys() if k != 'delete_target_file'][0] d = self._get_lbry_file(search_type, p[search_type], return_json=False) d.addCallback(lambda l: _delete_file(l) if l else False) @@ -2201,4 +2208,23 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.lbry_ui_manager.setup() d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + + def jsonrpc_reveal(self, p): + """ + Open a folder in finder/file explorer + + Args: + 'path': path to be selected in finder + Returns: + True, opens finder + """ + + path = p['path'] + if sys.platform == "darwin": + d = threads.deferToThread(subprocess.Popen, ("open -R %s" % path), shell=True) + else: + d = threads.deferToThread(subprocess.Popen, ("xdg-open %s" % path), shell=True) + + d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d \ No newline at end of file From 772866389f248e43f88a89c77e545119ffae0ea7 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 02:36:13 -0400 Subject: [PATCH 219/462] ui manager fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit load a ui when it’s supposed to be loaded --- lbrynet/lbrynet_daemon/LBRYUIManager.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index 6c48d018e..baa4b09f9 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -55,6 +55,8 @@ class LBRYUIManager(object): if not os.path.isfile(os.path.join(self.config)): self.loaded_git_version = None + self.loaded_branch = None + self.loaded_requirements = None else: try: f = open(self.config, "r") @@ -75,19 +77,22 @@ class LBRYUIManager(object): log.info("Checking user specified UI directory: " + str(user_specified)) self.branch = "user-specified" self.loaded_git_version = "user-specified" - self.ui_dir = user_specified d = self.migrate_ui(source=user_specified) d.addCallback(lambda _: self._load_ui()) return d else: log.info("User specified UI directory doesn't exist, using " + branch) + elif self.loaded_branch == "user-specified": + log.info("Loading user provided UI") + d = self._load_ui() + return d else: log.info("Checking for updates for UI branch: " + branch) self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/%s" % branch self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % branch d = self._up_to_date() - d.addCallback(lambda r: self._download_ui() if not r else self.branch) + d.addCallback(lambda r: self._download_ui() if not r else self._load_ui()) return d def _up_to_date(self): From c56b3e75eae1e5e73d65930de40efbe675c1535e Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 02:42:33 -0400 Subject: [PATCH 220/462] add download_directory to lbry_file --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index bd8c75f62..5518bef1c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1217,7 +1217,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): if status[0] == DOWNLOAD_RUNNING_CODE: d = f.status() d.addCallback(_get_file_status) - d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, 'key': key, + d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, + 'download_directory': f.download_directory,'key': key, 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, @@ -1229,6 +1230,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'message': message}) else: d = defer.succeed({'completed': f.completed, 'file_name': f.file_name, 'key': key, + 'download_directory': f.download_directory, 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, From 16ef259ae129c502d8c12671affac67de0542994 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 02:53:36 -0400 Subject: [PATCH 221/462] add full_path to lbry_file --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 5518bef1c..cd472d677 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1218,7 +1218,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = f.status() d.addCallback(_get_file_status) d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, - 'download_directory': f.download_directory,'key': key, + 'download_directory': f.download_directory, + 'full_path': os.path.join(f.download_directory, f.file_name), + 'key': key, 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, @@ -1231,6 +1233,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: d = defer.succeed({'completed': f.completed, 'file_name': f.file_name, 'key': key, 'download_directory': f.download_directory, + 'full_path': os.path.join(f.download_directory, f.file_name), 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, From 2c3b625b6b7010649c2007e04eb34c6d01ea1daa Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 02:56:08 -0400 Subject: [PATCH 222/462] change full_path to path --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index cd472d677..7e87d4ff2 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1219,7 +1219,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(_get_file_status) d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, 'download_directory': f.download_directory, - 'full_path': os.path.join(f.download_directory, f.file_name), + 'path': os.path.join(f.download_directory, f.file_name), 'key': key, 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, @@ -1233,7 +1233,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: d = defer.succeed({'completed': f.completed, 'file_name': f.file_name, 'key': key, 'download_directory': f.download_directory, - 'full_path': os.path.join(f.download_directory, f.file_name), + 'path': os.path.join(f.download_directory, f.file_name), 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, From da68bcf952c46f66c33d1c58a8935a5376424600 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 03:14:59 -0400 Subject: [PATCH 223/462] third time's the charm --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 7e87d4ff2..32e811fc9 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1219,7 +1219,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(_get_file_status) d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, 'download_directory': f.download_directory, - 'path': os.path.join(f.download_directory, f.file_name), + 'download_path': os.path.join(f.download_directory, f.file_name), 'key': key, 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, @@ -1233,7 +1233,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: d = defer.succeed({'completed': f.completed, 'file_name': f.file_name, 'key': key, 'download_directory': f.download_directory, - 'path': os.path.join(f.download_directory, f.file_name), + 'download_path': os.path.join(f.download_directory, f.file_name), 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, From f9c644b96494a9d5c3ed01f32e8db80320ec844f Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 04:00:47 -0400 Subject: [PATCH 224/462] fix switching between --branch and --ui --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 6 ++++-- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 13 ++++++++----- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 4 ++-- lbrynet/lbrynet_daemon/LBRYUIManager.py | 4 ++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 32e811fc9..4d1494f80 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -382,7 +382,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.error(failure) return jsonrpclib.Fault(self.FAILURE, "error") - def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False): + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False): def _log_starting_vals(): d = self._get_lbry_files() d.addCallback(lambda r: json.dumps([d[1] for d in r])) @@ -427,7 +427,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connection_problem_checker.start(1) d = defer.Deferred() - d.addCallback(lambda _: self.lbry_ui_manager.setup(branch=branch, user_specified=user_specified)) + d.addCallback(lambda _: self.lbry_ui_manager.setup(branch=branch, + user_specified=user_specified, + branch_specified=branch_specified)) d.addCallback(lambda _: self._initial_setup()) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 1d7531331..72c8d2c1e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -13,7 +13,8 @@ from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer -from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET, UI_ADDRESS +from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, \ + DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH if sys.platform != "darwin": @@ -68,12 +69,11 @@ def start(): help="path to custom UI folder", default=None) parser.add_argument("--branch", - help="Branch of lbry-web-ui repo to use, defaults on master", - default="master") + help="Branch of lbry-web-ui repo to use, defaults on master") parser.add_argument('--no-launch', dest='launchui', action="store_false") parser.add_argument('--log-to-console', dest='logtoconsole', action="store_true") parser.add_argument('--quiet', dest='quiet', action="store_true") - parser.set_defaults(launchui=True, logtoconsole=False, quiet=False) + parser.set_defaults(branch=False, launchui=True, logtoconsole=False, quiet=False) args = parser.parse_args() if args.logtoconsole: @@ -104,7 +104,10 @@ def start(): if test_internet_connection(): lbry = LBRYDaemonServer() - d = lbry.start(branch=args.branch, user_specified=args.ui, wallet=args.wallet) + d = lbry.start(branch=args.branch if args.branch else DEFAULT_UI_BRANCH, + user_specified=args.ui, + wallet=args.wallet, + branch_specified=True if args.branch else False) if args.launchui: d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 948c3ea2e..393a87b72 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -198,7 +198,7 @@ class LBRYDaemonServer(object): self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) - def start(self, branch=DEFAULT_UI_BRANCH, user_specified=False, wallet=DEFAULT_WALLET): + def start(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, wallet=DEFAULT_WALLET): d = self._setup_server(self._setup_server(wallet)) - d.addCallback(lambda _: self._api.setup(branch, user_specified)) + d.addCallback(lambda _: self._api.setup(branch, user_specified, branch_specified)) return d diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index baa4b09f9..b4aa33cd5 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -70,7 +70,7 @@ class LBRYUIManager(object): self.loaded_branch = None self.loaded_requirements = None - def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=None): + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=None, branch_specified=False): self.branch = branch if user_specified: if os.path.isdir(user_specified): @@ -82,7 +82,7 @@ class LBRYUIManager(object): return d else: log.info("User specified UI directory doesn't exist, using " + branch) - elif self.loaded_branch == "user-specified": + elif self.loaded_branch == "user-specified" and not branch_specified: log.info("Loading user provided UI") d = self._load_ui() return d From 4cd1cdf495e9e503fed34e8643c1e88203587cab Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 04:37:34 -0400 Subject: [PATCH 225/462] escape spaces in paths given to reveal() --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 4d1494f80..8c4389c89 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -2229,9 +2229,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): path = p['path'] if sys.platform == "darwin": - d = threads.deferToThread(subprocess.Popen, ("open -R %s" % path), shell=True) + d = threads.deferToThread(subprocess.Popen, ("open -R %s" % path.replace(' ', '\ ')), shell=True) else: - d = threads.deferToThread(subprocess.Popen, ("xdg-open %s" % path), shell=True) + d = threads.deferToThread(subprocess.Popen, ("xdg-open %s" % path.replace(' ', '\ ')), shell=True) d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d \ No newline at end of file From ba605e985a44a99365c7e5f37c23b58455bffd73 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 04:40:58 -0400 Subject: [PATCH 226/462] no shell=True --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 8c4389c89..cf38ee02e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -2229,9 +2229,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): path = p['path'] if sys.platform == "darwin": - d = threads.deferToThread(subprocess.Popen, ("open -R %s" % path.replace(' ', '\ ')), shell=True) + d = threads.deferToThread(subprocess.Popen, ['open', '-R', path]) else: - d = threads.deferToThread(subprocess.Popen, ("xdg-open %s" % path.replace(' ', '\ ')), shell=True) + d = threads.deferToThread(subprocess.Popen, ['xdg-open', '-R', path]) d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d \ No newline at end of file From c10e7b2b4196c5f06e7b972790032330d5b33728 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 15:39:27 -0400 Subject: [PATCH 227/462] fix import --- lbrynet/lbrylive/LBRYStdoutDownloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrylive/LBRYStdoutDownloader.py b/lbrynet/lbrylive/LBRYStdoutDownloader.py index 0c1fe8c60..8c413e129 100644 --- a/lbrynet/lbrylive/LBRYStdoutDownloader.py +++ b/lbrynet/lbrylive/LBRYStdoutDownloader.py @@ -1,7 +1,7 @@ import logging import sys -from lbrynet.lbrynet_console.plugins.LBRYLive.LBRYLiveStreamDownloader import LBRYLiveStreamDownloader +from lbrynet.lbrylive.client.LiveStreamDownloader import LBRYLiveStreamDownloader from lbrynet.core.BlobManager import TempBlobManager from lbrynet.core.Session import LBRYSession from lbrynet.core.client.StandaloneBlobDownloader import StandaloneBlobDownloader From 2026024c8a8d879f6dcca77c08bdc1eaeaa44e1b Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 15:49:25 -0400 Subject: [PATCH 228/462] updates from development --- lbrynet/__init__.py | 2 +- lbrynet/conf.py | 4 +- lbrynet/core/LBRYcrdWallet.py | 5 + .../lbrynet_daemon/Apps/LBRYOSXStatusBar.py | 108 -------- lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py | 60 ----- lbrynet/lbrynet_daemon/LBRYDaemon.py | 246 +++++++++++++----- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 13 +- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 207 +++------------ lbrynet/lbrynet_daemon/LBRYUIManager.py | 224 ++++++++++++++++ .../daemon_scripts/Autofetcher.py | 68 +++++ .../{Apps => daemon_scripts}/__init__.py | 0 .../daemon_scripts/migrateto025.py | 33 +++ .../daemon_scripts/network_tester.py | 160 ++++++++++++ packaging/osx/certs/cert.cer.enc | 30 +++ packaging/osx/certs/cert.p12.enc | 33 +++ setup_uri_handler.py | 25 -- 16 files changed, 780 insertions(+), 438 deletions(-) delete mode 100644 lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py delete mode 100644 lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py create mode 100644 lbrynet/lbrynet_daemon/LBRYUIManager.py create mode 100644 lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py rename lbrynet/lbrynet_daemon/{Apps => daemon_scripts}/__init__.py (100%) create mode 100644 lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py create mode 100644 lbrynet/lbrynet_daemon/daemon_scripts/network_tester.py create mode 100644 packaging/osx/certs/cert.cer.enc create mode 100644 packaging/osx/certs/cert.p12.enc delete mode 100644 setup_uri_handler.py diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 26dafe45d..3d80f11a2 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ import logging logging.getLogger(__name__).addHandler(logging.NullHandler()) -version = (0, 2, 4) +version = (0, 2, 5) __version__ = ".".join([str(x) for x in version]) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index dfae3d274..526dc711d 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -36,8 +36,10 @@ UI_ADDRESS = "http://" + API_INTERFACE + ":" + str(API_PORT) PROTOCOL_PREFIX = "lbry" DEFAULT_WALLET = "lbryum" +WALLET_TYPES = ["lbryum", "lbrycrd"] DEFAULT_TIMEOUT = 30 DEFAULT_MAX_SEARCH_RESULTS = 25 DEFAULT_MAX_KEY_FEE = 100.0 DEFAULT_SEARCH_TIMEOUT = 3.0 -DEFAULT_CACHE_TIME = 3600 \ No newline at end of file +DEFAULT_CACHE_TIME = 3600 +DEFAULT_UI_BRANCH = "master" diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 8ec200611..17fd0e229 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -295,6 +295,11 @@ class LBRYWallet(object): d.addCallback(self._get_stream_info_from_value, name) return d + def get_txid_for_name(self, name): + d = self._get_value_for_name(name) + d.addCallback(lambda r: None if 'txid' not in r else r['txid']) + return d + def get_stream_info_from_txid(self, name, txid): d = self.get_claims_from_tx(txid) diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py b/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py deleted file mode 100644 index 5ca433f72..000000000 --- a/lbrynet/lbrynet_daemon/Apps/LBRYOSXStatusBar.py +++ /dev/null @@ -1,108 +0,0 @@ -import rumps -import xmlrpclib -import os -import webbrowser -import subprocess -import argparse - - -class DaemonStatusBarApp(rumps.App): - def __init__(self): - icon_path = 'app.icns' - if os.path.isfile(icon_path): - rumps.App.__init__(self, name="LBRY", icon=icon_path, quit_button=None, - menu=["Open", "Preferences", "View balance", "Quit"]) - else: - rumps.App.__init__(self, name="LBRY", title="LBRY", quit_button=None, - menu=["Open", "Preferences", "View balance", "Quit"]) - - @rumps.timer(1) - def alert_daemon_start(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - start_msg = daemon.is_running() - if isinstance(start_msg, str): - rumps.notification(title='LBRY', subtitle='', message=str(start_msg), sound=True) - update_info = daemon.check_for_new_version() - update_msg = "" - for p in update_info: - if not p[0]: - update_msg += p[1] + "\n" - if update_msg: - update_msg += "\n Try running the installer again to fix this" - rumps.notification(title='LBRY', subtitle='', message=update_msg, sound=True) - except: - pass - - @rumps.clicked('Open') - def get_ui(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - daemon.is_running() - webbrowser.get('safari').open("lbry://lbry") - except: - try: - rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) - except: - rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") - - @rumps.clicked("Preferences") - def prefs(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - daemon.is_running() - webbrowser.get('safari').open("lbry://settings") - except: - rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) - - @rumps.clicked("View balance") - def disp_balance(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - balance = daemon.get_balance() - r = round(float(balance), 2) - try: - rumps.notification(title='LBRY', subtitle='', message=str("Your balance is %.2f LBC" % r), sound=False) - except: - rumps.alert(title='LBRY', message=str("Your balance is %.2f LBC" % r)) - - except: - try: - rumps.notification(title='LBRY', subtitle='', message="Couldn't connect to lbrynet daemon", sound=True) - except: - rumps.alert(title='LBRY', message="Couldn't connect to lbrynet daemon") - - @rumps.clicked('Quit') - def clean_quit(self): - daemon = xmlrpclib.ServerProxy("http://localhost:7080/") - try: - daemon.stop() - except: - pass - rumps.quit_application() - - -def main(): - parser = argparse.ArgumentParser(description="Launch lbrynet status bar application") - parser.add_argument("--startdaemon", - help="true or false, default true", - type=str, - default="true") - args = parser.parse_args() - - if str(args.startdaemon).lower() == "true": - daemon = xmlrpclib.ServerProxy('http://localhost:7080') - try: - daemon.is_running() - except: - subprocess.Popen("screen -dmS lbrynet bash -c " - "'PYTHONPATH=$PYTHONPATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.python_path`; " - "PATH=$PATH:`cat /Users/${USER}/Library/Application\ Support/lbrynet/.lbry_bin_path`; " - "lbrynet-daemon --update=False'", shell=True) - - status_app = DaemonStatusBarApp() - status_app.run() - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py deleted file mode 100644 index f6990cfea..000000000 --- a/lbrynet/lbrynet_daemon/Apps/LBRYURIHandler.py +++ /dev/null @@ -1,60 +0,0 @@ -import os -import json -import webbrowser -import subprocess -import sys - -from time import sleep -from jsonrpc.proxy import JSONRPCProxy - -API_CONNECTION_STRING = "http://localhost:5279/lbryapi" -UI_ADDRESS = "http://localhost:5279" - - -class LBRYURIHandler(object): - def __init__(self): - self.started_daemon = False - self.daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING) - - def handle_osx(self, lbry_name): - try: - status = self.daemon.is_running() - except: - os.system("open /Applications/LBRY.app") - sleep(3) - - if lbry_name == "lbry" or lbry_name == "": - webbrowser.open(UI_ADDRESS) - else: - webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) - - def handle_linux(self, lbry_name): - try: - status = self.daemon.is_running() - except: - cmd = r'DIR = "$( cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )"' \ - r'if [-z "$(pgrep lbrynet-daemon)"]; then' \ - r'echo "running lbrynet-daemon..."' \ - r'$DIR / lbrynet - daemon &' \ - r'sleep 3 # let the daemon load before connecting' \ - r'fi' - subprocess.Popen(cmd, shell=True) - - if lbry_name == "lbry" or lbry_name == "": - webbrowser.open(UI_ADDRESS) - else: - webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) - - -def main(args): - if len(args) != 1: - args = ['lbry://lbry'] - - name = args[0][7:] - if sys.platform == "darwin": - LBRYURIHandler().handle_osx(lbry_name=name) - else: - LBRYURIHandler().handle_linux(lbry_name=name) - -if __name__ == "__main__": - main(sys.argv[1:]) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0a10dff9d..cf38ee02e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,5 +1,6 @@ import locale import os +import subprocess import sys import simplejson as json import binascii @@ -31,13 +32,14 @@ from lbrynet.core.Error import UnknownNameError, InsufficientFundsError from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier +from lbrynet.lbrynet_daemon.LBRYUIManager import LBRYUIManager from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ - DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME -from lbrynet.conf import API_CONNECTION_STRING, API_PORT, API_ADDRESS, DEFAULT_TIMEOUT, UI_ADDRESS + DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH +from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet @@ -109,7 +111,7 @@ CONNECTION_PROBLEM_CODES = [ ALLOWED_DURING_STARTUP = ['is_running', 'is_first_run', 'get_time_behind_blockchain', 'stop', 'daemon_status', 'get_start_notice', - 'version', 'check_for_new_version'] + 'version'] BAD_REQUEST = 400 NOT_FOUND = 404 @@ -129,7 +131,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): isLeaf = True - def __init__(self, ui_version_info, wallet_type=DEFAULT_WALLET): + def __init__(self, root, wallet_type=DEFAULT_WALLET): jsonrpc.JSONRPC.__init__(self) reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) @@ -139,9 +141,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connected_to_internet = True self.connection_problem = None self.query_handlers = {} - self.ui_version = ui_version_info.replace('\n', '') self.git_lbrynet_version = None self.git_lbryum_version = None + self.ui_version = None + self.ip = None self.wallet_type = wallet_type self.first_run = None self.log_file = LOG_FILENAME @@ -151,20 +154,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.waiting_on = {} self.streams = {} self.known_dht_nodes = KNOWN_DHT_NODES - self.platform_info = { - "processor": platform.processor(), - "python_version: ": platform.python_version(), - "platform": platform.platform(), - "os_release": platform.release(), - "os_system": platform.system(), - "lbrynet_version: ": lbrynet_version, - "lbryum_version: ": lbryum_version, - "ui_version": self.ui_version, - } - try: - self.platform_info['ip'] = json.load(urlopen('http://jsonip.com'))['ip'] - except: - self.platform_info['ip'] = "Could not determine" + self.first_run_after_update = False if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle @@ -197,7 +187,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'use_upnp': True, 'start_lbrycrdd': True, 'requested_first_run_credits': False, - 'cache_time': DEFAULT_CACHE_TIME + 'cache_time': DEFAULT_CACHE_TIME, + 'startup_scripts': [], + 'last_version': {'lbrynet': lbrynet_version, 'lbryum': lbryum_version} } if os.path.isfile(self.daemon_conf): @@ -234,6 +226,20 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.session_settings = settings_dict + if 'last_version' in missing_settings.keys(): + self.session_settings['last_version'] = None + + if self.session_settings['last_version'] != self.default_settings['last_version']: + self.session_settings['last_version'] = self.default_settings['last_version'] + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() + + self.first_run_after_update = True + log.info("First run after update") + if lbrynet_version == '0.2.5': + self.session_settings['startup_scripts'].append({'script_name': 'migrateto025', 'run_once': True}) + self.run_on_startup = self.session_settings['run_on_startup'] self.data_rate = self.session_settings['data_rate'] self.max_key_fee = self.session_settings['max_key_fee'] @@ -244,7 +250,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.search_timeout = self.session_settings['search_timeout'] self.download_timeout = self.session_settings['download_timeout'] self.max_search_results = self.session_settings['max_search_results'] - self.wallet_type = self.session_settings['wallet_type'] if self.session_settings['wallet_type'] == wallet_type else wallet_type + self.wallet_type = self.session_settings['wallet_type'] if self.session_settings['wallet_type'] in WALLET_TYPES else wallet_type self.delete_blobs_on_remove = self.session_settings['delete_blobs_on_remove'] self.peer_port = self.session_settings['peer_port'] self.dht_node_port = self.session_settings['dht_node_port'] @@ -252,6 +258,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.start_lbrycrdd = self.session_settings['start_lbrycrdd'] self.requested_first_run_credits = self.session_settings['requested_first_run_credits'] self.cache_time = self.session_settings['cache_time'] + self.startup_scripts = self.session_settings['startup_scripts'] if os.path.isfile(os.path.join(self.db_dir, "stream_info_cache.json")): f = open(os.path.join(self.db_dir, "stream_info_cache.json"), "r") @@ -301,6 +308,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.sd_identifier = StreamDescriptorIdentifier() self.stream_info_manager = TempLBRYFileMetadataManager() self.settings = LBRYSettings(self.db_dir) + self.lbry_ui_manager = LBRYUIManager(root) self.blob_request_payment_rate_manager = None self.lbry_file_metadata_manager = None self.lbry_file_manager = None @@ -374,7 +382,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.error(failure) return jsonrpclib.Fault(self.FAILURE, "error") - def setup(self): + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False): def _log_starting_vals(): d = self._get_lbry_files() d.addCallback(lambda r: json.dumps([d[1] for d in r])) @@ -394,6 +402,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.announced_startup = True self.startup_status = STARTUP_STAGES[5] log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") + if len(self.startup_scripts): + log.info("Scheduling scripts") + reactor.callLater(3, self._run_scripts) + # self.lbrynet_connection_checker.start(3600) if self.first_run: @@ -415,6 +427,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connection_problem_checker.start(1) d = defer.Deferred() + d.addCallback(lambda _: self.lbry_ui_manager.setup(branch=branch, + user_specified=user_specified, + branch_specified=branch_specified)) d.addCallback(lambda _: self._initial_setup()) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) @@ -433,9 +448,29 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) + def _get_platform(self): + r = { + "processor": platform.processor(), + "python_version: ": platform.python_version(), + "platform": platform.platform(), + "os_release": platform.release(), + "os_system": platform.system(), + "lbrynet_version: ": lbrynet_version, + "lbryum_version: ": lbryum_version, + "ui_version": self.lbry_ui_manager.loaded_git_version, + } + if not self.ip: + try: + r['ip'] = json.load(urlopen('http://jsonip.com'))['ip'] + self.ip = r['ip'] + except: + r['ip'] = "Could not determine" + + return r + def _initial_setup(self): def _log_platform(): - log.info("Platform: " + json.dumps(self.platform_info)) + log.info("Platform: " + json.dumps(self._get_platform())) return defer.succeed(None) d = _log_platform() @@ -523,10 +558,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _stop_server(self): - if self.lbry_server_port is not None: - self.lbry_server_port, p = None, self.lbry_server_port - return defer.maybeDeferred(p.stopListening) - else: + try: + if self.lbry_server_port is not None: + self.lbry_server_port, p = None, self.lbry_server_port + return defer.maybeDeferred(p.stopListening) + else: + return defer.succeed(True) + except AttributeError: return defer.succeed(True) def _setup_server(self): @@ -608,14 +646,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _shutdown(self): log.info("Closing lbrynet session") log.info("Status at time of shutdown: " + self.startup_status[0]) + if self.internet_connection_checker.running: + self.internet_connection_checker.stop() + if self.version_checker.running: + self.version_checker.stop() + if self.connection_problem_checker.running: + self.connection_problem_checker.stop() d = self._upload_log(name_prefix="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) + d.addErrback(lambda err: True) d.addCallback(lambda _: self.lbry_file_manager.stop()) - d.addErrback(lambda err: log.info("Bad server shutdown: " + err.getTraceback())) + d.addErrback(lambda err: True) if self.session is not None: d.addCallback(lambda _: self.session.shut_down()) - d.addErrback(lambda err: log.info("Bad session shutdown: " + err.getTraceback())) + d.addErrback(lambda err: True) return d def _update_settings(self, settings): @@ -794,7 +839,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("Using PTC wallet") d = defer.succeed(PTCWallet(self.db_dir)) else: - d = defer.fail() + log.info("Requested unknown wallet '%s', using default lbryum" % self.wallet_type) + d = defer.succeed(LBRYumWallet(self.db_dir)) d.addCallback(lambda wallet: {"wallet": wallet}) return d @@ -1017,19 +1063,31 @@ class LBRYDaemon(jsonrpc.JSONRPC): f.close() return defer.succeed(True) - def _resolve_name(self, name): + def _resolve_name(self, name, force_refresh=False): def _cache_stream_info(stream_info): + def _add_txid(txid): + self.name_cache[name]['txid'] = txid + return defer.succeed(None) + self.name_cache[name] = {'claim_metadata': stream_info, 'timestamp': self._get_long_count_timestamp()} - d = self._update_claim_cache() + d = self.session.wallet.get_txid_for_name(name) + d.addCallback(_add_txid) + d.addCallback(lambda _: self._update_claim_cache()) d.addCallback(lambda _: self.name_cache[name]['claim_metadata']) + return d - if name in self.name_cache.keys(): - if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: - log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) - d = defer.succeed(self.name_cache[name]['claim_metadata']) + if not force_refresh: + if name in self.name_cache.keys(): + if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: + log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) + d = defer.succeed(self.name_cache[name]['claim_metadata']) + else: + log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) else: - log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) + log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) d = self.session.wallet.get_stream_info_for_name(name) d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) else: @@ -1039,7 +1097,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d - def _delete_lbry_file(self, lbry_file): + def _delete_lbry_file(self, lbry_file, delete_file=True): d = self.lbry_file_manager.delete_lbry_file(lbry_file) def finish_deletion(lbry_file): @@ -1052,7 +1110,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.lbry_file_manager.get_count_for_stream_hash(s_h) # TODO: could possibly be a timing issue here d.addCallback(lambda c: self.stream_info_manager.delete_stream(s_h) if c == 0 else True) - d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if + if delete_file: + d.addCallback(lambda _: os.remove(os.path.join(self.download_directory, lbry_file.file_name)) if os.path.isfile(os.path.join(self.download_directory, lbry_file.file_name)) else defer.succeed(None)) return d @@ -1160,7 +1219,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): if status[0] == DOWNLOAD_RUNNING_CODE: d = f.status() d.addCallback(_get_file_status) - d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, 'key': key, + d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, + 'download_directory': f.download_directory, + 'download_path': os.path.join(f.download_directory, f.file_name), + 'key': key, 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, @@ -1172,6 +1234,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'message': message}) else: d = defer.succeed({'completed': f.completed, 'file_name': f.file_name, 'key': key, + 'download_directory': f.download_directory, + 'download_path': os.path.join(f.download_directory, f.file_name), 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, @@ -1221,6 +1285,31 @@ class LBRYDaemon(jsonrpc.JSONRPC): requests.post(URL, json.dumps({"text": msg})) return defer.succeed(None) + def _run_scripts(self): + if len([k for k in self.startup_scripts if 'run_once' in k.keys()]): + log.info("Removing one time startup scripts") + f = open(self.daemon_conf, "r") + initialsettings = json.loads(f.read()) + f.close() + t = [s for s in self.startup_scripts if 'run_once' not in s.keys()] + initialsettings['startup_scripts'] = t + f = open(self.daemon_conf, "w") + f.write(json.dumps(initialsettings)) + f.close() + + for script in self.startup_scripts: + if script['script_name'] == 'migrateto025': + log.info("Running migrator to 0.2.5") + from lbrynet.lbrynet_daemon.daemon_scripts.migrateto025 import run as run_migrate + run_migrate(self) + + if script['script_name'] == 'Autofetcher': + log.info("Starting autofetcher script") + from lbrynet.lbrynet_daemon.daemon_scripts.Autofetcher import run as run_autofetcher + run_autofetcher(self) + + return defer.succeed(None) + def _render_response(self, result, code): return defer.succeed({'result': result, 'code': code}) @@ -1330,10 +1419,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): "remote_lbryum": most recent lbryum version available from github """ + platform_info = self._get_platform() msg = { - 'platform': self.platform_info['platform'], - 'os_release': self.platform_info['os_release'], - 'os_system': self.platform_info['os_system'], + 'platform': platform_info['platform'], + 'os_release': platform_info['os_release'], + 'os_system': platform_info['os_system'], 'lbrynet_version': lbrynet_version, 'lbryum_version': lbryum_version, 'ui_version': self.ui_version, @@ -1711,14 +1801,19 @@ class LBRYDaemon(jsonrpc.JSONRPC): confirmation message """ + if 'delete_target_file' in p.keys(): + delete_file = p['delete_target_file'] + else: + delete_file = True + def _delete_file(f): file_name = f.file_name - d = self._delete_lbry_file(f) + d = self._delete_lbry_file(f, delete_file=delete_file) d.addCallback(lambda _: "Deleted LBRY file" + file_name) return d - if p.keys()[0] in ['name', 'sd_hash', 'file_name']: - search_type = p.keys()[0] + if 'name' in p.keys() or 'sd_hash' in p.keys() or 'file_name' in p.keys(): + search_type = [k for k in p.keys() if k != 'delete_target_file'][0] d = self._get_lbry_file(search_type, p[search_type], return_json=False) d.addCallback(lambda l: _delete_file(l) if l else False) @@ -2051,25 +2146,18 @@ class LBRYDaemon(jsonrpc.JSONRPC): # # return d - def jsonrpc_check_for_new_version(self): + def jsonrpc_log(self, message): """ - Checks local version against versions in __init__.py and version.py in the lbrynet and lbryum repos + Log message Args: - None + message: message to be logged Returns: - true/false, true meaning that there is a new version available + True """ - def _check_version(): - if (lbrynet_version >= self.git_lbrynet_version) and (lbryum_version >= self.git_lbryum_version): - log.info("[" + str(datetime.now()) + "] Up to date") - return self._render_response(False, OK_CODE) - else: - log.info("[" + str(datetime.now()) + "] Updates available") - return self._render_response(True, OK_CODE) - - return _check_version() + log.info(message) + return self._render_response(True, OK_CODE) def jsonrpc_upload_log(self, p=None): """ @@ -2078,7 +2166,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): Args, optional: 'name_prefix': prefix to indicate what is requesting the log upload 'exclude_previous': true/false, whether or not to exclude previous sessions from upload, defaults on true - Returns + Returns: True """ @@ -2109,3 +2197,41 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._log_to_slack(p['message'])) d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d + + def jsonrpc_configure_ui(self, p): + """ + Configure the UI being hosted + + Args, optional: + 'branch': a branch name on lbryio/lbry-web-ui + 'path': path to a ui folder + """ + + if 'path' in p.keys(): + d = self.lbry_ui_manager.setup(user_specified=p['path']) + elif 'branch' in p.keys(): + d = self.lbry_ui_manager.setup(branch=p['branch']) + else: + d = self.lbry_ui_manager.setup() + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + + return d + + def jsonrpc_reveal(self, p): + """ + Open a folder in finder/file explorer + + Args: + 'path': path to be selected in finder + Returns: + True, opens finder + """ + + path = p['path'] + if sys.platform == "darwin": + d = threads.deferToThread(subprocess.Popen, ['open', '-R', path]) + else: + d = threads.deferToThread(subprocess.Popen, ['xdg-open', '-R', path]) + + d.addCallback(lambda _: self._render_response(True, OK_CODE)) + return d \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 1d7531331..72c8d2c1e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -13,7 +13,8 @@ from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer -from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, DEFAULT_WALLET, UI_ADDRESS +from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, \ + DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH if sys.platform != "darwin": @@ -68,12 +69,11 @@ def start(): help="path to custom UI folder", default=None) parser.add_argument("--branch", - help="Branch of lbry-web-ui repo to use, defaults on master", - default="master") + help="Branch of lbry-web-ui repo to use, defaults on master") parser.add_argument('--no-launch', dest='launchui', action="store_false") parser.add_argument('--log-to-console', dest='logtoconsole', action="store_true") parser.add_argument('--quiet', dest='quiet', action="store_true") - parser.set_defaults(launchui=True, logtoconsole=False, quiet=False) + parser.set_defaults(branch=False, launchui=True, logtoconsole=False, quiet=False) args = parser.parse_args() if args.logtoconsole: @@ -104,7 +104,10 @@ def start(): if test_internet_connection(): lbry = LBRYDaemonServer() - d = lbry.start(branch=args.branch, user_specified=args.ui, wallet=args.wallet) + d = lbry.start(branch=args.branch if args.branch else DEFAULT_UI_BRANCH, + user_specified=args.ui, + wallet=args.wallet, + branch_specified=True if args.branch else False) if args.launchui: d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 85494d21c..393a87b72 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -18,7 +18,7 @@ from txjsonrpc.web import jsonrpc from zope.interface import implements from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon -from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS +from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH if sys.platform != "darwin": @@ -149,22 +149,23 @@ class HostedLBRYFile(resource.Resource): self._producer = None resource.Resource.__init__(self) - def makeProducer(self, request, stream): - def _save_producer(producer): - self._producer = producer - return defer.succeed(None) - - range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-') - start, stop = int(range_header[0]), range_header[1] - log.info("[" + str(datetime.now()) + "] GET range %s-%s" % (start, stop)) - path = os.path.join(self._api.download_directory, stream.file_name) - - d = stream.get_total_bytes() - d.addCallback(lambda size: _save_producer(LBRYFileStreamer(request, path, start, stop, size))) - d.addCallback(lambda _: request.registerProducer(self._producer, streaming=True)) - # request.notifyFinish().addCallback(lambda _: self._producer.stopProducing()) - request.notifyFinish().addErrback(self._responseFailed, d) - return d + # todo: fix LBRYFileStreamer and use it instead of static.File + # def makeProducer(self, request, stream): + # def _save_producer(producer): + # self._producer = producer + # return defer.succeed(None) + # + # range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-') + # start, stop = int(range_header[0]), range_header[1] + # log.info("[" + str(datetime.now()) + "] GET range %s-%s" % (start, stop)) + # path = os.path.join(self._api.download_directory, stream.file_name) + # + # d = stream.get_total_bytes() + # d.addCallback(lambda size: _save_producer(LBRYFileStreamer(request, path, start, stop, size))) + # d.addCallback(lambda _: request.registerProducer(self._producer, streaming=True)) + # # request.notifyFinish().addCallback(lambda _: self._producer.stopProducing()) + # request.notifyFinish().addErrback(self._responseFailed, d) + # return d def render_GET(self, request): if 'name' in request.args.keys(): @@ -182,172 +183,22 @@ class HostedLBRYFile(resource.Resource): request.finish() return server.NOT_DONE_YET - def _responseFailed(self, err, call): - call.addErrback(lambda err: err.trap(error.ConnectionDone)) - call.addErrback(lambda err: err.trap(defer.CancelledError)) - call.addErrback(lambda err: log.info("Error: " + str(err))) - call.cancel() - - -class MyLBRYFiles(resource.Resource): - isLeaf = False - - def __init__(self): - resource.Resource.__init__(self) - self.files_table = None - - def delayed_render(self, request, result): - request.write(result.encode('utf-8')) - request.finish() - - def render_GET(self, request): - self.files_table = None - api = jsonrpc.Proxy(API_CONNECTION_STRING) - d = api.callRemote("get_lbry_files", {}) - d.addCallback(self._get_table) - d.addCallback(lambda results: self.delayed_render(request, results)) - - return server.NOT_DONE_YET - - def _get_table(self, files): - if not self.files_table: - self.files_table = r'My LBRY files' - self.files_table += r'' - self.files_table += r'' - self.files_table += r'' - self.files_table += r'' - self.files_table += r'' - self.files_table += r'' - return self._get_table(files) - if not len(files): - self.files_table += r'
Stream nameCompletedToggleRemove
' - return self.files_table - else: - f = files.pop() - self.files_table += r'' - self.files_table += r'%s' % (f['stream_name']) - self.files_table += r'%s' % (f['completed']) - self.files_table += r'Start' if f['stopped'] else r'Stop' - self.files_table += r'Delete' - self.files_table += r'' - return self._get_table(files) + # def _responseFailed(self, err, call): + # call.addErrback(lambda err: err.trap(error.ConnectionDone)) + # call.addErrback(lambda err: err.trap(defer.CancelledError)) + # call.addErrback(lambda err: log.info("Error: " + str(err))) + # call.cancel() class LBRYDaemonServer(object): - def __init__(self): - self.data_dir = user_data_dir("LBRY") - if not os.path.isdir(self.data_dir): - os.mkdir(self.data_dir) - self.version_dir = os.path.join(self.data_dir, "ui_version_history") - if not os.path.isdir(self.version_dir): - os.mkdir(self.version_dir) - self.config = os.path.join(self.version_dir, "active.json") - self.ui_dir = os.path.join(self.data_dir, "lbry-web-ui") - self.git_version = None - self._api = None - self.root = None - - if not os.path.isfile(os.path.join(self.config)): - self.loaded_git_version = None - else: - try: - f = open(self.config, "r") - loaded_ui = json.loads(f.read()) - f.close() - self.loaded_git_version = loaded_ui['commit'] - self.loaded_branch = loaded_ui['branch'] - version_log.info("[" + str(datetime.now()) + "] Last used " + self.loaded_branch + " commit " + str(self.loaded_git_version).replace("\n", "")) - except: - self.loaded_git_version = None - self.loaded_branch = None - - def setup(self, branch="master", user_specified=None): - self.branch = branch - if user_specified: - if os.path.isdir(user_specified): - log.info("Using user specified UI directory: " + str(user_specified)) - self.branch = "user-specified" - self.loaded_git_version = "user-specified" - self.ui_dir = user_specified - return defer.succeed("user-specified") - else: - log.info("User specified UI directory doesn't exist, using " + branch) - else: - log.info("Using UI branch: " + branch) - self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/%s" % branch - self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % branch - - d = self._up_to_date() - d.addCallback(lambda r: self._download_ui() if not r else self.branch) - return d - - def _up_to_date(self): - def _get_git_info(): - response = urlopen(self._git_url) - data = json.loads(response.read()) - return defer.succeed(data['object']['sha']) - - def _set_git(version): - self.git_version = version - version_log.info("[" + str(datetime.now()) + "] UI branch " + self.branch + " has a most recent commit of: " + str(self.git_version).replace("\n", "")) - - if self.git_version == self.loaded_git_version and os.path.isdir(self.ui_dir): - version_log.info("[" + str(datetime.now()) + "] local copy of " + self.branch + " is up to date") - return defer.succeed(True) - else: - if self.git_version == self.loaded_git_version: - version_log.info("[" + str(datetime.now()) + "] Can't find ui files, downloading them again") - else: - version_log.info("[" + str(datetime.now()) + "] local copy of " + self.branch + " branch is out of date, updating") - f = open(self.config, "w") - f.write(json.dumps({'commit': self.git_version, - 'time': str(datetime.now()), - 'branch': self.branch})) - f.close() - return defer.succeed(False) - - d = _get_git_info() - d.addCallback(_set_git) - return d - - def _download_ui(self): - def _delete_ui_dir(): - if os.path.isdir(self.ui_dir): - if self.loaded_git_version: - version_log.info("[" + str(datetime.now()) + "] Removed ui files for commit " + str(self.loaded_git_version).replace("\n", "")) - log.info("Removing out of date ui files") - shutil.rmtree(self.ui_dir) - return defer.succeed(None) - - def _dl_ui(): - url = urlopen(self._dist_url) - z = ZipFile(StringIO(url.read())) - names = [i for i in z.namelist() if '.DS_exStore' not in i and '__MACOSX' not in i] - z.extractall(self.ui_dir, members=names) - version_log.info("[" + str(datetime.now()) + "] Updated branch " + self.branch + ": " + str(self.loaded_git_version).replace("\n", "") + " --> " + self.git_version.replace("\n", "")) - log.info("Downloaded files for UI commit " + str(self.git_version).replace("\n", "")) - self.loaded_git_version = self.git_version - return self.branch - - d = _delete_ui_dir() - d.addCallback(lambda _: _dl_ui()) - return d - - def _setup_server(self, ui_ver, wallet): - self._api = LBRYDaemon(ui_ver, wallet_type=wallet) - self.root = LBRYindex(self.ui_dir) - self.root.putChild("css", static.File(os.path.join(self.ui_dir, "css"))) - self.root.putChild("font", static.File(os.path.join(self.ui_dir, "font"))) - self.root.putChild("img", static.File(os.path.join(self.ui_dir, "img"))) - self.root.putChild("js", static.File(os.path.join(self.ui_dir, "js"))) + def _setup_server(self, wallet): + self.root = LBRYindex(os.path.join(os.path.join(data_dir, "lbry-ui"), "active")) + self._api = LBRYDaemon(self.root, wallet_type=wallet) self.root.putChild("view", HostedLBRYFile(self._api)) - self.root.putChild("files", MyLBRYFiles()) self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) - def start(self, branch="master", user_specified=False, wallet=DEFAULT_WALLET): - d = self.setup(branch=branch, user_specified=user_specified) - d.addCallback(lambda v: self._setup_server(v, wallet)) - d.addCallback(lambda _: self._api.setup()) - + def start(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, wallet=DEFAULT_WALLET): + d = self._setup_server(self._setup_server(wallet)) + d.addCallback(lambda _: self._api.setup(branch, user_specified, branch_specified)) return d diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py new file mode 100644 index 000000000..b4aa33cd5 --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -0,0 +1,224 @@ +import os +import logging +import shutil +import sys +import json + +from urllib2 import urlopen +from StringIO import StringIO +from twisted.web import static +from twisted.internet import defer +from lbrynet.conf import DEFAULT_UI_BRANCH +from lbrynet import __version__ as lbrynet_version +from lbryum.version import ELECTRUM_VERSION as lbryum_version +from zipfile import ZipFile +from appdirs import user_data_dir + +if sys.platform != "darwin": + data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + data_dir = user_data_dir("LBRY") + +if not os.path.isdir(data_dir): + os.mkdir(data_dir) +version_dir = os.path.join(data_dir, "ui_version_history") +if not os.path.isdir(version_dir): + os.mkdir(version_dir) + +log = logging.getLogger(__name__) +log.addHandler(logging.FileHandler(os.path.join(data_dir, 'lbrynet-daemon.log'))) +log.setLevel(logging.INFO) + + +class LBRYUIManager(object): + def __init__(self, root): + self.data_dir = user_data_dir("LBRY") + self.ui_root = os.path.join(self.data_dir, "lbry-ui") + self.active_dir = os.path.join(self.ui_root, "active") + self.update_dir = os.path.join(self.ui_root, "update") + + if not os.path.isdir(self.data_dir): + os.mkdir(self.data_dir) + if not os.path.isdir(self.ui_root): + os.mkdir(self.ui_root) + if not os.path.isdir(self.ui_root): + os.mkdir(self.ui_root) + if not os.path.isdir(self.ui_root): + os.mkdir(self.ui_root) + + self.config = os.path.join(self.ui_root, "active.json") + self.update_requires = os.path.join(self.update_dir, "requirements.txt") + self.requirements = {} + self.ui_dir = self.active_dir + self.git_version = None + self.root = root + + if not os.path.isfile(os.path.join(self.config)): + self.loaded_git_version = None + self.loaded_branch = None + self.loaded_requirements = None + else: + try: + f = open(self.config, "r") + loaded_ui = json.loads(f.read()) + f.close() + self.loaded_git_version = loaded_ui['commit'] + self.loaded_branch = loaded_ui['branch'] + self.loaded_requirements = loaded_ui['requirements'] + except: + self.loaded_git_version = None + self.loaded_branch = None + self.loaded_requirements = None + + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=None, branch_specified=False): + self.branch = branch + if user_specified: + if os.path.isdir(user_specified): + log.info("Checking user specified UI directory: " + str(user_specified)) + self.branch = "user-specified" + self.loaded_git_version = "user-specified" + d = self.migrate_ui(source=user_specified) + d.addCallback(lambda _: self._load_ui()) + return d + else: + log.info("User specified UI directory doesn't exist, using " + branch) + elif self.loaded_branch == "user-specified" and not branch_specified: + log.info("Loading user provided UI") + d = self._load_ui() + return d + else: + log.info("Checking for updates for UI branch: " + branch) + self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/%s" % branch + self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % branch + + d = self._up_to_date() + d.addCallback(lambda r: self._download_ui() if not r else self._load_ui()) + return d + + def _up_to_date(self): + def _get_git_info(): + response = urlopen(self._git_url) + data = json.loads(response.read()) + return defer.succeed(data['object']['sha']) + + def _set_git(version): + self.git_version = version.replace('\n', '') + if self.git_version == self.loaded_git_version: + log.info("UI is up to date") + return defer.succeed(True) + else: + log.info("UI updates available, checking if installation meets requirements") + return defer.succeed(False) + + d = _get_git_info() + d.addCallback(_set_git) + return d + + def migrate_ui(self, source=None): + if not source: + requires_file = self.update_requires + source_dir = self.update_dir + delete_source = True + else: + requires_file = os.path.join(source, "requirements.txt") + source_dir = source + delete_source = False + + def _check_requirements(): + if not os.path.isfile(requires_file): + log.info("No requirements.txt file, rejecting request to migrate this UI") + return defer.succeed(False) + + f = open(requires_file, "r") + for requirement in [line for line in f.read().split('\n') if line]: + t = requirement.split('=') + if len(t) == 3: + self.requirements[t[0]] = {'version': t[1], 'operator': '=='} + elif t[0][-1] == ">": + self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '>='} + elif t[0][-1] == "<": + self.requirements[t[0][:-1]] = {'version': t[1], 'operator': '<='} + f.close() + passed_requirements = True + for r in self.requirements: + if r == 'lbrynet': + c = lbrynet_version + elif r == 'lbryum': + c = lbryum_version + else: + c = None + if c: + if self.requirements[r]['operator'] == '==': + if not self.requirements[r]['version'] == c: + passed_requirements = False + log.info("Local version %s of %s does not meet UI requirement for version %s" % ( + c, r, self.requirements[r]['version'])) + else: + log.info("Local version of %s meets ui requirement" % r) + if self.requirements[r]['operator'] == '>=': + if not self.requirements[r]['version'] <= c: + passed_requirements = False + log.info("Local version %s of %s does not meet UI requirement for version %s" % ( + c, r, self.requirements[r]['version'])) + else: + log.info("Local version of %s meets ui requirement" % r) + if self.requirements[r]['operator'] == '<=': + if not self.requirements[r]['version'] >= c: + passed_requirements = False + log.info("Local version %s of %s does not meet UI requirement for version %s" % ( + c, r, self.requirements[r]['version'])) + else: + log.info("Local version of %s meets ui requirement" % r) + return defer.succeed(passed_requirements) + + def _disp_failure(): + log.info("Failed to satisfy requirements for branch '%s', update was not loaded" % self.branch) + return defer.succeed(False) + + def _do_migrate(): + if os.path.isdir(self.active_dir): + shutil.rmtree(self.active_dir) + shutil.copytree(source_dir, self.active_dir) + if delete_source: + shutil.rmtree(source_dir) + + log.info("Loaded UI update") + + f = open(self.config, "w") + loaded_ui = {'commit': self.git_version, 'branch': self.branch, 'requirements': self.requirements} + f.write(json.dumps(loaded_ui)) + f.close() + + self.loaded_git_version = loaded_ui['commit'] + self.loaded_branch = loaded_ui['branch'] + self.loaded_requirements = loaded_ui['requirements'] + return defer.succeed(True) + + d = _check_requirements() + d.addCallback(lambda r: _do_migrate() if r else _disp_failure()) + return d + + def _download_ui(self): + def _delete_update_dir(): + if os.path.isdir(self.update_dir): + shutil.rmtree(self.update_dir) + return defer.succeed(None) + + def _dl_ui(): + url = urlopen(self._dist_url) + z = ZipFile(StringIO(url.read())) + names = [i for i in z.namelist() if '.DS_exStore' not in i and '__MACOSX' not in i] + z.extractall(self.update_dir, members=names) + log.info("Downloaded files for UI commit " + str(self.git_version).replace("\n", "")) + return self.branch + + d = _delete_update_dir() + d.addCallback(lambda _: _dl_ui()) + d.addCallback(lambda _: self.migrate_ui()) + d.addCallback(lambda _: self._load_ui()) + return d + + def _load_ui(self): + for d in [i[0] for i in os.walk(self.active_dir) if os.path.dirname(i[0]) == self.active_dir]: + self.root.putChild(os.path.basename(d), static.File(d)) + return defer.succeed(True) \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py new file mode 100644 index 000000000..cd7ed02cb --- /dev/null +++ b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py @@ -0,0 +1,68 @@ +import json +import logging.handlers +import sys +import os + +from appdirs import user_data_dir +from twisted.internet.task import LoopingCall +from twisted.internet import reactor + + +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') + +if os.path.isfile(LOG_FILENAME): + f = open(LOG_FILENAME, 'r') + PREVIOUS_LOG = len(f.read()) + f.close() +else: + PREVIOUS_LOG = 0 + +log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) + + +class Autofetcher(object): + """ + Download name claims as they occur + """ + + def __init__(self, api): + self._api = api + self._checker = LoopingCall(self._check_for_new_claims) + self.best_block = None + + def start(self): + reactor.addSystemEventTrigger('before', 'shutdown', self.stop) + self._checker.start(5) + + def stop(self): + log.info("Stopping autofetcher") + self._checker.stop() + + def _check_for_new_claims(self): + block = self._api.get_best_blockhash() + if block != self.best_block: + log.info("Checking new block for name claims, block hash: %s" % block) + self.best_block = block + transactions = self._api.get_block({'blockhash': block})['tx'] + for t in transactions: + c = self._api.get_claims_for_tx({'txid': t}) + if len(c): + for i in c: + log.info("Downloading stream for claim txid: %s" % t) + self._api.get({'name': t, 'stream_info': json.loads(i['value'])}) + + +def run(api): + fetcher = Autofetcher(api) + fetcher.start() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/Apps/__init__.py b/lbrynet/lbrynet_daemon/daemon_scripts/__init__.py similarity index 100% rename from lbrynet/lbrynet_daemon/Apps/__init__.py rename to lbrynet/lbrynet_daemon/daemon_scripts/__init__.py diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py b/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py new file mode 100644 index 000000000..9283b48aa --- /dev/null +++ b/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py @@ -0,0 +1,33 @@ +from twisted.internet import defer + + +class migrator(object): + """ + Re-resolve lbry names to write missing data to blockchain.db and to cache the nametrie + """ + + def __init__(self, api): + self._api = api + + def start(self): + def _resolve_claims(claimtrie): + claims = [i for i in claimtrie if 'txid' in i.keys()] + r = defer.DeferredList([self._api._resolve_name(claim['name'], force_refresh=True) for claim in claims], consumeErrors=True) + return r + + def _restart_lbry_files(): + def _restart_lbry_file(lbry_file): + return lbry_file.restore() + + r = defer.DeferredList([_restart_lbry_file(lbry_file) for lbry_file in self._api.lbry_file_manager.lbry_files if not lbry_file.txid], consumeErrors=True) + r.callback(None) + return r + + d = self._api.session.wallet.get_nametrie() + d.addCallback(_resolve_claims) + d.addCallback(lambda _: _restart_lbry_files()) + + +def run(api): + refresher = migrator(api) + refresher.start() diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/network_tester.py b/lbrynet/lbrynet_daemon/daemon_scripts/network_tester.py new file mode 100644 index 000000000..d62bdc3b1 --- /dev/null +++ b/lbrynet/lbrynet_daemon/daemon_scripts/network_tester.py @@ -0,0 +1,160 @@ +import json +import logging +import os +import sys + +from appdirs import user_data_dir +from datetime import datetime +from twisted.internet import defer +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.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory +from lbrynet.conf import DEFAULT_TIMEOUT + +INITIALIZING_CODE = 'initializing' +DOWNLOAD_METADATA_CODE = 'downloading_metadata' +DOWNLOAD_TIMEOUT_CODE = 'timeout' +DOWNLOAD_RUNNING_CODE = 'running' +DOWNLOAD_STOPPED_CODE = 'stopped' +STREAM_STAGES = [ + (INITIALIZING_CODE, 'Initializing...'), + (DOWNLOAD_METADATA_CODE, 'Downloading metadata'), + (DOWNLOAD_RUNNING_CODE, 'Started stream'), + (DOWNLOAD_STOPPED_CODE, 'Paused stream'), + (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') + ] + +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') +log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) + + +class GetStream(object): + def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5, + timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None): + self.wallet = wallet + self.resolved_name = None + self.description = None + self.key_fee = None + self.key_fee_address = None + self.data_rate = data_rate + self.pay_key = pay_key + self.name = None + self.file_name = file_name + self.session = session + self.payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager) + self.lbry_file_manager = lbry_file_manager + self.sd_identifier = sd_identifier + self.stream_hash = None + self.max_key_fee = max_key_fee + self.stream_info = None + self.stream_info_manager = None + self.d = defer.Deferred(None) + self.timeout = timeout + self.timeout_counter = 0 + self.download_directory = download_directory + self.download_path = None + self.downloader = None + self.finished = defer.Deferred() + self.checker = LoopingCall(self.check_status) + self.code = STREAM_STAGES[0] + + def check_status(self): + self.timeout_counter += 1 + + if self.download_path: + self.checker.stop() + self.finished.callback((self.stream_hash, self.download_path)) + + elif self.timeout_counter >= self.timeout: + log.info("Timeout downloading lbry://" + self.resolved_name + ", " + str(self.stream_info)) + self.checker.stop() + self.d.cancel() + self.code = STREAM_STAGES[4] + self.finished.callback(False) + + def start(self, stream_info, name): + self.resolved_name = name + self.stream_info = stream_info + if 'stream_hash' in self.stream_info.keys(): + self.stream_hash = self.stream_info['stream_hash'] + elif 'sources' in self.stream_info.keys(): + self.stream_hash = self.stream_info['sources']['lbry_sd_hash'] + else: + raise InvalidStreamInfoError(self.stream_info) + if 'description' in self.stream_info.keys(): + self.description = self.stream_info['description'] + if 'key_fee' in self.stream_info.keys(): + self.key_fee = float(self.stream_info['key_fee']) + if 'key_fee_address' in self.stream_info.keys(): + self.key_fee_address = self.stream_info['key_fee_address'] + else: + self.key_fee_address = None + else: + self.key_fee = None + self.key_fee_address = None + if self.key_fee > self.max_key_fee: + if self.pay_key: + log.info("Key fee (" + str(self.key_fee) + ") above limit of " + str( + self.max_key_fee) + ", didn't download lbry://" + str(self.resolved_name)) + return defer.fail(None) + else: + pass + + def _cause_timeout(): + self.timeout_counter = self.timeout * 2 + + def _set_status(x, status): + self.code = next(s for s in STREAM_STAGES if s[0] == status) + return x + + self.checker.start(1) + + self.d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE)) + self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) + self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) + self.d.addCallback(lambda r: _set_status(r, DOWNLOAD_RUNNING_CODE)) + self.d.addCallback(lambda metadata: ( + next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), + metadata)) + self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, + [self.data_rate, True], + self.payment_rate_manager, + download_directory=self.download_directory, + file_name=self.file_name)) + self.d.addCallbacks(self._start_download, lambda _: _cause_timeout()) + self.d.callback(None) + + return self.finished + + 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 reserved_points is None: + return defer.fail(InsufficientFundsError()) + log.info("Key fee: " + str(self.key_fee) + " | " + str(self.key_fee_address)) + return self.wallet.send_points_to_address(reserved_points, self.key_fee) + return defer.succeed(None) + + if self.pay_key: + d = _pay_key_fee() + else: + d = defer.Deferred() + self.downloader = downloader + self.download_path = os.path.join(downloader.download_directory, downloader.file_name) + d.addCallback(lambda _: log.info("[" + str(datetime.now()) + "] Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) + d.addCallback(lambda _: self.downloader.start()) diff --git a/packaging/osx/certs/cert.cer.enc b/packaging/osx/certs/cert.cer.enc new file mode 100644 index 000000000..4dc96d9ba --- /dev/null +++ b/packaging/osx/certs/cert.cer.enc @@ -0,0 +1,30 @@ +U2FsdGVkX1/oLoj7zhPd0imh2T8RhLpKzZLk4EHQV0GUJ1g8nwGvxWov+CUMnmjh +Y+LNdomGBYWoUilhe4JaWDUYepwIXn6+TvuWBdVEMGpJhXGbmIf+ncMXo6AP8Fh/ +g9x79SE4RJxFj3utc02B2ivVehoQno5sEvNSZMVml5n9skJoJUBbAsbp1p+7Hm5j +p2Z7UI7/qiih6TmszX5KQvOl/DPezVNksn1c1mUShuaBTjxbprGlr/LvtboR3mAd +8DN4yTGLLJAQ2+FNftM4rAedrr6db2AhQ8WxRmiwTfdubnEoC6zYySFq2kmdjj3S +gPnK0atx+ZihMp+S+GqMnvfAHEtb0vqxoq6nFcSIvcQVxKPyzu5E1kMriY4Oq3xr +K6ebc1NKJHjh7niaUmR3NImBx2h1LAAf/hcKRH2+ATEVczGtI1AsSGgGhUM34eGH +7G+m7+bIkgb8AtlaIGS/VVHsIZCNSgzwZJoNd3hD6ZV65Hb2yeT6Hhos88/05iFT +ewYasa73TqFm5SJHRwt4d1W9WVIJKJPDJ910p+V+NZVUsKOx34+vMNrjCrqW9p9x +gQnza2V/F6blIHTbSzIGc+MFbeHYBO80d+v5jVxheL8z6ollDVts1SyJ5rKJBY6c +quvSgmc/ltE0dqRxLOQJ9mAFbayuMIUP6CbRkPXp8GfE55UtUJkDilalzcpCPrUC +YJpuAI61INOQZZPEVKWW8L68/tLY+oEwWpexQX7xs4FUCblIFf20T3XE2lVuBHf9 +Bp9k7cD2m4mNrbzWOJuqrVt1pr176l9+VSP/ESdDFbmPch2FHl8HK8kgfJvkV+iB +kudmAmzI9DTUpWd5lJp6Fr/rLCMjslFDs37zMg4/E5ikKFSDNeYMtgPZhCwM83kh +OAktow4QAzh3RdbVZMFxaKk9nbiGPuBEsgvraPjb2gY8U34RC9R2FINIuTnJttLK +q7CKFTdbJIf+TIIgzfNu/c978adsK/qS68iltyyx8WFflcybnlqVgja192Ptqw1M +PXBQkH4mUrAeWDfmCPPh/mhO67Bau5u9Wzv/qZ2RXcX0dgXOoMa2sO6ZpR2SzxCJ +/XZwXnElMl+pvojLURDOV16fMPpjMCbzCN+hQabiTASqFNCsz4C9hmOquNh2t+V9 +8xvU/bnOM+/SMhahjYnvdhmRMcY+5Wv32ZnKATq88dq4T7/OZI7q3IsROZ7MnucT +x4vADvcFOfOdtPK35IFfMTfl+Ri3q7REIHMts2WEwXddf8CUiVeIaf8NgrWYW0hP +f9DQbMGKFcqqCHlKrQkv2dBKX/qEbIzN7T7535Ly68zyFuBS252gsLO7nrf+CLEZ +AROOfmt2jv0BvQ4MI5dslzsXFAU11tS36gOZ303R+NJVVqySkza964h2rH5M1F7i +A5p7w/l0OVV7r6aXkmsrIcsUZuY7QnZJORQ1MxNtK20weKfrqs90nMTklUVPc4V8 +LnAW6AYem0ZaeDHn2kx947sglMYxf0h/mFECGhif9hfDTErw7TkSJ26t9ByuEyEf +vGpp3P4iTXHUx7HSh7L4KDva6CP6slGjFMAFUEETn7N5uX3VEYeztMBdHLz0XHZc +PcgVZ8kytXVTEg95upvWmliEbQqWRsy6sr9PanaN1QY6re6RLlYj4pOWVm8qgCXU +IJVTWkROMlYZTWCibCsTsY8fk8aNObZamHjzZGvnU8nEGTx7xQJS8i0r3NM1j2Ka +ehBA+WfXbTplI/Fq8Z2Nrb/O39hQpGbXp4ERsEmVbK0twwsqVNehI0CdobxmGsd5 +E9Afb9iL97DTXsna1Il6FXnHvj3iAQsxxaNLIaj0sN1GaQd9N1mbxThlFNOM3wry +jI8TKCWEfLRQzykkcR3sMg== diff --git a/packaging/osx/certs/cert.p12.enc b/packaging/osx/certs/cert.p12.enc new file mode 100644 index 000000000..40828aff6 --- /dev/null +++ b/packaging/osx/certs/cert.p12.enc @@ -0,0 +1,33 @@ +U2FsdGVkX1+DAD1J9fegD2PjAVffLjKB5urEZYVfRRsZ9uCYeGggOyopseTFPACo +IGBkauMQ1lrQWSltYzDzbzPdhe02w6xWHx8hh9QRepSSWlTUHjIxr8A1GryZo7a8 +4dLs4qxjQDcDdp+csOrBqm3AKS4oeVFRXWxvmr2AueUQ/CEyvhAR1wS3XZ1L0Pod +6XJWAhDIPtT9zfSQbCiVvHtjK7VxVjIMv9VwDfE2Gny/otaNf9Wuor6luiDMF3Z8 +H6X5yh/mkmNZvI/bcOrCmGUkDEVvw/pessdZwwTIdNSzkBE8GqC9Oc5jdOMpW7J1 +afyZDslB1SaNXm/9HDPnl67guZRUM1j6QJxBwIyj8vUhygcG4J6HOAQrWi61ebSX +5ZZrNddMycVRDhE1GphhRPJm7S/v8aeASc8dlAy3ircERJXIO/WhWpdysKgVB8/u +wtc6TVK2MUD0CvmG7hatqCQcwsgK7Goy7zFN4kkNviAegxpm5OAmEtHH5SrlHXWI +CmMAZyNAkwmcAPwviXXaSSA9z/A++KDqQHbRJJKz/fFZ98OsVs64zPwMT3uMAp2E +FiBkCqpxj6q0EFHJwhpNtEPdPF62T9CzcV2CoNr1qyDS7UqlKBi2vkGHNALtBqbm +69rN3ESpjhRzK4pbRFBM0R73JWVW8LM/jWIOFOPh1qd5yKNALKGqw4sEtZ96YJju +Y4tP17+kRknzgSVn6zuUSg/wznIVs+eQ9eYQVd+T70XDUGe2PfQTRm3bz/8W7m8u +tDqE/yhgBJDXuc0zlmXxXxH4cXEhKPA2ScrEw974nWKWrNgtmN+skaJVQELFqVm8 +47amfobRAsp/l0+d86shUg9QC3XzrI/jkPPpKsQUKoYF1OULpXwjMJs7o0e/Ajo6 +S32DWVMqHfhd/M1LBUSFqLb802Y+qFVOXRSJOV2VEqfplbsnEPnmkBrUjVT4y6x6 +HxxqPq5IQM6qLK9TCPXbYCzp3knWim8A5jDFXYNHHeTkuA1xbpkM4lCas64pYV9V +fkokG4fdFM09oileakOxt0iz0DJjXlb/XZLOvuhMeAWPcJC9UTrmMUdXCBgem3Nk +vT40dxCxMK3EREM8dvbNndC7sg9mVJ6dRY7+inDnhhdGhy9FM592lBvFDTS9oJm0 +ZX+0FeDvIGnG1kEIYSrBhCP/9X++6EzF+YzO1zo2YXtVlP2JT/9cD5g6SajvI1+5 +pdv2zzdFRfEKDpJ8bRDr6iMJLCmllWSWkeSE2VNo30+atCorc5/6vfjD/BOJtZDj +vUxPsZxulxiNp24YwDBJ+B+uid8x6xC7h1hId9QF51wUA54AzHRtypAuAOVHjdyj +W+EkCpic1eDyFMVhfy7hB/Ef9lpvuQsKfmvTu3ege8TOMQBeaKmlKBAIyGeTcTH/ +vRz/UAYXEzTRNWkfCFZQ6oucVWSSUxX53DnvD4NcT0AX7+kRY+bhZcZW/nc/NEqN +Tzs3Zv9N9h3M618FK/mqSvhqxukMIRXRhyiISEQyAJtm0SuMu9SXG9Q+G766KOWm ++votjNrHQKIojPI3BcbFHCfXET5qPoUQVPw3M5Av0E3Tm36ZAdl+bhl852H9Vf2M +TprNFmr4U/sljyetEpywG1aEzxijISCflFNBZrqMIwcdYdduLCKPcMNtqSpFiXLV +WtDPBvoz4XldIkZIA+70oBqCwJchILI5ujlo1haF7/ILIK5aynITu2zoaDE6gtE8 +VFl30aGF1uRKYYle8E+RLxv5ID/xFuPlNsBQ3ZsfNbsE9GEoVFmTTGneN+wuTl7G +NNRdyjv7Py3zgC1sqA6cmzRJkgX+CGKm3aCJTvflDKYVGRpmphsYWLqZp7i12Noj +/eHzfYkMU2uOh50IUls8l2fYRlkwPuMQxVtn2g7/3dUXna8zQ0LSqAPRf8zZAszx +nGG1kwpYyJ4YknC8oKhnt3LZWfmAEJFRNSYHDTbBncynqADoUB6EH5j5qcdI/pFG +lsrrw+lbCPbN7dDbbbg685ESKI4WZ7j0zkJIrDWdSFYCitmo437h+t9AcWBF5SEd +vOtCHu46xXuBJbDmz2mslw== diff --git a/setup_uri_handler.py b/setup_uri_handler.py deleted file mode 100644 index e9ba6749c..000000000 --- a/setup_uri_handler.py +++ /dev/null @@ -1,25 +0,0 @@ -from setuptools import setup -import os - -APP = [os.path.join('lbrynet', 'lbrynet_daemon', 'Apps', 'LBRYURIHandler.py')] -DATA_FILES = [] -OPTIONS = {'argv_emulation': True, - 'packages': ['jsonrpc'], - 'plist': { - 'LSUIElement': True, - 'CFBundleIdentifier': 'io.lbry.LBRYURIHandler', - 'CFBundleURLTypes': [ - { - 'CFBundleURLTypes': 'LBRYURIHandler', - 'CFBundleURLSchemes': ['lbry'] - } - ] - } - } - -setup( - app=APP, - data_files=DATA_FILES, - options={'py2app': OPTIONS}, - setup_requires=['py2app'], -) \ No newline at end of file From 0151dd887551d643fc92eeae8851e3e5178cce74 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 16:14:32 -0400 Subject: [PATCH 229/462] remove test_bot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test bot was moved to keynes repo, and shouldn’t be with the tests script --- .../install_dependencies_and_run_tests.sh | 2 +- tests/lbrynet_test_bot.py | 60 ------------------- 2 files changed, 1 insertion(+), 61 deletions(-) delete mode 100644 tests/lbrynet_test_bot.py diff --git a/packaging/travis/install_dependencies_and_run_tests.sh b/packaging/travis/install_dependencies_and_run_tests.sh index 9bef0afc4..f55ab609b 100755 --- a/packaging/travis/install_dependencies_and_run_tests.sh +++ b/packaging/travis/install_dependencies_and_run_tests.sh @@ -39,7 +39,7 @@ rm get-pip.py pip install -r requirements.txt pip install nose coverage coveralls pylint -nosetests --with-coverage --cover-package=lbrynet -v -I lbrynet_test_bot.py -I functional_tests.py tests/ +nosetests --with-coverage --cover-package=lbrynet -v -I functional_tests.py tests/ # TODO: submit coverage report to coveralls # TODO: as code quality improves, make pylint be more strict diff --git a/tests/lbrynet_test_bot.py b/tests/lbrynet_test_bot.py deleted file mode 100644 index 46e5d9f0a..000000000 --- a/tests/lbrynet_test_bot.py +++ /dev/null @@ -1,60 +0,0 @@ -import xmlrpclib -import json -from datetime import datetime -from time import sleep -from slackclient import SlackClient - -def get_conf(): - f = open('testbot.conf', 'r') - token = f.readline().replace('\n', '') - f.close() - return token - -def test_lbrynet(lbry, slack, channel): - logfile = open('lbrynet_test_log.txt', 'a') - - try: - path = lbry.get('testlbrynet')['path'] - except: - msg = '[' + str(datetime.now()) + '] ! Failed to obtain LBRYnet test file' - slack.rtm_connect() - slack.rtm_send_message(channel, msg) - print msg - logfile.write(msg + '\n') - - file_name = path.split('/')[len(path.split('/'))-1] - - for n in range(10): - files = [f for f in lbry.get_lbry_files() if (json.loads(f)['file_name'] == file_name) and json.loads(f)['completed']] - if files: - break - sleep(30) - - if files: - msg = '[' + str(datetime.now()) + '] LBRYnet download test successful' - slack.rtm_connect() - # slack.rtm_send_message(channel, msg) - print msg - logfile.write(msg + '\n') - - else: - msg = '[' + str(datetime.now()) + '] ! Failed to obtain LBRYnet test file' - slack.rtm_connect() - slack.rtm_send_message(channel, msg) - print msg - logfile.write(msg + '\n') - - lbry.delete_lbry_file('test.jpg') - logfile.close() - -token = get_conf() - -sc = SlackClient(token) -sc.rtm_connect() -channel = [c['id'] for c in json.loads(sc.api_call('channels.list'))['channels'] if c['name'] == 'tech'][0] -print 'Connected to slack' -daemon = xmlrpclib.ServerProxy("http://localhost:7080") - -while True: - test_lbrynet(daemon, sc, channel) - sleep(600) From ef62fd7e346a6621a750a818242339797bbadabc Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 30 May 2016 16:37:47 -0400 Subject: [PATCH 230/462] fix pip install jsonrpc --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 482489063..c7a722242 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,5 +27,5 @@ script: # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi # py2app fails to find jsonrpc unless json-rpc is installed. why? I don't know. - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install json-rpc; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install jsonrpc; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi \ No newline at end of file From a4a5d63da74605d1ec016b60c1c2b25e2136b029 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 31 May 2016 17:38:00 -0400 Subject: [PATCH 231/462] fix update settings problem also move log upload url constant to conf.py --- lbrynet/conf.py | 2 ++ lbrynet/lbrynet_daemon/LBRYDaemon.py | 27 +++++++++++++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 526dc711d..06b455392 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -24,6 +24,8 @@ KNOWN_DHT_NODES = [('104.236.42.182', 4000)] POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' #POINTTRADER_SERVER = 'http://127.0.0.1:2424' +LOG_POST_URL = "https://lbry.io/log-upload" + CRYPTSD_FILE_EXTENSION = ".cryptsd" API_INTERFACE = "localhost" diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index b68e1feca..5f5e3fbec 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -38,7 +38,7 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ - DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH + DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -410,8 +410,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): if self.first_run: d = self._upload_log(log_type="first_run") - else: + elif self.upload_log: d = self._upload_log(exclude_previous=True, log_type="start") + else: + d = defer.succeed(None) if float(self.session.wallet.wallet_balance) == 0.0: d.addCallback(lambda _: self._check_first_run()) @@ -619,7 +621,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _upload_log(self, log_type=None, exclude_previous=False, force=False): if self.upload_log or force: - LOG_URL = "https://lbry.io/log-upload" if exclude_previous: f = open(self.log_file, "r") f.seek(PREVIOUS_LOG) @@ -633,11 +634,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'date': datetime.utcnow().strftime('%Y%m%d-%H%M%S'), 'hash': base58.b58encode(self.lbryid)[:20], 'sys': platform.system(), - 'type': log_type, + 'type': log_type if log_type else 'default', 'log': log_contents } - requests.post(LOG_URL, params) + requests.post(LOG_POST_URL, params) return defer.succeed(None) else: return defer.succeed(None) @@ -713,16 +714,22 @@ class LBRYDaemon(jsonrpc.JSONRPC): elif k == 'download_timeout': if type(settings['download_timeout']) is int: self.session_settings['download_timeout'] = settings['download_timeout'] + elif type(settings['download_timeout']) is float: + self.session_settings['download_timeout'] = int(settings['download_timeout']) else: return defer.fail() elif k == 'search_timeout': if type(settings['search_timeout']) is float: self.session_settings['search_timeout'] = settings['search_timeout'] + elif type(settings['search_timeout']) is int: + self.session_settings['search_timeout'] = float(settings['search_timeout']) else: return defer.fail() elif k == 'cache_time': if type(settings['cache_time']) is int: self.session_settings['cache_time'] = settings['cache_time'] + elif type(settings['cache_time']) is float: + self.session_settings['cache_time'] = int(settings['cache_time']) else: return defer.fail() self.run_on_startup = self.session_settings['run_on_startup'] @@ -1482,6 +1489,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("[" + str(datetime.now()) + "] Set daemon settings to " + json.dumps(self.session_settings)) d = self._update_settings(p) + d.addErrback(lambda err: log.info(err.getTraceback())) d.addCallback(lambda _: _log_settings_change()) d.addCallback(lambda _: self._render_response(self.session_settings, OK_CODE)) @@ -2145,17 +2153,18 @@ class LBRYDaemon(jsonrpc.JSONRPC): # # return d - def jsonrpc_log(self, message): + def jsonrpc_log(self, p): """ Log message Args: - message: message to be logged + 'message': message to be logged Returns: True """ - log.info(message) + message = p['message'] + log.info("API client log request: %s" % message) return self._render_response(True, OK_CODE) def jsonrpc_upload_log(self, p=None): @@ -2172,6 +2181,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): if p: if 'name_prefix' in p.keys(): log_type = p['name_prefix'] + '_api' + elif 'log_type' in p.keys(): + log_type = p['log_type'] + '_api' else: log_type = None From f50c98e10d907e61c05739a57e765cbb87abb74a Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 31 May 2016 17:45:04 -0400 Subject: [PATCH 232/462] revert import see if this helps travis --- lbrynet/lbrylive/LBRYStdoutDownloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrylive/LBRYStdoutDownloader.py b/lbrynet/lbrylive/LBRYStdoutDownloader.py index 8c413e129..0c1fe8c60 100644 --- a/lbrynet/lbrylive/LBRYStdoutDownloader.py +++ b/lbrynet/lbrylive/LBRYStdoutDownloader.py @@ -1,7 +1,7 @@ import logging import sys -from lbrynet.lbrylive.client.LiveStreamDownloader import LBRYLiveStreamDownloader +from lbrynet.lbrynet_console.plugins.LBRYLive.LBRYLiveStreamDownloader import LBRYLiveStreamDownloader from lbrynet.core.BlobManager import TempBlobManager from lbrynet.core.Session import LBRYSession from lbrynet.core.client.StandaloneBlobDownloader import StandaloneBlobDownloader From 284d307c0f6b8fdc214b693f190bd4a481edeb25 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 31 May 2016 22:49:00 -0500 Subject: [PATCH 233/462] fix logging-too-few-args errors --- lbrynet/core/client/DownloadManager.py | 4 ++-- .../plugins/BlindRepeater/ValuableBlobQueryHandler.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lbrynet/core/client/DownloadManager.py b/lbrynet/core/client/DownloadManager.py index fced77969..d601833dd 100644 --- a/lbrynet/core/client/DownloadManager.py +++ b/lbrynet/core/client/DownloadManager.py @@ -52,7 +52,7 @@ class DownloadManager(object): def check_stop(result, manager): if isinstance(result, failure.Failure): - log.error("Failed to stop the %s: %s", manager. result.getErrorMessage()) + log.error("Failed to stop the %s: %s", manager, result.getErrorMessage()) return False return True @@ -115,4 +115,4 @@ class DownloadManager(object): if not self.blobs: return self.calculate_total_bytes() else: - return sum([b.length for b in self.needed_blobs() if b.length is not None]) \ No newline at end of file + return sum([b.length for b in self.needed_blobs() if b.length is not None]) diff --git a/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py b/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py index 08d5a57a0..d8dd0009e 100644 --- a/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py +++ b/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py @@ -101,7 +101,7 @@ class ValuableBlobHashQueryHandler(ValuableQueryHandler): for blob_hash, count in valuable_hashes: hashes_and_scores.append((blob_hash, 1.0 * count / 10.0)) if len(hashes_and_scores) != 0: - log.info("Responding to a valuable blob hashes request with %s blob hashes: %s", + log.info("Responding to a valuable blob hashes request with %s blob hashes", str(len(hashes_and_scores))) expected_payment = 1.0 * len(hashes_and_scores) * self.valuable_blob_hash_payment_rate / 1000.0 self.wallet.add_expected_payment(self.peer, expected_payment) @@ -193,10 +193,10 @@ class ValuableBlobLengthQueryHandler(ValuableQueryHandler): if success is True: lengths.append(response_pair) if len(lengths) > 0: - log.info("Responding with %s blob lengths: %s", str(len(lengths))) + log.info("Responding with %s blob lengths", str(len(lengths))) expected_payment = 1.0 * len(lengths) * self.blob_length_payment_rate / 1000.0 self.wallet.add_expected_payment(self.peer, expected_payment) self.peer.update_stats('uploaded_valuable_blob_infos', len(lengths)) return {'blob_length': {'blob_lengths': lengths}} - dl.addCallback(make_response) \ No newline at end of file + dl.addCallback(make_response) From 104cafedb02186713b06a30c365f163f19024665 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 1 Jun 2016 20:52:15 -0400 Subject: [PATCH 234/462] --wallet fix --- lbrynet/lbrylive/LBRYStdoutDownloader.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 10 ++++++++-- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lbrynet/lbrylive/LBRYStdoutDownloader.py b/lbrynet/lbrylive/LBRYStdoutDownloader.py index 0c1fe8c60..8c413e129 100644 --- a/lbrynet/lbrylive/LBRYStdoutDownloader.py +++ b/lbrynet/lbrylive/LBRYStdoutDownloader.py @@ -1,7 +1,7 @@ import logging import sys -from lbrynet.lbrynet_console.plugins.LBRYLive.LBRYLiveStreamDownloader import LBRYLiveStreamDownloader +from lbrynet.lbrylive.client.LiveStreamDownloader import LBRYLiveStreamDownloader from lbrynet.core.BlobManager import TempBlobManager from lbrynet.core.Session import LBRYSession from lbrynet.core.client.StandaloneBlobDownloader import StandaloneBlobDownloader diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 5f5e3fbec..2625b5709 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -131,7 +131,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): isLeaf = True - def __init__(self, root, wallet_type=DEFAULT_WALLET): + def __init__(self, root, wallet_type=None): jsonrpc.JSONRPC.__init__(self) reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) @@ -250,7 +250,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.search_timeout = self.session_settings['search_timeout'] self.download_timeout = self.session_settings['download_timeout'] self.max_search_results = self.session_settings['max_search_results'] - self.wallet_type = self.session_settings['wallet_type'] if self.session_settings['wallet_type'] in WALLET_TYPES else wallet_type + if self.session_settings['wallet_type'] in WALLET_TYPES and not wallet_type: + self.wallet_type = self.session_settings['wallet_type'] + log.info("Using wallet type %s from config" % self.wallet_type) + else: + self.wallet_type = wallet_type + self.session_settings['wallet_type'] = wallet_type + log.info("Using wallet type %s specified from command line" % self.wallet_type) self.delete_blobs_on_remove = self.session_settings['delete_blobs_on_remove'] self.peer_port = self.session_settings['peer_port'] self.dht_node_port = self.session_settings['dht_node_port'] diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 72c8d2c1e..e441ca923 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -64,7 +64,7 @@ def start(): parser.add_argument("--wallet", help="lbrycrd or lbryum, default lbryum", type=str, - default=DEFAULT_WALLET) + default='') parser.add_argument("--ui", help="path to custom UI folder", default=None) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 393a87b72..aa5b70b11 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -198,7 +198,7 @@ class LBRYDaemonServer(object): self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) - def start(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, wallet=DEFAULT_WALLET): - d = self._setup_server(self._setup_server(wallet)) + def start(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, wallet=None): + d = self._setup_server(wallet) d.addCallback(lambda _: self._api.setup(branch, user_specified, branch_specified)) return d From 36787eeeeb0f73bcd73622755f582635d5152acd Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 1 Jun 2016 21:05:38 -0400 Subject: [PATCH 235/462] default value --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 2625b5709..dc453f27d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -217,8 +217,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("Loaded lbrynet-daemon configuration") settings_dict = loaded_settings else: - log.info( - "Writing default settings : " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) + missing_settings = self.default_settings + log.info("Writing default settings : " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) f = open(self.daemon_conf, "w") f.write(json.dumps(self.default_settings)) f.close() From a30a9ad763955f0fb1df8cc5ded076e573eb5aa0 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 1 Jun 2016 22:31:41 -0500 Subject: [PATCH 236/462] have pylint skip currently unmaintained files --- lbrynet/lbrylive/LBRYStdinUploader.py | 5 ++++- lbrynet/lbrylive/LBRYStdoutDownloader.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrylive/LBRYStdinUploader.py b/lbrynet/lbrylive/LBRYStdinUploader.py index 03570a975..7637e29e1 100644 --- a/lbrynet/lbrylive/LBRYStdinUploader.py +++ b/lbrynet/lbrylive/LBRYStdinUploader.py @@ -1,3 +1,6 @@ +# pylint: skip-file +# This file is not maintained, but might be used in the future +# import logging import sys from lbrynet.lbrylive.LiveStreamCreator import StdOutLiveStreamCreator @@ -114,4 +117,4 @@ def launch_stdin_uploader(): d.addCallback(lambda _: start_stdin_uploader()) d.addCallback(lambda _: shut_down()) reactor.addSystemEventTrigger('before', 'shutdown', uploader.shut_down) - reactor.run() \ No newline at end of file + reactor.run() diff --git a/lbrynet/lbrylive/LBRYStdoutDownloader.py b/lbrynet/lbrylive/LBRYStdoutDownloader.py index 8c413e129..e23cb36f1 100644 --- a/lbrynet/lbrylive/LBRYStdoutDownloader.py +++ b/lbrynet/lbrylive/LBRYStdoutDownloader.py @@ -1,3 +1,6 @@ +# pylint: skip-file +# This file is not maintained, but might be used in the future +# import logging import sys @@ -93,4 +96,4 @@ def launch_stdout_downloader(): d.addErrback(print_error) d.addCallback(lambda _: shut_down()) reactor.addSystemEventTrigger('before', 'shutdown', downloader.shut_down) - reactor.run() \ No newline at end of file + reactor.run() From 98efa9f46661e75f98afd326f7571a0bb9875807 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 1 Jun 2016 22:35:30 -0500 Subject: [PATCH 237/462] pylint: ignore incorrect not-callable message --- lbrynet/lbrynet_console/ControlHandlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_console/ControlHandlers.py b/lbrynet/lbrynet_console/ControlHandlers.py index cbe5dd270..d19d55c88 100644 --- a/lbrynet/lbrynet_console/ControlHandlers.py +++ b/lbrynet/lbrynet_console/ControlHandlers.py @@ -120,7 +120,7 @@ class CommandHandlerFactory(object): return self.control_handler_class.prompt_description def get_handler(self, console): - return self.control_handler_class(console, *self.args) + return self.control_handler_class(console, *self.args) # pylint: disable=not-callable class CommandHandler(object): From 85a571521bbb038718d56351ef720dcdb397d9c5 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 1 Jun 2016 22:38:24 -0500 Subject: [PATCH 238/462] disambuguate popup functions --- lbrynet/lbrynet_gui/GuiApp.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_gui/GuiApp.py b/lbrynet/lbrynet_gui/GuiApp.py index 94c977759..176cce9d7 100644 --- a/lbrynet/lbrynet_gui/GuiApp.py +++ b/lbrynet/lbrynet_gui/GuiApp.py @@ -176,10 +176,10 @@ class DownloaderApp(object): style="Stop.TButton", cursor=button_cursor) self.wallet_menu_button.grid(row=0, column=1, padx=(5, 0)) - def popup(event): + def popup_wallet(event): self.wallet_menu.tk_popup(event.x_root, event.y_root) - self.wallet_menu_button.bind("", popup) + self.wallet_menu_button.bind("", popup_wallet) self.uri_frame = ttk.Frame(self.frame, style="B.TFrame") self.uri_frame.grid() @@ -204,7 +204,7 @@ class DownloaderApp(object): def paste_command(): self.uri_entry.event_generate('') - def popup(event): + def popup_uri(event): selection_menu = tk.Menu( self.master, tearoff=0 ) @@ -214,7 +214,7 @@ class DownloaderApp(object): selection_menu.add_command(label=" Paste ", command=paste_command) selection_menu.tk_popup(event.x_root, event.y_root) - self.uri_entry.bind("", popup) + self.uri_entry.bind("", popup_uri) self.uri_button = ttk.Button( self.uri_frame, text="Go", command=self._open_stream, @@ -349,4 +349,4 @@ class AddressWindow(object): window, text="OK", command=window.destroy, style="LBRY.TButton" ) done_button.grid(row=1, column=1, pady=(0, 5), padx=5, sticky=tk.W) - window.focus_set() \ No newline at end of file + window.focus_set() From 80b912ea14f921b0998ed763c99333d1a71f51d2 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 1 Jun 2016 22:40:01 -0500 Subject: [PATCH 239/462] pylint: disable incorrect not-callable message --- lbrynet/lbrynet_gui/StreamFrame.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_gui/StreamFrame.py b/lbrynet/lbrynet_gui/StreamFrame.py index defcbacc9..de07c4d55 100644 --- a/lbrynet/lbrynet_gui/StreamFrame.py +++ b/lbrynet/lbrynet_gui/StreamFrame.py @@ -118,7 +118,7 @@ class StreamFrame(object): def cancel(self): if self.cancel_func is not None: - self.cancel_func() + self.cancel_func() # pylint: disable=not-callable self.stream_frame.destroy() self.app.stream_removed() @@ -460,4 +460,4 @@ class StreamFrame(object): if self.cost_label is not None and self.cost_label.winfo_exists(): self.cost_label.config(text=locale.format_string("%.2f LBC", (round(total_points_paid, 2),), - grouping=True)) \ No newline at end of file + grouping=True)) From e224c3381721195866d17057ec72cefe9b7e0e10 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 3 Jun 2016 03:45:46 -0400 Subject: [PATCH 240/462] add mime_type to get_lbry_file --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index dc453f27d..757e211d4 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,4 +1,5 @@ import locale +import mimetypes import os import subprocess import sys @@ -1234,6 +1235,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda message: {'completed': f.completed, 'file_name': f.file_name, 'download_directory': f.download_directory, 'download_path': os.path.join(f.download_directory, f.file_name), + 'mime_type': mimetypes.guess_type(os.path.join(f.download_directory, f.file_name))[0], 'key': key, 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, @@ -1248,6 +1250,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = defer.succeed({'completed': f.completed, 'file_name': f.file_name, 'key': key, 'download_directory': f.download_directory, 'download_path': os.path.join(f.download_directory, f.file_name), + 'mime_type': mimetypes.guess_type(os.path.join(f.download_directory, f.file_name))[0], 'points_paid': f.points_paid, 'stopped': f.stopped, 'stream_hash': f.stream_hash, 'stream_name': f.stream_name, 'suggested_file_name': f.suggested_file_name, 'upload_allowed': f.upload_allowed, 'sd_hash': f.sd_hash, 'total_bytes': size, From a9f99d05b9634c6de15c1744bb910b3c51847b55 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 3 Jun 2016 20:20:16 -0400 Subject: [PATCH 241/462] add more known dht nodes also fix migrator bug --- lbrynet/conf.py | 5 ++++- lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 06b455392..3c77bfb9f 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -19,7 +19,10 @@ MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .05 # points/1000 infos MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .05 # points/1000 infos MAX_CONNECTIONS_PER_STREAM = 5 -KNOWN_DHT_NODES = [('104.236.42.182', 4000)] +KNOWN_DHT_NODES = [('104.236.42.182', 4000), + ('lbryum1.lbry.io', 4444), + ('lbryum2.lbry.io', 4444), + ('lbryum3.lbry.io', 4444)] POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' #POINTTRADER_SERVER = 'http://127.0.0.1:2424' diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py b/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py index 9283b48aa..79fb156d0 100644 --- a/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py +++ b/lbrynet/lbrynet_daemon/daemon_scripts/migrateto025.py @@ -20,7 +20,6 @@ class migrator(object): return lbry_file.restore() r = defer.DeferredList([_restart_lbry_file(lbry_file) for lbry_file in self._api.lbry_file_manager.lbry_files if not lbry_file.txid], consumeErrors=True) - r.callback(None) return r d = self._api.session.wallet.get_nametrie() From 8c46dd194d626a357f63e74b442316ee72326a66 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 4 Jun 2016 14:18:58 -0400 Subject: [PATCH 242/462] save command line wallet choice to config --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 36 +++++++++++++--------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 757e211d4..71c578b82 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -210,37 +210,34 @@ class LBRYDaemon(jsonrpc.JSONRPC): for k in missing_settings.keys(): log.info("Adding missing setting: " + k + " with default value: " + str(missing_settings[k])) loaded_settings[k] = missing_settings[k] + if loaded_settings['wallet_type'] != self.wallet_type and self.wallet_type: + loaded_settings['wallet_type'] = self.wallet_type + if missing_settings or removed_settings: - f = open(self.daemon_conf, "w") - f.write(json.dumps(loaded_settings)) - f.close() + log.info("Updated and loaded lbrynet-daemon configuration") else: log.info("Loaded lbrynet-daemon configuration") - settings_dict = loaded_settings + self.session_settings = loaded_settings else: missing_settings = self.default_settings log.info("Writing default settings : " + json.dumps(self.default_settings) + " --> " + str(self.daemon_conf)) - f = open(self.daemon_conf, "w") - f.write(json.dumps(self.default_settings)) - f.close() - settings_dict = self.default_settings - - self.session_settings = settings_dict + self.session_settings = self.default_settings if 'last_version' in missing_settings.keys(): self.session_settings['last_version'] = None if self.session_settings['last_version'] != self.default_settings['last_version']: self.session_settings['last_version'] = self.default_settings['last_version'] - f = open(self.daemon_conf, "w") - f.write(json.dumps(self.session_settings)) - f.close() self.first_run_after_update = True log.info("First run after update") if lbrynet_version == '0.2.5': self.session_settings['startup_scripts'].append({'script_name': 'migrateto025', 'run_once': True}) + f = open(self.daemon_conf, "w") + f.write(json.dumps(self.session_settings)) + f.close() + self.run_on_startup = self.session_settings['run_on_startup'] self.data_rate = self.session_settings['data_rate'] self.max_key_fee = self.session_settings['max_key_fee'] @@ -1303,16 +1300,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _run_scripts(self): if len([k for k in self.startup_scripts if 'run_once' in k.keys()]): log.info("Removing one time startup scripts") - f = open(self.daemon_conf, "r") - initialsettings = json.loads(f.read()) - f.close() - t = [s for s in self.startup_scripts if 'run_once' not in s.keys()] - initialsettings['startup_scripts'] = t + remaining_scripts = [s for s in self.startup_scripts if 'run_once' not in s.keys()] + startup_scripts = self.startup_scripts + self.startup_scripts = self.session_settings['startup_scripts'] = remaining_scripts + f = open(self.daemon_conf, "w") - f.write(json.dumps(initialsettings)) + f.write(json.dumps(self.session_settings)) f.close() - for script in self.startup_scripts: + for script in startup_scripts: if script['script_name'] == 'migrateto025': log.info("Running migrator to 0.2.5") from lbrynet.lbrynet_daemon.daemon_scripts.migrateto025 import run as run_migrate From 3f929aee3cec61f2cc545bf14ee06a8909026216 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 4 Jun 2016 14:28:32 -0400 Subject: [PATCH 243/462] merge travis fixes --- lbrynet/core/Session.py | 13 ++++++++----- lbrynet/core/client/DownloadManager.py | 4 ++-- .../BlindRepeater/ValuableBlobQueryHandler.py | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lbrynet/core/Session.py b/lbrynet/core/Session.py index d919e33b3..c8ff3f290 100644 --- a/lbrynet/core/Session.py +++ b/lbrynet/core/Session.py @@ -28,7 +28,7 @@ class LBRYSession(object): def __init__(self, blob_data_payment_rate, db_dir=None, lbryid=None, peer_manager=None, dht_node_port=None, known_dht_nodes=None, peer_finder=None, hash_announcer=None, blob_dir=None, blob_manager=None, peer_port=None, use_upnp=True, - rate_limiter=None, wallet=None): + rate_limiter=None, wallet=None, dht_node_class=node.Node): """ @param blob_data_payment_rate: The default payment rate for blob data @@ -99,7 +99,7 @@ class LBRYSession(object): self.upnp_redirects = [] self.wallet = wallet - + self.dht_node_class = dht_node_class self.dht_node = None self.base_payment_rate_manager = BasePaymentRateManager(blob_data_payment_rate) @@ -220,8 +220,11 @@ class LBRYSession(object): d.addCallback(match_port, port) ds.append(d) - self.dht_node = node.Node(udpPort=self.dht_node_port, lbryid=self.lbryid, - externalIP=self.external_ip) + self.dht_node = self.dht_node_class( + udpPort=self.dht_node_port, + lbryid=self.lbryid, + externalIP=self.external_ip + ) self.peer_finder = DHTPeerFinder(self.dht_node, self.peer_manager) if self.hash_announcer is None: self.hash_announcer = DHTHashAnnouncer(self.dht_node, self.peer_port) @@ -269,4 +272,4 @@ class LBRYSession(object): d = threads.deferToThread(threaded_unset_upnp) d.addErrback(lambda err: str(err)) - return d \ No newline at end of file + return d diff --git a/lbrynet/core/client/DownloadManager.py b/lbrynet/core/client/DownloadManager.py index fced77969..d601833dd 100644 --- a/lbrynet/core/client/DownloadManager.py +++ b/lbrynet/core/client/DownloadManager.py @@ -52,7 +52,7 @@ class DownloadManager(object): def check_stop(result, manager): if isinstance(result, failure.Failure): - log.error("Failed to stop the %s: %s", manager. result.getErrorMessage()) + log.error("Failed to stop the %s: %s", manager, result.getErrorMessage()) return False return True @@ -115,4 +115,4 @@ class DownloadManager(object): if not self.blobs: return self.calculate_total_bytes() else: - return sum([b.length for b in self.needed_blobs() if b.length is not None]) \ No newline at end of file + return sum([b.length for b in self.needed_blobs() if b.length is not None]) diff --git a/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py b/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py index 08d5a57a0..d8dd0009e 100644 --- a/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py +++ b/lbrynet/lbrynet_console/plugins/BlindRepeater/ValuableBlobQueryHandler.py @@ -101,7 +101,7 @@ class ValuableBlobHashQueryHandler(ValuableQueryHandler): for blob_hash, count in valuable_hashes: hashes_and_scores.append((blob_hash, 1.0 * count / 10.0)) if len(hashes_and_scores) != 0: - log.info("Responding to a valuable blob hashes request with %s blob hashes: %s", + log.info("Responding to a valuable blob hashes request with %s blob hashes", str(len(hashes_and_scores))) expected_payment = 1.0 * len(hashes_and_scores) * self.valuable_blob_hash_payment_rate / 1000.0 self.wallet.add_expected_payment(self.peer, expected_payment) @@ -193,10 +193,10 @@ class ValuableBlobLengthQueryHandler(ValuableQueryHandler): if success is True: lengths.append(response_pair) if len(lengths) > 0: - log.info("Responding with %s blob lengths: %s", str(len(lengths))) + log.info("Responding with %s blob lengths", str(len(lengths))) expected_payment = 1.0 * len(lengths) * self.blob_length_payment_rate / 1000.0 self.wallet.add_expected_payment(self.peer, expected_payment) self.peer.update_stats('uploaded_valuable_blob_infos', len(lengths)) return {'blob_length': {'blob_lengths': lengths}} - dl.addCallback(make_response) \ No newline at end of file + dl.addCallback(make_response) From febc66fb02162a7d5a2e4aaec65f6caeb24737e4 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 4 Jun 2016 15:03:16 -0400 Subject: [PATCH 244/462] missing keyword args for travis --- lbrynet/lbrylive/LBRYStdinUploader.py | 7 ++++--- lbrynet/lbrylive/LBRYStdoutDownloader.py | 8 +++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lbrynet/lbrylive/LBRYStdinUploader.py b/lbrynet/lbrylive/LBRYStdinUploader.py index 03570a975..d9873b362 100644 --- a/lbrynet/lbrylive/LBRYStdinUploader.py +++ b/lbrynet/lbrylive/LBRYStdinUploader.py @@ -15,7 +15,8 @@ from twisted.internet import defer, task class LBRYStdinUploader(): """This class reads from standard in, creates a stream, and makes it available on the network.""" - def __init__(self, peer_port, dht_node_port, known_dht_nodes): + def __init__(self, peer_port, dht_node_port, known_dht_nodes, + stream_info_manager_class=DBLiveStreamMetadataManager, blob_manager_class=TempBlobManager): """ @param peer_port: the network port on which to listen for peers @@ -25,8 +26,8 @@ class LBRYStdinUploader(): """ self.peer_port = peer_port self.lbry_server_port = None - self.session = LBRYSession(blob_manager_class=TempBlobManager, - stream_info_manager_class=DBLiveStreamMetadataManager, + self.session = LBRYSession(blob_manager_class=blob_manager_class, + stream_info_manager_class=stream_info_manager_class, dht_node_class=Node, dht_node_port=dht_node_port, known_dht_nodes=known_dht_nodes, peer_port=self.peer_port, use_upnp=False) diff --git a/lbrynet/lbrylive/LBRYStdoutDownloader.py b/lbrynet/lbrylive/LBRYStdoutDownloader.py index 8c413e129..97a91fbfe 100644 --- a/lbrynet/lbrylive/LBRYStdoutDownloader.py +++ b/lbrynet/lbrylive/LBRYStdoutDownloader.py @@ -15,15 +15,17 @@ from twisted.internet import task class LBRYStdoutDownloader(): """This class downloads a live stream from the network and outputs it to standard out.""" - def __init__(self, dht_node_port, known_dht_nodes): + def __init__(self, dht_node_port, known_dht_nodes, + stream_info_manager_class=DBLiveStreamMetadataManager, blob_manager_class=TempBlobManager): """ @param dht_node_port: the network port on which to listen for DHT node requests @param known_dht_nodes: a list of (ip_address, dht_port) which will be used to join the DHT network """ - self.session = LBRYSession(blob_manager_class=TempBlobManager, - stream_info_manager_class=DBLiveStreamMetadataManager, + + self.session = LBRYSession(blob_manager_class=blob_manager_class, + stream_info_manager_class=stream_info_manager_class, dht_node_class=Node, dht_node_port=dht_node_port, known_dht_nodes=known_dht_nodes, use_upnp=False) self.payment_rate_manager = BaseLiveStreamPaymentRateManager() From 85b56247a33a191448bf4dd22c0de287aca46787 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 4 Jun 2016 15:11:51 -0400 Subject: [PATCH 245/462] merge add-tests-to-travis updates --- .travis.yml | 15 +++++++++++++-- lbrynet/lbrylive/LBRYStdinUploader.py | 5 ++++- lbrynet/lbrylive/LBRYStdoutDownloader.py | 5 ++++- lbrynet/lbrynet_console/ControlHandlers.py | 2 +- lbrynet/lbrynet_gui/GuiApp.py | 10 +++++----- lbrynet/lbrynet_gui/StreamFrame.py | 4 ++-- packaging/ubuntu/ubuntu_package_setup.sh | 4 ++++ 7 files changed, 33 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index c7a722242..865ea7427 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,17 @@ script: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade gmp; fi # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi - # py2app fails to find jsonrpc unless json-rpc is installed. why? I don't know. - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install jsonrpc; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi \ No newline at end of file + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi + # fail the build if this is a build for a tag and we don't have the versions matching + - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi + +deploy: + provider: releases + file: "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" + skip_cleanup: true + on: + tags: true + # this is the oauth token for the lbry-ci user + api_key: + secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc= diff --git a/lbrynet/lbrylive/LBRYStdinUploader.py b/lbrynet/lbrylive/LBRYStdinUploader.py index d9873b362..086cb52a0 100644 --- a/lbrynet/lbrylive/LBRYStdinUploader.py +++ b/lbrynet/lbrylive/LBRYStdinUploader.py @@ -1,3 +1,6 @@ +# pylint: skip-file +# This file is not maintained, but might be used in the future +# import logging import sys from lbrynet.lbrylive.LiveStreamCreator import StdOutLiveStreamCreator @@ -115,4 +118,4 @@ def launch_stdin_uploader(): d.addCallback(lambda _: start_stdin_uploader()) d.addCallback(lambda _: shut_down()) reactor.addSystemEventTrigger('before', 'shutdown', uploader.shut_down) - reactor.run() \ No newline at end of file + reactor.run() diff --git a/lbrynet/lbrylive/LBRYStdoutDownloader.py b/lbrynet/lbrylive/LBRYStdoutDownloader.py index 97a91fbfe..317cfefa2 100644 --- a/lbrynet/lbrylive/LBRYStdoutDownloader.py +++ b/lbrynet/lbrylive/LBRYStdoutDownloader.py @@ -1,3 +1,6 @@ +# pylint: skip-file +# This file is not maintained, but might be used in the future +# import logging import sys @@ -95,4 +98,4 @@ def launch_stdout_downloader(): d.addErrback(print_error) d.addCallback(lambda _: shut_down()) reactor.addSystemEventTrigger('before', 'shutdown', downloader.shut_down) - reactor.run() \ No newline at end of file + reactor.run() diff --git a/lbrynet/lbrynet_console/ControlHandlers.py b/lbrynet/lbrynet_console/ControlHandlers.py index cbe5dd270..d19d55c88 100644 --- a/lbrynet/lbrynet_console/ControlHandlers.py +++ b/lbrynet/lbrynet_console/ControlHandlers.py @@ -120,7 +120,7 @@ class CommandHandlerFactory(object): return self.control_handler_class.prompt_description def get_handler(self, console): - return self.control_handler_class(console, *self.args) + return self.control_handler_class(console, *self.args) # pylint: disable=not-callable class CommandHandler(object): diff --git a/lbrynet/lbrynet_gui/GuiApp.py b/lbrynet/lbrynet_gui/GuiApp.py index 94c977759..176cce9d7 100644 --- a/lbrynet/lbrynet_gui/GuiApp.py +++ b/lbrynet/lbrynet_gui/GuiApp.py @@ -176,10 +176,10 @@ class DownloaderApp(object): style="Stop.TButton", cursor=button_cursor) self.wallet_menu_button.grid(row=0, column=1, padx=(5, 0)) - def popup(event): + def popup_wallet(event): self.wallet_menu.tk_popup(event.x_root, event.y_root) - self.wallet_menu_button.bind("", popup) + self.wallet_menu_button.bind("", popup_wallet) self.uri_frame = ttk.Frame(self.frame, style="B.TFrame") self.uri_frame.grid() @@ -204,7 +204,7 @@ class DownloaderApp(object): def paste_command(): self.uri_entry.event_generate('') - def popup(event): + def popup_uri(event): selection_menu = tk.Menu( self.master, tearoff=0 ) @@ -214,7 +214,7 @@ class DownloaderApp(object): selection_menu.add_command(label=" Paste ", command=paste_command) selection_menu.tk_popup(event.x_root, event.y_root) - self.uri_entry.bind("", popup) + self.uri_entry.bind("", popup_uri) self.uri_button = ttk.Button( self.uri_frame, text="Go", command=self._open_stream, @@ -349,4 +349,4 @@ class AddressWindow(object): window, text="OK", command=window.destroy, style="LBRY.TButton" ) done_button.grid(row=1, column=1, pady=(0, 5), padx=5, sticky=tk.W) - window.focus_set() \ No newline at end of file + window.focus_set() diff --git a/lbrynet/lbrynet_gui/StreamFrame.py b/lbrynet/lbrynet_gui/StreamFrame.py index defcbacc9..de07c4d55 100644 --- a/lbrynet/lbrynet_gui/StreamFrame.py +++ b/lbrynet/lbrynet_gui/StreamFrame.py @@ -118,7 +118,7 @@ class StreamFrame(object): def cancel(self): if self.cancel_func is not None: - self.cancel_func() + self.cancel_func() # pylint: disable=not-callable self.stream_frame.destroy() self.app.stream_removed() @@ -460,4 +460,4 @@ class StreamFrame(object): if self.cost_label is not None and self.cost_label.winfo_exists(): self.cost_label.config(text=locale.format_string("%.2f LBC", (round(total_points_paid, 2),), - grouping=True)) \ No newline at end of file + grouping=True)) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index f35a78b72..70cd064e4 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -164,3 +164,7 @@ $SUDO chown root:root debian-binary control.tar.gz data.tar.xz ar r "$PACKAGE" debian-binary control.tar.gz data.tar.xz # TODO: we can append to data.tar instead of extracting it all and recompressing + +if [[ -n "${TRAVIS_BUILD_DIR}" ]]; then + mv "${PACKAGE}" "${TRAVIS_BUILD_DIR}/${PACKAGE}" +fi From 20ca2eac37742d8c70ee650c756a8fbd66aea9e8 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 4 Jun 2016 17:05:14 -0400 Subject: [PATCH 246/462] lbryum logging in lbryum.log, add to upload_log() --- lbrynet/__init__.py | 23 ++++++- lbrynet/conf.py | 4 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 75 ++++++++++----------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 19 +----- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 14 ---- lbrynet/lbrynet_daemon/LBRYDownloader.py | 12 ---- lbrynet/lbrynet_daemon/LBRYPublisher.py | 12 ---- lbrynet/lbrynet_daemon/LBRYUIManager.py | 13 ---- lbrynet/lbrynet_daemon/__init__.py | 4 ++ 9 files changed, 66 insertions(+), 110 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 3d80f11a2..2ad73a6a5 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -1,8 +1,29 @@ import logging +import sys +import os +from lbrynet.conf import LOG_FILE_NAME +from appdirs import user_data_dir +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") -logging.getLogger(__name__).addHandler(logging.NullHandler()) +if not os.path.isdir(log_dir): + os.mkdir(log_dir) +LOG_PATH = os.path.join(log_dir, LOG_FILE_NAME) + +if os.path.isfile(LOG_PATH): + f = open(LOG_PATH, 'r') + PREVIOUS_LOG = len(f.read()) + f.close() +else: + PREVIOUS_LOG = 0 + +log = logging.getLogger(__name__) +log.addHandler(logging.FileHandler(filename=LOG_PATH)) +log.setLevel(logging.ERROR) version = (0, 2, 5) __version__ = ".".join([str(x) for x in version]) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 3c77bfb9f..116a395f2 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -3,9 +3,6 @@ Some network wide and also application specific parameters """ -import os - - MAX_HANDSHAKE_SIZE = 2**16 MAX_REQUEST_SIZE = 2**16 MAX_BLOB_REQUEST_SIZE = 2**16 @@ -27,6 +24,7 @@ KNOWN_DHT_NODES = [('104.236.42.182', 4000), POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' #POINTTRADER_SERVER = 'http://127.0.0.1:2424' +LOG_FILE_NAME = "lbrynet.log" LOG_POST_URL = "https://lbry.io/log-upload" CRYPTSD_FILE_EXTENSION = ".cryptsd" diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 71c578b82..79d2bc016 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -40,6 +40,7 @@ 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL +from lbrynet import LOG_PATH from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -47,29 +48,23 @@ from lbrynet.core.PTCWallet import PTCWallet from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager - - -if sys.platform != "darwin": - log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") -else: - log_dir = user_data_dir("LBRY") - -if not os.path.isdir(log_dir): - os.mkdir(log_dir) - -LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') - -if os.path.isfile(LOG_FILENAME): - f = open(LOG_FILENAME, 'r') - PREVIOUS_LOG = len(f.read()) - f.close() -else: - PREVIOUS_LOG = 0 +from lbryum.util import LOG_PATH as lbryum_log +from lbrynet import LOG_PATH as lbrynet_log log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) -log.addHandler(handler) -log.setLevel(logging.INFO) + +if os.path.isfile(lbryum_log): + f = open(lbryum_log, 'r') + PREVIOUS_LBRYUM_LOG = len(f.read()) + f.close() +else: + PREVIOUS_LBRYUM_LOG = 0 +if os.path.isfile(lbrynet_log): + f = open(lbrynet_log, 'r') + PREVIOUS_LBRYNET_LOG = len(f.read()) + f.close() +else: + PREVIOUS_LBRYNET_LOG = 0 INITIALIZING_CODE = 'initializing' LOADING_DB_CODE = 'loading_db' @@ -148,7 +143,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.ip = None self.wallet_type = wallet_type self.first_run = None - self.log_file = LOG_FILENAME + self.log_file = LOG_PATH self.current_db_revision = 1 self.run_server = True self.session = None @@ -625,24 +620,26 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _upload_log(self, log_type=None, exclude_previous=False, force=False): if self.upload_log or force: - if exclude_previous: - f = open(self.log_file, "r") - f.seek(PREVIOUS_LOG) - log_contents = f.read() - f.close() - else: - f = open(self.log_file, "r") - log_contents = f.read() - f.close() - params = { - 'date': datetime.utcnow().strftime('%Y%m%d-%H%M%S'), - 'hash': base58.b58encode(self.lbryid)[:20], - 'sys': platform.system(), - 'type': log_type if log_type else 'default', - 'log': log_contents - } + for lm, lp in [('lbrynet', lbrynet_log), ('lbryum', lbryum_log)]: + if os.path.isfile(lp): + if exclude_previous: + f = open(lp, "r") + f.seek(PREVIOUS_LBRYNET_LOG if lm == 'lbrynet' else PREVIOUS_LBRYUM_LOG) + log_contents = f.read() + f.close() + else: + f = open(lp, "r") + log_contents = f.read() + f.close() + params = { + 'date': datetime.utcnow().strftime('%Y%m%d-%H%M%S'), + 'hash': base58.b58encode(self.lbryid)[:20], + 'sys': platform.system(), + 'type': "%s-%s" % (lm, log_type) if log_type else lm, + 'log': log_contents + } + requests.post(LOG_POST_URL, params) - requests.post(LOG_POST_URL, params) return defer.succeed(None) else: return defer.succeed(None) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index e441ca923..05134488a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -7,7 +7,6 @@ import sys import socket import platform -from appdirs import user_data_dir from twisted.web import server from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy @@ -15,22 +14,10 @@ from jsonrpc.proxy import JSONRPCProxy from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, \ DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH +from lbrynet import LOG_PATH -if sys.platform != "darwin": - log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") -else: - log_dir = user_data_dir("LBRY") - -if not os.path.isdir(log_dir): - os.mkdir(log_dir) - -LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') -log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) -log.addHandler(handler) -log.setLevel(logging.INFO) - +log = logging.getLogger(LOG_PATH) REMOTE_SERVER = "www.google.com" @@ -96,7 +83,7 @@ def start(): if not args.logtoconsole and not args.quiet: print "Starting lbrynet-daemon from command line" - print "To view activity, view the log file here: " + LOG_FILENAME + print "To view activity, view the log file here: " + LOG_PATH print "Web UI is available at http://%s:%i" % (API_INTERFACE, API_PORT) print "JSONRPC API is available at " + API_CONNECTION_STRING print "To quit press ctrl-c or call 'stop' via the API" diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index aa5b70b11..fd86887c9 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -5,15 +5,10 @@ import json import sys import mimetypes -from StringIO import StringIO -from zipfile import ZipFile -from urllib import urlopen from datetime import datetime from appdirs import user_data_dir from twisted.web import server, static, resource from twisted.internet import defer, interfaces, error, reactor, task, threads -from twisted.python.failure import Failure -from txjsonrpc.web import jsonrpc from zope.interface import implements @@ -25,19 +20,10 @@ if sys.platform != "darwin": data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: data_dir = user_data_dir("LBRY") - if not os.path.isdir(data_dir): os.mkdir(data_dir) -version_dir = os.path.join(data_dir, "ui_version_history") -if not os.path.isdir(version_dir): - os.mkdir(version_dir) -version_log = logging.getLogger("lbry_version") -version_log.addHandler(logging.FileHandler(os.path.join(version_dir, "lbry_version.log"))) -version_log.setLevel(logging.INFO) log = logging.getLogger(__name__) -log.addHandler(logging.FileHandler(os.path.join(data_dir, 'lbrynet-daemon.log'))) -log.setLevel(logging.INFO) class LBRYindex(resource.Resource): diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index d62bdc3b1..47ec47cd7 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -27,19 +27,7 @@ STREAM_STAGES = [ (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') ] -if sys.platform != "darwin": - log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") -else: - log_dir = user_data_dir("LBRY") - -if not os.path.isdir(log_dir): - os.mkdir(log_dir) - -LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) -log.addHandler(handler) -log.setLevel(logging.INFO) class GetStream(object): diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index e68a06498..181dbe1bf 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -12,19 +12,7 @@ from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader from twisted.internet import threads, defer -if sys.platform != "darwin": - log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") -else: - log_dir = user_data_dir("LBRY") - -if not os.path.isdir(log_dir): - os.mkdir(log_dir) - -LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) -log.addHandler(handler) -log.setLevel(logging.INFO) class Publisher(object): diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index b4aa33cd5..9a21b3594 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -14,20 +14,7 @@ from lbryum.version import ELECTRUM_VERSION as lbryum_version from zipfile import ZipFile from appdirs import user_data_dir -if sys.platform != "darwin": - data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") -else: - data_dir = user_data_dir("LBRY") - -if not os.path.isdir(data_dir): - os.mkdir(data_dir) -version_dir = os.path.join(data_dir, "ui_version_history") -if not os.path.isdir(version_dir): - os.mkdir(version_dir) - log = logging.getLogger(__name__) -log.addHandler(logging.FileHandler(os.path.join(data_dir, 'lbrynet-daemon.log'))) -log.setLevel(logging.INFO) class LBRYUIManager(object): diff --git a/lbrynet/lbrynet_daemon/__init__.py b/lbrynet/lbrynet_daemon/__init__.py index e69de29bb..1a5ad27bc 100644 --- a/lbrynet/lbrynet_daemon/__init__.py +++ b/lbrynet/lbrynet_daemon/__init__.py @@ -0,0 +1,4 @@ +import logging + +log = logging.getLogger(__name__) +log.setLevel(logging.INFO) \ No newline at end of file From 8faa0d9e013c6fe6076367a06973d1f6cdf2bd46 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 6 Jun 2016 15:24:29 -0400 Subject: [PATCH 247/462] update lbryum log path --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 79d2bc016..e90343795 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -48,7 +48,7 @@ from lbrynet.core.PTCWallet import PTCWallet from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager -from lbryum.util import LOG_PATH as lbryum_log +from lbryum import LOG_PATH as lbryum_log from lbrynet import LOG_PATH as lbrynet_log log = logging.getLogger(__name__) From ecd85a53a1eff3f668b3205164c091aef079a9ce Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 7 Jun 2016 01:20:44 -0400 Subject: [PATCH 248/462] ui dir for linux --- lbrynet/lbrynet_daemon/LBRYUIManager.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index 9a21b3594..425d31df4 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -19,7 +19,11 @@ log = logging.getLogger(__name__) class LBRYUIManager(object): def __init__(self, root): - self.data_dir = user_data_dir("LBRY") + if sys.platform != "darwin": + self.data_dir = os.path.join(os.path.expanduser("~"), '.lbrynet') + else: + self.data_dir = user_data_dir("LBRY") + self.ui_root = os.path.join(self.data_dir, "lbry-ui") self.active_dir = os.path.join(self.ui_root, "active") self.update_dir = os.path.join(self.ui_root, "update") @@ -28,10 +32,10 @@ class LBRYUIManager(object): os.mkdir(self.data_dir) if not os.path.isdir(self.ui_root): os.mkdir(self.ui_root) - if not os.path.isdir(self.ui_root): - os.mkdir(self.ui_root) - if not os.path.isdir(self.ui_root): - os.mkdir(self.ui_root) + if not os.path.isdir(self.active_dir): + os.mkdir(self.active_dir) + if not os.path.isdir(self.update_dir): + os.mkdir(self.update_dir) self.config = os.path.join(self.ui_root, "active.json") self.update_requires = os.path.join(self.update_dir, "requirements.txt") From 755338ded0d6147766b501d950e4bd09f40f61d4 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 7 Jun 2016 03:08:32 -0400 Subject: [PATCH 249/462] import logging.handlers --- lbrynet/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 2ad73a6a5..4882c1e23 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -1,4 +1,5 @@ import logging +import logging.handlers import sys import os from lbrynet.conf import LOG_FILE_NAME From a0f823910cc1d8f634e101fd7ed3a12562a241a6 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 7 Jun 2016 03:16:22 -0400 Subject: [PATCH 250/462] delete accidentally committed file --- .../daemon_scripts/network_tester.py | 160 ------------------ 1 file changed, 160 deletions(-) delete mode 100644 lbrynet/lbrynet_daemon/daemon_scripts/network_tester.py diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/network_tester.py b/lbrynet/lbrynet_daemon/daemon_scripts/network_tester.py deleted file mode 100644 index d62bdc3b1..000000000 --- a/lbrynet/lbrynet_daemon/daemon_scripts/network_tester.py +++ /dev/null @@ -1,160 +0,0 @@ -import json -import logging -import os -import sys - -from appdirs import user_data_dir -from datetime import datetime -from twisted.internet import defer -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.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory -from lbrynet.conf import DEFAULT_TIMEOUT - -INITIALIZING_CODE = 'initializing' -DOWNLOAD_METADATA_CODE = 'downloading_metadata' -DOWNLOAD_TIMEOUT_CODE = 'timeout' -DOWNLOAD_RUNNING_CODE = 'running' -DOWNLOAD_STOPPED_CODE = 'stopped' -STREAM_STAGES = [ - (INITIALIZING_CODE, 'Initializing...'), - (DOWNLOAD_METADATA_CODE, 'Downloading metadata'), - (DOWNLOAD_RUNNING_CODE, 'Started stream'), - (DOWNLOAD_STOPPED_CODE, 'Paused stream'), - (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') - ] - -if sys.platform != "darwin": - log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") -else: - log_dir = user_data_dir("LBRY") - -if not os.path.isdir(log_dir): - os.mkdir(log_dir) - -LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') -log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) -log.addHandler(handler) -log.setLevel(logging.INFO) - - -class GetStream(object): - def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5, - timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None): - self.wallet = wallet - self.resolved_name = None - self.description = None - self.key_fee = None - self.key_fee_address = None - self.data_rate = data_rate - self.pay_key = pay_key - self.name = None - self.file_name = file_name - self.session = session - self.payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager) - self.lbry_file_manager = lbry_file_manager - self.sd_identifier = sd_identifier - self.stream_hash = None - self.max_key_fee = max_key_fee - self.stream_info = None - self.stream_info_manager = None - self.d = defer.Deferred(None) - self.timeout = timeout - self.timeout_counter = 0 - self.download_directory = download_directory - self.download_path = None - self.downloader = None - self.finished = defer.Deferred() - self.checker = LoopingCall(self.check_status) - self.code = STREAM_STAGES[0] - - def check_status(self): - self.timeout_counter += 1 - - if self.download_path: - self.checker.stop() - self.finished.callback((self.stream_hash, self.download_path)) - - elif self.timeout_counter >= self.timeout: - log.info("Timeout downloading lbry://" + self.resolved_name + ", " + str(self.stream_info)) - self.checker.stop() - self.d.cancel() - self.code = STREAM_STAGES[4] - self.finished.callback(False) - - def start(self, stream_info, name): - self.resolved_name = name - self.stream_info = stream_info - if 'stream_hash' in self.stream_info.keys(): - self.stream_hash = self.stream_info['stream_hash'] - elif 'sources' in self.stream_info.keys(): - self.stream_hash = self.stream_info['sources']['lbry_sd_hash'] - else: - raise InvalidStreamInfoError(self.stream_info) - if 'description' in self.stream_info.keys(): - self.description = self.stream_info['description'] - if 'key_fee' in self.stream_info.keys(): - self.key_fee = float(self.stream_info['key_fee']) - if 'key_fee_address' in self.stream_info.keys(): - self.key_fee_address = self.stream_info['key_fee_address'] - else: - self.key_fee_address = None - else: - self.key_fee = None - self.key_fee_address = None - if self.key_fee > self.max_key_fee: - if self.pay_key: - log.info("Key fee (" + str(self.key_fee) + ") above limit of " + str( - self.max_key_fee) + ", didn't download lbry://" + str(self.resolved_name)) - return defer.fail(None) - else: - pass - - def _cause_timeout(): - self.timeout_counter = self.timeout * 2 - - def _set_status(x, status): - self.code = next(s for s in STREAM_STAGES if s[0] == status) - return x - - self.checker.start(1) - - self.d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE)) - self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) - self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) - self.d.addCallback(lambda r: _set_status(r, DOWNLOAD_RUNNING_CODE)) - self.d.addCallback(lambda metadata: ( - next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), - metadata)) - self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, - [self.data_rate, True], - self.payment_rate_manager, - download_directory=self.download_directory, - file_name=self.file_name)) - self.d.addCallbacks(self._start_download, lambda _: _cause_timeout()) - self.d.callback(None) - - return self.finished - - 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 reserved_points is None: - return defer.fail(InsufficientFundsError()) - log.info("Key fee: " + str(self.key_fee) + " | " + str(self.key_fee_address)) - return self.wallet.send_points_to_address(reserved_points, self.key_fee) - return defer.succeed(None) - - if self.pay_key: - d = _pay_key_fee() - else: - d = defer.Deferred() - self.downloader = downloader - self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d.addCallback(lambda _: log.info("[" + str(datetime.now()) + "] Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) - d.addCallback(lambda _: self.downloader.start()) From 3441dd4d2d1ed771f241cd1a140bc7d2d2dfcd4c Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 7 Jun 2016 04:19:51 -0400 Subject: [PATCH 251/462] don't mess with logging --- lbrynet/__init__.py | 24 +-------------------- lbrynet/lbrynet_daemon/LBRYDaemon.py | 22 +++++++++++++++---- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 17 ++++++++++++--- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 6 +++++- lbrynet/lbrynet_daemon/LBRYDownloader.py | 15 +++++++++++-- lbrynet/lbrynet_daemon/LBRYPublisher.py | 13 +++++++++++ lbrynet/lbrynet_daemon/LBRYUIManager.py | 14 +++++++++++- lbrynet/lbrynet_daemon/__init__.py | 4 ---- 8 files changed, 77 insertions(+), 38 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 4882c1e23..7bb1777c8 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -1,29 +1,7 @@ import logging -import logging.handlers -import sys -import os -from lbrynet.conf import LOG_FILE_NAME -from appdirs import user_data_dir - -if sys.platform != "darwin": - log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") -else: - log_dir = user_data_dir("LBRY") - -if not os.path.isdir(log_dir): - os.mkdir(log_dir) - -LOG_PATH = os.path.join(log_dir, LOG_FILE_NAME) - -if os.path.isfile(LOG_PATH): - f = open(LOG_PATH, 'r') - PREVIOUS_LOG = len(f.read()) - f.close() -else: - PREVIOUS_LOG = 0 log = logging.getLogger(__name__) -log.addHandler(logging.FileHandler(filename=LOG_PATH)) +logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) version = (0, 2, 5) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index e90343795..ac157bd71 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -39,8 +39,7 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ - DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL -from lbrynet import LOG_PATH + DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -49,10 +48,25 @@ from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager from lbryum import LOG_PATH as lbryum_log -from lbrynet import LOG_PATH as lbrynet_log log = logging.getLogger(__name__) + +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) + +log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) + if os.path.isfile(lbryum_log): f = open(lbryum_log, 'r') PREVIOUS_LBRYUM_LOG = len(f.read()) @@ -143,7 +157,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.ip = None self.wallet_type = wallet_type self.first_run = None - self.log_file = LOG_PATH + self.log_file = lbrynet_log self.current_db_revision = 1 self.run_server = True self.session = None diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 05134488a..8b4c95e41 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -6,6 +6,7 @@ import webbrowser import sys import socket import platform +from appdirs import user_data_dir from twisted.web import server from twisted.internet import reactor, defer @@ -13,11 +14,21 @@ from jsonrpc.proxy import JSONRPCProxy from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, \ - DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH -from lbrynet import LOG_PATH + DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") -log = logging.getLogger(LOG_PATH) +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, LOG_FILE_NAME) +log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) REMOTE_SERVER = "www.google.com" diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index fd86887c9..ec5bebbc7 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -13,7 +13,7 @@ from twisted.internet import defer, interfaces, error, reactor, task, threads from zope.interface import implements from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon -from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH +from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME if sys.platform != "darwin": @@ -23,7 +23,11 @@ else: if not os.path.isdir(data_dir): os.mkdir(data_dir) +LOG_FILENAME = os.path.join(data_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) class LBRYindex(resource.Resource): diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 47ec47cd7..6e3126958 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -12,7 +12,7 @@ from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.StreamDescriptor import download_sd_blob from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory -from lbrynet.conf import DEFAULT_TIMEOUT +from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME INITIALIZING_CODE = 'initializing' DOWNLOAD_METADATA_CODE = 'downloading_metadata' @@ -27,8 +27,19 @@ STREAM_STAGES = [ (DOWNLOAD_TIMEOUT_CODE, 'Stream timed out') ] -log = logging.getLogger(__name__) +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, LOG_FILE_NAME) +log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) class GetStream(object): def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5, diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index 181dbe1bf..a2804aecb 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -10,9 +10,22 @@ from lbrynet.lbryfilemanager.LBRYFileCreator import create_lbry_file from lbrynet.lbryfile.StreamDescriptor import publish_sd_blob from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader +from lbrynet.conf import LOG_FILE_NAME from twisted.internet import threads, defer +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) class Publisher(object): diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index 425d31df4..76a75e284 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -8,13 +8,25 @@ from urllib2 import urlopen from StringIO import StringIO from twisted.web import static from twisted.internet import defer -from lbrynet.conf import DEFAULT_UI_BRANCH +from lbrynet.conf import DEFAULT_UI_BRANCH, LOG_FILE_NAME from lbrynet import __version__ as lbrynet_version from lbryum.version import ELECTRUM_VERSION as lbryum_version from zipfile import ZipFile from appdirs import user_data_dir +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +log.setLevel(logging.INFO) class LBRYUIManager(object): diff --git a/lbrynet/lbrynet_daemon/__init__.py b/lbrynet/lbrynet_daemon/__init__.py index 1a5ad27bc..e69de29bb 100644 --- a/lbrynet/lbrynet_daemon/__init__.py +++ b/lbrynet/lbrynet_daemon/__init__.py @@ -1,4 +0,0 @@ -import logging - -log = logging.getLogger(__name__) -log.setLevel(logging.INFO) \ No newline at end of file From 761e4f2718af5cb83dacd84e72204889ab1a46a1 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 7 Jun 2016 04:30:22 -0400 Subject: [PATCH 252/462] change variable name to lbrynet_log --- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 6 +++--- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 4 ++-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 4 ++-- lbrynet/lbrynet_daemon/LBRYPublisher.py | 4 ++-- lbrynet/lbrynet_daemon/LBRYUIManager.py | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 8b4c95e41..309570873 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -24,9 +24,9 @@ else: if not os.path.isdir(log_dir): os.mkdir(log_dir) -LOG_FILENAME = os.path.join(log_dir, LOG_FILE_NAME) +lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) log.addHandler(handler) log.setLevel(logging.INFO) @@ -94,7 +94,7 @@ def start(): if not args.logtoconsole and not args.quiet: print "Starting lbrynet-daemon from command line" - print "To view activity, view the log file here: " + LOG_PATH + print "To view activity, view the log file here: " + lbrynet_log print "Web UI is available at http://%s:%i" % (API_INTERFACE, API_PORT) print "JSONRPC API is available at " + API_CONNECTION_STRING print "To quit press ctrl-c or call 'stop' via the API" diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index ec5bebbc7..74906da0b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -23,9 +23,9 @@ else: if not os.path.isdir(data_dir): os.mkdir(data_dir) -LOG_FILENAME = os.path.join(data_dir, LOG_FILE_NAME) +lbrynet_log = os.path.join(data_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) log.addHandler(handler) log.setLevel(logging.INFO) diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 6e3126958..a134c44e5 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -35,9 +35,9 @@ else: if not os.path.isdir(log_dir): os.mkdir(log_dir) -LOG_FILENAME = os.path.join(log_dir, LOG_FILE_NAME) +lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) log.addHandler(handler) log.setLevel(logging.INFO) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index a2804aecb..f7fd64f57 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -21,9 +21,9 @@ else: if not os.path.isdir(log_dir): os.mkdir(log_dir) -LOG_FILENAME = os.path.join(log_dir, LOG_FILE_NAME) +lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) log.addHandler(handler) log.setLevel(logging.INFO) diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index 76a75e284..e7c405352 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -22,9 +22,9 @@ else: if not os.path.isdir(log_dir): os.mkdir(log_dir) -LOG_FILENAME = os.path.join(log_dir, LOG_FILE_NAME) +lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) log.addHandler(handler) log.setLevel(logging.INFO) From 8629076814b6a5cb3202d67e5cbe891608d90b77 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 7 Jun 2016 04:41:26 -0400 Subject: [PATCH 253/462] use lbryio/lbry-osx-app.git for lbry/packaging/osx/lbry-osx-app --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index a8c2e8de6..60a3e5d06 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "packaging/osx/lbry-osx-app"] path = packaging/osx/lbry-osx-app - url = https://github.com/jobevers/lbry-osx-app.git + url = https://github.com/lbryio/lbry-osx-app.git From 4b817cef6523bed128451bdadc78a7e963938234 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 7 Jun 2016 10:52:21 -0500 Subject: [PATCH 254/462] activate tracing flag on build script --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 865ea7427..64fa247ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ before_script: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/osx/add-key.sh; fi script: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh; fi + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh -t; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade gmp; fi # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi From 12c9e16f37e884862f504c2ac5fffb93c0cfa6c9 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Fri, 10 Jun 2016 01:22:51 -0500 Subject: [PATCH 255/462] fix typo to actually use ubuntu trusty --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 64fa247ea..83f5cf454 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ matrix: include: - os: linux sudo: required - dist: trust + dist: trusty # dh-virtualenv requires that we use the same python interpreter # that comes with the system, so we don't want to use anything that # travis would try to set-up for us in python From b57212d5f78e090c95bc4bcf3de4e713f688acba Mon Sep 17 00:00:00 2001 From: Job Evers Date: Fri, 10 Jun 2016 01:38:05 -0500 Subject: [PATCH 256/462] print package size in travis --- packaging/ubuntu/ubuntu_package_setup.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 70cd064e4..813cd397d 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -107,7 +107,7 @@ $SUDO pip install make-deb # # dpkg-buildpackage outputs its results into '..' so # we need to move/clone lbry into the build directory -if [ "$CLONE" == true]; then +if [ "$CLONE" == true ]; then cp -a $SOURCE_DIR lbry else git clone https://github.com/lbryio/lbry.git @@ -165,6 +165,10 @@ ar r "$PACKAGE" debian-binary control.tar.gz data.tar.xz # TODO: we can append to data.tar instead of extracting it all and recompressing -if [[ -n "${TRAVIS_BUILD_DIR}" ]]; then +if [[ ! -z "${TRAVIS_BUILD_DIR+x}" ]]; then + # move it to a consistent place so that later it can be uploaded + # to the github releases page mv "${PACKAGE}" "${TRAVIS_BUILD_DIR}/${PACKAGE}" + # want to be able to check the size of the result in the log + ls -l "${TRAVIS_BUILD_DIR}/${PACKAGE}" fi From da1c23d7ddacc3650e0edc8d5e8d9c6f704d18a3 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Fri, 10 Jun 2016 18:28:21 -0400 Subject: [PATCH 257/462] update readme --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cd1693811..07689d001 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,25 @@ know all of the necessary chunks. ## For Developers -LBRY comes with an file sharing application, called 'lbrynet-console', which breaks +The bundled LBRY application uses the JSONRPC api to LBRYnet found in `lbrynet.lbrynet_daemon.LBRYDaemon`. With a normal installation of the app, lbry command line is not available. To install lbrynet, see [INSTALL.md](INSTALL.md). The following uses the JSONRPC api to show the help for all the available commands: + +``` +from jsonrpc.proxy import JSONRPCProxy +from lbrynet.conf import API_CONNECTION_STRING + +api = JSONRPCProxy.from_url(API_CONNECTION_STRING) +if not api.is_running(): + print api.daemon_status() +else: + for func in api.help(): + print "%s:\n%s" % (func, api.help({'function': func})) +``` + +If you've installed lbrynet, it comes with a file sharing application, called `lbrynet-daemon`, which breaks files into chunks, encrypts them with a symmetric key, computes their sha384 hash sum, generates a special file called a 'stream descriptor' containing the hash sums and some other file metadata, and makes the chunks available for download by other peers. A peer wishing to download the file -must first obtain the 'stream descriptor' and then may open it with his 'lbrynet-console' client, +must first obtain the 'stream descriptor' and then may open it with his `lbrynet-daemon` client, download all of the chunks by locating peers with the chunks via the DHT, and then combine the chunks into the original file, according to the metadata included in the 'stream descriptor'. From 7133f05fea1a91eb22a5c49d1aa6c9f0edc1646f Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Fri, 10 Jun 2016 18:39:35 -0400 Subject: [PATCH 258/462] update readme --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 07689d001..646b333f7 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,16 @@ know all of the necessary chunks. ## For Developers -The bundled LBRY application uses the JSONRPC api to LBRYnet found in `lbrynet.lbrynet_daemon.LBRYDaemon`. With a normal installation of the app, lbry command line is not available. To install lbrynet, see [INSTALL.md](INSTALL.md). The following uses the JSONRPC api to show the help for all the available commands: +The bundled LBRY application uses the lbrynet JSONRPC api found in `lbrynet.lbrynet_daemon.LBRYDaemon`. This api allows for applications and web services like the lbry browser UI to interact with lbrynet. If you've installed lbrynet, you can run `lbrynet-daemon` without running the app. While the app or `lbrynet-daemon` is running, you can use the following to show the help for all the available commands: ``` from jsonrpc.proxy import JSONRPCProxy -from lbrynet.conf import API_CONNECTION_STRING + +try: + from lbrynet.conf import API_CONNECTION_STRING +except: + print "You don't have lbrynet installed!" + API_CONNECTION_STRING = "http://localhost:5279/lbryapi" api = JSONRPCProxy.from_url(API_CONNECTION_STRING) if not api.is_running(): From 0b046ea8cf6224b4d12550cb576c958398e021b3 Mon Sep 17 00:00:00 2001 From: Alex Grin Date: Thu, 16 Jun 2016 22:41:08 -0400 Subject: [PATCH 259/462] fix flipped if statement --- packaging/ubuntu/ubuntu_package_setup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 813cd397d..3388570a7 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -108,9 +108,9 @@ $SUDO pip install make-deb # dpkg-buildpackage outputs its results into '..' so # we need to move/clone lbry into the build directory if [ "$CLONE" == true ]; then - cp -a $SOURCE_DIR lbry -else git clone https://github.com/lbryio/lbry.git +else + cp -a $SOURCE_DIR lbry fi ( cd lbry From 652c7d72f690866a84010f6fd1d8a026f5aa0c07 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Wed, 25 May 2016 17:27:29 -0500 Subject: [PATCH 260/462] Add encrypted certificate needed for code signing This is a cleaned up version of commit c5dc06962d12599879257d937771908debbe0d3e add OSX build to travis matrix fix the OSX build not failing add encrypted certificate and identity file Update submodule to lbry-osx-app/master --- .travis.yml | 18 +++++++-- packaging/osx/certs/cert.cer.enc | 30 -------------- packaging/osx/certs/cert.p12.enc | 33 ---------------- packaging/osx/certs/dist.cer.enc | 30 ++++++++++++++ packaging/osx/certs/dist.p12.enc | 67 ++++++++++++++++++++++++++++++++ packaging/osx/lbry-osx-app | 2 +- 6 files changed, 113 insertions(+), 67 deletions(-) delete mode 100644 packaging/osx/certs/cert.cer.enc delete mode 100644 packaging/osx/certs/cert.p12.enc create mode 100644 packaging/osx/certs/dist.cer.enc create mode 100644 packaging/osx/certs/dist.p12.enc diff --git a/.travis.yml b/.travis.yml index 83f5cf454..6af0a761e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,11 +7,16 @@ matrix: # that comes with the system, so we don't want to use anything that # travis would try to set-up for us in python language: generic + - os: osx + # Use generic language for osx + # python 2.7 is broken on osx on travis, so follow we have to specify the installation ourselves + # https://github.com/travis-ci/travis-ci/issues/2312#issuecomment-195620855 + language: generic before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; brew install python; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew install python; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo pip install --upgrade pip virtualenv; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then virtualenv $HOME/venv; source $HOME/venv/bin/activate; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then virtualenv $HOME/venv && source $HOME/venv/bin/activate; fi install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./packaging/travis/install_dependencies_and_run_tests.sh; fi @@ -27,7 +32,7 @@ script: # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install jsonrpc; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app; ./setup_app.sh; cd $TRAVIS_BUILD_DIR; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app && ./setup_app.sh && cd $TRAVIS_BUILD_DIR; fi # fail the build if this is a build for a tag and we don't have the versions matching - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi @@ -40,3 +45,10 @@ deploy: # this is the oauth token for the lbry-ci user api_key: secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc= + +env: + global: + # needed to unlock the identity file when its on the keychain + - secure: "aqgVNHfeh6JUqTTWG5+W+tTsIJkePS6HyLkcZlq6ODYrfdGKa90EeV4q4wdGfP5IkrBbf+WBFACDGVp574E7vfMLMNKUDuUtgnGVPwqOIhjG82ud8Xa7qF7lsw50QOnRYYVd2GLlCIzk8sffT5ncjSPN2ClNVM5iTwCkC8TNSAVnEJxu6bG6hcaT4uCWWjs0m+39O1xJwxJ4vTjpE/gy+j2FTSaUR5cNavOVCyJqpeKlga9aoBVsQHWvxlYurdWbwRLwVIV7bDE+sYPnwn1nMFQpx5RZ1AX1Z2UxNFKcYzLJgcWe85OjxLyT4udX+XZ9SLsdOjm1n201OLEKsmTxmHS8yqpbu6+pKQ1rLMEVpgPGfS8DdtqZyb3z0u6q4jztpm+uBe8hnFgwQFGXO63nOQsI0n1PMR3evAnVx7jYt7y/UALs2A9yjosMwDqgql1FMhyd0OJGo8Ky8YpDsr2J5zGVvIqt2/N+lP+SOe2D1J/5EhGlY0o4tqIAFskh3q8/GR6UGm70KT2l4LJqrU1WGrGxPJ+HoOEmvG6eqLesk03fAX3v5+DgXZWErnzXMIOGDPaFVpmx+G9VZTIlmf+3Wbu9TnZE5PRwFTdP1rqjGpjUhHeF0VTc2qgNq1OfL98CBO1wLgA195+em58cELalnwDMWUTmY1Jt1kUuAtCc11U=" + # used to decypt the aes decrypt the certificate and identity files + - secure: "FzftiEBFMngQIci5KZ+tpKs/BubalhWXJ9f8yogGNVa3XplLheUXYgcsNU8sYT2MJaKAHN9fSjHWb67UtKT7yXddXxeOPnW6wJ3ua/FXnpynsmeGKTfh3stvgjpzdXous67uHmCWMMklfb6z7UuDohjUMAe5n5HKw5tq1RzTKpc2kJacOC6qUT5laUOvULyCaO9E9HmbHeR5ZeXAC6pnzX2ccsSrcXvPozHzBIZ9RyothKs+CZw8PEuJo07RRL8meboegqYOUrOYuz6A2gS2mZJoy59ivZKOFxS5shEuv2Jt80RyfyxBoUpKFq8OG3Am5nAEzDiTIGzmIoKDEGKeTagk/sEtSZXiMDkzDT4GX/j1rUNLCBU87bXEFS2zfRsrfg8c1XZPIzDYBT1PY2QtLBdddF5zzDoKdPLJ3sjN+fZFE5RlnwfwnMHriVRZZlzjcdk0Z06gKTBCxUg5BZamnOOK+K8qunMJXVS+Vmi5u4RoTZZiCosUlYKnSKJ8suO9C0+znxoViusPqP4ONprNHgDoZw+UKio84QW3PrZv9h4zH/D+msDgJRZ0ceqDD+6Wz1J8Mm5ptW1GOLh/IU12TPXjxteqh0Um2vv5eIPmjK9uEK666kK7PqtPDkYhAfWvF+nmOOyPMJfbP4MW/i9WHNF4ghsIMbPKfqNhgSmfrYw=" diff --git a/packaging/osx/certs/cert.cer.enc b/packaging/osx/certs/cert.cer.enc deleted file mode 100644 index 4dc96d9ba..000000000 --- a/packaging/osx/certs/cert.cer.enc +++ /dev/null @@ -1,30 +0,0 @@ -U2FsdGVkX1/oLoj7zhPd0imh2T8RhLpKzZLk4EHQV0GUJ1g8nwGvxWov+CUMnmjh -Y+LNdomGBYWoUilhe4JaWDUYepwIXn6+TvuWBdVEMGpJhXGbmIf+ncMXo6AP8Fh/ -g9x79SE4RJxFj3utc02B2ivVehoQno5sEvNSZMVml5n9skJoJUBbAsbp1p+7Hm5j -p2Z7UI7/qiih6TmszX5KQvOl/DPezVNksn1c1mUShuaBTjxbprGlr/LvtboR3mAd -8DN4yTGLLJAQ2+FNftM4rAedrr6db2AhQ8WxRmiwTfdubnEoC6zYySFq2kmdjj3S -gPnK0atx+ZihMp+S+GqMnvfAHEtb0vqxoq6nFcSIvcQVxKPyzu5E1kMriY4Oq3xr -K6ebc1NKJHjh7niaUmR3NImBx2h1LAAf/hcKRH2+ATEVczGtI1AsSGgGhUM34eGH -7G+m7+bIkgb8AtlaIGS/VVHsIZCNSgzwZJoNd3hD6ZV65Hb2yeT6Hhos88/05iFT -ewYasa73TqFm5SJHRwt4d1W9WVIJKJPDJ910p+V+NZVUsKOx34+vMNrjCrqW9p9x -gQnza2V/F6blIHTbSzIGc+MFbeHYBO80d+v5jVxheL8z6ollDVts1SyJ5rKJBY6c -quvSgmc/ltE0dqRxLOQJ9mAFbayuMIUP6CbRkPXp8GfE55UtUJkDilalzcpCPrUC -YJpuAI61INOQZZPEVKWW8L68/tLY+oEwWpexQX7xs4FUCblIFf20T3XE2lVuBHf9 -Bp9k7cD2m4mNrbzWOJuqrVt1pr176l9+VSP/ESdDFbmPch2FHl8HK8kgfJvkV+iB -kudmAmzI9DTUpWd5lJp6Fr/rLCMjslFDs37zMg4/E5ikKFSDNeYMtgPZhCwM83kh -OAktow4QAzh3RdbVZMFxaKk9nbiGPuBEsgvraPjb2gY8U34RC9R2FINIuTnJttLK -q7CKFTdbJIf+TIIgzfNu/c978adsK/qS68iltyyx8WFflcybnlqVgja192Ptqw1M -PXBQkH4mUrAeWDfmCPPh/mhO67Bau5u9Wzv/qZ2RXcX0dgXOoMa2sO6ZpR2SzxCJ -/XZwXnElMl+pvojLURDOV16fMPpjMCbzCN+hQabiTASqFNCsz4C9hmOquNh2t+V9 -8xvU/bnOM+/SMhahjYnvdhmRMcY+5Wv32ZnKATq88dq4T7/OZI7q3IsROZ7MnucT -x4vADvcFOfOdtPK35IFfMTfl+Ri3q7REIHMts2WEwXddf8CUiVeIaf8NgrWYW0hP -f9DQbMGKFcqqCHlKrQkv2dBKX/qEbIzN7T7535Ly68zyFuBS252gsLO7nrf+CLEZ -AROOfmt2jv0BvQ4MI5dslzsXFAU11tS36gOZ303R+NJVVqySkza964h2rH5M1F7i -A5p7w/l0OVV7r6aXkmsrIcsUZuY7QnZJORQ1MxNtK20weKfrqs90nMTklUVPc4V8 -LnAW6AYem0ZaeDHn2kx947sglMYxf0h/mFECGhif9hfDTErw7TkSJ26t9ByuEyEf -vGpp3P4iTXHUx7HSh7L4KDva6CP6slGjFMAFUEETn7N5uX3VEYeztMBdHLz0XHZc -PcgVZ8kytXVTEg95upvWmliEbQqWRsy6sr9PanaN1QY6re6RLlYj4pOWVm8qgCXU -IJVTWkROMlYZTWCibCsTsY8fk8aNObZamHjzZGvnU8nEGTx7xQJS8i0r3NM1j2Ka -ehBA+WfXbTplI/Fq8Z2Nrb/O39hQpGbXp4ERsEmVbK0twwsqVNehI0CdobxmGsd5 -E9Afb9iL97DTXsna1Il6FXnHvj3iAQsxxaNLIaj0sN1GaQd9N1mbxThlFNOM3wry -jI8TKCWEfLRQzykkcR3sMg== diff --git a/packaging/osx/certs/cert.p12.enc b/packaging/osx/certs/cert.p12.enc deleted file mode 100644 index 40828aff6..000000000 --- a/packaging/osx/certs/cert.p12.enc +++ /dev/null @@ -1,33 +0,0 @@ -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 -vOtCHu46xXuBJbDmz2mslw== diff --git a/packaging/osx/certs/dist.cer.enc b/packaging/osx/certs/dist.cer.enc new file mode 100644 index 000000000..6431def6b --- /dev/null +++ b/packaging/osx/certs/dist.cer.enc @@ -0,0 +1,30 @@ +U2FsdGVkX184Qhj+Znx23me5PxRw3d8AgHu/h2uingV6T0lAb9/xDlxOU7E0HEsE +NIVvS0r5kqK6FXhUODny567FR+OGihl/XiKMjMoJSxNIAjYcuo91hVZ2mN+AbIDl +OaaSRSXdwg948eNYhLsjfjyxU4fpZ5P+fSvcMZ4y4xSm7gwOCPrTFhRXmiCxFVsY +x8td9OtmnGwRMnkTz7les3ZW7lHFbsmiHwct+L3QCWcLZ+xklbsLLVkXOuYpws7J +pWKc8YgmyySH9uXnzuxWuRrqvw4coq1pO51WB/6ZaSbiE5FzIq32usnQocl8hjY2 +0rveOAR5nLSNA4YQY6O2gbnN6Fq0TDGOIJ1Lvl8XkHKrMqSu9ifFXAmebHH5xfFS +HFZ9mije0lNSxg5a6b2EJkCmbIE5GHzqzzWccAlmgCrOtd6ZpytpW1oTJZEvboo5 +G4TdZ3te31ltn+d/2Jr7Z3q2ByueTOVj01fx/mJcDCK+q5ytWOTvqkoGzrHIDbxK +eV/XfhcQ1+dCFIYu89++/bt19NZ7KrxBQ5D2W6G3+71BGIxXYlyGfyTy7dbyl/EY +f9ddk+BxDQgGpj+fRLAOIboKp94bUcneG79H5Fw+w+aTHQM5T/Ilmjq60sUft+2u +gcs0H8Slb3Gnf/QTwSLoxd/GJofAIhIcTD/HSWD8NH9YsK7lvLuLTamnLkprtdvB +NfhsLHENg0Ha/s/eEtU2GAG/RBFT0XwZKR0O19YNSWjEvop7w/cSlwv+be7gT09O +0/vO6xouqG16bSWEg7nxTYs/jMPPfrdn6fhNWEUo2p7FYDbq5BerN/1Eh1xjHwq3 +a1pcnFkRumpjMH32aBMS79Ute1ij5xPfFKT/Bh+J4wCTlnKp0EsyhTY9DHtVaw9G ++IfLiFTkN2MQSCGGTcGx6KDAWkjXui/8WLM/adtcLPUBrAHd4S4DoJ8v9sxACRDb +iX950xj0IRqdzb8xF6EPCvb8t02ldzKjQw69FvFFlW4P+La+qvTSgIPo1SJ/uPGm +Asutx5EL51b1zCQk/YrH93pAK2RIqMn40I7sB9t5kcN/rhzcVcgW3ENb4wLynK5C ++gyr65cBgwHIZK7Lpq4rUaWh9TliDpkJqspDJb81IaQjvEKKD3weAg28H4969mju +7Q+Cg1X4ciHZo9aydD0le3PC//lOZ6huPEW51azFKII2QQEG4JKFT6Q57F1tXNqw +sXi0HaW9MW3doHh589NNFFU3/7zrZfMHsh5l9cA/TY7oUZFj+lWSPhZsuoy+J7e2 +7r5NfmuV35Z9v1suuEbGZ4Un0ZvVWWhW4/fVhFjEr9hjVb20kd1//EJKQoK5WMFC +MkFNpi5hIaCXiLEh7B3e95XFXddZKf/IBgeCeYSnUOHwq6TFezifah9J9polovB2 +bwf+2HUh8buPUN+Zo2mxh3J/eJjvoY75dSuqk6wPRvGSkTmk8w2zToqUwFXBEoi0 +on3rxJB/dpFrC/zYz8c6IuIM3Zi5FAAgOrBD4gr9M9NEnt13rwsx+YxpSgPsB/LK +3j6XMrClj1faFLEpqsrSUUMRT27m9tro353JQJhTITg9oQywi9nKixNbCM72n262 +FSucD8L07p+Q+tiw+ShwjJ8CW/t97lk5b9gfbQgvVThfQrarBYml8Fj4/lK+uO9q +wjnOHzjEAN6MAxy8Nbfp3xz7LB18aShMuLLwWayKBGlECkbaGj0eH1+ZfvF6QPOq +CsUnzFFR4TyeITNJyj8S1LrMUxMzPyHgTVHShECDrjILJJnSt4yzGZXMweoWV62n +AwHqiP+sEEOu7ihOySsoW/3kqpxKhAoNxbW4Kh1Lk2KgebLjcdfDIQQLK0N0VXu5 +wHO80TEZVEqyfOeJTST/jA== diff --git a/packaging/osx/certs/dist.p12.enc b/packaging/osx/certs/dist.p12.enc new file mode 100644 index 000000000..595f15773 --- /dev/null +++ b/packaging/osx/certs/dist.p12.enc @@ -0,0 +1,67 @@ +U2FsdGVkX1/nZdeV0RBXBMg3aUrBekilENXXcvQ1sR5cLfA+TLOecPR+TtkXvRPk +ZsRUDMAyE53eOuam2DMZgRx65V9lBYNrzWoUS0AQr+TX2s/NItjj/owiJyOb1tcP +FPcw0K7oEA5BCD+iqN66YIbPOuQ1AohPl0A8Ee1mP8OrwlzIiu3nSf/kGGlORZX1 +lA4Hhmc1PMdO7DHWxg78+QVPw1t7oI4bIublY0byl6b1dU0Zo8ALD/mCPwI5iusF +fmWRAjO7l+DIDDud6S0jXujtC7Ppq1KO4no9E85QYCC1eO6HdigyptAVNcSnVsIC +NYicQ2C8fkplncoF+2ECH7hGa9Ne+/TogVzsOaOgcdpfdSq/hsF7uUwdZVngmH2+ +VNJZZxPRQU7zZ5nsuUqeGF/9cDnTEEza8Al98zmDeGE2UjFcejHEKXU+PAr+AZ87 +CTFVyZn0nIiTEyT7Fnct9IlePtKl8dkR3brXTuzfAZlmeVKiDTNdR+ULLZ0ewvim +wW/2wIi3nrIs0uB6YWUnbGkDnR1XT5TLsQ+hfpMW5uo48jgxQvu6U83uIZjaT+O9 +yvXNRuqn23JNtDSp3E+wp9/5G3STnJxAlKKKG+WXXRCOUwD4C5jzFfZfy0WIvp+5 +gVvBsp9kz+XszCU5xlFCRUT+CsAyPhCZqgQrLJ6DEFt+9M/3/njudSEjuXcMxm0h +F2pAz6Llox7YS7IHlTywnAl04l4UhoHcFzTupE9NFM3NASSlMwN6BwGn9Rd0N6Sr +sr7JPWdYWBFr2+HSf9FHfM75GycYx9l+Kt2Igz1qidgYZfzepyuLJ7Ffib0+in5f +s9nL3GfPGTJAsSK5OcDaOWE3ae2bmZL6P2ztpZP4yec1DBS3+YA1L+gh5P3m4xrE +EphmtfJPozGCrk9cbtW9xT5z2Npj1p6UhtQ/DPEbbqggnwzYsoLGL5k3LXJdnj3u +BVokDuq2Cz+ChXWLFvVVf3XGHLfdSDveXXyWuMquVrurTYxIgiiOi9Lskl5m/GS7 +Ngz0mbqf5aQ+LclMoc5T9r3Ah1CC1Rso4mu88WL4PfIkMK8Q83OFtax766j571gG +Xs/Zd44uO6/w4Ewh9r7qGu4hW92lwn7SgshiXfmrp8+eca8hbCT33icioGUm5lFB +z5gaPE77YI3ZVnNrGfIgd9NEH3w6JU6V/wMnOTPwP6Jkg6oB0VcynEaBBOwLleWc +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 +EjIMJeXgdGFW7PEdY/kROQ== diff --git a/packaging/osx/lbry-osx-app b/packaging/osx/lbry-osx-app index e5b465205..21be499dd 160000 --- a/packaging/osx/lbry-osx-app +++ b/packaging/osx/lbry-osx-app @@ -1 +1 @@ -Subproject commit e5b465205469e8f14999b56138b3bea6666e006d +Subproject commit 21be499ddb1da97c2a0cdaebd6a71786526eef46 From 2e26bb0e57816253b280ecab6a56b12582299eaf Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 20 Jun 2016 03:40:12 -0500 Subject: [PATCH 261/462] remove submodule --- .gitmodules | 3 --- packaging/osx/lbry-osx-app | 1 - 2 files changed, 4 deletions(-) delete mode 160000 packaging/osx/lbry-osx-app diff --git a/.gitmodules b/.gitmodules index 60a3e5d06..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "packaging/osx/lbry-osx-app"] - path = packaging/osx/lbry-osx-app - url = https://github.com/lbryio/lbry-osx-app.git diff --git a/packaging/osx/lbry-osx-app b/packaging/osx/lbry-osx-app deleted file mode 160000 index 21be499dd..000000000 --- a/packaging/osx/lbry-osx-app +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 21be499ddb1da97c2a0cdaebd6a71786526eef46 From 628d052749cb39116bbdba909a8fa50ee0b798d3 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 20 Jun 2016 03:42:06 -0500 Subject: [PATCH 262/462] move contents of lbry-osx-app into lbry repo --- packaging/osx/lbry-osx-app/.gitignore | 12 +++ packaging/osx/lbry-osx-app/app.icns | Bin 0 -> 8362 bytes packaging/osx/lbry-osx-app/build_app.sh | 63 ++++++++++++++ .../lbry_uri_handler/LBRYURIHandler.py | 60 ++++++++++++++ packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py | 77 ++++++++++++++++++ .../osx/lbry-osx-app/lbrygui/LBRYNotify.py | 27 ++++++ .../osx/lbry-osx-app/lbrygui/__init__.py | 0 packaging/osx/lbry-osx-app/lbrygui/app.icns | Bin 0 -> 8362 bytes packaging/osx/lbry-osx-app/lbrygui/main.py | 35 ++++++++ packaging/osx/lbry-osx-app/libgmp.10.dylib | Bin 0 -> 479388 bytes packaging/osx/lbry-osx-app/setup_app.py | 29 +++++++ packaging/osx/lbry-osx-app/setup_app.sh | 64 +++++++++++++++ .../osx/lbry-osx-app/setup_uri_handler.py | 25 ++++++ 13 files changed, 392 insertions(+) create mode 100644 packaging/osx/lbry-osx-app/.gitignore create mode 100644 packaging/osx/lbry-osx-app/app.icns create mode 100755 packaging/osx/lbry-osx-app/build_app.sh create mode 100644 packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py create mode 100644 packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py create mode 100644 packaging/osx/lbry-osx-app/lbrygui/LBRYNotify.py create mode 100644 packaging/osx/lbry-osx-app/lbrygui/__init__.py create mode 100644 packaging/osx/lbry-osx-app/lbrygui/app.icns create mode 100644 packaging/osx/lbry-osx-app/lbrygui/main.py create mode 100755 packaging/osx/lbry-osx-app/libgmp.10.dylib create mode 100644 packaging/osx/lbry-osx-app/setup_app.py create mode 100755 packaging/osx/lbry-osx-app/setup_app.sh create mode 100644 packaging/osx/lbry-osx-app/setup_uri_handler.py diff --git a/packaging/osx/lbry-osx-app/.gitignore b/packaging/osx/lbry-osx-app/.gitignore new file mode 100644 index 000000000..bc318a057 --- /dev/null +++ b/packaging/osx/lbry-osx-app/.gitignore @@ -0,0 +1,12 @@ + +*.pyc + +*.pyo + +*.so + +*.xml + +*.iml + +id.conf diff --git a/packaging/osx/lbry-osx-app/app.icns b/packaging/osx/lbry-osx-app/app.icns new file mode 100644 index 0000000000000000000000000000000000000000..b4d00d2f26dc4f4221a0d3e93e988a15b0ada3b8 GIT binary patch literal 8362 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 literal 0 HcmV?d00001 diff --git a/packaging/osx/lbry-osx-app/build_app.sh b/packaging/osx/lbry-osx-app/build_app.sh new file mode 100755 index 000000000..59828f6b4 --- /dev/null +++ b/packaging/osx/lbry-osx-app/build_app.sh @@ -0,0 +1,63 @@ +dest=`pwd` +tmp="${dest}/build" +id=`cat id.conf` + +rm -rf build dist LBRY.app + +mkdir -p $tmp +cd $tmp + +echo "Updating lbryum" +git clone --depth 1 http://github.com/lbryio/lbryum.git +cd lbryum +python setup.py install &>/dev/null +cd .. +echo "Updating lbrynet" +git clone --depth 1 -b development http://github.com/lbryio/lbry.git +cd lbry +python setup.py install &>/dev/null + +cd $dest +echo "Building URI Handler" +python setup_uri_handler.py py2app &>/dev/null + +echo "Signing URI Handler" +codesign -s "Developer ID Application: LBRY Inc (${id})" -f "dist/LBRYURIHandler.app/Contents/Frameworks/Python.framework/Versions/2.7" +codesign -s "Developer ID Application: LBRY Inc (${id})" -f "dist/LBRYURIHandler.app/Contents/MacOS/python" +codesign -s "Developer ID Application: LBRY Inc (${id})" -f "dist/LBRYURIHandler.app/Contents/MacOS/LBRYURIHandler" +codesign -vvvv "dist/LBRYURIHandler.app" +mv "dist/LBRYURIHandler.app" "LBRYURIHandler.app" +rm -rf build dist + +echo "Building app" +python setup_app.py py2app &>/dev/null + +echo "Moving in correct libgmp" +rm "${dest}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" +cp "${dest}/libgmp.10.dylib" "${dest}/dist/LBRY.app/Contents/Frameworks" + +echo "Removing i386 libraries" + +remove_arch () { + lipo -output build/lipo.tmp -remove "$1" "$2" && mv build/lipo.tmp "$2" +} +for i in dist/LBRY.app/Contents/Resources/lib/python2.7/lib-dynload/* ; do + #remove_arch ppc ${i} + remove_arch i386 ${i} +done + +echo "Moving LBRYURIHandler.app into LBRY.app" +mv "${dest}/LBRYURIHandler.app" "${dest}/dist/LBRY.app/Contents/Resources" + +echo "Signing LBRY.app" +codesign -s "Developer ID Application: LBRY Inc (${id})" -f "${dest}/dist/LBRY.app/Contents/Frameworks/Python.framework/Versions/2.7" +codesign -s "Developer ID Application: LBRY Inc (${id})" -f "${dest}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" +codesign -s "Developer ID Application: LBRY Inc (${id})" -f "${dest}/dist/LBRY.app/Contents/MacOS/python" +codesign -s "Developer ID Application: LBRY Inc (${id})" -f "${dest}/dist/LBRY.app/Contents/MacOS/LBRY" +codesign -vvvv "${dest}/dist/LBRY.app" + +rm -rf $tmp +mv dist/LBRY.app LBRY.app +rm -rf dist + +chown -R ${SUDO_USER} LBRY.app \ No newline at end of file diff --git a/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py b/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py new file mode 100644 index 000000000..f6990cfea --- /dev/null +++ b/packaging/osx/lbry-osx-app/lbry_uri_handler/LBRYURIHandler.py @@ -0,0 +1,60 @@ +import os +import json +import webbrowser +import subprocess +import sys + +from time import sleep +from jsonrpc.proxy import JSONRPCProxy + +API_CONNECTION_STRING = "http://localhost:5279/lbryapi" +UI_ADDRESS = "http://localhost:5279" + + +class LBRYURIHandler(object): + def __init__(self): + self.started_daemon = False + self.daemon = JSONRPCProxy.from_url(API_CONNECTION_STRING) + + def handle_osx(self, lbry_name): + try: + status = self.daemon.is_running() + except: + os.system("open /Applications/LBRY.app") + sleep(3) + + if lbry_name == "lbry" or lbry_name == "": + webbrowser.open(UI_ADDRESS) + else: + webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) + + def handle_linux(self, lbry_name): + try: + status = self.daemon.is_running() + except: + cmd = r'DIR = "$( cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )"' \ + r'if [-z "$(pgrep lbrynet-daemon)"]; then' \ + r'echo "running lbrynet-daemon..."' \ + r'$DIR / lbrynet - daemon &' \ + r'sleep 3 # let the daemon load before connecting' \ + r'fi' + subprocess.Popen(cmd, shell=True) + + if lbry_name == "lbry" or lbry_name == "": + webbrowser.open(UI_ADDRESS) + else: + webbrowser.open(UI_ADDRESS + "/?watch=" + lbry_name) + + +def main(args): + if len(args) != 1: + args = ['lbry://lbry'] + + name = args[0][7:] + if sys.platform == "darwin": + LBRYURIHandler().handle_osx(lbry_name=name) + else: + LBRYURIHandler().handle_linux(lbry_name=name) + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py b/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py new file mode 100644 index 000000000..56f02846f --- /dev/null +++ b/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py @@ -0,0 +1,77 @@ +import AppKit +import webbrowser +import sys +import logging +import socket +import platform + +from PyObjCTools import AppHelper + +from twisted.internet import reactor +from twisted.web import server + +from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer +from lbrynet.conf import API_PORT, API_INTERFACE, ICON_PATH, APP_NAME +from lbrynet.conf import UI_ADDRESS + +if platform.mac_ver()[0] >= "10.10": + from LBRYNotify import LBRYNotify + +log = logging.getLogger(__name__) + +REMOTE_SERVER = "www.google.com" + + +def test_internet_connection(): + try: + host = socket.gethostbyname(REMOTE_SERVER) + s = socket.create_connection((host, 80), 2) + return True + except: + return False + + +class LBRYDaemonApp(AppKit.NSApplication): + def finishLaunching(self): + self.connection = False + statusbar = AppKit.NSStatusBar.systemStatusBar() + self.statusitem = statusbar.statusItemWithLength_(AppKit.NSVariableStatusItemLength) + self.icon = AppKit.NSImage.alloc().initByReferencingFile_(ICON_PATH) + self.icon.setScalesWhenResized_(True) + self.icon.setSize_((20, 20)) + self.statusitem.setImage_(self.icon) + self.menubarMenu = AppKit.NSMenu.alloc().init() + self.open = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_("Open", "openui:", "") + self.menubarMenu.addItem_(self.open) + self.quit = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_("Quit", "replyToApplicationShouldTerminate:", "") + self.menubarMenu.addItem_(self.quit) + self.statusitem.setMenu_(self.menubarMenu) + self.statusitem.setToolTip_(APP_NAME) + + + if test_internet_connection(): + if platform.mac_ver()[0] >= "10.10": + LBRYNotify("Starting LBRY") + else: + if platform.mac_ver()[0] >= "10.10": + LBRYNotify("LBRY needs an internet connection to start, try again when one is available") + sys.exit(0) + + # if not subprocess.check_output("git ls-remote https://github.com/lbryio/lbry-web-ui.git | grep HEAD | cut -f 1", + # shell=True): + # LBRYNotify( + # "You should have been prompted to install xcode command line tools, please do so and then start LBRY") + # sys.exit(0) + + lbry = LBRYDaemonServer() + d = lbry.start() + d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) + reactor.listenTCP(API_PORT, server.Site(lbry.root), interface=API_INTERFACE) + + def openui_(self, sender): + webbrowser.open(UI_ADDRESS) + + def replyToApplicationShouldTerminate_(self, shouldTerminate): + if platform.mac_ver()[0] >= "10.10": + LBRYNotify("Goodbye!") + reactor.stop() diff --git a/packaging/osx/lbry-osx-app/lbrygui/LBRYNotify.py b/packaging/osx/lbry-osx-app/lbrygui/LBRYNotify.py new file mode 100644 index 000000000..d4a88e7ce --- /dev/null +++ b/packaging/osx/lbry-osx-app/lbrygui/LBRYNotify.py @@ -0,0 +1,27 @@ +import Foundation +import objc +import AppKit + +NSUserNotification = objc.lookUpClass('NSUserNotification') +NSUserNotificationCenter = objc.lookUpClass('NSUserNotificationCenter') + +def LBRYNotify(message): + notification = NSUserNotification.alloc().init() + notification.setTitle_("LBRY") + notification.setSubtitle_("") + notification.setInformativeText_(message) + notification.setUserInfo_({}) + notification.setSoundName_("NSUserNotificationDefaultSoundName") + notification.setDeliveryDate_(Foundation.NSDate.dateWithTimeInterval_sinceDate_(0, Foundation.NSDate.date())) + NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification) + +def notify(title, subtitle, info_text, delay=0, sound=False, userInfo={}): + notification = NSUserNotification.alloc().init() + notification.setTitle_(title) + notification.setSubtitle_(subtitle) + notification.setInformativeText_(info_text) + notification.setUserInfo_(userInfo) + if sound: + notification.setSoundName_("NSUserNotificationDefaultSoundName") + notification.setDeliveryDate_(Foundation.NSDate.dateWithTimeInterval_sinceDate_(delay, Foundation.NSDate.date())) + NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notification) \ No newline at end of file diff --git a/packaging/osx/lbry-osx-app/lbrygui/__init__.py b/packaging/osx/lbry-osx-app/lbrygui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packaging/osx/lbry-osx-app/lbrygui/app.icns b/packaging/osx/lbry-osx-app/lbrygui/app.icns new file mode 100644 index 0000000000000000000000000000000000000000..b4d00d2f26dc4f4221a0d3e93e988a15b0ada3b8 GIT binary patch literal 8362 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 literal 0 HcmV?d00001 diff --git a/packaging/osx/lbry-osx-app/lbrygui/main.py b/packaging/osx/lbry-osx-app/lbrygui/main.py new file mode 100644 index 000000000..d7ca4dc00 --- /dev/null +++ b/packaging/osx/lbry-osx-app/lbrygui/main.py @@ -0,0 +1,35 @@ +from PyObjCTools import AppHelper +from twisted.internet.cfreactor import install +install(runner=AppHelper.runEventLoop) +from twisted.internet import reactor + +import logging +import sys +import os +from appdirs import user_data_dir + +from LBRYApp import LBRYDaemonApp + +if sys.platform != "darwin": + log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") +else: + log_dir = user_data_dir("LBRY") + +if not os.path.isdir(log_dir): + os.mkdir(log_dir) + +LOG_FILENAME = os.path.join(log_dir, 'lbrynet-daemon.log') + +log = logging.getLogger(__name__) +handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=2097152, backupCount=5) +log.addHandler(handler) +logging.basicConfig(level=logging.INFO) + + +def main(): + app = LBRYDaemonApp.sharedApplication() + reactor.addSystemEventTrigger("after", "shutdown", AppHelper.stopEventLoop) + reactor.run() + +if __name__ == "__main__": + main() diff --git a/packaging/osx/lbry-osx-app/libgmp.10.dylib b/packaging/osx/lbry-osx-app/libgmp.10.dylib new file mode 100755 index 0000000000000000000000000000000000000000..09f809987b9afa9f70d0bebe7f556f99d9b5a407 GIT binary patch literal 479388 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

=-&e45fF1 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/dev/null + +echo "Moving in correct libgmp" +rm "${dest}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" +cp "${dest}/libgmp.10.dylib" "${dest}/dist/LBRY.app/Contents/Frameworks" + +echo "Removing i386 libraries" + +remove_arch () { + lipo -output build/lipo.tmp -remove "$1" "$2" && mv build/lipo.tmp "$2" +} +for i in dist/LBRY.app/Contents/Resources/lib/python2.7/lib-dynload/* ; do + remove_arch i386 ${i} +done + +echo "Moving LBRYURIHandler.app into LBRY.app" +mv "${LBRY}/dist/LBRYURIHandler.app" "${dest}/dist/LBRY.app/Contents/Resources" + +echo "Signing LBRY.app" +codesign -s "${LBRY_DEVELOPER_ID}" -f "${dest}/dist/LBRY.app/Contents/Frameworks/Python.framework/Versions/2.7" +codesign -s "${LBRY_DEVELOPER_ID}" -f "${dest}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" +codesign -s "${LBRY_DEVELOPER_ID}" -f "${dest}/dist/LBRY.app/Contents/MacOS/python" +codesign -s "${LBRY_DEVELOPER_ID}" -f "${dest}/dist/LBRY.app/Contents/MacOS/LBRY" +codesign -vvvv "${dest}/dist/LBRY.app" + +rm -rf $tmp +mv dist/LBRY.app LBRY.app +rm -rf dist diff --git a/packaging/osx/lbry-osx-app/setup_uri_handler.py b/packaging/osx/lbry-osx-app/setup_uri_handler.py new file mode 100644 index 000000000..26097d8a4 --- /dev/null +++ b/packaging/osx/lbry-osx-app/setup_uri_handler.py @@ -0,0 +1,25 @@ +from setuptools import setup +import os + +APP = [os.path.join('lbry_uri_handler', 'LBRYURIHandler.py')] +DATA_FILES = [] +OPTIONS = {'argv_emulation': True, + 'packages': ['jsonrpc'], + 'plist': { + 'LSUIElement': True, + 'CFBundleIdentifier': 'io.lbry.LBRYURIHandler', + 'CFBundleURLTypes': [ + { + 'CFBundleURLTypes': 'LBRYURIHandler', + 'CFBundleURLSchemes': ['lbry'] + } + ] + } + } + +setup( + app=APP, + data_files=DATA_FILES, + options={'py2app': OPTIONS}, + setup_requires=['py2app'], +) \ No newline at end of file From f16111369f27562ab01dd220dc96afcc8f924f70 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 20 Jun 2016 03:42:43 -0500 Subject: [PATCH 263/462] Update build script setup_uri_handler.py has moved, update build script to reflect again, setup_uri_handler changed location remove pipe to /dev/null try updating openssl try deep signing another file renamed install a dependency prereqs install requirements install certifi only install requirements on osx thin binaries only if they're actually fat --- .travis.yml | 12 +++-- packaging/osx/lbry-osx-app/build_app.sh | 63 ------------------------- packaging/osx/lbry-osx-app/setup_app.sh | 47 ++++++++++-------- 3 files changed, 35 insertions(+), 87 deletions(-) delete mode 100755 packaging/osx/lbry-osx-app/build_app.sh diff --git a/.travis.yml b/.travis.yml index 6af0a761e..9c3dc638f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,13 @@ before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew install python; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo pip install --upgrade pip virtualenv; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then virtualenv $HOME/venv && source $HOME/venv/bin/activate; fi - + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated gmp || brew upgrade gmp; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated openssl || brew upgrade openssl; fi + # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install jsonrpc; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install -r requirements.txt; fi + install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./packaging/travis/install_dependencies_and_run_tests.sh; fi @@ -28,10 +34,6 @@ before_script: script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh -t; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade gmp; fi - # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install jsonrpc; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app && ./setup_app.sh && cd $TRAVIS_BUILD_DIR; fi # fail the build if this is a build for a tag and we don't have the versions matching - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi diff --git a/packaging/osx/lbry-osx-app/build_app.sh b/packaging/osx/lbry-osx-app/build_app.sh deleted file mode 100755 index 59828f6b4..000000000 --- a/packaging/osx/lbry-osx-app/build_app.sh +++ /dev/null @@ -1,63 +0,0 @@ -dest=`pwd` -tmp="${dest}/build" -id=`cat id.conf` - -rm -rf build dist LBRY.app - -mkdir -p $tmp -cd $tmp - -echo "Updating lbryum" -git clone --depth 1 http://github.com/lbryio/lbryum.git -cd lbryum -python setup.py install &>/dev/null -cd .. -echo "Updating lbrynet" -git clone --depth 1 -b development http://github.com/lbryio/lbry.git -cd lbry -python setup.py install &>/dev/null - -cd $dest -echo "Building URI Handler" -python setup_uri_handler.py py2app &>/dev/null - -echo "Signing URI Handler" -codesign -s "Developer ID Application: LBRY Inc (${id})" -f "dist/LBRYURIHandler.app/Contents/Frameworks/Python.framework/Versions/2.7" -codesign -s "Developer ID Application: LBRY Inc (${id})" -f "dist/LBRYURIHandler.app/Contents/MacOS/python" -codesign -s "Developer ID Application: LBRY Inc (${id})" -f "dist/LBRYURIHandler.app/Contents/MacOS/LBRYURIHandler" -codesign -vvvv "dist/LBRYURIHandler.app" -mv "dist/LBRYURIHandler.app" "LBRYURIHandler.app" -rm -rf build dist - -echo "Building app" -python setup_app.py py2app &>/dev/null - -echo "Moving in correct libgmp" -rm "${dest}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" -cp "${dest}/libgmp.10.dylib" "${dest}/dist/LBRY.app/Contents/Frameworks" - -echo "Removing i386 libraries" - -remove_arch () { - lipo -output build/lipo.tmp -remove "$1" "$2" && mv build/lipo.tmp "$2" -} -for i in dist/LBRY.app/Contents/Resources/lib/python2.7/lib-dynload/* ; do - #remove_arch ppc ${i} - remove_arch i386 ${i} -done - -echo "Moving LBRYURIHandler.app into LBRY.app" -mv "${dest}/LBRYURIHandler.app" "${dest}/dist/LBRY.app/Contents/Resources" - -echo "Signing LBRY.app" -codesign -s "Developer ID Application: LBRY Inc (${id})" -f "${dest}/dist/LBRY.app/Contents/Frameworks/Python.framework/Versions/2.7" -codesign -s "Developer ID Application: LBRY Inc (${id})" -f "${dest}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" -codesign -s "Developer ID Application: LBRY Inc (${id})" -f "${dest}/dist/LBRY.app/Contents/MacOS/python" -codesign -s "Developer ID Application: LBRY Inc (${id})" -f "${dest}/dist/LBRY.app/Contents/MacOS/LBRY" -codesign -vvvv "${dest}/dist/LBRY.app" - -rm -rf $tmp -mv dist/LBRY.app LBRY.app -rm -rf dist - -chown -R ${SUDO_USER} LBRY.app \ No newline at end of file diff --git a/packaging/osx/lbry-osx-app/setup_app.sh b/packaging/osx/lbry-osx-app/setup_app.sh index d4141326a..b8c601522 100755 --- a/packaging/osx/lbry-osx-app/setup_app.sh +++ b/packaging/osx/lbry-osx-app/setup_app.sh @@ -3,8 +3,8 @@ set -o errexit set -o xtrace -dest=`pwd` -tmp="${dest}/build" +DEST=`pwd` +tmp="${DEST}/build" rm -rf build dist LBRY.app @@ -23,41 +23,50 @@ else LBRY=${TRAVIS_BUILD_DIR} fi python setup.py install + echo "Building URI Handler" +cd "${DEST}" rm -rf build dist python setup_uri_handler.py py2app echo "Signing URI Handler" -codesign -s "${LBRY_DEVELOPER_ID}" -f "${LBRY}/dist/LBRYURIHandler.app/Contents/Frameworks/Python.framework/Versions/2.7" -codesign -s "${LBRY_DEVELOPER_ID}" -f "${LBRY}/dist/LBRYURIHandler.app/Contents/MacOS/python" -codesign -s "${LBRY_DEVELOPER_ID}" -f "${LBRY}/dist/LBRYURIHandler.app/Contents/MacOS/LBRYURIHandler" -codesign -vvvv "${LBRY}/dist/LBRYURIHandler.app" +codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/Frameworks/Python.framework/Versions/2.7" +codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/MacOS/python" +# not sure if --deep is appropriate here, but need to get LBRYURIHandler.app/Contents/Frameworks/libcrypto.1.0.0.dylib signed +codesign --deep -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/MacOS/LBRYURIHandler" +codesign -vvvv "${DEST}/dist/LBRYURIHandler.app" -cd $dest -python setup.py py2app &>/dev/null +# why isn't certifi installed automatically by setup_app.py? +pip install certifi +python setup_app.py py2app echo "Moving in correct libgmp" -rm "${dest}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" -cp "${dest}/libgmp.10.dylib" "${dest}/dist/LBRY.app/Contents/Frameworks" +rm "${DEST}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" +cp "${DEST}/libgmp.10.dylib" "${DEST}/dist/LBRY.app/Contents/Frameworks" echo "Removing i386 libraries" remove_arch () { - lipo -output build/lipo.tmp -remove "$1" "$2" && mv build/lipo.tmp "$2" + if [[ `lipo "$2" -verify_arch "$1"` ]]; then + lipo -output build/lipo.tmp -remove "$1" "$2" && mv build/lipo.tmp "$2" + fi } -for i in dist/LBRY.app/Contents/Resources/lib/python2.7/lib-dynload/* ; do - remove_arch i386 ${i} + +for i in `find dist/LBRY.app/Contents/Resources/lib/python2.7/lib-dynload/ -name "*.so"`; do + remove_arch i386 $i done + echo "Moving LBRYURIHandler.app into LBRY.app" -mv "${LBRY}/dist/LBRYURIHandler.app" "${dest}/dist/LBRY.app/Contents/Resources" +mv "${DEST}/dist/LBRYURIHandler.app" "${DEST}/dist/LBRY.app/Contents/Resources" echo "Signing LBRY.app" -codesign -s "${LBRY_DEVELOPER_ID}" -f "${dest}/dist/LBRY.app/Contents/Frameworks/Python.framework/Versions/2.7" -codesign -s "${LBRY_DEVELOPER_ID}" -f "${dest}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" -codesign -s "${LBRY_DEVELOPER_ID}" -f "${dest}/dist/LBRY.app/Contents/MacOS/python" -codesign -s "${LBRY_DEVELOPER_ID}" -f "${dest}/dist/LBRY.app/Contents/MacOS/LBRY" -codesign -vvvv "${dest}/dist/LBRY.app" +codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRY.app/Contents/Frameworks/Python.framework/Versions/2.7" +codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" +codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRY.app/Contents/MacOS/python" +# adding deep here as well because of subcomponent issues +codesign --deep -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRY.app/Contents/MacOS/LBRY" +codesign -vvvv "${DEST}/dist/LBRY.app" rm -rf $tmp mv dist/LBRY.app LBRY.app From 52625b710b1b0370e9f7fbe95f82ef5b89dce953 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 20 Jun 2016 22:55:55 -0500 Subject: [PATCH 264/462] trying to release linux and OSX files --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9c3dc638f..c29c8252f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,9 @@ script: deploy: provider: releases - file: "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" + file: + - "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" + - "${TRAVIS_BUILD_DIR}/packaging/osx/lbry-osx-app/LBRY.app" skip_cleanup: true on: tags: true From 4c2b3e157cb83e5acc503952d5fa489d72da9892 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 20 Jun 2016 22:57:04 -0500 Subject: [PATCH 265/462] deactivate version check so that I can do a test release --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c29c8252f..e1ad1140c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh -t; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app && ./setup_app.sh && cd $TRAVIS_BUILD_DIR; fi # fail the build if this is a build for a tag and we don't have the versions matching - - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi + #- if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi deploy: provider: releases From cdb311a5018bdaeaa7f8b68e8e96ab17d6bff3e0 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 21 Jun 2016 00:19:40 -0500 Subject: [PATCH 266/462] build dmg from LBRY.app --- .travis.yml | 2 +- packaging/osx/lbry-osx-app/setup_app.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e1ad1140c..17b041f47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ deploy: provider: releases file: - "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" - - "${TRAVIS_BUILD_DIR}/packaging/osx/lbry-osx-app/LBRY.app" + - "${TRAVIS_BUILD_DIR}/packaging/osx/lbry-osx-app/`python setup.py --name`.`python setup.py -V`.dmg" skip_cleanup: true on: tags: true diff --git a/packaging/osx/lbry-osx-app/setup_app.sh b/packaging/osx/lbry-osx-app/setup_app.sh index b8c601522..1403a6d64 100755 --- a/packaging/osx/lbry-osx-app/setup_app.sh +++ b/packaging/osx/lbry-osx-app/setup_app.sh @@ -22,6 +22,8 @@ else cd ${TRAVIS_BUILD_DIR} LBRY=${TRAVIS_BUILD_DIR} fi +NAME=`python setup.py --name` +VERSION=`python setup.py -V` python setup.py install echo "Building URI Handler" @@ -71,3 +73,4 @@ codesign -vvvv "${DEST}/dist/LBRY.app" rm -rf $tmp mv dist/LBRY.app LBRY.app rm -rf dist +hdiutil create "${NAME}.${VERSION}.dmg" -volname lbry -srcfolder LBRY.app From f2e66abe67062a6cce42f5f65816d6e923e8b3ed Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 21 Jun 2016 01:08:06 -0500 Subject: [PATCH 267/462] can there be two deploys? --- .travis.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17b041f47..5f7539d0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,12 +40,22 @@ script: deploy: provider: releases - file: - - "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" - - "${TRAVIS_BUILD_DIR}/packaging/osx/lbry-osx-app/`python setup.py --name`.`python setup.py -V`.dmg" + file: "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" skip_cleanup: true on: tags: true + condition: "$TRAVIS_OS_NAME = linux" + # this is the oauth token for the lbry-ci user + api_key: + secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc= + +deploy: + provider: releases + file: "${TRAVIS_BUILD_DIR}/packaging/osx/lbry-osx-app/`python setup.py --name`.`python setup.py -V`.dmg" + skip_cleanup: true + on: + tags: true + condition: "$TRAVIS_OS_NAME = osx" # this is the oauth token for the lbry-ci user api_key: secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc= From 30afa43ed456b9a6f2523f1e40e30026270e6fc4 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 21 Jun 2016 01:24:34 -0500 Subject: [PATCH 268/462] how about two providers? --- .travis.yml | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5f7539d0c..a68729866 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,26 +39,24 @@ script: #- if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi deploy: - provider: releases - file: "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_OS_NAME = linux" - # this is the oauth token for the lbry-ci user - api_key: - secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc= - -deploy: - provider: releases - file: "${TRAVIS_BUILD_DIR}/packaging/osx/lbry-osx-app/`python setup.py --name`.`python setup.py -V`.dmg" - skip_cleanup: true - on: - tags: true - condition: "$TRAVIS_OS_NAME = osx" - # this is the oauth token for the lbry-ci user - api_key: - secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc= + - provider: releases + file: "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" + skip_cleanup: true + on: + tags: true + condition: "$TRAVIS_OS_NAME = linux" + # this is the oauth token for the lbry-ci user + api_key: + secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc= + - provider: releases + file: "${TRAVIS_BUILD_DIR}/packaging/osx/lbry-osx-app/`python setup.py --name`.`python setup.py -V`.dmg" + skip_cleanup: true + on: + tags: true + condition: "$TRAVIS_OS_NAME = osx" + # this is the oauth token for the lbry-ci user + api_key: + secure: nKdWGROnLNodx9k9nWvq2wezkPvSVL8zF63qjPXjhdOe9kCUbcLp7WnFDYpn9EJj4Pofq/ejeCHwjA+5x7JUP3Szk7SlJV61B4c/5hl64rl7oSKOoKskjdE2jaOG3CJuOUrh0yQ59U3vMMABcsnw/wJaCIuu/erdPIm8g8R+stu1YHOGtl5Y9WiW+zLJn2vc3GooV1TWtki9EnrmFfw0Vrqc4RMVMFB1ojE7ggrK1LIwcmGSbLIYzker1ZRz8SCy+84sGk4//V+2i2NNiz5AkPuG7BBGrU2twE9nD23IlruJAdVdi71P3ytAmi0kKyvxIU4VeNaqyTk9zeL5IB9J5IIgvekHgKcsKhFUZ6QcXT1Xfxl4ELftvWCTHWiewnXFdqLcG9GZiUaE6+7wdalwDAP3tqS2emiibetlBZERHR+RMR00ej+1MBYWGMlTse/0Tglndv0a2qqgAJYLKPRT02hTRYGxZ1MrJe+WGnChRmzwgLVTIgZuiDciFOahN0TYGSORk6OpnZBsxvpzSqDw5UDJx0BmbJ1xMNDFbOs8ubZ9yIpB9yNMGw66FPacOF61XNYnmA68ILC28UtOFKuuHLrUPbM5JmQkDVhtTfFbBnyHefyCLAL4MHvJJKGi1oaOXjYaJ/J095h636/kQ0cHHuVMgoWUQZOQ44xRAz7tMuc= env: global: From c3aa5b051597b8a25c1af57f276de7d2f5c911e4 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 21 Jun 2016 01:36:03 -0500 Subject: [PATCH 269/462] restore version check --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a68729866..b5dbba148 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh -t; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app && ./setup_app.sh && cd $TRAVIS_BUILD_DIR; fi # fail the build if this is a build for a tag and we don't have the versions matching - #- if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi + - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi deploy: - provider: releases From 2bac0a25e0bec3ed1ccda498ebf5047c2ece756d Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 21 Jun 2016 13:28:29 -0500 Subject: [PATCH 270/462] install pyobjc dependencies --- packaging/osx/lbry-osx-app/setup_app.py | 8 +++++--- packaging/osx/lbry-osx-app/setup_app.sh | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packaging/osx/lbry-osx-app/setup_app.py b/packaging/osx/lbry-osx-app/setup_app.py index f6d2a9145..1fdbe2ab7 100644 --- a/packaging/osx/lbry-osx-app/setup_app.py +++ b/packaging/osx/lbry-osx-app/setup_app.py @@ -16,8 +16,10 @@ OPTIONS = { 'CFBundleIdentifier': 'io.lbry.LBRY', 'LSUIElement': True, }, - 'packages': ['lbrynet', 'lbryum', 'requests', 'unqlite', 'certifi', - 'pkg_resources', 'json', 'jsonrpc', 'seccure',], + 'packages': [ + 'lbrynet', 'lbryum', 'requests', 'unqlite', 'certifi', + 'pkg_resources', 'json', 'jsonrpc', 'seccure', + ], } @@ -26,4 +28,4 @@ setup( app=APP, options={'py2app': OPTIONS}, data_files=DATA_FILES, -) \ No newline at end of file +) diff --git a/packaging/osx/lbry-osx-app/setup_app.sh b/packaging/osx/lbry-osx-app/setup_app.sh index 1403a6d64..68c6ea9f3 100755 --- a/packaging/osx/lbry-osx-app/setup_app.sh +++ b/packaging/osx/lbry-osx-app/setup_app.sh @@ -38,8 +38,7 @@ codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/ codesign --deep -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/MacOS/LBRYURIHandler" codesign -vvvv "${DEST}/dist/LBRYURIHandler.app" -# why isn't certifi installed automatically by setup_app.py? -pip install certifi +pip install certifi pyobjc-core pyobjc-framework-Cocoa pyobjc-framework-CFNetwork python setup_app.py py2app echo "Moving in correct libgmp" From 56305c4edeea3fc7333b70aed166ccdd9339ac7f Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 21 Jun 2016 15:05:15 -0500 Subject: [PATCH 271/462] allow tags that start with test to pass --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b5dbba148..15d89ccf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,8 +35,8 @@ before_script: script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then bash packaging/ubuntu/ubuntu_package_setup.sh -t; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then cd packaging/osx/lbry-osx-app && ./setup_app.sh && cd $TRAVIS_BUILD_DIR; fi - # fail the build if this is a build for a tag and we don't have the versions matching - - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi + # fail the build if this is a build for a tag and we don't have the versions matching; allow tags that start with 'test' to pass + - if [[ -n "${TRAVIS_TAG}" ]]; then if [[ "${TRAVIS_TAG}" == test* ]] || [[ "v`python setup.py -V`" = "${TRAVIS_TAG}" ]]; then true; else false; fi; fi deploy: - provider: releases From 16954c56266541521e337b638a9859ab699cfc35 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Tue, 21 Jun 2016 16:54:22 -0400 Subject: [PATCH 272/462] add FAQ --- FAQ.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 FAQ.md diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 000000000..85dc5f99e --- /dev/null +++ b/FAQ.md @@ -0,0 +1,77 @@ +#### Getting LBRY for development + +Q: How do I get lbry for command line? + +A: In order to run lbry from command line, you need more than the packaged app/deb. + +######On OS X + +You can install LBRY command line by running `curl -sL https://rawgit.com/lbryio/lbry-setup/master/lbry_setup_osx.sh | sudo bash` in a terminal. This script will install lbrynet and its dependancies, as well as the app. + +######On Linux + +On Ubuntu or Mint you can install the prerequisites and lbrynet by running + + sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip + git clone https://github.com/lbryio/lbry.git + cd lbry + sudo python setup.py install + +#### Using LBRY + +Q: How do I run lbry from command line? + +A: The command is `lbrynet-daemon` + +*********** + +Q: How do I stop lbry from the command line? + +A: You can ctrl-c or run `stop-lbrynet-daemon` + +*********** + +Q: How do I run lbry with lbrycrdd (the blockchain node application)? + +A: Start lbry with the --wallet flag set: `lbrynet-daemon --wallet=lbrycrd` + +Note: when you change the wallet it is persistant until you specify you want to use another wallet - lbryum - with the --wallet flag again. + +*********** + +Q: Where are all the behind the scenes files? + +A: On linux, the relevant directories are `~/.lbrynet`, `~/.lbrycrd`, and `~/.lbryum`, depending on which wallets you've used. On OS X, the folders of interest are `~/Library/Application Support/LBRY`, `~/.lbrycrd` and `~/.lbryum`, also depending on which wallets you've used. + +*********** + +Q: How do I specify a web-UI to use? + +A: If the files for the UI you'd like to use are storred locally on your computer, start lbry with the --ui flag: `lbrynet-daemon --ui=/full/path/to/ui/files/root/folder` + +Note, once set with the UI flag the given UI will be cached by lbry and used as the default going forward. Also, it will only successfully load a UI if it contains a conforming requirements.txt file to specify required lbrynet and lbryum versions. [Here][https://github.com/lbryio/lbry-web-ui/blob/master/dist/requirements.txt] is an example requirements.txt file. + +To reset your ui to pull from lbryio, or to try a UI still in development, run lbry with the --branch flag: `lbrynet=daemon --branch=master` + +*********** + +Q: How do I see the list of API functions I can call, and how do I call them? + +A: Here is an example script to get the documentation for the various API calls. To use any of the functions displayed, just provide any specified arguments in a dictionary. + + import sys + from jsonrpc.proxy import JSONRPCProxy + + try: + from lbrynet.conf import API_CONNECTION_STRING + except: + print "You don't have lbrynet installed!" + sys.exit(0) + + api = JSONRPCProxy.from_url(API_CONNECTION_STRING) + if not api.is_running(): + print api.daemon_status() + else: + for func in api.help(): + print "%s:\n%s" % (func, api.help({'function': func})) + From a8e2dad6d02766da388f716706f93e305d6c9bce Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Tue, 21 Jun 2016 16:55:52 -0400 Subject: [PATCH 273/462] fix link --- FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index 85dc5f99e..dfcc9119e 100644 --- a/FAQ.md +++ b/FAQ.md @@ -49,7 +49,7 @@ Q: How do I specify a web-UI to use? A: If the files for the UI you'd like to use are storred locally on your computer, start lbry with the --ui flag: `lbrynet-daemon --ui=/full/path/to/ui/files/root/folder` -Note, once set with the UI flag the given UI will be cached by lbry and used as the default going forward. Also, it will only successfully load a UI if it contains a conforming requirements.txt file to specify required lbrynet and lbryum versions. [Here][https://github.com/lbryio/lbry-web-ui/blob/master/dist/requirements.txt] is an example requirements.txt file. +Note, once set with the UI flag the given UI will be cached by lbry and used as the default going forward. Also, it will only successfully load a UI if it contains a conforming requirements.txt file to specify required lbrynet and lbryum versions. [Here](https://github.com/lbryio/lbry-web-ui/blob/master/dist/requirements.txt) is an example requirements.txt file. To reset your ui to pull from lbryio, or to try a UI still in development, run lbry with the --branch flag: `lbrynet=daemon --branch=master` From 79adc677a117d06ac4e93cfecd5e8c56cc06fc91 Mon Sep 17 00:00:00 2001 From: Jack Robison Date: Tue, 21 Jun 2016 16:58:08 -0400 Subject: [PATCH 274/462] add --log-to-console --- FAQ.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/FAQ.md b/FAQ.md index dfcc9119e..6336272da 100644 --- a/FAQ.md +++ b/FAQ.md @@ -45,6 +45,12 @@ A: On linux, the relevant directories are `~/.lbrynet`, `~/.lbrycrd`, and `~/.lb *********** +Q: How can I see the log in the console? + +A: Run lbry with the --log-to-console flag set: `lbrynet-daemon --log-to-console` + +*********** + Q: How do I specify a web-UI to use? A: If the files for the UI you'd like to use are storred locally on your computer, start lbry with the --ui flag: `lbrynet-daemon --ui=/full/path/to/ui/files/root/folder` @@ -59,6 +65,8 @@ Q: How do I see the list of API functions I can call, and how do I call them? A: Here is an example script to get the documentation for the various API calls. To use any of the functions displayed, just provide any specified arguments in a dictionary. +Note: the lbry api can only be used while either the app or lbrynet-daemon command line are running + import sys from jsonrpc.proxy import JSONRPCProxy From 2d882a11a5949011f538b76fcf7f7f4bca0680fa Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 21 Jun 2016 16:21:46 -0500 Subject: [PATCH 275/462] No longer swap libgmp The correct libgmp builds on travis so we don't need to swap it out anymore --- packaging/osx/lbry-osx-app/setup_app.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packaging/osx/lbry-osx-app/setup_app.sh b/packaging/osx/lbry-osx-app/setup_app.sh index 68c6ea9f3..bd8318066 100755 --- a/packaging/osx/lbry-osx-app/setup_app.sh +++ b/packaging/osx/lbry-osx-app/setup_app.sh @@ -41,10 +41,6 @@ codesign -vvvv "${DEST}/dist/LBRYURIHandler.app" pip install certifi pyobjc-core pyobjc-framework-Cocoa pyobjc-framework-CFNetwork python setup_app.py py2app -echo "Moving in correct libgmp" -rm "${DEST}/dist/LBRY.app/Contents/Frameworks/libgmp.10.dylib" -cp "${DEST}/libgmp.10.dylib" "${DEST}/dist/LBRY.app/Contents/Frameworks" - echo "Removing i386 libraries" remove_arch () { From da13192cfec55d47671f6d89fce0dcea5ec48d5e Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 22 Jun 2016 20:27:18 -0500 Subject: [PATCH 276/462] Use a more up-to-date OSX version Also related: - install framework version of python - ensure unencrypted keys are never accidently added to repo --- .travis.yml | 12 +++--------- packaging/osx/certs/.gitignore | 2 ++ packaging/osx/lbry-osx-app/setup_app.py | 1 - packaging/osx/lbry-osx-app/setup_app.sh | 5 +++++ 4 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 packaging/osx/certs/.gitignore diff --git a/.travis.yml b/.travis.yml index 15d89ccf7..1eb916926 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,17 +12,11 @@ matrix: # python 2.7 is broken on osx on travis, so follow we have to specify the installation ourselves # https://github.com/travis-ci/travis-ci/issues/2312#issuecomment-195620855 language: generic + osx_image: xcode7.3 before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew install python; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo pip install --upgrade pip virtualenv; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then virtualenv $HOME/venv && source $HOME/venv/bin/activate; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated gmp || brew upgrade gmp; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew outdated openssl || brew upgrade openssl; fi - # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install git+https://github.com/metachris/py2app; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install jsonrpc; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install -r requirements.txt; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade && brew install python --framework; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gmp; fi install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./packaging/travis/install_dependencies_and_run_tests.sh; fi diff --git a/packaging/osx/certs/.gitignore b/packaging/osx/certs/.gitignore new file mode 100644 index 000000000..9746cae2b --- /dev/null +++ b/packaging/osx/certs/.gitignore @@ -0,0 +1,2 @@ +dist.cer +dist.p12 diff --git a/packaging/osx/lbry-osx-app/setup_app.py b/packaging/osx/lbry-osx-app/setup_app.py index 1fdbe2ab7..28e0ca981 100644 --- a/packaging/osx/lbry-osx-app/setup_app.py +++ b/packaging/osx/lbry-osx-app/setup_app.py @@ -10,7 +10,6 @@ DATA_FILES = [] DATA_FILES.append('app.icns') OPTIONS = { - # 'argv_emulation': True, 'iconfile': ICON_PATH, 'plist': { 'CFBundleIdentifier': 'io.lbry.LBRY', diff --git a/packaging/osx/lbry-osx-app/setup_app.sh b/packaging/osx/lbry-osx-app/setup_app.sh index bd8318066..e1d0dd43d 100755 --- a/packaging/osx/lbry-osx-app/setup_app.sh +++ b/packaging/osx/lbry-osx-app/setup_app.sh @@ -8,6 +8,10 @@ tmp="${DEST}/build" rm -rf build dist LBRY.app +# the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app +pip install git+https://github.com/metachris/py2app +pip install jsonrpc + mkdir -p $tmp cd $tmp @@ -24,6 +28,7 @@ else fi NAME=`python setup.py --name` VERSION=`python setup.py -V` +pip install -r requirements.txt python setup.py install echo "Building URI Handler" From ee889700756e5dadf1ac75648a377186c44dcf59 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Thu, 23 Jun 2016 11:58:11 -0500 Subject: [PATCH 277/462] use the python installer from python.org The python from brew links to brew's version of openssl while the python one works with what osx has by default. We need the later or else the resultiing app bundle only works on machines that have brew's version of openssl. --- .travis.yml | 3 +-- packaging/osx/lbry-osx-app/setup_app.sh | 2 ++ packaging/travis/setup_osx.sh | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100755 packaging/travis/setup_osx.sh diff --git a/.travis.yml b/.travis.yml index 1eb916926..28f830f8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,7 @@ matrix: osx_image: xcode7.3 before_install: - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew upgrade && brew install python --framework; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install gmp; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/travis/setup_osx.sh; fi install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./packaging/travis/install_dependencies_and_run_tests.sh; fi diff --git a/packaging/osx/lbry-osx-app/setup_app.sh b/packaging/osx/lbry-osx-app/setup_app.sh index e1d0dd43d..8be935514 100755 --- a/packaging/osx/lbry-osx-app/setup_app.sh +++ b/packaging/osx/lbry-osx-app/setup_app.sh @@ -29,6 +29,8 @@ fi NAME=`python setup.py --name` VERSION=`python setup.py -V` pip install -r requirements.txt +# not totally sure if pyOpenSSl is needed (JIE) +pip install pyOpenSSL python setup.py install echo "Building URI Handler" diff --git a/packaging/travis/setup_osx.sh b/packaging/travis/setup_osx.sh new file mode 100755 index 000000000..aeae7824b --- /dev/null +++ b/packaging/travis/setup_osx.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +set -euo pipefail +set -o xtrace + +wget https://www.python.org/ftp/python/2.7.11/python-2.7.11-macosx10.6.pkg +sudo installer -pkg python-2.7.11-macosx10.6.pkg -target / +pip install -U pip +brew install gmp + From f89486f5fe364e493845c09abac5e5a206b32b3c Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 24 Jun 2016 13:12:27 -0400 Subject: [PATCH 278/462] port and import name --- lbrynet/core/LBRYcrdWallet.py | 4 ++-- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- lbrynet/lbrynet_daemon/LBRYUIManager.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 17fd0e229..8807e757c 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -634,7 +634,7 @@ class LBRYcrdWallet(LBRYWallet): def _get_rpc_conf(self): settings = {"username": "rpcuser", "password": "rpcpassword", - "rpc_port": 8332} + "rpc_port": 9245} if os.path.exists(self.wallet_conf): conf = open(self.wallet_conf) for l in conf: @@ -846,7 +846,7 @@ class LBRYcrdWallet(LBRYWallet): @_catch_connection_error def _get_nametrie_rpc(self): rpc_conn = self._get_rpc_conn() - return rpc_conn.getnametrie() + return rpc_conn.getclaimtrie() @_catch_connection_error def _get_wallet_balance_rpc(self): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index ac157bd71..aa47f021d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -24,7 +24,7 @@ from appdirs import user_data_dir from urllib2 import urlopen from lbrynet import __version__ as lbrynet_version -from lbryum.version import ELECTRUM_VERSION as lbryum_version +from lbryum.version import LBRYUM_VERSION as lbryum_version from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index e7c405352..ce36aaf01 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -10,7 +10,7 @@ from twisted.web import static from twisted.internet import defer from lbrynet.conf import DEFAULT_UI_BRANCH, LOG_FILE_NAME from lbrynet import __version__ as lbrynet_version -from lbryum.version import ELECTRUM_VERSION as lbryum_version +from lbryum.version import LBRYUM_VERSION as lbryum_version from zipfile import ZipFile from appdirs import user_data_dir From 8d5c315817c7bfc08dab5294d427b9bbff9e9014 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Mon, 27 Jun 2016 11:21:30 -0500 Subject: [PATCH 279/462] add caching for travis --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 15d89ccf7..808010841 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,11 @@ matrix: # https://github.com/travis-ci/travis-ci/issues/2312#issuecomment-195620855 language: generic +cache: + directories: + - $HOME/.cache/pip + - $HOME/Library/Caches/pip + before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew install python; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo pip install --upgrade pip virtualenv; fi From 7cc3e9d5eff93765395b26e2a19bad7e00b4226e Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 27 Jun 2016 17:07:59 -0400 Subject: [PATCH 280/462] publish updates -adds a base set of metadata fields required for results to be rendered in conf.py, including language and content-type -dont support old style claims on the new blockchain --- lbrynet/__init__.py | 2 +- lbrynet/conf.py | 12 ++-- lbrynet/core/LBRYcrdWallet.py | 88 ++++++++++-------------- lbrynet/lbrynet_daemon/LBRYDaemon.py | 62 ++++++----------- lbrynet/lbrynet_daemon/LBRYDownloader.py | 31 ++++----- lbrynet/lbrynet_daemon/LBRYPublisher.py | 53 ++++++-------- 6 files changed, 98 insertions(+), 150 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 7bb1777c8..655afaa08 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -version = (0, 2, 5) +version = (0, 2, 6) __version__ = ".".join([str(x) for x in version]) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 116a395f2..9ead15f4d 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -17,9 +17,9 @@ MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .05 # points/1000 infos MAX_CONNECTIONS_PER_STREAM = 5 KNOWN_DHT_NODES = [('104.236.42.182', 4000), - ('lbryum1.lbry.io', 4444), - ('lbryum2.lbry.io', 4444), - ('lbryum3.lbry.io', 4444)] + ('lbrynet1.lbry.io', 4444), + ('lbrynet2.lbry.io', 4444), + ('lbrynet3.lbry.io', 4444)] POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' #POINTTRADER_SERVER = 'http://127.0.0.1:2424' @@ -35,7 +35,7 @@ API_PORT = 5279 ICON_PATH = "app.icns" APP_NAME = "LBRY" API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRESS) -UI_ADDRESS = "http://" + API_INTERFACE + ":" + str(API_PORT) +UI_ADDRESS = "http://%s:%i" % (API_INTERFACE, API_PORT) PROTOCOL_PREFIX = "lbry" DEFAULT_WALLET = "lbryum" @@ -46,3 +46,7 @@ DEFAULT_MAX_KEY_FEE = 100.0 DEFAULT_SEARCH_TIMEOUT = 3.0 DEFAULT_CACHE_TIME = 3600 DEFAULT_UI_BRANCH = "master" + +SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih'] +BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type'] +OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey'] \ No newline at end of file diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 8807e757c..92f8d1fdf 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -4,6 +4,7 @@ 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 lbryum import SimpleConfig, Network from lbryum.bitcoin import COIN, TYPE_ADDRESS @@ -318,68 +319,51 @@ class LBRYWallet(object): r_dict = {} if 'value' in result: value = result['value'] + try: value_dict = json.loads(value) except (ValueError, TypeError): return Failure(InvalidStreamInfoError(name)) - known_fields = ['stream_hash', 'name', 'description', 'key_fee', 'key_fee_address', 'thumbnail', - 'content_license', 'sources', 'fee', 'author'] - known_sources = ['lbry_sd_hash', 'btih', 'url'] - known_fee_types = {'LBC': ['amount', 'address']} - for field in known_fields: - if field in value_dict: - if field == 'sources': - for source in known_sources: - if source in value_dict[field]: - if source == 'lbry_sd_hash': - r_dict['stream_hash'] = value_dict[field][source] - else: - r_dict[source] = value_dict[field][source] - elif field == 'fee': - fee = value_dict['fee'] - if 'type' in fee: - if fee['type'] in known_fee_types: - fee_fields = known_fee_types[fee['type']] - if all([f in fee for f in fee_fields]): - r_dict['key_fee'] = fee['amount'] - r_dict['key_fee_address'] = fee['address'] - else: - for f in ['key_fee', 'key_fee_address']: - if f in r_dict: - del r_dict[f] - else: - r_dict[field] = value_dict[field] - if 'stream_hash' in r_dict and 'txid' in result: - d = self._save_name_metadata(name, r_dict['stream_hash'], str(result['txid'])) - else: - d = defer.succeed(True) + 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] + + if 'txid' in result: + d = self._save_name_metadata(name, r_dict['sources']['lbry_sd_hash'], str(result['txid'])) d.addCallback(lambda _: r_dict) return d elif 'error' in result: log.warning("Got an error looking up a name: %s", result['error']) - return Failure(UnknownNameError(name)) + return Failure(UnknownNameError(name)) + else: + log.warning("Got an error looking up a name: %s", json.dumps(result)) + return Failure(UnknownNameError(name)) - def claim_name(self, name, sd_hash, amount, description=None, key_fee=None, - key_fee_address=None, thumbnail=None, content_license=None, author=None, sources=None): - value = {"sources": {'lbry_sd_hash': sd_hash}} - if description is not None: - value['description'] = description - if key_fee is not None and key_fee_address is not None: - value['fee'] = {'type': 'LBC', 'amount': key_fee, 'address': key_fee_address} - if thumbnail is not None: - value['thumbnail'] = thumbnail - if content_license is not None: - value['content_license'] = content_license - if author is not None: - value['author'] = author - if isinstance(sources, dict): - sources['lbry_sd_hash'] = sd_hash - value['sources'] = sources + 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") + for k in BASE_METADATA_FIELDS: + if k not in metadata: + return defer.fail("Missing required field '%s'" % k) + value[k] = metadata[k] + for k in metadata: + if k not in BASE_METADATA_FIELDS: + value[k] = metadata[k] + if fee is not None: + if "LBC" in fee: + value['fee'] = {'LBC': {'amount': fee['LBC']['amount'], 'address': fee['LBC']['address']}} - d = self._send_name_claim(name, json.dumps(value), amount) + d = self._send_name_claim(name, json.dumps(value), bid) def _save_metadata(txid): - d = self._save_name_metadata(name, sd_hash, txid) + d = self._save_name_metadata(name, value['sources']['lbry_sd_hash'], txid) d.addCallback(lambda _: txid) return d @@ -409,7 +393,7 @@ class LBRYWallet(object): def abandon(results): if results[0][0] and results[1][0]: address = results[0][1] - amount = results[1][1] + amount = float(results[1][1]) return self._send_abandon(txid, address, amount) elif results[0][0] is False: return defer.fail(Failure(ValueError("Couldn't get a new address"))) @@ -826,7 +810,7 @@ class LBRYcrdWallet(LBRYWallet): @_catch_connection_error def _send_abandon_rpc(self, txid, address, amount): rpc_conn = self._get_rpc_conn() - return rpc_conn.abandonname(txid, address, amount) + return rpc_conn.abandonclaim(txid, address, amount) @_catch_connection_error def _get_blockchain_info_rpc(self): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index aa47f021d..0a90bd92b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1151,12 +1151,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _add_key_fee(data_cost): d = self._resolve_name(name) - d.addCallback(lambda info: data_cost + info['key_fee'] if 'key_fee' in info.keys() else data_cost) + d.addCallback(lambda info: data_cost if 'fee' not in info else data_cost + info['fee']['LBC']['amount']) return d d = self._resolve_name(name) - d.addCallback(lambda info: info['stream_hash'] if isinstance(info['stream_hash'], str) - else info['stream_hash']['sd_hash']) + d.addCallback(lambda info: info['sources']['lbry_sd_hash']) d.addCallback(lambda sd_hash: download_sd_blob(self.session, sd_hash, self.blob_request_payment_rate_manager)) d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) @@ -1170,10 +1169,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _get_lbry_file_by_uri(self, name): def _get_file(stream_info): - if isinstance(stream_info['stream_hash'], str) or isinstance(stream_info['stream_hash'], unicode): - sd = stream_info['stream_hash'] - elif isinstance(stream_info['stream_hash'], dict): - sd = stream_info['stream_hash']['sd_hash'] + sd = stream_info['sources']['lbry_sd_hash'] for l in self.lbry_file_manager.lbry_files: if l.sd_hash == sd: @@ -1791,15 +1787,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): for r in results: t = {} t.update(r[0]) - if 'name' in r[1].keys(): - r[1]['stream_name'] = r[1]['name'] - del r[1]['name'] + if not 'thumbnail' in r[1].keys(): + r[1]['thumbnail'] = "img/Free-speech-flag.svg" t.update(r[1]) t['cost_est'] = r[2] - if not 'thumbnail' in t.keys(): - t['thumbnail'] = "img/Free-speech-flag.svg" consolidated_results.append(t) # log.info(str(t)) + return consolidated_results log.info('[' + str(datetime.now()) + '] Search nametrie: ' + search) @@ -1845,46 +1839,30 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_publish(self, p): """ - Make a new name claim + Make a new name claim and publish associated data to lbrynet Args: 'name': name to be claimed, string 'file_path': path to file to be associated with name, string 'bid': amount of credits to commit in this claim, float - optional 'author': author, string - optional 'title': title, description - optional 'description': content description, string - optional 'thumbnail': thumbnail image url - optional 'key_fee': key fee to be paid to publisher, float (default 0.0) - optional 'key_fee_address': address for key fee to be sent to, string (defaults on new address) - optional 'content_license': content license string - optional 'sources': alternative sources dict, keys 'lbry_sd_hash', 'btih', 'url' + 'metadata': metadata dictionary + optional 'fee' Returns: - Confirmation message + Claim txid """ - - metadata_fields = ["name", "file_path", "bid", "author", "title", - "description", "thumbnail", "key_fee", "key_fee_address", - "content_license", "sources"] - - for k in metadata_fields: - if k not in p.keys(): - p[k] = None + # start(self, name, file_path, bid, metadata, fee=None, sources=None): + name = p['name'] + bid = p['bid'] + file_path = p['file_path'] + metadata = p['metadata'] + if 'fee' in p: + fee = p['fee'] + else: + fee = None pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - d = pub.start(p['name'], - p['file_path'], - p['bid'], - title=p['title'], - description=p['description'], - thumbnail=p['thumbnail'], - key_fee=p['key_fee'], - key_fee_address=p['key_fee_address'], - content_license=p['content_license'], - author=p['author'], - sources=p['sources']) - + d = pub.start(name, file_path, bid, metadata, fee) 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 a134c44e5..ba59227c6 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -42,15 +42,14 @@ log.addHandler(handler) log.setLevel(logging.INFO) class GetStream(object): - def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, pay_key=True, data_rate=0.5, - timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None): + def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, data_rate=0.5, + timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None): self.wallet = wallet self.resolved_name = None self.description = None self.key_fee = None self.key_fee_address = None self.data_rate = data_rate - self.pay_key = pay_key self.name = None self.file_name = file_name self.session = session @@ -79,7 +78,7 @@ class GetStream(object): self.finished.callback((self.stream_hash, self.download_path)) elif self.timeout_counter >= self.timeout: - log.info("Timeout downloading lbry://" + self.resolved_name + ", " + str(self.stream_info)) + log.info("Timeout downloading lbry://%s" % self.resolved_name) self.checker.stop() self.d.cancel() self.code = STREAM_STAGES[4] @@ -88,28 +87,24 @@ class GetStream(object): def start(self, stream_info, name): self.resolved_name = name self.stream_info = stream_info - if 'stream_hash' in self.stream_info.keys(): - self.stream_hash = self.stream_info['stream_hash'] - elif 'sources' in self.stream_info.keys(): + 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.keys(): + if 'description' in self.stream_info: self.description = self.stream_info['description'] - if 'key_fee' in self.stream_info.keys(): - self.key_fee = float(self.stream_info['key_fee']) - if 'key_fee_address' in self.stream_info.keys(): - self.key_fee_address = self.stream_info['key_fee_address'] + if 'fee' in self.stream_info: + if 'LBC' in self.stream_info['fee']: + self.key_fee = float(self.stream_info['fee']['LBC']['amount']) + self.key_fee_address = self.stream_info['fee']['LBC']['address'] else: self.key_fee_address = None else: self.key_fee = None self.key_fee_address = None if self.key_fee > self.max_key_fee: - if self.pay_key: - log.info("Key fee (" + str(self.key_fee) + ") above limit of " + str( - self.max_key_fee) + ", didn't download lbry://" + str(self.resolved_name)) - return defer.fail(None) + 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 @@ -145,7 +140,7 @@ class GetStream(object): reserved_points = self.wallet.reserve_points(self.key_fee_address, self.key_fee) if reserved_points is None: return defer.fail(InsufficientFundsError()) - log.info("Key fee: " + str(self.key_fee) + " | " + str(self.key_fee_address)) + log.info("Key fee: %f --> %s" % (self.key_fee, self.key_fee_address)) return self.wallet.send_points_to_address(reserved_points, self.key_fee) return defer.succeed(None) @@ -155,5 +150,5 @@ class GetStream(object): d = defer.Deferred() self.downloader = downloader self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d.addCallback(lambda _: log.info("[" + str(datetime.now()) + "] Downloading " + str(self.stream_hash) + " --> " + str(self.download_path))) + d.addCallback(lambda _: log.info("[%s] Downloading %s --> %s" % (datetime.now(), self.stream_hash, self.file_name))) d.addCallback(lambda _: self.downloader.start()) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index f7fd64f57..084731a87 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -1,4 +1,5 @@ import logging +import mimetypes import os import sys @@ -36,42 +37,27 @@ class Publisher(object): self.received_file_name = False self.file_path = None self.file_name = None - self.thumbnail = None - self.title = None self.publish_name = None self.bid_amount = None - self.key_fee = None - self.key_fee_address = None - self.key_fee_address_chosen = False - self.description = None self.verified = False self.lbry_file = None - self.sd_hash = None - self.tx_hash = None - self.content_license = None - self.author = None - self.sources = None + self.txid = None + self.sources = {} + self.fee = None - def start(self, name, file_path, bid, title=None, description=None, thumbnail=None, - key_fee=None, key_fee_address=None, content_license=None, author=None, sources=None): + def start(self, name, file_path, bid, metadata, fee=None, sources={}): def _show_result(): - message = "[" + str(datetime.now()) + "] Published " + self.file_name + " --> lbry://" + \ - str(self.publish_name) + " with txid: " + str(self.tx_hash) + + message = "[%s] Published %s --> lbry://%s txid: %s" % (datetime.now(), self.file_name, self.publish_name, self.txid) log.info(message) - return defer.succeed(self.tx_hash) + return defer.succeed(self.txid) self.publish_name = name self.file_path = file_path self.bid_amount = bid - self.title = title - self.description = description - self.thumbnail = thumbnail - self.key_fee = key_fee - self.key_fee_address = key_fee_address - self.content_license = content_license - self.author = author - self.sources = sources + self.fee = fee + self.metadata = metadata d = self._check_file_path(self.file_path) d.addCallback(lambda _: create_lbry_file(self.session, self.lbry_file_manager, @@ -118,20 +104,21 @@ class Publisher(object): self.lbry_file.stream_hash) def set_sd_hash(sd_hash): - self.sd_hash = sd_hash + self.sources['lbry_sd_hash'] = sd_hash d.addCallback(set_sd_hash) return d def _claim_name(self): - d = self.wallet.claim_name(self.publish_name, self.sd_hash, self.bid_amount, - description=self.description, key_fee=self.key_fee, - key_fee_address=self.key_fee_address, thumbnail=self.thumbnail, - content_license=self.content_license, author=self.author, - sources=self.sources) - - def set_tx_hash(tx_hash): - self.tx_hash = tx_hash + self.metadata['content-type'] = mimetypes.guess_type(os.path.join(self.lbry_file.download_directory, + self.lbry_file.file_name))[0] + d = self.wallet.claim_name(self.publish_name, + self.bid_amount, + self.sources, + self.metadata, + fee=self.fee) + def set_tx_hash(txid): + self.txid = txid d.addCallback(set_tx_hash) return d From cc5d917c722adfead1056e16e8512ab6c1244501 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 28 Jun 2016 01:51:05 -0400 Subject: [PATCH 281/462] download fix --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 21 ++++++++++++--------- lbrynet/lbrynet_daemon/LBRYDownloader.py | 14 ++++++-------- lbrynet/lbrynet_daemon/LBRYUIManager.py | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0a90bd92b..6bb957ff0 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -24,7 +24,7 @@ from appdirs import user_data_dir from urllib2 import urlopen from lbrynet import __version__ as lbrynet_version -from lbryum.version import LBRYUM_VERSION as lbryum_version +from lbryum.version import ELECTRUM_VERSION as lbryum_version from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory @@ -398,7 +398,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False): def _log_starting_vals(): d = self._get_lbry_files() - d.addCallback(lambda r: json.dumps([d[1] for d in r])) + d.addCallback(lambda r: json.dumps([d[1] if not isinstance(d[1], UnknownNameError) else {'error': 'Pending claim'} for d in r])) d.addCallback(lambda r: log.info("LBRY Files: " + r)) d.addCallback(lambda _: log.info("Starting balance: " + str(self.session.wallet.wallet_balance))) return d @@ -1038,12 +1038,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _get_stream(stream_info): def _wait_for_write(): - if os.path.isfile(os.path.join(self.download_directory, self.streams[name].downloader.file_name)): - written_file = file(os.path.join(self.download_directory, self.streams[name].downloader.file_name)) - written_file.seek(0, os.SEEK_END) - written_bytes = written_file.tell() - written_file.close() - else: + try: + if os.path.isfile(os.path.join(self.download_directory, self.streams[name].downloader.file_name)): + written_file = file(os.path.join(self.download_directory, self.streams[name].downloader.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + except: written_bytes = False if not written_bytes: @@ -2238,4 +2241,4 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = threads.deferToThread(subprocess.Popen, ['xdg-open', '-R', path]) d.addCallback(lambda _: self._render_response(True, OK_CODE)) - return d \ No newline at end of file + return d diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index ba59227c6..c72444c70 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -112,6 +112,7 @@ class GetStream(object): self.timeout_counter = self.timeout * 2 def _set_status(x, status): + log.info("Download lbry://%s status changed to %s" % (self.resolved_name, status)) self.code = next(s for s in STREAM_STAGES if s[0] == status) return x @@ -121,9 +122,8 @@ class GetStream(object): self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) self.d.addCallback(lambda r: _set_status(r, DOWNLOAD_RUNNING_CODE)) - self.d.addCallback(lambda metadata: ( - next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), - metadata)) + self.d.addCallback(lambda metadata: (next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), + metadata)) self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager, @@ -144,11 +144,9 @@ class GetStream(object): return self.wallet.send_points_to_address(reserved_points, self.key_fee) return defer.succeed(None) - if self.pay_key: - d = _pay_key_fee() - else: - d = defer.Deferred() + d = _pay_key_fee() self.downloader = downloader self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d.addCallback(lambda _: log.info("[%s] Downloading %s --> %s" % (datetime.now(), self.stream_hash, self.file_name))) + d.addCallback(lambda _: log.info("[%s] Downloading %s --> %s" % (datetime.now(), self.stream_hash, self.downloader.file_name))) d.addCallback(lambda _: self.downloader.start()) + diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index ce36aaf01..e7c405352 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -10,7 +10,7 @@ from twisted.web import static from twisted.internet import defer from lbrynet.conf import DEFAULT_UI_BRANCH, LOG_FILE_NAME from lbrynet import __version__ as lbrynet_version -from lbryum.version import LBRYUM_VERSION as lbryum_version +from lbryum.version import ELECTRUM_VERSION as lbryum_version from zipfile import ZipFile from appdirs import user_data_dir From 09d8a86e30b16b7722a1aeefafa97bd400a86bae Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 28 Jun 2016 02:06:04 -0400 Subject: [PATCH 282/462] rename variable --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- lbrynet/lbrynet_daemon/LBRYUIManager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 6bb957ff0..f35b5f089 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -24,7 +24,7 @@ from appdirs import user_data_dir from urllib2 import urlopen from lbrynet import __version__ as lbrynet_version -from lbryum.version import ELECTRUM_VERSION as lbryum_version +from lbryum.version import LBRYUM_VERSION as lbryum_version from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index e7c405352..ce36aaf01 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -10,7 +10,7 @@ from twisted.web import static from twisted.internet import defer from lbrynet.conf import DEFAULT_UI_BRANCH, LOG_FILE_NAME from lbrynet import __version__ as lbrynet_version -from lbryum.version import ELECTRUM_VERSION as lbryum_version +from lbryum.version import LBRYUM_VERSION as lbryum_version from zipfile import ZipFile from appdirs import user_data_dir From b3b804bf2ddc9fa9de1db96b6beacc55a4562ee2 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 28 Jun 2016 14:28:59 -0400 Subject: [PATCH 283/462] add update_name --- lbrynet/core/LBRYcrdWallet.py | 18 +++++--- lbrynet/lbrynet_daemon/LBRYDaemon.py | 61 +++++++++++++++------------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 92f8d1fdf..0d1d2dc65 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -408,8 +408,10 @@ class LBRYWallet(object): d.addCallback(self._get_decoded_tx) return d - # def update_name(self, name_value): - # return self._update_name(name_value) + def update_name(self, name, value, amount): + d = self._get_value_for_name(name) + d.addCallback(lambda r: self._update_name(r['txid'], json.dumps(value), amount)) + return d def get_name_and_validity_for_sd_hash(self, sd_hash): d = self._get_claim_metadata_for_sd_hash(sd_hash) @@ -565,6 +567,9 @@ class LBRYWallet(object): def _send_abandon(self, txid, address, amount): return defer.fail(NotImplementedError()) + def _update_name(self, txid, value, amount): + return defer.fail(NotImplementedError()) + def _do_send_many(self, payments_to_send): return defer.fail(NotImplementedError()) @@ -697,6 +702,9 @@ class LBRYcrdWallet(LBRYWallet): def _send_abandon(self, txid, address, amount): return threads.deferToThread(self._send_abandon_rpc, txid, address, amount) + def _update_name(self, txid, value, amount): + return threads.deferToThread(self._update_name_rpc, txid, value, amount) + def get_claims_from_tx(self, txid): return threads.deferToThread(self._get_claims_from_tx_rpc, txid) @@ -847,9 +855,9 @@ class LBRYcrdWallet(LBRYWallet): rpc_conn = self._get_rpc_conn() return rpc_conn.getvalueforname(name) - # def _update_name_rpc(self, name_value): - # rpc_conn = self._get_rpc_conn() - # return rpc_conn.updatename(name_value) + def _update_name_rpc(self, txid, value, amount): + rpc_conn = self._get_rpc_conn() + return rpc_conn.updatename(txid, value, amount) @_catch_connection_error def _send_name_claim_rpc(self, name, value, amount): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index f35b5f089..89e650eb0 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -39,7 +39,8 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ - DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME + DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME, \ + BASE_METADATA_FIELDS, OPTIONAL_METADATA_FIELDS, SOURCE_TYPES from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -47,7 +48,7 @@ from lbrynet.core.PTCWallet import PTCWallet from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager -from lbryum import LOG_PATH as lbryum_log +# from lbryum import LOG_PATH as lbryum_log log = logging.getLogger(__name__) @@ -67,12 +68,13 @@ handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, ba log.addHandler(handler) log.setLevel(logging.INFO) -if os.path.isfile(lbryum_log): - f = open(lbryum_log, 'r') - PREVIOUS_LBRYUM_LOG = len(f.read()) - f.close() -else: - PREVIOUS_LBRYUM_LOG = 0 +# if os.path.isfile(lbryum_log): +# f = open(lbryum_log, 'r') +# PREVIOUS_LBRYUM_LOG = len(f.read()) +# f.close() +# else: +# PREVIOUS_LBRYUM_LOG = 0 + if os.path.isfile(lbrynet_log): f = open(lbrynet_log, 'r') PREVIOUS_LBRYNET_LOG = len(f.read()) @@ -634,11 +636,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _upload_log(self, log_type=None, exclude_previous=False, force=False): if self.upload_log or force: - for lm, lp in [('lbrynet', lbrynet_log), ('lbryum', lbryum_log)]: + for lm, lp in [('lbrynet', lbrynet_log)]: #, ('lbryum', lbryum_log)]: if os.path.isfile(lp): if exclude_previous: f = open(lp, "r") - f.seek(PREVIOUS_LBRYNET_LOG if lm == 'lbrynet' else PREVIOUS_LBRYUM_LOG) + f.seek(PREVIOUS_LBRYNET_LOG) # if lm == 'lbrynet' else PREVIOUS_LBRYUM_LOG) log_contents = f.read() f.close() else: @@ -2130,25 +2132,26 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - # def jsonrpc_update_name(self, metadata): - # def _disp(x): - # print x - # return x - # - # metadata = json.loads(metadata) - # - # required = ['name', 'file_path', 'bid'] - # - # for r in required: - # if not r in metadata.keys(): - # return defer.fail() - # - # d = defer.Deferred() - # d.addCallback(lambda _: self.session.wallet.update_name(metadata)) - # d.addCallback(_disp) - # d.callback(None) - # - # return d + def jsonrpc_update_name(self, p): + """ + Update name claim + + Args: + 'name': the uri of the claim to be updated + 'value': new metadata dict + 'amount': bid amount of updated claim + Returns: + txid + """ + + name = p['name'] + value = p['value'] if isinstance(p['value'], dict) else json.loads(p['value']) + amount = p['amount'] + + d = self.session.wallet.update_name(name, value, amount) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + + return d def jsonrpc_log(self, p): """ From 08b612575ce53e8a649bca5950fd1081ce50e889 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 28 Jun 2016 23:20:28 -0400 Subject: [PATCH 284/462] update_claim fix and clean up publish keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -replace ‘value’ key with ‘metadata’ -update claim metadata in db when an update is published --- lbrynet/core/LBRYcrdWallet.py | 9 ++++++++- lbrynet/lbrynet_daemon/LBRYDaemon.py | 15 ++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 0d1d2dc65..970072f77 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -410,7 +410,8 @@ class LBRYWallet(object): def update_name(self, name, value, amount): d = self._get_value_for_name(name) - d.addCallback(lambda r: self._update_name(r['txid'], json.dumps(value), amount)) + d.addCallback(lambda r: (self._update_name(json.loads(r)['txid'], json.dumps(value), amount), json.loads(r)['txid'])) + d.addCallback(lambda new_txid, old_txid: self._update_name_metadata(name, value['sources']['lbry_sd_hash'], old_txid, new_txid)) return d def get_name_and_validity_for_sd_hash(self, sd_hash): @@ -527,6 +528,12 @@ class LBRYWallet(object): return d + def _update_name_metadata(self, name, sd_hash, old_txid, new_txid): + d = self.db.runQuery("delete from name_metadata where txid=? and sd_hash=?", (old_txid, sd_hash)) + d.addCallback(lambda _: self.db.runQuery("insert into name_metadata values (?, ?, ?)", (name, new_txid, sd_hash))) + d.addCallback(lambda _: new_txid) + return d + def _get_claim_metadata_for_sd_hash(self, sd_hash): d = self.db.runQuery("select name, txid from name_metadata where sd_hash=?", (sd_hash,)) d.addCallback(lambda r: r[0] if len(r) else None) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 89e650eb0..acd13939f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -399,11 +399,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False): def _log_starting_vals(): - d = self._get_lbry_files() - d.addCallback(lambda r: json.dumps([d[1] if not isinstance(d[1], UnknownNameError) else {'error': 'Pending claim'} for d in r])) - d.addCallback(lambda r: log.info("LBRY Files: " + r)) - d.addCallback(lambda _: log.info("Starting balance: " + str(self.session.wallet.wallet_balance))) - return d + log.info("Starting balance: " + str(self.session.wallet.wallet_balance)) + return defer.succeed(None) def _announce_startup(): def _wait_for_credits(): @@ -2138,17 +2135,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): Args: 'name': the uri of the claim to be updated - 'value': new metadata dict + 'metadata': new metadata dict 'amount': bid amount of updated claim Returns: txid """ - + name = p['name'] - value = p['value'] if isinstance(p['value'], dict) else json.loads(p['value']) + metadata = p['metadata'] if isinstance(p['metadata'], dict) else json.loads(p['metadata']) amount = p['amount'] - d = self.session.wallet.update_name(name, value, amount) + d = self.session.wallet.update_name(name, metadata, amount) d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d From 62b80c13cc870372e06514463a02c190973d2af5 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 30 Jun 2016 01:26:33 -0400 Subject: [PATCH 285/462] version bump --- lbrynet/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 655afaa08..7027afdd4 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -version = (0, 2, 6) +version = (0, 3, 0) __version__ = ".".join([str(x) for x in version]) From 3cea41a85494aab06be455cc3122ccd703a759e6 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 1 Jul 2016 02:42:34 -0400 Subject: [PATCH 286/462] remove lbrynet_gui --- lbrynet/lbrynet_gui/GuiApp.py | 352 ----------- .../Ic_arrow_drop_down_48px.svg.LICENSE | 393 ------------ lbrynet/lbrynet_gui/LBRYGui.py | 571 ------------------ lbrynet/lbrynet_gui/StreamFrame.py | 463 -------------- lbrynet/lbrynet_gui/__init__.py | 1 - lbrynet/lbrynet_gui/close.gif | Bin 188 -> 0 bytes lbrynet/lbrynet_gui/close1.png | Bin 1369 -> 0 bytes lbrynet/lbrynet_gui/close2.gif | Bin 195 -> 0 bytes lbrynet/lbrynet_gui/drop_down.gif | Bin 72 -> 0 bytes lbrynet/lbrynet_gui/gui.py | 33 - lbrynet/lbrynet_gui/hide_options.gif | Bin 174 -> 0 bytes lbrynet/lbrynet_gui/lbry-dark-242x80.gif | Bin 3647 -> 0 bytes lbrynet/lbrynet_gui/lbry-dark-icon.ico | Bin 370070 -> 0 bytes lbrynet/lbrynet_gui/lbry-dark-icon.xbm | 14 - lbrynet/lbrynet_gui/lbry.conf | 108 ---- lbrynet/lbrynet_gui/lbry.png | Bin 18944 -> 0 bytes lbrynet/lbrynet_gui/show_options.gif | Bin 155 -> 0 bytes 17 files changed, 1935 deletions(-) delete mode 100644 lbrynet/lbrynet_gui/GuiApp.py delete mode 100644 lbrynet/lbrynet_gui/Ic_arrow_drop_down_48px.svg.LICENSE delete mode 100644 lbrynet/lbrynet_gui/LBRYGui.py delete mode 100644 lbrynet/lbrynet_gui/StreamFrame.py delete mode 100644 lbrynet/lbrynet_gui/__init__.py delete mode 100644 lbrynet/lbrynet_gui/close.gif delete mode 100644 lbrynet/lbrynet_gui/close1.png delete mode 100644 lbrynet/lbrynet_gui/close2.gif delete mode 100644 lbrynet/lbrynet_gui/drop_down.gif delete mode 100644 lbrynet/lbrynet_gui/gui.py delete mode 100644 lbrynet/lbrynet_gui/hide_options.gif delete mode 100755 lbrynet/lbrynet_gui/lbry-dark-242x80.gif delete mode 100755 lbrynet/lbrynet_gui/lbry-dark-icon.ico delete mode 100644 lbrynet/lbrynet_gui/lbry-dark-icon.xbm delete mode 100644 lbrynet/lbrynet_gui/lbry.conf delete mode 100644 lbrynet/lbrynet_gui/lbry.png delete mode 100644 lbrynet/lbrynet_gui/show_options.gif diff --git a/lbrynet/lbrynet_gui/GuiApp.py b/lbrynet/lbrynet_gui/GuiApp.py deleted file mode 100644 index 176cce9d7..000000000 --- a/lbrynet/lbrynet_gui/GuiApp.py +++ /dev/null @@ -1,352 +0,0 @@ -import Tkinter as tk -import logging -import sys -import tkFont -import tkMessageBox -import ttk -from lbrynet.lbrynet_gui.LBRYGui import LBRYDownloader -from lbrynet.lbrynet_gui.StreamFrame import StreamFrame -import locale -import os -from twisted.internet import defer, reactor, tksupport, task - - -log = logging.getLogger(__name__) - - -class DownloaderApp(object): - def __init__(self): - self.master = None - self.downloader = None - self.wallet_balance_check = None - self.streams_frame = None - - def start(self): - - d = defer.maybeDeferred(self._start_root) - d.addCallback(lambda _: self._draw_main()) - d.addCallback(lambda _: self._start_downloader()) - d.addCallback(lambda _: self._start_checking_wallet_balance()) - d.addCallback(lambda _: self._enable_lookup()) - - def show_error_and_stop(err): - log.error(err.getErrorMessage()) - tkMessageBox.showerror(title="Start Error", message=err.getErrorMessage()) - return self.stop() - - d.addErrback(show_error_and_stop) - return d - - def stop(self): - - def log_error(err): - log.error(err.getErrorMessage()) - - if self.downloader is not None: - d = self.downloader.stop() - else: - d = defer.succeed(True) - d.addErrback(log_error) - d.addCallback(lambda _: self._stop_checking_wallet_balance()) - d.addErrback(log_error) - d.addCallback(lambda _: reactor.stop()) - d.addErrback(log_error) - return d - - def _start_root(self): - if os.name == "nt": - button_foreground = "#104639" - lookup_button_padding = 10 - else: - button_foreground = "#FFFFFF" - lookup_button_padding = 11 - - root = tk.Tk() - root.resizable(0, 0) - root.wm_title("LBRY") - - tksupport.install(root) - - if os.name == "nt": - root.iconbitmap(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), - "lbry-dark-icon.ico")) - else: - root.wm_iconbitmap("@" + os.path.join(os.path.dirname(__file__), "lbry-dark-icon.xbm")) - - root.button_font = tkFont.Font(size=9) - - ttk.Style().configure(".", background="#FFFFFF") - ttk.Style().configure("LBRY.TButton", background="#104639", foreground=button_foreground, - borderwidth=1, relief="solid", font=root.button_font) - ttk.Style().map("LBRY.TButton", - background=[('pressed', "#104639"), - ('active', "#104639")]) - #ttk.Style().configure("LBRY.TButton.border", background="#808080") - ttk.Style().configure("Lookup.LBRY.TButton", padding=lookup_button_padding) - ttk.Style().configure("Stop.TButton", padding=1, background="#FFFFFF", relief="flat", borderwidth=0) - ttk.Style().configure("TEntry", padding=11) - ttk.Style().configure("Float.TEntry", padding=2) - #ttk.Style().configure("A.TFrame", background="red") - #ttk.Style().configure("B.TFrame", background="green") - #ttk.Style().configure("B2.TFrame", background="#80FF80") - #ttk.Style().configure("C.TFrame", background="orange") - #ttk.Style().configure("D.TFrame", background="blue") - #ttk.Style().configure("E.TFrame", background="yellow") - #ttk.Style().configure("F.TFrame", background="#808080") - #ttk.Style().configure("G.TFrame", background="#FF80FF") - #ttk.Style().configure("H.TFrame", background="#0080FF") - #ttk.Style().configure("LBRY.TProgressbar", background="#104639", orient="horizontal", thickness=5) - #ttk.Style().configure("LBRY.TProgressbar") - #ttk.Style().layout("Horizontal.LBRY.TProgressbar", ttk.Style().layout("Horizontal.TProgressbar")) - - root.configure(background="#FFFFFF") - - root.protocol("WM_DELETE_WINDOW", self.stop) - - self.master = root - - def _draw_main(self): - self.frame = ttk.Frame(self.master, style="A.TFrame") - self.frame.grid(padx=20, pady=20) - - logo_file_name = "lbry-dark-242x80.gif" - if os.name == "nt": - logo_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), logo_file_name) - else: - logo_file = os.path.join(os.path.dirname(__file__), logo_file_name) - - self.logo_picture = tk.PhotoImage(file=logo_file) - - self.logo_frame = ttk.Frame(self.frame, style="B.TFrame") - self.logo_frame.grid(pady=5, sticky=tk.W + tk.E) - - self.dummy_frame = ttk.Frame(self.logo_frame, style="C.TFrame") # keeps the logo in the middle - self.dummy_frame.grid(row=0, column=1, padx=5) - - self.logo_label = ttk.Label(self.logo_frame, image=self.logo_picture) - self.logo_label.grid(row=0, column=1, padx=5) - - self.wallet_balance_frame = ttk.Frame(self.logo_frame, style="C.TFrame") - self.wallet_balance_frame.grid(sticky=tk.E + tk.N, row=0, column=2) - - self.logo_frame.grid_columnconfigure(0, weight=1, uniform="a") - self.logo_frame.grid_columnconfigure(1, weight=2, uniform="b") - self.logo_frame.grid_columnconfigure(2, weight=1, uniform="a") - - self.wallet_balance = ttk.Label( - self.wallet_balance_frame, - text=" -- LBC" - ) - self.wallet_balance.grid(row=0, column=0) - - dropdown_file_name = "drop_down.gif" - if os.name == "nt": - dropdown_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), - dropdown_file_name) - else: - dropdown_file = os.path.join(os.path.dirname(__file__), dropdown_file_name) - - self.dropdown_picture = tk.PhotoImage( - file=dropdown_file - ) - - def get_new_address(): - def show_address(address): - w = AddressWindow(self.master, address) - w.show() - d = defer.maybeDeferred(self.downloader.get_new_address) - d.addCallback(show_address) - - def show_error(err): - tkMessageBox.showerror(title="Failed to get new address", message=err.getErrorMessage()) - - d.addErrback(show_error) - - self.wallet_menu = tk.Menu( - self.master, tearoff=0 - ) - self.wallet_menu.add_command(label="Get new LBRYcrd address", command=get_new_address) - - if os.name == "nt": - button_cursor = "" - else: - button_cursor = "hand1" - - self.wallet_menu_button = ttk.Button(self.wallet_balance_frame, image=self.dropdown_picture, - style="Stop.TButton", cursor=button_cursor) - self.wallet_menu_button.grid(row=0, column=1, padx=(5, 0)) - - def popup_wallet(event): - self.wallet_menu.tk_popup(event.x_root, event.y_root) - - self.wallet_menu_button.bind("", popup_wallet) - - self.uri_frame = ttk.Frame(self.frame, style="B.TFrame") - self.uri_frame.grid() - - self.uri_label = ttk.Label( - self.uri_frame, text="lbry://" - ) - self.uri_label.grid(row=0, column=0, sticky=tk.E, pady=2) - - self.entry_font = tkFont.Font(size=11) - - self.uri_entry = ttk.Entry(self.uri_frame, width=50, foreground="#222222", font=self.entry_font, - state=tk.DISABLED) - self.uri_entry.grid(row=0, column=1, padx=2, pady=2) - - def copy_command(): - self.uri_entry.event_generate('') - - def cut_command(): - self.uri_entry.event_generate('') - - def paste_command(): - self.uri_entry.event_generate('') - - def popup_uri(event): - selection_menu = tk.Menu( - self.master, tearoff=0 - ) - if self.uri_entry.selection_present(): - selection_menu.add_command(label=" Cut ", command=cut_command) - selection_menu.add_command(label=" Copy ", command=copy_command) - selection_menu.add_command(label=" Paste ", command=paste_command) - selection_menu.tk_popup(event.x_root, event.y_root) - - self.uri_entry.bind("", popup_uri) - - self.uri_button = ttk.Button( - self.uri_frame, text="Go", command=self._open_stream, - style='Lookup.LBRY.TButton', cursor=button_cursor - ) - self.uri_button.grid(row=0, column=2, pady=2, padx=0) - - def _start_downloader(self): - self.downloader = LBRYDownloader() - d = self.downloader.start() - d.addCallback(lambda _: self.downloader.check_first_run()) - d.addCallback(self._show_welcome_message) - return d - - def _show_welcome_message(self, points_sent): - if points_sent != 0.0: - w = WelcomeWindow(self.master, points_sent) - w.show() - - def stream_removed(self): - if self.streams_frame is not None: - if len(self.streams_frame.winfo_children()) == 0: - self.streams_frame.destroy() - self.streams_frame = None - - def _start_checking_wallet_balance(self): - - def set_balance(balance): - self.wallet_balance.configure(text=locale.format_string("%.2f LBC", (round(balance, 2),), - grouping=True)) - - def update_balance(): - balance = self.downloader.session.wallet.get_available_balance() - set_balance(balance) - - def start_looping_call(): - self.wallet_balance_check = task.LoopingCall(update_balance) - self.wallet_balance_check.start(5) - - d = self.downloader.session.wallet.get_balance() - d.addCallback(set_balance) - d.addCallback(lambda _: start_looping_call()) - - def _stop_checking_wallet_balance(self): - if self.wallet_balance_check is not None: - self.wallet_balance_check.stop() - - def _enable_lookup(self): - self.uri_entry.bind('', self._open_stream) - self.uri_entry.config(state=tk.NORMAL) - - def _open_stream(self, event=None): - if self.streams_frame is None: - self.streams_frame = ttk.Frame(self.frame, style="B2.TFrame") - self.streams_frame.grid(sticky=tk.E + tk.W) - uri = self.uri_entry.get() - self.uri_entry.delete(0, tk.END) - stream_frame = StreamFrame(self, "lbry://" + uri) - - self.downloader.download_stream(stream_frame, uri) - - -class WelcomeWindow(object): - def __init__(self, root, points_sent): - self.root = root - self.points_sent = points_sent - - def show(self): - window = tk.Toplevel(self.root, background="#FFFFFF") - window.transient(self.root) - window.wm_title("Welcome to LBRY") - window.protocol("WM_DELETE_WINDOW", window.destroy) - window.resizable(0, 0) - - text_box = tk.Text(window, width=45, height=3, relief=tk.FLAT, borderwidth=0, - highlightthickness=0) - - points_string = locale.format_string("%.2f LBC", (round(self.points_sent, 2),), - grouping=True) - - text_box.insert(tk.END, "Thank you for using LBRY! You have been\n" - "given %s for free because we love\n" - "you. Please give them 60 seconds to show up." % points_string) - text_box.grid(row=0, padx=10, pady=5, columnspan=2) - text_box.config(state='normal') - - window.focus_set() - - -class AddressWindow(object): - def __init__(self, root, address): - self.root = root - self.address = address - - def show(self): - window = tk.Toplevel(self.root, background="#FFFFFF") - window.transient(self.root) - window.wm_title("New address") - window.protocol("WM_DELETE_WINDOW", window.destroy) - window.resizable(0, 0) - - text_box = tk.Text(window, width=35, height=1, relief=tk.FLAT, borderwidth=0, - highlightthickness=0) - text_box.insert(tk.END, self.address) - text_box.grid(row=0, padx=10, pady=5, columnspan=2) - text_box.config(state='normal') - - def copy_to_clipboard(): - self.root.clipboard_clear() - self.root.clipboard_append(text_box.get('1.0', 'end-1c')) - - def copy_command(): - text_box.event_generate("") - - copy_menu = tk.Menu( - self.root, tearoff=0 - ) - copy_menu.add_command(label=" Copy ", command=copy_command) - - def popup(event): - if text_box.tag_ranges("sel"): - copy_menu.tk_popup(event.x_root, event.y_root) - - text_box.bind("", popup) - - copy_button = ttk.Button( - window, text="Copy", command=copy_to_clipboard, style="LBRY.TButton" - ) - copy_button.grid(row=1, column=0, pady=(0, 5), padx=5, sticky=tk.E) - - done_button = ttk.Button( - window, text="OK", command=window.destroy, style="LBRY.TButton" - ) - done_button.grid(row=1, column=1, pady=(0, 5), padx=5, sticky=tk.W) - window.focus_set() diff --git a/lbrynet/lbrynet_gui/Ic_arrow_drop_down_48px.svg.LICENSE b/lbrynet/lbrynet_gui/Ic_arrow_drop_down_48px.svg.LICENSE deleted file mode 100644 index 69face7d0..000000000 --- a/lbrynet/lbrynet_gui/Ic_arrow_drop_down_48px.svg.LICENSE +++ /dev/null @@ -1,393 +0,0 @@ -Attribution 4.0 International - -======================================================================= - -Creative Commons Corporation ("Creative Commons") is not a law firm and -does not provide legal services or legal advice. Distribution of -Creative Commons public licenses does not create a lawyer-client or -other relationship. Creative Commons makes its licenses and related -information available on an "as-is" basis. Creative Commons gives no -warranties regarding its licenses, any material licensed under their -terms and conditions, or any related information. Creative Commons -disclaims all liability for damages resulting from their use to the -fullest extent possible. - -Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and -conditions that creators and other rights holders may use to share -original works of authorship and other material subject to copyright -and certain other rights specified in the public license below. The -following considerations are for informational purposes only, are not -exhaustive, and do not form part of our licenses. - - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors - - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More_considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees - -======================================================================= - -Creative Commons Attribution 4.0 International Public License - -By exercising the Licensed Rights (defined below), You accept and agree -to be bound by the terms and conditions of this Creative Commons -Attribution 4.0 International Public License ("Public License"). To the -extent this Public License may be interpreted as a contract, You are -granted the Licensed Rights in consideration of Your acceptance of -these terms and conditions, and the Licensor grants You such rights in -consideration of benefits the Licensor receives from making the -Licensed Material available under these terms and conditions. - - -Section 1 -- Definitions. - - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - - c. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - - d. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - - e. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - - f. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - - g. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - - h. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - - i. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - - j. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - - k. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - - -Section 2 -- Scope. - - a. License grant. - - 1. Subject to the terms and conditions of this Public License, - the Licensor hereby grants You a worldwide, royalty-free, - non-sublicensable, non-exclusive, irrevocable license to - exercise the Licensed Rights in the Licensed Material to: - - a. reproduce and Share the Licensed Material, in whole or - in part; and - - b. produce, reproduce, and Share Adapted Material. - - 2. Exceptions and Limitations. For the avoidance of doubt, where - Exceptions and Limitations apply to Your use, this Public - License does not apply, and You do not need to comply with - its terms and conditions. - - 3. Term. The term of this Public License is specified in Section - 6(a). - - 4. Media and formats; technical modifications allowed. The - Licensor authorizes You to exercise the Licensed Rights in - all media and formats whether now known or hereafter created, - and to make technical modifications necessary to do so. The - Licensor waives and/or agrees not to assert any right or - authority to forbid You from making technical modifications - necessary to exercise the Licensed Rights, including - technical modifications necessary to circumvent Effective - Technological Measures. For purposes of this Public License, - simply making modifications authorized by this Section 2(a) - (4) never produces Adapted Material. - - 5. Downstream recipients. - - a. Offer from the Licensor -- Licensed Material. Every - recipient of the Licensed Material automatically - receives an offer from the Licensor to exercise the - Licensed Rights under the terms and conditions of this - Public License. - - b. No downstream restrictions. You may not offer or impose - any additional or different terms or conditions on, or - apply any Effective Technological Measures to, the - Licensed Material if doing so restricts exercise of the - Licensed Rights by any recipient of the Licensed - Material. - - 6. No endorsement. Nothing in this Public License constitutes or - may be construed as permission to assert or imply that You - are, or that Your use of the Licensed Material is, connected - with, or sponsored, endorsed, or granted official status by, - the Licensor or others designated to receive attribution as - provided in Section 3(a)(1)(A)(i). - - b. Other rights. - - 1. Moral rights, such as the right of integrity, are not - licensed under this Public License, nor are publicity, - privacy, and/or other similar personality rights; however, to - the extent possible, the Licensor waives and/or agrees not to - assert any such rights held by the Licensor to the limited - extent necessary to allow You to exercise the Licensed - Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this - Public License. - - 3. To the extent possible, the Licensor waives any right to - collect royalties from You for the exercise of the Licensed - Rights, whether directly or through a collecting society - under any voluntary or waivable statutory or compulsory - licensing scheme. In all other cases the Licensor expressly - reserves any right to collect such royalties. - - -Section 3 -- License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the -following conditions. - - a. Attribution. - - 1. If You Share the Licensed Material (including in modified - form), You must: - - a. retain the following if it is supplied by the Licensor - with the Licensed Material: - - i. identification of the creator(s) of the Licensed - Material and any others designated to receive - attribution, in any reasonable manner requested by - the Licensor (including by pseudonym if - designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of - warranties; - - v. a URI or hyperlink to the Licensed Material to the - extent reasonably practicable; - - b. indicate if You modified the Licensed Material and - retain an indication of any previous modifications; and - - c. indicate the Licensed Material is licensed under this - Public License, and include the text of, or the URI or - hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any - reasonable manner based on the medium, means, and context in - which You Share the Licensed Material. For example, it may be - reasonable to satisfy the conditions by providing a URI or - hyperlink to a resource that includes the required - information. - - 3. If requested by the Licensor, You must remove any of the - information required by Section 3(a)(1)(A) to the extent - reasonably practicable. - - 4. If You Share Adapted Material You produce, the Adapter's - License You apply must not prevent recipients of the Adapted - Material from complying with this Public License. - - -Section 4 -- Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that -apply to Your use of the Licensed Material: - - a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database; - - b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material; and - - c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not -replace Your obligations under this Public License where the Licensed -Rights include other Copyright and Similar Rights. - - -Section 5 -- Disclaimer of Warranties and Limitation of Liability. - - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - - c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - - -Section 6 -- Term and Termination. - - a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. - - b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided - it is cured within 30 days of Your discovery of the - violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any - right the Licensor may have to seek remedies for Your violations - of this Public License. - - c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. - - -Section 7 -- Other Terms and Conditions. - - a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. - - -Section 8 -- Interpretation. - - a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - - c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - - d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. - - -======================================================================= - -Creative Commons is not a party to its public licenses. -Notwithstanding, Creative Commons may elect to apply one of its public -licenses to material it publishes and in those instances will be -considered the "Licensor." Except for the limited purpose of indicating -that material is shared under a Creative Commons public license or as -otherwise permitted by the Creative Commons policies published at -creativecommons.org/policies, Creative Commons does not authorize the -use of the trademark "Creative Commons" or any other trademark or logo -of Creative Commons without its prior written consent including, -without limitation, in connection with any unauthorized modifications -to any of its public licenses or any other arrangements, -understandings, or agreements concerning use of licensed material. For -the avoidance of doubt, this paragraph does not form part of the public -licenses. - -Creative Commons may be contacted at creativecommons.org. \ No newline at end of file diff --git a/lbrynet/lbrynet_gui/LBRYGui.py b/lbrynet/lbrynet_gui/LBRYGui.py deleted file mode 100644 index 3c83308a8..000000000 --- a/lbrynet/lbrynet_gui/LBRYGui.py +++ /dev/null @@ -1,571 +0,0 @@ -import binascii -import logging -import tkMessageBox -from Crypto import Random -from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE -from lbrynet.core import StreamDescriptor -from lbrynet.core.Error import UnknownNameError, UnknownStreamTypeError, InvalidStreamDescriptorError -from lbrynet.core.Error import InvalidStreamInfoError -from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet -from lbrynet.core.PaymentRateManager import PaymentRateManager -from lbrynet.core.Session import LBRYSession -from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier -from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory -from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory -from lbrynet.core.server.ServerProtocol import ServerProtocolFactory -from lbrynet.lbryfile.LBRYFileMetadataManager import TempLBRYFileMetadataManager -from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType -from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory -from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier -import os -import requests -import shutil -import sys -from twisted.internet import threads, defer, task - - -log = logging.getLogger(__name__) - - -class LBRYDownloader(object): - def __init__(self): - self.session = None - self.known_dht_nodes = [('104.236.42.182', 4000)] - self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrydownloader") - self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") - self.peer_port = 3333 - self.dht_node_port = 4444 - self.run_server = True - self.first_run = False - self.current_db_revision = 1 - if os.name == "nt": - from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle - self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current) - self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbrycrd") - else: - if sys.platform == 'darwin': - self.download_directory = os.path.join(os.path.expanduser("~"), "Downloads") - self.wallet_dir = os.path.join(os.path.expanduser("~"), "Library/Application Support/lbrycrd") - else: - self.download_directory = os.getcwd() - self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") - self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf") - self.wallet_user = None - self.wallet_password = None - self.sd_identifier = StreamDescriptorIdentifier() - self.wallet_rpc_port = 8332 - self.download_deferreds = [] - self.stream_frames = [] - self.default_blob_data_payment_rate = MIN_BLOB_DATA_PAYMENT_RATE - self.use_upnp = True - self.start_lbrycrdd = True - if os.name == "nt": - self.lbrycrdd_path = "lbrycrdd.exe" - else: - self.lbrycrdd_path = None - self.default_lbrycrdd_path = "./lbrycrdd" - self.delete_blobs_on_remove = True - self.blob_request_payment_rate_manager = None - - def start(self): - d = self._load_conf_options() - d.addCallback(lambda _: threads.deferToThread(self._create_directory)) - d.addCallback(lambda _: self._check_db_migration()) - d.addCallback(lambda _: self._get_session()) - d.addCallback(lambda _: self._setup_stream_info_manager()) - d.addCallback(lambda _: self._setup_stream_identifier()) - d.addCallback(lambda _: self.start_server()) - return d - - def stop(self): - dl = defer.DeferredList(self.download_deferreds) - for stream_frame in self.stream_frames: - stream_frame.cancel_func() - if self.session is not None: - dl.addBoth(lambda _: self.stop_server()) - dl.addBoth(lambda _: self.session.shut_down()) - return dl - - def get_new_address(self): - return self.session.wallet.get_new_address() - - def _check_db_migration(self): - old_revision = 0 - db_revision_file = os.path.join(self.db_dir, "db_revision") - if os.path.exists(db_revision_file): - old_revision = int(open(db_revision_file).read().strip()) - if old_revision < self.current_db_revision: - if os.name == "nt": - import subprocess - import sys - - def run_migrator(): - migrator_exe = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), - "dbmigrator", "migrator.exe") - print "trying to find the migrator at", migrator_exe - si = subprocess.STARTUPINFO - si.dwFlags = subprocess.STARTF_USESHOWWINDOW - si.wShowWindow = subprocess.SW_HIDE - print "trying to run the migrator" - migrator_proc = subprocess.Popen([migrator_exe, self.db_dir, str(old_revision), - str(self.current_db_revision)], startupinfo=si) - print "started the migrator" - migrator_proc.wait() - print "migrator has returned" - - return threads.deferToThread(run_migrator) - else: - from lbrynet.db_migrator import dbmigrator - return threads.deferToThread(dbmigrator.migrate_db, self.db_dir, old_revision, - self.current_db_revision) - return defer.succeed(True) - - def _load_conf_options(self): - - def get_lbrycrdd_path_conf_file(): - if os.name == "nt": - return "" - lbrycrdd_path_conf_path = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf") - if not os.path.exists(lbrycrdd_path_conf_path): - return "" - lbrycrdd_path_conf = open(lbrycrdd_path_conf_path) - lines = lbrycrdd_path_conf.readlines() - return lines - - d = threads.deferToThread(get_lbrycrdd_path_conf_file) - - def load_lbrycrdd_path(conf): - for line in conf: - if len(line.strip()) and line.strip()[0] != "#": - self.lbrycrdd_path = line.strip() - - d.addCallback(load_lbrycrdd_path) - - def get_configuration_file(): - if os.name == "nt": - lbry_conf_path = "lbry.conf" - if not os.path.exists(lbry_conf_path): - log.debug("Could not read lbry.conf") - return "" - else: - lbry_conf_path = os.path.join(os.path.expanduser("~"), ".lbrynetgui.conf") - if not os.path.exists(lbry_conf_path): - clean_conf_path = os.path.join(os.path.dirname(__file__), "lbry.conf") - shutil.copy(clean_conf_path, lbry_conf_path) - lbry_conf = open(lbry_conf_path) - log.debug("Loading configuration options from %s", lbry_conf_path) - lines = lbry_conf.readlines() - log.debug("%s file contents:\n%s", lbry_conf_path, str(lines)) - return lines - - d.addCallback(lambda _: threads.deferToThread(get_configuration_file)) - - def load_configuration_file(conf): - for line in conf: - if len(line.strip()) and line.strip()[0] != "#": - try: - field_name, field_value = map(lambda x: x.strip(), line.strip().split("=", 1)) - field_name = field_name.lower() - except ValueError: - raise ValueError("Invalid configuration line: %s" % line) - if field_name == "known_dht_nodes": - known_nodes = [] - nodes = field_value.split(",") - for n in nodes: - if n.strip(): - try: - ip_address, port_string = map(lambda x: x.strip(), n.split(":")) - ip_numbers = ip_address.split(".") - assert len(ip_numbers) == 4 - for ip_num in ip_numbers: - num = int(ip_num) - assert 0 <= num <= 255 - known_nodes.append((ip_address, int(port_string))) - except (ValueError, AssertionError): - raise ValueError("Expected known nodes in format 192.168.1.1:4000,192.168.1.2:4001. Got %s" % str(field_value)) - log.debug("Setting known_dht_nodes to %s", str(known_nodes)) - self.known_dht_nodes = known_nodes - elif field_name == "run_server": - if field_value.lower() == "true": - run_server = True - elif field_value.lower() == "false": - run_server = False - else: - raise ValueError("run_server must be set to True or False. Got %s" % field_value) - log.debug("Setting run_server to %s", str(run_server)) - self.run_server = run_server - elif field_name == "data_dir": - log.debug("Setting data_dir to %s", str(field_value)) - self.db_dir = field_value - self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") - elif field_name == "wallet_dir": - log.debug("Setting wallet_dir to %s", str(field_value)) - self.wallet_dir = field_value - elif field_name == "wallet_conf": - log.debug("Setting wallet_conf to %s", str(field_value)) - self.wallet_conf = field_value - elif field_name == "peer_port": - try: - peer_port = int(field_value) - assert 0 <= peer_port <= 65535 - log.debug("Setting peer_port to %s", str(peer_port)) - self.peer_port = peer_port - except (ValueError, AssertionError): - raise ValueError("peer_port must be set to an integer between 1 and 65535. Got %s" % field_value) - elif field_name == "dht_port": - try: - dht_port = int(field_value) - assert 0 <= dht_port <= 65535 - log.debug("Setting dht_node_port to %s", str(dht_port)) - self.dht_node_port = dht_port - except (ValueError, AssertionError): - raise ValueError("dht_port must be set to an integer between 1 and 65535. Got %s" % field_value) - elif field_name == "use_upnp": - if field_value.lower() == "true": - use_upnp = True - elif field_value.lower() == "false": - use_upnp = False - else: - raise ValueError("use_upnp must be set to True or False. Got %s" % str(field_value)) - log.debug("Setting use_upnp to %s", str(use_upnp)) - self.use_upnp = use_upnp - elif field_name == "default_blob_data_payment_rate": - try: - rate = float(field_value) - assert rate >= 0.0 - log.debug("Setting default_blob_data_payment_rate to %s", str(rate)) - self.default_blob_data_payment_rate = rate - except (ValueError, AssertionError): - raise ValueError("default_blob_data_payment_rate must be a positive floating point number, e.g. 0.5. Got %s" % str(field_value)) - elif field_name == "start_lbrycrdd": - if field_value.lower() == "true": - start_lbrycrdd = True - elif field_value.lower() == "false": - start_lbrycrdd = False - else: - raise ValueError("start_lbrycrdd must be set to True or False. Got %s" % field_value) - log.debug("Setting start_lbrycrdd to %s", str(start_lbrycrdd)) - self.start_lbrycrdd = start_lbrycrdd - elif field_name == "lbrycrdd_path": - self.lbrycrdd_path = field_value - elif field_name == "download_directory": - log.debug("Setting download_directory to %s", str(field_value)) - self.download_directory = field_value - elif field_name == "delete_blobs_on_stream_remove": - if field_value.lower() == "true": - self.delete_blobs_on_remove = True - elif field_value.lower() == "false": - self.delete_blobs_on_remove = False - else: - raise ValueError("delete_blobs_on_stream_remove must be set to True or False") - else: - log.warning("Got unknown configuration field: %s", field_name) - - d.addCallback(load_configuration_file) - return d - - def _create_directory(self): - if not os.path.exists(self.db_dir): - os.makedirs(self.db_dir) - db_revision = open(os.path.join(self.db_dir, "db_revision"), mode='w') - db_revision.write(str(self.current_db_revision)) - db_revision.close() - log.debug("Created the configuration directory: %s", str(self.db_dir)) - if not os.path.exists(self.blobfile_dir): - os.makedirs(self.blobfile_dir) - log.debug("Created the data directory: %s", str(self.blobfile_dir)) - if os.name == "nt": - if not os.path.exists(self.wallet_dir): - os.makedirs(self.wallet_dir) - if not os.path.exists(self.wallet_conf): - lbrycrd_conf = open(self.wallet_conf, mode='w') - self.wallet_user = "rpcuser" - lbrycrd_conf.write("rpcuser=%s\n" % self.wallet_user) - self.wallet_password = binascii.hexlify(Random.new().read(20)) - lbrycrd_conf.write("rpcpassword=%s\n" % self.wallet_password) - lbrycrd_conf.write("server=1\n") - lbrycrd_conf.close() - else: - lbrycrd_conf = open(self.wallet_conf) - for l in lbrycrd_conf: - if l.startswith("rpcuser="): - self.wallet_user = l[8:].rstrip('\n') - if l.startswith("rpcpassword="): - self.wallet_password = l[12:].rstrip('\n') - if l.startswith("rpcport="): - self.wallet_rpc_port = int(l[8:-1].rstrip('\n')) - - def _get_session(self): - lbrycrdd_path = None - if self.start_lbrycrdd is True: - lbrycrdd_path = self.lbrycrdd_path - if not lbrycrdd_path: - lbrycrdd_path = self.default_lbrycrdd_path - - if sys.platform == 'darwin': - os.chdir("/Applications/LBRY.app/Contents/Resources") - - wallet = LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.wallet_conf, - lbrycrdd_path=lbrycrdd_path) - - peer_port = None - if self.run_server: - peer_port = self.peer_port - self.session = LBRYSession(self.default_blob_data_payment_rate, db_dir=self.db_dir, - blob_dir=self.blobfile_dir, use_upnp=self.use_upnp, wallet=wallet, - known_dht_nodes=self.known_dht_nodes, dht_node_port=self.dht_node_port, - peer_port=peer_port) - return self.session.setup() - - def _setup_stream_info_manager(self): - self.stream_info_manager = TempLBRYFileMetadataManager() - return defer.succeed(True) - - def start_server(self): - - if self.run_server: - self.blob_request_payment_rate_manager = PaymentRateManager( - self.session.base_payment_rate_manager, - self.default_blob_data_payment_rate - ) - handlers = [ - BlobAvailabilityHandlerFactory(self.session.blob_manager), - self.session.wallet.get_wallet_info_query_handler_factory(), - BlobRequestHandlerFactory(self.session.blob_manager, self.session.wallet, - self.blob_request_payment_rate_manager) - ] - - server_factory = ServerProtocolFactory(self.session.rate_limiter, - handlers, - self.session.peer_manager) - from twisted.internet import reactor - self.lbry_server_port = reactor.listenTCP(self.peer_port, server_factory) - - return defer.succeed(True) - - def stop_server(self): - if self.lbry_server_port is not None: - self.lbry_server_port, p = None, self.lbry_server_port - return defer.maybeDeferred(p.stopListening) - else: - return defer.succeed(True) - - def _setup_stream_identifier(self): - add_lbry_file_to_sd_identifier(self.sd_identifier) - file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter, - self.session.blob_manager, self.stream_info_manager, - self.session.wallet, self.download_directory) - self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_saver_factory) - file_opener_factory = LBRYFileOpenerFactory(self.session.peer_finder, self.session.rate_limiter, - self.session.blob_manager, self.stream_info_manager, - self.session.wallet) - self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_opener_factory) - - def check_first_run(self): - d = self.session.wallet.is_first_run() - d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run else 0.0) - return d - - def _do_first_run(self): - d = self.session.wallet.get_new_address() - - def send_request(url, data): - r = requests.post(url, json=data) - if r.status_code == 200: - return r.json()['credits_sent'] - return 0.0 - - def log_error(err): - log.warning("unable to request free credits. %s", err.getErrorMessage()) - return 0.0 - - def request_credits(address): - url = "http://credreq.lbry.io/requestcredits" - data = {"address": address} - d = threads.deferToThread(send_request, url, data) - d.addErrback(log_error) - return d - - d.addCallback(request_credits) - return d - - def _resolve_name(self, uri): - return self.session.wallet.get_stream_info_for_name(uri) - - def download_stream(self, stream_frame, uri): - resolve_d = self._resolve_name(uri) - - stream_frame.show_metadata_status("resolving name...") - - stream_frame.cancel_func = resolve_d.cancel - payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager) - - def update_stream_name(value): - if 'name' in value: - stream_frame.show_name(value['name']) - if 'description' in value: - stream_frame.show_description(value['description']) - if 'thumbnail' in value: - stream_frame.show_thumbnail(value['thumbnail']) - return value - - def get_sd_hash(value): - if 'stream_hash' in value: - return value['stream_hash'] - raise UnknownNameError(uri) - - def get_sd_blob(sd_hash): - stream_frame.show_metadata_status("name resolved, fetching metadata...") - get_sd_d = StreamDescriptor.download_sd_blob(self.session, sd_hash, - payment_rate_manager) - get_sd_d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) - get_sd_d.addCallbacks(choose_download_factory, bad_sd_blob) - return get_sd_d - - def get_info_from_validator(info_validator): - stream_name = None - stream_size = None - for field, val in info_validator.info_to_show(): - if field == "suggested_file_name": - stream_name = val - elif field == "stream_name" and stream_name is None: - stream_name = val - elif field == "stream_size": - stream_size = int(val) - if stream_size is None: - stream_size = "unknown" - if stream_name is None: - stream_name = "unknown" - return stream_name, stream_size - - def choose_download_factory(metadata): - #info_validator, options, factories = info_and_factories - stream_name, stream_size = get_info_from_validator(metadata.validator) - if isinstance(stream_size, (int, long)): - price = payment_rate_manager.get_effective_min_blob_data_payment_rate() - estimated_cost = stream_size * 1.0 / 2**20 * price - else: - estimated_cost = "unknown" - - stream_frame.show_stream_metadata(stream_name, stream_size, estimated_cost) - - available_options = metadata.options.get_downloader_options(metadata.validator, - payment_rate_manager) - - stream_frame.show_download_options(available_options) - - get_downloader_d = defer.Deferred() - - def create_downloader(f, chosen_options): - - def fire_get_downloader_d(downloader): - if not get_downloader_d.called: - get_downloader_d.callback(downloader) - - stream_frame.disable_download_buttons() - d = f.make_downloader(metadata, chosen_options, - payment_rate_manager) - d.addCallback(fire_get_downloader_d) - - for factory in metadata.factories: - - def choose_factory(f=factory): - chosen_options = stream_frame.get_chosen_options() - create_downloader(f, chosen_options) - - stream_frame.add_download_factory(factory, choose_factory) - - get_downloader_d.addCallback(start_download) - - return get_downloader_d - - def show_stream_status(downloader): - total_bytes = downloader.get_total_bytes_cached() - bytes_left_to_download = downloader.get_bytes_left_to_download() - points_paid = payment_rate_manager.points_paid - payment_rate = payment_rate_manager.get_effective_min_blob_data_payment_rate() - points_remaining = 1.0 * bytes_left_to_download * payment_rate / 2**20 - stream_frame.show_progress(total_bytes, bytes_left_to_download, - points_paid, points_remaining) - - def show_finished(arg, downloader): - show_stream_status(downloader) - stream_frame.show_download_done(payment_rate_manager.points_paid) - return arg - - def start_download(downloader): - stream_frame.stream_hash = downloader.stream_hash - l = task.LoopingCall(show_stream_status, downloader) - l.start(1) - d = downloader.start() - stream_frame.cancel_func = downloader.stop - - def stop_looping_call(arg): - l.stop() - stream_frame.cancel_func = resolve_d.cancel - return arg - - d.addBoth(stop_looping_call) - d.addCallback(show_finished, downloader) - return d - - def lookup_failed(err): - stream_frame.show_metadata_status("name lookup failed") - return err - - def bad_sd_blob(err): - stream_frame.show_metadata_status("Unknown type or badly formed metadata") - return err - - resolve_d.addCallback(update_stream_name) - resolve_d.addCallback(get_sd_hash) - resolve_d.addCallbacks(get_sd_blob, lookup_failed) - - def show_err(err): - tkMessageBox.showerror(title="Download Error", message=err.getErrorMessage()) - log.error(err.getErrorMessage()) - stream_frame.show_download_done(payment_rate_manager.points_paid) - - resolve_d.addErrback(lambda err: err.trap(defer.CancelledError, UnknownNameError, - UnknownStreamTypeError, InvalidStreamDescriptorError, - InvalidStreamInfoError)) - resolve_d.addErrback(show_err) - - def delete_associated_blobs(): - if stream_frame.stream_hash is None or self.delete_blobs_on_remove is False: - return defer.succeed(True) - d1 = self.stream_info_manager.get_blobs_for_stream(stream_frame.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(stream_frame.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): - return self.session.blob_manager.delete_blobs(blob_hashes) - - dl = defer.DeferredList([d1, d2], fireOnOneErrback=True) - dl.addCallback(combine_blob_hashes) - dl.addCallback(delete_blobs) - return dl - - resolve_d.addCallback(lambda _: delete_associated_blobs()) - self._add_download_deferred(resolve_d, stream_frame) - - def _add_download_deferred(self, d, stream_frame): - self.download_deferreds.append(d) - self.stream_frames.append(stream_frame) - - def remove_from_list(): - self.download_deferreds.remove(d) - self.stream_frames.remove(stream_frame) - - d.addBoth(lambda _: remove_from_list()) diff --git a/lbrynet/lbrynet_gui/StreamFrame.py b/lbrynet/lbrynet_gui/StreamFrame.py deleted file mode 100644 index de07c4d55..000000000 --- a/lbrynet/lbrynet_gui/StreamFrame.py +++ /dev/null @@ -1,463 +0,0 @@ -import Tkinter as tk -import sys -import tkFont -import ttk -import locale -import os - - -class StreamFrame(object): - def __init__(self, app, uri): - self.app = app - self.uri = uri - self.stream_hash = None - self.cancel_func = None - - self.stream_frame = ttk.Frame(self.app.streams_frame, style="B.TFrame") - - self.stream_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=(30, 0)) - - self.stream_frame_header = ttk.Frame(self.stream_frame, style="C.TFrame") - self.stream_frame_header.grid(sticky=tk.E + tk.W) - - self.uri_font = tkFont.Font(size=8) - self.uri_label = ttk.Label( - self.stream_frame_header, text=self.uri, font=self.uri_font, foreground="#666666" - ) - self.uri_label.grid(row=0, column=0, sticky=tk.W) - - if os.name == "nt": - self.button_cursor = "" - else: - self.button_cursor = "hand1" - - close_file_name = "close2.gif" - if os.name == "nt": - close_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), close_file_name) - else: - close_file = os.path.join(os.path.dirname(__file__), close_file_name) - - self.close_picture = tk.PhotoImage( - file=close_file - ) - self.close_button = ttk.Button( - self.stream_frame_header, command=self.cancel, style="Stop.TButton", cursor=self.button_cursor - ) - self.close_button.config(image=self.close_picture) - self.close_button.grid(row=0, column=1, sticky=tk.E + tk.N) - - self.stream_frame_header.grid_columnconfigure(0, weight=1) - - self.stream_frame.grid_columnconfigure(0, weight=1) - - self.stream_frame_body = ttk.Frame(self.stream_frame, style="C.TFrame") - self.stream_frame_body.grid(row=1, column=0, sticky=tk.E + tk.W) - - self.name_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame") - self.name_frame.grid(sticky=tk.W + tk.E) - self.name_frame.grid_columnconfigure(0, weight=16) - self.name_frame.grid_columnconfigure(1, weight=1) - - self.stream_frame_body.grid_columnconfigure(0, weight=1) - - self.metadata_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame") - self.metadata_frame.grid(sticky=tk.W + tk.E, row=1) - self.metadata_frame.grid_columnconfigure(0, weight=1) - - self.options_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame") - - self.outer_button_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame") - self.outer_button_frame.grid(sticky=tk.W + tk.E, row=4) - - #show_options_picture_file_name = "show_options.gif" - #if os.name == "nt": - # show_options_picture_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), - # "lbrynet", "lbrynet_downloader_gui", - # show_options_picture_file_name) - #else: - # show_options_picture_file = os.path.join(os.path.dirname(__file__), - # show_options_picture_file_name) - - #self.show_options_picture = tk.PhotoImage( - # file=show_options_picture_file - #) - - #hide_options_picture_file_name = "hide_options.gif" - #if os.name == "nt": - # hide_options_picture_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), - # "lbrynet", "lbrynet_downloader_gui", - # hide_options_picture_file_name) - #else: - # hide_options_picture_file = os.path.join(os.path.dirname(__file__), - # hide_options_picture_file_name) - - #self.hide_options_picture = tk.PhotoImage( - # file=hide_options_picture_file - #) - - #self.show_options_button = None - - self.status_label = None - #self.name_label = None - self.name_text = None - self.estimated_cost_frame = None - self.bytes_downloaded_label = None - self.button_frame = None - self.download_buttons = [] - self.option_frames = [] - self.name_font = None - self.description_text = None - #self.description_label = None - self.file_name_frame = None - self.cost_frame = None - self.cost_description = None - self.remaining_cost_description = None - self.cost_label = None - self.remaining_cost_label = None - self.progress_frame = None - - def cancel(self): - if self.cancel_func is not None: - self.cancel_func() # pylint: disable=not-callable - self.stream_frame.destroy() - self.app.stream_removed() - - def _resize_text(self, text_widget): - actual_height = text_widget.tk.call(text_widget._w, "count", "-displaylines", "0.0", "end") - text_widget.config(height=int(actual_height)) - - def show_name(self, name): - self.name_font = tkFont.Font(size=16) - #self.name_label = ttk.Label( - # self.name_frame, text=name, font=self.name_font - #) - #self.name_label.grid(row=0, column=0, sticky=tk.W) - self.name_text = tk.Text( - self.name_frame, font=self.name_font, wrap=tk.WORD, relief=tk.FLAT, borderwidth=0, - highlightthickness=0, width=1, height=1 - ) - self.name_text.insert(tk.INSERT, name) - self.name_text.config(state=tk.DISABLED) - self.name_text.grid(row=0, column=0, sticky=tk.W+tk.E+tk.N+tk.S) - self.name_text.update() - self._resize_text(self.name_text) - - def show_description(self, description): - #if os.name == "nt": - # wraplength = 580 - #else: - # wraplength = 600 - #self.description_label = ttk.Label( - # self.name_frame, text=description, wraplength=wraplength - #) - #self.description_label.grid(row=1, column=0, sticky=tk.W) - self.description_text = tk.Text( - self.name_frame, wrap=tk.WORD, relief=tk.FLAT, borderwidth=0, - highlightthickness=0, width=1, height=1 - ) - self.description_text.insert(tk.INSERT, description) - self.description_text.config(state=tk.DISABLED) - self.description_text.grid(row=1, column=0, sticky=tk.W+tk.E+tk.N+tk.S) - self.description_text.update() - self._resize_text(self.description_text) - - def show_thumbnail(self, url=None): - thumbnail = None - if url is not None: - import urllib2 - import base64 - response = urllib2.urlopen(url) - thumbnail_data = base64.b64encode(response.read()) - thumbnail = tk.PhotoImage(data=thumbnail_data) - current_width = thumbnail.width() - current_height = thumbnail.height() - max_width = 130 - max_height = 90 - scale_ratio = max(1.0 * current_width / max_width, 1.0 * current_height / max_height) - if scale_ratio < 1: - scale_ratio = 1 - else: - scale_ratio = int(scale_ratio + 1) - thumbnail = thumbnail.subsample(scale_ratio) - if thumbnail is not None: - label = ttk.Label(self.name_frame, image=thumbnail) - label.safekeeping = thumbnail - label.grid(row=0, column=1, rowspan=2, sticky=tk.E+tk.N+tk.W+tk.S) - label.update() - self.description_text.update() - self.name_text.update() - self._resize_text(self.description_text) - self._resize_text(self.name_text) - - def show_metadata_status(self, value): - if self.status_label is None: - self.status_label = ttk.Label( - self.metadata_frame, text=value - ) - self.status_label.grid() - self.metadata_frame.grid_columnconfigure(0, weight=1) - else: - self.status_label.config(text=value) - - @staticmethod - def get_formatted_stream_size(stream_size): - if isinstance(stream_size, (int, long)): - if stream_size >= 2**40: - units = "TB" - factor = 2**40 - elif stream_size >= 2**30: - units = "GB" - factor = 2**30 - elif stream_size >= 2**20: - units = "MB" - factor = 2**20 - elif stream_size >= 2**10: - units = "KB" - factor = 2**10 - else: - return str(stream_size) + " B" - return "%.1f %s" % (round((stream_size * 1.0 / factor), 1), units) - return stream_size - - def show_stream_metadata(self, stream_name, stream_size, stream_cost): - if self.status_label is not None: - self.status_label.destroy() - - self.file_name_frame = ttk.Frame(self.metadata_frame, style="F.TFrame") - self.file_name_frame.grid(row=0, column=0, sticky=tk.W) - self.metadata_frame.grid_columnconfigure(0, weight=1, uniform="metadata") - - file_size_label = ttk.Label( - self.file_name_frame, - text=self.get_formatted_stream_size(stream_size) - ) - file_size_label.grid(row=0, column=2) - - file_name_label = ttk.Label( - self.file_name_frame, - text=" - " + stream_name, - ) - file_name_label.grid(row=0, column=3) - - self.estimated_cost_frame = ttk.Frame(self.metadata_frame, style="F.TFrame") - self.estimated_cost_frame.grid(row=1, column=0, sticky=tk.W) - - estimated_cost_label = ttk.Label( - self.estimated_cost_frame, - text=locale.format_string("%.2f LBC", - (round(stream_cost, 2)), grouping=True), - foreground="red" - ) - estimated_cost_label.grid(row=1, column=2) - - self.button_frame = ttk.Frame(self.outer_button_frame, style="E.TFrame") - self.button_frame.grid(row=0, column=1) - - self.outer_button_frame.grid_columnconfigure(0, weight=1, uniform="buttons") - self.outer_button_frame.grid_columnconfigure(1, weight=2, uniform="buttons1") - self.outer_button_frame.grid_columnconfigure(2, weight=1, uniform="buttons") - - def add_download_factory(self, factory, download_func): - download_button = ttk.Button( - self.button_frame, text=factory.get_description(), command=download_func, - style='LBRY.TButton', cursor=self.button_cursor - ) - self.download_buttons.append(download_button) - download_button.grid(row=0, column=len(self.download_buttons) - 1, padx=5, pady=(1, 2)) - - def disable_download_buttons(self): - for download_button in self.download_buttons: - download_button.config(state=tk.DISABLED) - - def remove_download_buttons(self): - for download_button in self.download_buttons: - download_button.destroy() - self.download_buttons = [] - - def get_option_widget(self, option_type, option_frame): - if option_type.value == float: - entry_frame = ttk.Frame( - option_frame, - style="H.TFrame" - ) - entry_frame.grid() - col = 0 - if option_type.short_description is not None: - entry_label = ttk.Label( - entry_frame, - #text=option_type.short_description - text="" - ) - entry_label.grid(row=0, column=0, sticky=tk.W) - col = 1 - entry = ttk.Entry( - entry_frame, - width=10, - style="Float.TEntry" - ) - entry_frame.entry = entry - entry.grid(row=0, column=col, sticky=tk.W) - return entry_frame - if option_type.value == bool: - bool_frame = ttk.Frame( - option_frame, - style="H.TFrame" - ) - bool_frame.chosen_value = tk.BooleanVar() - true_text = "True" - false_text = "False" - if option_type.bool_options_description is not None: - true_text, false_text = option_type.bool_options_description - true_radio_button = ttk.Radiobutton( - bool_frame, text=true_text, variable=bool_frame.chosen_value, value=True - ) - true_radio_button.grid(row=0, sticky=tk.W) - false_radio_button = ttk.Radiobutton( - bool_frame, text=false_text, variable=bool_frame.chosen_value, value=False - ) - false_radio_button.grid(row=1, sticky=tk.W) - return bool_frame - label = ttk.Label( - option_frame, - text="" - ) - return label - - def show_download_options(self, options): - left_padding = 20 - for option in options: - f = ttk.Frame( - self.options_frame, - style="E.TFrame" - ) - f.grid(sticky=tk.W + tk.E, padx=left_padding) - self.option_frames.append((option, f)) - description_label = ttk.Label( - f, - text=option.long_description - ) - description_label.grid(row=0, sticky=tk.W) - if len(option.option_types) > 1: - f.chosen_type = tk.IntVar() - choices_frame = ttk.Frame( - f, - style="F.TFrame" - ) - f.choices_frame = choices_frame - choices_frame.grid(row=1, sticky=tk.W, padx=left_padding) - choices_frame.choices = [] - for i, option_type in enumerate(option.option_types): - choice_frame = ttk.Frame( - choices_frame, - style="G.TFrame" - ) - choice_frame.grid(sticky=tk.W) - option_text = "" - if option_type.short_description is not None: - option_text = option_type.short_description - option_radio_button = ttk.Radiobutton( - choice_frame, text=option_text, variable=f.chosen_type, value=i - ) - option_radio_button.grid(row=0, column=0, sticky=tk.W) - option_widget = self.get_option_widget(option_type, choice_frame) - option_widget.grid(row=0, column=1, sticky=tk.W) - choices_frame.choices.append(option_widget) - if i == 0: - option_radio_button.invoke() - else: - choice_frame = ttk.Frame( - f, - style="F.TFrame" - ) - choice_frame.grid(sticky=tk.W, padx=left_padding) - option_widget = self.get_option_widget(option.option_types[0], choice_frame) - option_widget.grid(row=0, column=0, sticky=tk.W) - f.option_widget = option_widget - #self.show_options_button = ttk.Button( - # self.stream_frame_body, command=self._toggle_show_options, style="Stop.TButton", - # cursor=self.button_cursor - #) - #self.show_options_button.config(image=self.show_options_picture) - #self.show_options_button.grid(sticky=tk.W, row=2, column=0) - - def _get_chosen_option(self, option_type, option_widget): - if option_type.value == float: - return float(option_widget.entry.get()) - if option_type.value == bool: - return option_widget.chosen_value.get() - return option_type.value - - def get_chosen_options(self): - chosen_options = [] - for o, f in self.option_frames: - if len(o.option_types) > 1: - chosen_index = f.chosen_type.get() - option_type = o.option_types[chosen_index] - option_widget = f.choices_frame.choices[chosen_index] - chosen_options.append(self._get_chosen_option(option_type, option_widget)) - else: - option_type = o.option_types[0] - option_widget = f.option_widget - chosen_options.append(self._get_chosen_option(option_type, option_widget)) - return chosen_options - - #def _toggle_show_options(self): - # if self.options_frame.winfo_ismapped(): - # self.show_options_button.config(image=self.show_options_picture) - # self.options_frame.grid_forget() - # else: - # self.show_options_button.config(image=self.hide_options_picture) - # self.options_frame.grid(sticky=tk.W + tk.E, row=3) - - def show_progress(self, total_bytes, bytes_left_to_download, points_paid, - points_remaining): - if self.bytes_downloaded_label is None: - self.remove_download_buttons() - self.button_frame.destroy() - self.estimated_cost_frame.destroy() - for option, frame in self.option_frames: - frame.destroy() - self.options_frame.destroy() - #self.show_options_button.destroy() - - self.progress_frame = ttk.Frame(self.outer_button_frame, style="F.TFrame") - self.progress_frame.grid(row=0, column=0, sticky=tk.W, pady=(0, 8)) - - self.bytes_downloaded_label = ttk.Label( - self.progress_frame, - text="" - ) - self.bytes_downloaded_label.grid(row=0, column=0) - - self.cost_frame = ttk.Frame(self.outer_button_frame, style="F.TFrame") - self.cost_frame.grid(row=1, column=0, sticky=tk.W, pady=(0, 4)) - - self.cost_label = ttk.Label( - self.cost_frame, - text="", - foreground="red" - ) - self.cost_label.grid(row=0, column=1, padx=(1, 0)) - self.outer_button_frame.grid_columnconfigure(2, weight=0, uniform="") - - if self.bytes_downloaded_label.winfo_exists(): - percent_done = 0 - if total_bytes > 0: - percent_done = 100.0 * (total_bytes - bytes_left_to_download) / total_bytes - percent_done_string = locale.format_string(" (%.2f%%)", percent_done) - self.bytes_downloaded_label.config( - text=self.get_formatted_stream_size(total_bytes - bytes_left_to_download) + percent_done_string - ) - if self.cost_label.winfo_exists(): - total_points = points_remaining + points_paid - self.cost_label.config(text=locale.format_string("%.2f/%.2f LBC", - (round(points_paid, 2), round(total_points, 2)), - grouping=True)) - - def show_download_done(self, total_points_paid): - if self.bytes_downloaded_label is not None and self.bytes_downloaded_label.winfo_exists(): - self.bytes_downloaded_label.destroy() - if self.cost_label is not None and self.cost_label.winfo_exists(): - self.cost_label.config(text=locale.format_string("%.2f LBC", - (round(total_points_paid, 2),), - grouping=True)) diff --git a/lbrynet/lbrynet_gui/__init__.py b/lbrynet/lbrynet_gui/__init__.py deleted file mode 100644 index f58b75595..000000000 --- a/lbrynet/lbrynet_gui/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""A gui application for downloading LBRY files from LBRYnet""" \ No newline at end of file diff --git a/lbrynet/lbrynet_gui/close.gif b/lbrynet/lbrynet_gui/close.gif deleted file mode 100644 index 9c9e74a2f7245c16c9919dbca7d69f54d91ed555..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 188 zcmZ?wbhEHb6krfwXklRZ|NnnzXsCmO!=FEYe*XLkB!MEoe*FTHzP`SGetr-V$N+-$ z^z`!b@`i?ny1Kgh`uf`1+RDmGpa@V+R8$m@1nNQqia%KxxftXbbQpjDWG4fwD0S=G1A?g4C1m;OZ zK~zY`rIuNUTtyUyzf*O)Z{MEoS-LYxW|9mTV}g#8hzjOG#3ky3h>BtqM+q*VxWFWX z#JGzA#iu|-1W^!|2Q{E?g8HVQafwl*5{EcL49S>fdS*=DwT=(BdlrK)T5za3x2n#m z`p!pZ5wM^MVE&^>+sLSAb7S8Ql(}%?G`B$YnbqYjrJ;+~Iu)x8C{Xgv;hIat2vyyw<3f-euum7z4;EoNp+G)fZaEdDARumNU zpJS&$egIIiFr@j*uj$^WpWkznvKhbvyg5`G>3rz%Vd<)gCNWtU;-ZKw%KKcxTn|HB z6yl<0>szy>hlmJ;dbjE7t*`0(`nw-Lgdg#3{7Ks)}e! zce>zainjKk4iQ6PNq2EY!E7LLA9}rDqA2p>6UV_XLQohGQuFxH!gr)@0D&^U%d zA&06pug;N7Pm2@^MUn{50paP0nelOE5Bxwpc?#tHq=P2TwLk2hC zuDyZNFTX@b)NIWuwxdF^e*iK0TF(YBedGuy_8%aPQp7-(Kz;MA^o>4+UtdDxcQF3! zW0YMUVDFNR=sk}xojJ_ycQdu?MXF(h0IDaE-zE_CatHWDW=~C%Mj1{8GDs7p?~+UL z>q|)gn&q5(?aL1!mhlcRB?qD{KF(P>9p3gC_1DnNjH#7hyXIKtV21zP7ZPCsO zUyY7pmapKfO`FKFjQ-)f_;ad3`SL3`H8q6@5Jimb-c2xhlF-iO7#0ve*G;OBI8Zfn zB5DYtl&|i)k3*k)!fY6FbYgk)cIh+b5Yuj-Fkvw@eZp4j@Fx6;)K+*T9 zbXV!D1gv}RX(rdK<@YC^prX3y79#MMq`9*=%aJKrOW=6~o=3?T3hURCjf~KUBSfI6 z%GmZD*q?u9*~(RP2Ni}MewZ~cynvQUb9)2NqvTnPX$LuVs4d`EJ1KSMQVaszmMu)D zX70Ics5)OzrYZB+^- zeEV(u;bF91qxr!H42+G@yL2h4%8_TDrRbMfe%WPwwrv~bBq6BR=U`HoVve229aMmx zN{OIW!&+|+$K}pZ*t3TdS;op{liI3Ph~y?pS;o76ur^6@c>A@^;0MG%f0;Ie93@VlZmt~m#{xjxW5@A{ny+6Qpbrh4JRztkP8H?vA z+j7XY)Tc+Y5vAKUHm72J1dk{MZwLG>pGLJo(M`8QufJd3@J(F3R*)LHsXb b{)yP%K4@)8UH7Vx00000NkvXXu0mjf?%t3< diff --git a/lbrynet/lbrynet_gui/close2.gif b/lbrynet/lbrynet_gui/close2.gif deleted file mode 100644 index 5d1cf62e5bbee8325e87a5c875c09e095e6d0245..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 195 zcmZ?wbhEHbjEB+I7E=o--Nlj5G&n(GMaQE~LU{L(Y!pOzI$e;sK W1X9kx#MRQ@D7lMQGAB)p!5RR$IuU39 diff --git a/lbrynet/lbrynet_gui/gui.py b/lbrynet/lbrynet_gui/gui.py deleted file mode 100644 index 2457d52ea..000000000 --- a/lbrynet/lbrynet_gui/gui.py +++ /dev/null @@ -1,33 +0,0 @@ -import logging -import sys - -from lbrynet.lbrynet_gui.GuiApp import DownloaderApp -from twisted.internet import reactor, task -import locale - - -def start_gui(): - - log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s" - formatter = logging.Formatter(log_format) - - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - file_handler = logging.FileHandler("gui.log") - file_handler.setFormatter(formatter) - file_handler.addFilter(logging.Filter("lbrynet")) - logger.addHandler(file_handler) - - sys.stdout = open("gui.out.log", 'w') - sys.stderr = open("gui.err.log", 'w') - - locale.setlocale(locale.LC_ALL, '') - - app = DownloaderApp() - - d = task.deferLater(reactor, 0, app.start) - - reactor.run() - -if __name__ == "__main__": - start_gui() \ No newline at end of file diff --git a/lbrynet/lbrynet_gui/hide_options.gif b/lbrynet/lbrynet_gui/hide_options.gif deleted file mode 100644 index 9c95b5a9d3692981617338438f7cec1564c41569..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174 zcmZ?wbhEHb^k(2^XkcUjg8%>jEB+I7E=o--Nlj5G&n(GMaQE~LU{L(Y!pOzI$e;sK z1X9kxRM6AE^7LE&WEO{8)hninl=9fc8(5`wm_5Iq+jcB0^5&&=TH34M^nLR=^|iw3 z&daIJT8)nU6JJ($C7e_>kt~kdd^@`7Rc_YN)ABRD)2t3!e+{Tz=d!RsAXK{U{DwpS Z48Ml5T3GyWujokZ*m2@UM<4@(H2^-OMoa(z diff --git a/lbrynet/lbrynet_gui/lbry-dark-242x80.gif b/lbrynet/lbrynet_gui/lbry-dark-242x80.gif deleted file mode 100755 index 65ce976d432bff3f581ba5a0519ca31d18d6f63e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3647 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

U2Ho%8h$Z1A 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 O6N9I#pUXO@geCykjvm1P diff --git a/lbrynet/lbrynet_gui/show_options.gif b/lbrynet/lbrynet_gui/show_options.gif deleted file mode 100644 index c5ed0b6fd3e31d8c7aaa861a62e575434df31a2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmZ?wbhEHb^k(2^XkcUjg8%>jEB<6* Date: Fri, 1 Jul 2016 02:55:20 -0400 Subject: [PATCH 287/462] tanstaafl --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 6 +++--- lbrynet/lbrynet_daemon/LBRYPublisher.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index acd13939f..75203a9d0 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -427,9 +427,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: d = defer.succeed(None) - if float(self.session.wallet.wallet_balance) == 0.0: - d.addCallback(lambda _: self._check_first_run()) - d.addCallback(self._show_first_run_result) + # if float(self.session.wallet.wallet_balance) == 0.0: + # d.addCallback(lambda _: self._check_first_run()) + # d.addCallback(self._show_first_run_result) d.addCallback(lambda _: _wait_for_credits() if self.requested_first_run_credits else _announce()) return d diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index 084731a87..c2ef94d1d 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -127,6 +127,7 @@ class Publisher(object): log.info(err.getTraceback()) message = "An error occurred publishing %s to %s. Error: %s." if err.check(InsufficientFundsError): + d = defer.succeed(True) error_message = "Insufficient funds" else: d = defer.succeed(True) From 1cedd8ee5bd62c3e387a7c534ad2cb241eb56016 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 1 Jul 2016 03:16:08 -0400 Subject: [PATCH 288/462] update name --- lbrynet/core/LBRYcrdWallet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYcrdWallet.py index 970072f77..eea9ef915 100644 --- a/lbrynet/core/LBRYcrdWallet.py +++ b/lbrynet/core/LBRYcrdWallet.py @@ -410,8 +410,8 @@ class LBRYWallet(object): def update_name(self, name, value, amount): d = self._get_value_for_name(name) - d.addCallback(lambda r: (self._update_name(json.loads(r)['txid'], json.dumps(value), amount), json.loads(r)['txid'])) - d.addCallback(lambda new_txid, old_txid: self._update_name_metadata(name, value['sources']['lbry_sd_hash'], old_txid, new_txid)) + d.addCallback(lambda r: (self._update_name(r['txid'], json.dumps(value), amount), r['txid'])) + d.addCallback(lambda (new_txid, old_txid): self._update_name_metadata(name, value['sources']['lbry_sd_hash'], old_txid, new_txid)) return d def get_name_and_validity_for_sd_hash(self, sd_hash): @@ -529,7 +529,7 @@ class LBRYWallet(object): return d def _update_name_metadata(self, name, sd_hash, old_txid, new_txid): - d = self.db.runQuery("delete from name_metadata where txid=? and sd_hash=?", (old_txid, sd_hash)) + d = self.db.runQuery("delete * from name_metadata where txid=? and sd_hash=?", (old_txid, sd_hash)) d.addCallback(lambda _: self.db.runQuery("insert into name_metadata values (?, ?, ?)", (name, new_txid, sd_hash))) d.addCallback(lambda _: new_txid) return d @@ -864,7 +864,7 @@ class LBRYcrdWallet(LBRYWallet): def _update_name_rpc(self, txid, value, amount): rpc_conn = self._get_rpc_conn() - return rpc_conn.updatename(txid, value, amount) + return rpc_conn.updateclaim(txid, value, amount) @_catch_connection_error def _send_name_claim_rpc(self, name, value, amount): From e4f14d871f091e867a2b0f7c5defe809e6f73172 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 1 Jul 2016 03:36:51 -0400 Subject: [PATCH 289/462] update setup.py --- setup.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/setup.py b/setup.py index baf069fa7..eb6a6095e 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,6 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch 'lbrynet-launch-node = lbrynet.dht.node:main', 'lbrynet-launch-rpc-node = lbrynet.rpc_node:main', 'lbrynet-rpc-node-cli = lbrynet.node_rpc_cli:main', - 'lbrynet-gui = lbrynet.lbrynet_gui.gui:start_gui', 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:start', @@ -28,10 +27,6 @@ requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] -gui_data_files = ['close2.gif', 'lbry-dark-242x80.gif', 'lbry-dark-icon.xbm', 'lbry-dark-icon.ico', - 'drop_down.gif', 'show_options.gif', 'hide_options.gif', 'lbry.conf', 'lbry.png'] -gui_data_paths = [os.path.join(base_dir, 'lbrynet', 'lbrynet_gui', f) for f in gui_data_files] - setup(name='lbrynet', description='A fully-decentralized content marketplace', version=__version__, @@ -47,7 +42,6 @@ setup(name='lbrynet', 'blindrepeater.yapsy-plugin') ] ), - ('lbrynet/lbrynet_gui', gui_data_paths) ], dependency_links=['https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum'], ) From 808e3ea6e3673559c612154b95b72a8b06c33133 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 4 Jul 2016 04:57:30 -0400 Subject: [PATCH 290/462] rename LBRYcrdWallet.py to LBRYWallet.py and change default wallet to lbrycrd --- lbrynet/conf.py | 2 +- lbrynet/core/{LBRYcrdWallet.py => LBRYWallet.py} | 0 lbrynet/core/PTCWallet.py | 2 +- lbrynet/lbrynet_console/LBRYConsole.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 5 +++-- 5 files changed, 6 insertions(+), 5 deletions(-) rename lbrynet/core/{LBRYcrdWallet.py => LBRYWallet.py} (100%) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 9ead15f4d..d984f0454 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -38,7 +38,7 @@ API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRES UI_ADDRESS = "http://%s:%i" % (API_INTERFACE, API_PORT) PROTOCOL_PREFIX = "lbry" -DEFAULT_WALLET = "lbryum" +DEFAULT_WALLET = "lbrycrd" WALLET_TYPES = ["lbryum", "lbrycrd"] DEFAULT_TIMEOUT = 30 DEFAULT_MAX_SEARCH_RESULTS = 25 diff --git a/lbrynet/core/LBRYcrdWallet.py b/lbrynet/core/LBRYWallet.py similarity index 100% rename from lbrynet/core/LBRYcrdWallet.py rename to lbrynet/core/LBRYWallet.py diff --git a/lbrynet/core/PTCWallet.py b/lbrynet/core/PTCWallet.py index bf02a3b7e..38f187034 100644 --- a/lbrynet/core/PTCWallet.py +++ b/lbrynet/core/PTCWallet.py @@ -12,7 +12,7 @@ from lbrynet.pointtraderclient import pointtraderclient from twisted.internet import defer, threads from zope.interface import implements from twisted.python.failure import Failure -from lbrynet.core.LBRYcrdWallet import ReservedPoints +from lbrynet.core.LBRYWallet import ReservedPoints log = logging.getLogger(__name__) diff --git a/lbrynet/lbrynet_console/LBRYConsole.py b/lbrynet/lbrynet_console/LBRYConsole.py index fee6c2faa..72e40d467 100644 --- a/lbrynet/lbrynet_console/LBRYConsole.py +++ b/lbrynet/lbrynet_console/LBRYConsole.py @@ -43,7 +43,7 @@ from lbrynet.lbrynet_console.ControlHandlers import ShowServerStatusFactory, Mod from lbrynet.lbrynet_console.ControlHandlers import ModifyLBRYFileOptionsChooserFactory, StatusFactory from lbrynet.lbrynet_console.ControlHandlers import PeerStatsAndSettingsChooserFactory, PublishFactory from lbrynet.lbrynet_console.ControlHandlers import BlockchainStatusFactory -from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet +from lbrynet.core.LBRYWallet import LBRYcrdWallet, LBRYumWallet log = logging.getLogger(__name__) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 75203a9d0..4f4951876 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -45,7 +45,7 @@ from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession from lbrynet.core.PTCWallet import PTCWallet -from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet, LBRYumWallet +from lbrynet.core.LBRYWallet import LBRYcrdWallet, LBRYumWallet from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager # from lbryum import LOG_PATH as lbryum_log @@ -431,7 +431,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): # d.addCallback(lambda _: self._check_first_run()) # d.addCallback(self._show_first_run_result) - d.addCallback(lambda _: _wait_for_credits() if self.requested_first_run_credits else _announce()) + # d.addCallback(lambda _: _wait_for_credits() if self.requested_first_run_credits else _announce()) + d.addCallback(lambda _: _announce()) return d log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") From 568737e1234268a58270abfa15a6ad3d05f53d8b Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 4 Jul 2016 05:15:53 -0400 Subject: [PATCH 291/462] change lbryum version constant name --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 4f4951876..f8262bfc3 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -516,7 +516,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): try: r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') version = next(line.split("=")[1].split("#")[0].replace(" ", "") - for line in r if "ELECTRUM_VERSION" in line) + for line in r if "LBRYUM_VERSION" in line) version = version.replace("'", "") log.info("remote lbryum " + str(version) + " > local lbryum " + str(lbryum_version) + " = " + str( version > lbryum_version)) From 7b9e3aace9fa471ec059be06ce62a77668c2c539 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 4 Jul 2016 05:43:26 -0400 Subject: [PATCH 292/462] use lbrycrd --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index f8262bfc3..7549863ce 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -143,7 +143,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): isLeaf = True - def __init__(self, root, wallet_type=None): + def __init__(self, root, wallet_type="lbrycrd"): jsonrpc.JSONRPC.__init__(self) reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 74906da0b..0fae59410 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -183,7 +183,7 @@ class HostedLBRYFile(resource.Resource): class LBRYDaemonServer(object): def _setup_server(self, wallet): self.root = LBRYindex(os.path.join(os.path.join(data_dir, "lbry-ui"), "active")) - self._api = LBRYDaemon(self.root, wallet_type=wallet) + self._api = LBRYDaemon(self.root, wallet_type="lbrycrd") self.root.putChild("view", HostedLBRYFile(self._api)) self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) From 7cd30efbe9b7c2feba6f1b6512882a629fc93b32 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 4 Jul 2016 08:41:44 -0400 Subject: [PATCH 293/462] download lbrycrd binaries and set up lbrycrddpath.conf --- packaging/ubuntu/lbry | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index d24574f32..80b4618ed 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -1,5 +1,17 @@ #!/bin/bash +if [ -f "~/.lbrycrddpath.conf" ]; then + lbrycrdd_path = `cat ~/.lbrycrddpath.conf` +else + mkdir -p "~/.lbrycrd" + cd ~/.lbrycrd + echo "Downloading lbrycrd binaries" + wget http://s3.amazonaws.com/files.lbry.io/bins.zip + unzip -o bins.zip + rm bins.zip + echo `pwd`/lbrycrdd > ~/.lbrycrddpath.conf +fi + set -euo pipefail WEB_UI_BRANCH='master' From 6d7608d85123c7c0a8c5cfa81c0dad307774ff5b Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 4 Jul 2016 16:16:34 -0400 Subject: [PATCH 294/462] Create lbrycrd.conf if needed --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 7549863ce..f73c0eb35 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,8 +1,10 @@ +import string import locale import mimetypes import os import subprocess import sys +import random import simplejson as json import binascii import logging.handlers @@ -328,6 +330,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.lbry_file_metadata_manager = None self.lbry_file_manager = None + if self.wallet_type == "lbrycrd": + if os.path.isfile(self.lbrycrd_conf): + log.info("Using lbrycrd.conf found at " + self.lbrycrd_conf) + else: + log.info("No lbrycrd.conf found at " + self.lbrycrd_conf + ". Generating now...") + password = "".join(random.SystemRandom().choice(string.ascii_letters + string.digits + "_") for i in range(20)) + with open(self.lbrycrd_conf, 'w') as f: + f.write("rpcuser=rpcuser\n") + f.write("rpcpassword=" + password) + log.info("Done writing lbrycrd.conf") + def render(self, request): request.content.seek(0, 0) # Unmarshal the JSON-RPC data. From 16f48d040005274900c44bb4dc7df60ea1dba4b9 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 4 Jul 2016 15:18:08 -0500 Subject: [PATCH 295/462] Add lbrycrd to app bundle on macos --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 9 ++++++++- packaging/osx/lbry-osx-app/setup_app.sh | 11 +++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 7549863ce..c67db906c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -291,7 +291,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self.wallet_dir = os.path.join(get_path(FOLDERID.RoamingAppData, UserHandle.current), "lbryum") elif sys.platform == "darwin": - self.lbrycrdd_path = "./lbrycrdd" + # use the path from the bundle if its available. + try: + import Foundation + bundle = Foundation.NSBundle.mainBundle() + self.lbrycrdd_path = bundle.pathForResource_ofType_('lbrycrdd', None) + except Exception: + log.exception('Failed to get path from bundle, falling back to default') + self.lbrycrdd_path = "./lbrycrdd" if self.wallet_type == "lbrycrd": self.wallet_dir = user_data_dir("lbrycrd") else: diff --git a/packaging/osx/lbry-osx-app/setup_app.sh b/packaging/osx/lbry-osx-app/setup_app.sh index 8be935514..5630a1e3f 100755 --- a/packaging/osx/lbry-osx-app/setup_app.sh +++ b/packaging/osx/lbry-osx-app/setup_app.sh @@ -46,7 +46,13 @@ codesign --deep -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Co codesign -vvvv "${DEST}/dist/LBRYURIHandler.app" pip install certifi pyobjc-core pyobjc-framework-Cocoa pyobjc-framework-CFNetwork -python setup_app.py py2app + +# add lbrycrdd as a resource. Following +# http://stackoverflow.com/questions/11370012/can-executables-made-with-py2app-include-other-terminal-scripts-and-run-them +wget https://github.com/lbryio/lbrycrd/releases/download/v0.3-osx/lbrycrdd +python setup_app.py py2app --resources lbrycrdd + +chmod +x "${DEST}/dist/LBRY.app/Contents/Resources/lbrycrdd" echo "Removing i386 libraries" @@ -74,5 +80,6 @@ codesign -vvvv "${DEST}/dist/LBRY.app" rm -rf $tmp mv dist/LBRY.app LBRY.app -rm -rf dist +rm -rf dist "${NAME}.${VERSION}.dmg" +# TODO: make this pretty! hdiutil create "${NAME}.${VERSION}.dmg" -volname lbry -srcfolder LBRY.app From 976c8b3ce0531876c8eaa96c1c4e173fca190121 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 4 Jul 2016 15:40:52 -0500 Subject: [PATCH 296/462] add a more useful exception if auth fails on lbrycrd --- lbrynet/core/LBRYWallet.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index eea9ef915..7f7dd7e88 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -726,7 +726,11 @@ class LBRYcrdWallet(LBRYWallet): tries = 0 try: rpc_conn = self._get_rpc_conn() - rpc_conn.getinfo() + try: + rpc_conn.getinfo() + except ValueError: + log.exception('Failed to get rpc info. Rethrowing with a hopefully more useful error message') + raise Exception('Failed to get rpc info from lbrycrdd. Try restarting lbrycrdd') log.info("lbrycrdd was already running when LBRYcrdWallet was started.") return except (socket.error, JSONRPCException): @@ -1267,4 +1271,4 @@ class LBRYcrdAddressQueryHandler(object): log.warning("Expected a request for an address, but did not receive one") return defer.fail(Failure(ValueError("Expected but did not receive an address request"))) else: - return defer.succeed({}) \ No newline at end of file + return defer.succeed({}) From 6034bd3cb2b2f555e8e621e5256bc21c55899969 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 4 Jul 2016 15:46:27 -0500 Subject: [PATCH 297/462] version bump: 0.3.0.1 --- lbrynet/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 7027afdd4..ba3d3a95c 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -version = (0, 3, 0) +version = (0, 3, 0, 1) __version__ = ".".join([str(x) for x in version]) From e35dce530a51cacf0ed22d1e3c01088425b9da65 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Mon, 4 Jul 2016 17:46:26 -0400 Subject: [PATCH 298/462] Set default download dir to ~/Downloads on Linux --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 826857469..90575f328 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -178,7 +178,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): default_download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') self.db_dir = user_data_dir("LBRY") else: - default_download_directory = os.getcwd() + default_download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + + if os.exists(default_download_directory): + if os.isfile(default_download_directory): + # Weird, ~/Downloads is a file, not a directory. Revert to just the home dir. + default_download_directory = os.path.expanduser("~") + else: + os.makedirs(default_download_directory) + self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') From 794f2d8576d5ffdcf71cb648677ee953307616f5 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 4 Jul 2016 16:51:29 -0500 Subject: [PATCH 299/462] version bump: 0.3.0.2 --- lbrynet/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index ba3d3a95c..4b2e3ebd9 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -version = (0, 3, 0, 1) +version = (0, 3, 0, 2) __version__ = ".".join([str(x) for x in version]) From 5972dbb6a79d1ad005b49884b91eeda5ac403f0d Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Mon, 4 Jul 2016 19:18:22 -0400 Subject: [PATCH 300/462] fix lbry init script --- packaging/ubuntu/lbry | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index 80b4618ed..7336ba66b 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -1,18 +1,30 @@ #!/bin/bash -if [ -f "~/.lbrycrddpath.conf" ]; then - lbrycrdd_path = `cat ~/.lbrycrddpath.conf` -else - mkdir -p "~/.lbrycrd" - cd ~/.lbrycrd +set -euo pipefail + +LBRYCRDDPATHCONF="$HOME/.lbrycrddpath.conf" +LBRYCRDDIR="$HOME/.lbrycrd" +LBRYCRDDBIN="$LBRYCRDDIR/lbrycrdd" +LBRYCRDCONF="$LBRYCRDDIR/lbrycrd.conf" + +if [ ! -f "$LBRYCRDDBIN" ]; then + mkdir -p "$LBRYCRDDIR" + ( + cd "$LBRYCRDDIR" echo "Downloading lbrycrd binaries" wget http://s3.amazonaws.com/files.lbry.io/bins.zip unzip -o bins.zip rm bins.zip - echo `pwd`/lbrycrdd > ~/.lbrycrddpath.conf + ) fi -set -euo pipefail +if [ ! -f "$LBRYCRDDPATHCONF" ]; then + echo "$LBRYCRDDBIN" > "$LBRYCRDDPATHCONF" +fi + +if [ ! -f "$LBRYCRDCONF" ]; then + echo -e "rpcuser=lbryrpc\nrpcpassword=$(env LC_CTYPE=C LC_ALL=C tr -dc A-Za-z0-9 < /dev/urandom | head -c 16 | xargs)" > "$LBRYCRDCONF" +fi WEB_UI_BRANCH='master' From cca4331ac4cdd17904b8c3a04da10e50fb72504c Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 4 Jul 2016 19:34:51 -0400 Subject: [PATCH 301/462] create lbrycrddpath.conf if it doesnt exist --- lbrynet/__init__.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 4b2e3ebd9..b1c305fe3 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -version = (0, 3, 0, 2) +version = (0, 3, 1) __version__ = ".".join([str(x) for x in version]) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 90575f328..fe72f63b2 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -320,6 +320,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbryum") + if os.name != 'nt': + lbrycrdd_path_conf = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf") + if not os.path.isfile(lbrycrdd_path_conf): + f = open(lbrycrdd_path_conf) + f.write(self.lbrycrdd_path) + f.close() + self.created_data_dir = False if not os.path.exists(self.db_dir): os.mkdir(self.db_dir) From 69c5631b253e27650696ca9337032601d652f1c8 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 4 Jul 2016 19:53:01 -0400 Subject: [PATCH 302/462] make lbrycrddpath.conf if it doesnt exist, point to binary in app --- packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py b/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py index 56f02846f..c8dc04446 100644 --- a/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py +++ b/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py @@ -1,15 +1,32 @@ import AppKit import webbrowser import sys +import os import logging import socket import platform +import shutil +from appdirs import user_data_dir from PyObjCTools import AppHelper from twisted.internet import reactor from twisted.web import server +import Foundation +bundle = Foundation.NSBundle.mainBundle() +lbrycrdd_path = bundle.pathForResource_ofType_('lbrycrdd', None) +lbrycrdd_path_conf = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf") +wallet_dir = user_data_dir("lbrycrd") + +if not os.path.isdir(wallet_dir): + shutil.os.mkdir(wallet_dir) + +if not os.path.isfile(lbrycrdd_path_conf): + f = open(lbrycrdd_path_conf) + f.write(lbrycrdd_path) + f.close() + from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer from lbrynet.conf import API_PORT, API_INTERFACE, ICON_PATH, APP_NAME from lbrynet.conf import UI_ADDRESS From 0a3f81e364ce8dd41ffa61f7edd31fac4e7efaaa Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 4 Jul 2016 20:02:38 -0400 Subject: [PATCH 303/462] fix download directory not existing problem --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index fe72f63b2..95c42b5e1 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -179,15 +179,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.db_dir = user_data_dir("LBRY") else: default_download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') - - if os.exists(default_download_directory): - if os.isfile(default_download_directory): - # Weird, ~/Downloads is a file, not a directory. Revert to just the home dir. - default_download_directory = os.path.expanduser("~") - else: - os.makedirs(default_download_directory) - self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + try: + if not os.path.isdir(default_download_directory): + os.mkdir(default_download_directory) + except: + log.info("Couldn't make download directory, using home") + default_download_directory = os.path.expanduser("~") self.daemon_conf = os.path.join(self.db_dir, 'daemon_settings.json') From 4c2ab23c1f2acef343d3031bd482607ad767135a Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 4 Jul 2016 20:19:04 -0400 Subject: [PATCH 304/462] write to the file that's supposed to be written to --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 95c42b5e1..1d9c6b20f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -321,8 +321,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): if os.name != 'nt': lbrycrdd_path_conf = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf") if not os.path.isfile(lbrycrdd_path_conf): - f = open(lbrycrdd_path_conf) - f.write(self.lbrycrdd_path) + f = open(lbrycrdd_path_conf, "w") + f.write(str(self.lbrycrdd_path)) f.close() self.created_data_dir = False From df3c5b9a77aabb55f84486a67c91e25a09b2d231 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 4 Jul 2016 20:47:43 -0400 Subject: [PATCH 305/462] open conf for write in app --- packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py b/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py index c8dc04446..12315369c 100644 --- a/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py +++ b/packaging/osx/lbry-osx-app/lbrygui/LBRYApp.py @@ -23,7 +23,7 @@ if not os.path.isdir(wallet_dir): shutil.os.mkdir(wallet_dir) if not os.path.isfile(lbrycrdd_path_conf): - f = open(lbrycrdd_path_conf) + f = open(lbrycrdd_path_conf, "w") f.write(lbrycrdd_path) f.close() From 36a99e6ec8f5e900840999680b049bd7fc9ea48c Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 5 Jul 2016 14:06:37 -0400 Subject: [PATCH 306/462] run lbry as non-root user --- packaging/ubuntu/postinst_append | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packaging/ubuntu/postinst_append b/packaging/ubuntu/postinst_append index 2e040b492..2b3200504 100755 --- a/packaging/ubuntu/postinst_append +++ b/packaging/ubuntu/postinst_append @@ -16,7 +16,11 @@ case $? in esac if [ $RUN = 1 ]; then - xdg-open "lbry://lbry" + if [ -n $SUDO_USER ]; then + sudo --user="$SUDO_USER" xdg-open "lbry://lbry" + else + xdg-open "lbry://lbry" + fi else zenity --info --text="LBRY Installed\n\nNo problem. You can run LBRY later by going to From b55752e715f9378117de25da6855443ef1b5ae66 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 5 Jul 2016 14:25:36 -0400 Subject: [PATCH 307/462] add bumpversion script --- .bumpversion.cfg | 6 ++++++ lbrynet/__init__.py | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .bumpversion.cfg diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 000000000..c62815013 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,6 @@ +[bumpversion] +current_version = 0.3.1 +commit = True +tag = True + +[bumpversion:file:lbrynet/__init__.py] diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index b1c305fe3..958a7b813 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,4 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -version = (0, 3, 1) -__version__ = ".".join([str(x) for x in version]) +__version__ = "0.3.1" From f1c5d1cb23c5579ac97c90cbf3364726a4fc5391 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 5 Jul 2016 14:30:35 -0400 Subject: [PATCH 308/462] =?UTF-8?q?Bump=20version:=200.3.1=20=E2=86=92=200?= =?UTF-8?q?.3.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 3 ++- lbrynet/__init__.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index c62815013..f0af37203 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,6 +1,7 @@ [bumpversion] -current_version = 0.3.1 +current_version = 0.3.2 commit = True tag = True [bumpversion:file:lbrynet/__init__.py] + diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 958a7b813..c1857d897 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,4 +4,4 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.1" +__version__ = "0.3.2" From d875325ceebf3115261a2296b99d7cef45624c7e Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 5 Jul 2016 15:20:30 -0400 Subject: [PATCH 309/462] better non-root fix, restore version var --- lbrynet/__init__.py | 1 + packaging/ubuntu/postinst_append | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index c1857d897..a4aacb556 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -5,3 +5,4 @@ logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) __version__ = "0.3.2" +version = tuple(__version__.split('.')) diff --git a/packaging/ubuntu/postinst_append b/packaging/ubuntu/postinst_append index 2b3200504..e082aad14 100755 --- a/packaging/ubuntu/postinst_append +++ b/packaging/ubuntu/postinst_append @@ -16,11 +16,7 @@ case $? in esac if [ $RUN = 1 ]; then - if [ -n $SUDO_USER ]; then - sudo --user="$SUDO_USER" xdg-open "lbry://lbry" - else - xdg-open "lbry://lbry" - fi + /bin/su - "$(logname)" -c 'xdg-open "lbry://lbry"' else zenity --info --text="LBRY Installed\n\nNo problem. You can run LBRY later by going to From 8336930091c38db9ace8af67d3d6a10cfad53dbb Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 5 Jul 2016 15:20:35 -0400 Subject: [PATCH 310/462] =?UTF-8?q?Bump=20version:=200.3.2=20=E2=86=92=200?= =?UTF-8?q?.3.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- lbrynet/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f0af37203..45ccdcbab 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.2 +current_version = 0.3.3 commit = True tag = True diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index a4aacb556..c6db3be6d 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.2" +__version__ = "0.3.3" version = tuple(__version__.split('.')) From 8b37952128fc33355e63423823d280c8e5e56c83 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Tue, 5 Jul 2016 15:53:51 -0400 Subject: [PATCH 311/462] make-deb doesn't like utf8 arrow in commit message --- .bumpversion.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 45ccdcbab..0d9eca2ca 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -2,6 +2,7 @@ current_version = 0.3.3 commit = True tag = True +message = Bump version: {current_version} -> {new_version} [bumpversion:file:lbrynet/__init__.py] From 087a9b70b4bd45a177a88cf04954836dcc5c6589 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Tue, 5 Jul 2016 14:57:48 -0500 Subject: [PATCH 312/462] Use patched version of make-deb Ran into a bug in make-deb that fails when there is utf-8 characters in the git commit message. https://github.com/nylas/make-deb/issues/13. I wrote a patch to fix the bug and am installing that version until the make-deb maintainers incorporate it --- packaging/ubuntu/ubuntu_package_setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 3388570a7..6f237abf7 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -101,7 +101,7 @@ $SUDO apt-get ${QUIET} install -y --no-install-recommends \ # need a modern version of pip (more modern than ubuntu default) $SUDO pip install --upgrade pip -$SUDO pip install make-deb +$SUDO pip install git+https://github.com/jobevers/make-deb # build packages # From 8b1d75ea98cef1fddab663379435b37dab132b54 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 6 Jul 2016 01:27:25 -0400 Subject: [PATCH 313/462] check every 30 minutes for a new ui version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -previously the check for a new ui version was only at startup, which resulted in bugs persisting after they’d been fixed -add option to skip requirements check -try to load an existing ui if it can’t be obtained from github --- lbrynet/__init__.py | 4 ++-- lbrynet/lbrynet_daemon/LBRYDaemon.py | 32 ++++++++++++++++--------- lbrynet/lbrynet_daemon/LBRYUIManager.py | 25 +++++++++++++++---- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index c6db3be6d..77a3538e4 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.3" -version = tuple(__version__.split('.')) +__version__ = "0.3.4" +version = tuple(__version__.split('.')) \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 1d9c6b20f..ce30d4fb6 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -430,7 +430,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.error(failure) return jsonrpclib.Fault(self.FAILURE, "error") - def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False): + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=False, branch_specified=False, host_ui=True): def _log_starting_vals(): log.info("Starting balance: " + str(self.session.wallet.wallet_balance)) return defer.succeed(None) @@ -473,11 +473,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.internet_connection_checker.start(3600) self.version_checker.start(3600 * 12) self.connection_problem_checker.start(1) + if host_ui: + self.lbry_ui_manager.update_checker.start(1800, now=False) d = defer.Deferred() - d.addCallback(lambda _: self.lbry_ui_manager.setup(branch=branch, - user_specified=user_specified, - branch_specified=branch_specified)) + if host_ui: + d.addCallback(lambda _: self.lbry_ui_manager.setup(branch=branch, + user_specified=user_specified, + branch_specified=branch_specified)) d.addCallback(lambda _: self._initial_setup()) d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) @@ -563,8 +566,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _get_lbrynet_version(): try: r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') - vs = next(i for i in r if 'version =' in i).split("=")[1].replace(" ", "") - vt = tuple(int(x) for x in vs[1:-1].split(',')) + vs = next(i for i in r if '__version__ =' in i).split("=")[1].replace(" ", "") + vt = tuple(int(x) for x in vs[1:-1].split('.')) vr = ".".join([str(x) for x in vt]) log.info("remote lbrynet " + str(vr) + " > local lbrynet " + str(lbrynet_version) + " = " + str( vr > lbrynet_version)) @@ -700,6 +703,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.version_checker.stop() if self.connection_problem_checker.running: self.connection_problem_checker.stop() + if self.lbry_ui_manager.update_checker.running: + self.lbry_ui_manager.update_checker.stop() d = self._upload_log(log_type="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) @@ -2248,12 +2253,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'path': path to a ui folder """ - if 'path' in p.keys(): - d = self.lbry_ui_manager.setup(user_specified=p['path']) - elif 'branch' in p.keys(): - d = self.lbry_ui_manager.setup(branch=p['branch']) + if 'check_requirements' in p: + check_require = p['check_requirements'] else: - d = self.lbry_ui_manager.setup() + check_require = True + + if 'path' in p: + d = self.lbry_ui_manager.setup(user_specified=p['path'], check_requirements=check_require) + elif 'branch' in p: + d = self.lbry_ui_manager.setup(branch=p['branch'], check_requirements=check_require) + else: + d = self.lbry_ui_manager.setup(check_requirements=check_require) d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index ce36aaf01..0f0aa2a53 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -8,6 +8,7 @@ from urllib2 import urlopen from StringIO import StringIO from twisted.web import static from twisted.internet import defer +from twisted.internet.task import LoopingCall from lbrynet.conf import DEFAULT_UI_BRANCH, LOG_FILE_NAME from lbrynet import __version__ as lbrynet_version from lbryum.version import LBRYUM_VERSION as lbryum_version @@ -52,9 +53,12 @@ class LBRYUIManager(object): self.config = os.path.join(self.ui_root, "active.json") self.update_requires = os.path.join(self.update_dir, "requirements.txt") self.requirements = {} + self.check_requirements = True self.ui_dir = self.active_dir self.git_version = None self.root = root + self.branch = None + self.update_checker = LoopingCall(self.setup) if not os.path.isfile(os.path.join(self.config)): self.loaded_git_version = None @@ -73,8 +77,11 @@ class LBRYUIManager(object): self.loaded_branch = None self.loaded_requirements = None - def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=None, branch_specified=False): - self.branch = branch + def setup(self, branch=DEFAULT_UI_BRANCH, user_specified=None, branch_specified=False, check_requirements=None): + if check_requirements is not None: + self.check_requirements = check_requirements + if self.branch is not None: + self.branch = branch if user_specified: if os.path.isdir(user_specified): log.info("Checking user specified UI directory: " + str(user_specified)) @@ -84,7 +91,7 @@ class LBRYUIManager(object): d.addCallback(lambda _: self._load_ui()) return d else: - log.info("User specified UI directory doesn't exist, using " + branch) + log.info("User specified UI directory doesn't exist, using " + self.branch) elif self.loaded_branch == "user-specified" and not branch_specified: log.info("Loading user provided UI") d = self._load_ui() @@ -113,8 +120,12 @@ class LBRYUIManager(object): log.info("UI updates available, checking if installation meets requirements") return defer.succeed(False) + def _use_existing(): + log.info("Failed to check for new ui version, trying to use cached ui") + return defer.succeed(True) + d = _get_git_info() - d.addCallback(_set_git) + d.addCallbacks(_set_git, lambda _: _use_existing) return d def migrate_ui(self, source=None): @@ -127,6 +138,10 @@ class LBRYUIManager(object): source_dir = source delete_source = False + def _skip_requirements(): + log.info("Skipping ui requirement check") + return defer.succeed(True) + def _check_requirements(): if not os.path.isfile(requires_file): log.info("No requirements.txt file, rejecting request to migrate this UI") @@ -197,7 +212,7 @@ class LBRYUIManager(object): self.loaded_requirements = loaded_ui['requirements'] return defer.succeed(True) - d = _check_requirements() + d = _check_requirements() if self.check_requirements else _skip_requirements() d.addCallback(lambda r: _do_migrate() if r else _disp_failure()) return d From 1984132eae50b31cc3089cb66beea0dc6a1a663a Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 6 Jul 2016 01:46:49 -0400 Subject: [PATCH 314/462] add update_available keys to version() as to not require making the version string comparison in whatever is making the daemon call --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index ce30d4fb6..273c1b16e 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1487,7 +1487,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'lbryum_version': lbryum_version, 'ui_version': self.ui_version, 'remote_lbrynet': self.git_lbrynet_version, - 'remote_lbryum': self.git_lbryum_version + 'remote_lbryum': self.git_lbryum_version, + 'lbrynet_update_available': lbrynet_version < self.git_lbrynet_version, + 'lbryum_update_available': lbryum_version < self.git_lbryum_version } log.info("[" + str(datetime.now()) + "] Get version info: " + json.dumps(msg)) From 257e38de125e68406545980e4c512aaf907f1890 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 6 Jul 2016 02:17:38 -0400 Subject: [PATCH 315/462] add command line wrapper for lbrynet-daemon calls --- lbrynet/lbrynet_daemon/LBRYDaemonCLI.py | 36 +++++++++++++++++++++++++ setup.py | 3 ++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 lbrynet/lbrynet_daemon/LBRYDaemonCLI.py diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py new file mode 100644 index 000000000..c5b56a553 --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py @@ -0,0 +1,36 @@ +import sys +import json + +from lbrynet.conf import API_CONNECTION_STRING, LOG_FILE_NAME +from jsonrpc.proxy import JSONRPCProxy + +api = JSONRPCProxy.from_url(API_CONNECTION_STRING) + +try: + s = api.is_running() +except: + print "lbrynet-daemon isn't running" + +def main(): + args = sys.argv[1:] + meth = args[0] + if len(args) > 1: + if isinstance(args[1], dict): + params = args[1] + elif isinstance(args[1], str) or isinstance(args[1], unicode): + params = json.loads(args[1]) + else: + params = None + + if meth in api.help(): + if params: + r = api.call(meth, params) + else: + r = api.call(meth) + print r + else: + print "Unrecognized function" + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/setup.py b/setup.py index eb6a6095e..ce96e06a8 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,8 @@ console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch 'lbrynet-lookup-hosts-for-hash = lbrynet.dht_scripts:get_hosts_for_hash_in_dht', 'lbrynet-announce_hash_to_dht = lbrynet.dht_scripts:announce_hash_to_dht', 'lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:start', - 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:stop'] + 'stop-lbrynet-daemon = lbrynet.lbrynet_daemon.LBRYDaemonControl:stop', + 'lbrynet-cli = lbrynet.lbrynet_daemon.LBRYDaemonCLI:main'] requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', From 34b1259dc6b867763f0852d4991faf8f77b5abad Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 6 Jul 2016 02:20:18 -0400 Subject: [PATCH 316/462] move check for if daemon is running to inside of function --- lbrynet/lbrynet_daemon/LBRYDaemonCLI.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py index c5b56a553..91e7f2068 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py @@ -4,14 +4,16 @@ import json from lbrynet.conf import API_CONNECTION_STRING, LOG_FILE_NAME from jsonrpc.proxy import JSONRPCProxy -api = JSONRPCProxy.from_url(API_CONNECTION_STRING) - -try: - s = api.is_running() -except: - print "lbrynet-daemon isn't running" def main(): + api = JSONRPCProxy.from_url(API_CONNECTION_STRING) + + try: + s = api.is_running() + except: + print "lbrynet-daemon isn't running" + sys.exit(1) + args = sys.argv[1:] meth = args[0] if len(args) > 1: From 9277c3c674256dc2399678d4fbb9cc7f6633c83e Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 6 Jul 2016 03:02:55 -0400 Subject: [PATCH 317/462] add help for when things go wrong --- lbrynet/lbrynet_daemon/LBRYDaemonCLI.py | 33 ++++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py index 91e7f2068..b146cb1a6 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py @@ -4,6 +4,13 @@ import json from lbrynet.conf import API_CONNECTION_STRING, LOG_FILE_NAME from jsonrpc.proxy import JSONRPCProxy +help_msg = "Useage: lbrynet-cli method json-args\n" \ + + "Examples: " \ + + "lbrynet-cli resolve_name '{\"name\": \"what\"}'\n" \ + + "lbrynet-cli get_balance\n" \ + + "lbrynet-cli help '{\"function\": \"resolve_name\"}'\n" \ + + "\n******lbrynet-cli functions******\n" + def main(): api = JSONRPCProxy.from_url(API_CONNECTION_STRING) @@ -16,6 +23,15 @@ def main(): args = sys.argv[1:] meth = args[0] + + msg = help_msg + for f in api.help(): + msg += f + "\n" + + if meth in ['--help', '-h', 'help']: + print msg + sys.exit(1) + if len(args) > 1: if isinstance(args[1], dict): params = args[1] @@ -25,13 +41,18 @@ def main(): params = None if meth in api.help(): - if params: - r = api.call(meth, params) - else: - r = api.call(meth) - print r + try: + if params: + r = api.call(meth, params) + else: + r = api.call(meth) + print r + except: + print "Something went wrong, here's the usage for %s:" % meth + print api.help({'function': meth}) else: - print "Unrecognized function" + print "Unknown function" + print msg if __name__ == '__main__': From 03c02ff74fbed2aef6f33935f8150500b2be213b Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 6 Jul 2016 03:37:31 -0400 Subject: [PATCH 318/462] lbrycrdd path wierdness --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 39 ++++------------------------ 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 273c1b16e..16dc95c86 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -247,11 +247,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): if self.session_settings['last_version'] != self.default_settings['last_version']: self.session_settings['last_version'] = self.default_settings['last_version'] - self.first_run_after_update = True log.info("First run after update") - if lbrynet_version == '0.2.5': - self.session_settings['startup_scripts'].append({'script_name': 'migrateto025', 'run_once': True}) f = open(self.daemon_conf, "w") f.write(json.dumps(self.session_settings)) @@ -485,7 +482,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: threads.deferToThread(self._setup_data_directory)) d.addCallback(lambda _: self._check_db_migration()) d.addCallback(lambda _: self._get_settings()) - d.addCallback(lambda _: self._get_lbrycrdd_path()) d.addCallback(lambda _: self._get_session()) d.addCallback(lambda _: add_lbry_file_to_sd_identifier(self.sd_identifier)) d.addCallback(lambda _: self._setup_stream_identifier()) @@ -842,7 +838,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = self.settings.start() d.addCallback(lambda _: self.settings.get_lbryid()) d.addCallback(self._set_lbryid) - d.addCallback(lambda _: self._get_lbrycrdd_path()) return d def _set_lbryid(self, lbryid): @@ -882,18 +877,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def get_wallet(): - if self.wallet_type == "lbrycrd": + if self.wallet_type in ["lbrycrd", "lbryum"]: #force lbrycrd wallet no matter what while lbryum is down log.info("Using lbrycrd wallet") - lbrycrdd_path = None - if self.start_lbrycrdd is True: - lbrycrdd_path = self.lbrycrdd_path - if not lbrycrdd_path: - lbrycrdd_path = self.default_lbrycrdd_path d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.lbrycrd_conf, - lbrycrdd_path=lbrycrdd_path)) - elif self.wallet_type == "lbryum": - log.info("Using lbryum wallet") - d = defer.succeed(LBRYumWallet(self.db_dir)) + lbrycrdd_path=self.lbrycrdd_path)) + # elif self.wallet_type == "lbryum": + # log.info("Using lbryum wallet") + # d = defer.succeed(LBRYumWallet(self.db_dir)) elif self.wallet_type == "ptc": log.info("Using PTC wallet") d = defer.succeed(PTCWallet(self.db_dir)) @@ -984,25 +974,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self.startup_message = None - def _get_lbrycrdd_path(self): - def get_lbrycrdd_path_conf_file(): - lbrycrdd_path_conf_path = os.path.join(os.path.expanduser("~"), ".lbrycrddpath.conf") - if not os.path.exists(lbrycrdd_path_conf_path): - return "" - lbrycrdd_path_conf = open(lbrycrdd_path_conf_path) - lines = lbrycrdd_path_conf.readlines() - return lines - - d = threads.deferToThread(get_lbrycrdd_path_conf_file) - - def load_lbrycrdd_path(conf): - for line in conf: - if len(line.strip()) and line.strip()[0] != "#": - self.lbrycrdd_path = line.strip() - - d.addCallback(load_lbrycrdd_path) - return d - def _setup_stream_identifier(self): file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter, self.session.blob_manager, self.stream_info_manager, From 72b4a778a6809b5526afb06e5e9ff517ece5c7d5 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 10:09:40 -0400 Subject: [PATCH 319/462] dont forget bumpversion --- .bumpversion.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 0d9eca2ca..83d93855e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.3 +current_version = 0.3.4 commit = True tag = True message = Bump version: {current_version} -> {new_version} From 5171c801d9f3a29bec4b33bbb05be9eadfd7f3d2 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 13:34:17 -0400 Subject: [PATCH 320/462] add lbry icons, symlink into user path --- packaging/ubuntu/icons/lbry128.png | Bin 0 -> 7552 bytes packaging/ubuntu/icons/lbry256.png | Bin 0 -> 18944 bytes packaging/ubuntu/icons/lbry32.png | Bin 0 -> 1248 bytes packaging/ubuntu/icons/lbry48.png | Bin 0 -> 2675 bytes packaging/ubuntu/icons/lbry96.png | Bin 0 -> 6294 bytes packaging/ubuntu/ubuntu_package_setup.sh | 14 +++++++++++++- 6 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packaging/ubuntu/icons/lbry128.png create mode 100644 packaging/ubuntu/icons/lbry256.png create mode 100644 packaging/ubuntu/icons/lbry32.png create mode 100644 packaging/ubuntu/icons/lbry48.png create mode 100644 packaging/ubuntu/icons/lbry96.png diff --git a/packaging/ubuntu/icons/lbry128.png b/packaging/ubuntu/icons/lbry128.png new file mode 100644 index 0000000000000000000000000000000000000000..de083fcbc27e14cb4fc928101880faccb682e651 GIT binary patch literal 7552 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 JEQgo`{SV{^)71a~ literal 0 HcmV?d00001 diff --git a/packaging/ubuntu/icons/lbry256.png b/packaging/ubuntu/icons/lbry256.png new file mode 100644 index 0000000000000000000000000000000000000000..6599574065ccfcc176cdd5e8726ca783f85f7097 GIT binary patch literal 18944 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

U2Ho%8h$Z1A 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 O6N9I#pUXO@geCykjvm1P literal 0 HcmV?d00001 diff --git a/packaging/ubuntu/icons/lbry32.png b/packaging/ubuntu/icons/lbry32.png new file mode 100644 index 0000000000000000000000000000000000000000..ece10998cc9454bdd9887ccffbf558d1a3abbcf2 GIT binary patch literal 1248 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 literal 0 HcmV?d00001 diff --git a/packaging/ubuntu/icons/lbry96.png b/packaging/ubuntu/icons/lbry96.png new file mode 100644 index 0000000000000000000000000000000000000000..195c31ea73890c636c4d9b0d8fb87eaf5a8489db GIT binary patch literal 6294 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 eK;6Fo1%Y!%-PwqlJP-JTfS@F&E?XyU7V> control/md5sums } -addfile "$PACKAGING_DIR/lbry" usr/share/python/lbrynet/bin/lbry + + +addfile "$PACKAGING_DIR/icons/lbry32.png" usr/share/icons/hicolor/32x32/apps/lbry.png +addfile "$PACKAGING_DIR/icons/lbry48.png" usr/share/icons/hicolor/48x48/apps/lbry.png +addfile "$PACKAGING_DIR/icons/lbry96.png" usr/share/icons/hicolor/96x96/apps/lbry.png +addfile "$PACKAGING_DIR/icons/lbry128.png" usr/share/icons/hicolor/128x128/apps/lbry.png +addfile "$PACKAGING_DIR/icons/lbry256.png" usr/share/icons/hicolor/256x256/apps/lbry.png addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop +$BINPATH=usr/share/python/lbrynet/bin +addfile "$PACKAGING_DIR/lbry" "$BINPATH/lbry" + +ln -s "/$BINPATH/lbry" "$PACKAGING_DIR/lbry-temp-symlink" +addfile "$PACKAGING_DIR/lbry-temp-symlink" usr/local/bin/lbry + cat "$PACKAGING_DIR/postinst_append" >> control/postinst # repackage .deb From 7dbec9d395c0a267c21442fad5a453ee2b959ec9 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 13:41:47 -0400 Subject: [PATCH 321/462] typo --- packaging/ubuntu/ubuntu_package_setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index dbfbf754d..6c0107c4d 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -160,7 +160,7 @@ addfile "$PACKAGING_DIR/icons/lbry128.png" usr/share/icons/hicolor/128x128/apps/ addfile "$PACKAGING_DIR/icons/lbry256.png" usr/share/icons/hicolor/256x256/apps/lbry.png addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop -$BINPATH=usr/share/python/lbrynet/bin +BINPATH=usr/share/python/lbrynet/bin addfile "$PACKAGING_DIR/lbry" "$BINPATH/lbry" ln -s "/$BINPATH/lbry" "$PACKAGING_DIR/lbry-temp-symlink" From f83c892b1389ffdc90e6348f1e372b2cfd564c4e Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 14:13:11 -0400 Subject: [PATCH 322/462] fix lbry symlink, fix icon path, add .desktop file to bumpversion --- .bumpversion.cfg | 2 ++ packaging/ubuntu/lbry | 12 +++++++++++- packaging/ubuntu/lbry.desktop | 4 ++-- packaging/ubuntu/ubuntu_package_setup.sh | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 83d93855e..59949225b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -6,3 +6,5 @@ message = Bump version: {current_version} -> {new_version} [bumpversion:file:lbrynet/__init__.py] +[bumpversion:file:packaging/ubuntu/lbry.desktop] + diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index 7336ba66b..9d5eafa3f 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -40,7 +40,17 @@ urlencode() { done } -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# find true dir of executable +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located +done +DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + + if [ -z "$(pgrep lbrynet-daemon)" ]; then echo "running lbrynet-daemon..." $DIR/lbrynet-daemon --branch="$WEB_UI_BRANCH" & diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index 832d673c3..e918c529f 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,8 +1,8 @@ [Desktop Entry] -Version=0.2.2 +Version=0.3.4 Name=LBRY Comment=The world's first user-owned content marketplace -Icon=/usr/share/python/lbrynet/lbrynet/lbrynet_gui/lbry.png +Icon=lbry GenericName=Content Marketplace Categories=Network;Internet;Filesharing Terminal=false diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 6c0107c4d..340e7998f 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -148,7 +148,7 @@ function addfile() { FILE="$1" TARGET="$2" mkdir -p "$(dirname "data/$TARGET")" - cp "$FILE" "data/$TARGET" + cp -d "$FILE" "data/$TARGET" echo "$(md5sum "data/$TARGET" | cut -d' ' -f1) $TARGET" >> control/md5sums } From 08d50ddfc5c35df0559fd1b04ca69b365a828ec6 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 14:58:06 -0400 Subject: [PATCH 323/462] bundle lbrycrdd bins instead of downloading them --- packaging/ubuntu/lbry | 15 ++------------- packaging/ubuntu/ubuntu_package_setup.sh | 12 +++++++++++- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index 9d5eafa3f..b22309c45 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -4,25 +4,14 @@ set -euo pipefail LBRYCRDDPATHCONF="$HOME/.lbrycrddpath.conf" LBRYCRDDIR="$HOME/.lbrycrd" -LBRYCRDDBIN="$LBRYCRDDIR/lbrycrdd" LBRYCRDCONF="$LBRYCRDDIR/lbrycrd.conf" -if [ ! -f "$LBRYCRDDBIN" ]; then - mkdir -p "$LBRYCRDDIR" - ( - cd "$LBRYCRDDIR" - echo "Downloading lbrycrd binaries" - wget http://s3.amazonaws.com/files.lbry.io/bins.zip - unzip -o bins.zip - rm bins.zip - ) -fi - if [ ! -f "$LBRYCRDDPATHCONF" ]; then - echo "$LBRYCRDDBIN" > "$LBRYCRDDPATHCONF" + echo "/usr/local/bin/lbrycrdd" > "$LBRYCRDDPATHCONF" fi if [ ! -f "$LBRYCRDCONF" ]; then + mkdir -p "$LBRYCRDDIR" echo -e "rpcuser=lbryrpc\nrpcpassword=$(env LC_CTYPE=C LC_ALL=C tr -dc A-Za-z0-9 < /dev/urandom | head -c 16 | xargs)" > "$LBRYCRDCONF" fi diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 340e7998f..d7b564d0a 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -152,7 +152,7 @@ function addfile() { echo "$(md5sum "data/$TARGET" | cut -d' ' -f1) $TARGET" >> control/md5sums } - +# add icons addfile "$PACKAGING_DIR/icons/lbry32.png" usr/share/icons/hicolor/32x32/apps/lbry.png addfile "$PACKAGING_DIR/icons/lbry48.png" usr/share/icons/hicolor/48x48/apps/lbry.png addfile "$PACKAGING_DIR/icons/lbry96.png" usr/share/icons/hicolor/96x96/apps/lbry.png @@ -160,12 +160,22 @@ addfile "$PACKAGING_DIR/icons/lbry128.png" usr/share/icons/hicolor/128x128/apps/ addfile "$PACKAGING_DIR/icons/lbry256.png" usr/share/icons/hicolor/256x256/apps/lbry.png addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop +# add lbry executable script BINPATH=usr/share/python/lbrynet/bin addfile "$PACKAGING_DIR/lbry" "$BINPATH/lbry" +# symlink script into /usr/local/bin ln -s "/$BINPATH/lbry" "$PACKAGING_DIR/lbry-temp-symlink" addfile "$PACKAGING_DIR/lbry-temp-symlink" usr/local/bin/lbry +# add lbrycrdd and lbrycrd-cli +mkdir -p "$PACKAGING_DIR/bins" +wget http://s3.amazonaws.com/files.lbry.io/bins.zip --output-file "$PACKAGING_DIR/bins.zip" +unzip -o "$PACKAGING_DIR/bins/zip" -d "$PACKAGING_DIR/bins/" +addfile "$PACKAGING_DIR/bins/lbrycrdd" usr/local/bin/lbrycrdd +addfile "$PACKAGING_DIR/bins/lbrycrd-cli" usr/local/bin/lbrycrd-cli + +# add postinstall script cat "$PACKAGING_DIR/postinst_append" >> control/postinst # repackage .deb From 344eeeb8c06bcef3d6c203bcca94cfc008adae24 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 15:04:41 -0400 Subject: [PATCH 324/462] typo --- packaging/ubuntu/ubuntu_package_setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index d7b564d0a..24cdf1c28 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -171,7 +171,7 @@ addfile "$PACKAGING_DIR/lbry-temp-symlink" usr/local/bin/lbry # add lbrycrdd and lbrycrd-cli mkdir -p "$PACKAGING_DIR/bins" wget http://s3.amazonaws.com/files.lbry.io/bins.zip --output-file "$PACKAGING_DIR/bins.zip" -unzip -o "$PACKAGING_DIR/bins/zip" -d "$PACKAGING_DIR/bins/" +unzip -o "$PACKAGING_DIR/bins.zip" -d "$PACKAGING_DIR/bins/" addfile "$PACKAGING_DIR/bins/lbrycrdd" usr/local/bin/lbrycrdd addfile "$PACKAGING_DIR/bins/lbrycrd-cli" usr/local/bin/lbrycrd-cli From 7a266d49c1d10925eb4c40fed1687d2d320cd7a7 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 15:33:06 -0400 Subject: [PATCH 325/462] fix --- packaging/ubuntu/ubuntu_package_setup.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 24cdf1c28..3ea6aa463 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -164,16 +164,16 @@ addfile "$PACKAGING_DIR/lbry.desktop" usr/share/applications/lbry.desktop BINPATH=usr/share/python/lbrynet/bin addfile "$PACKAGING_DIR/lbry" "$BINPATH/lbry" -# symlink script into /usr/local/bin +# symlink script into /usr/bin ln -s "/$BINPATH/lbry" "$PACKAGING_DIR/lbry-temp-symlink" -addfile "$PACKAGING_DIR/lbry-temp-symlink" usr/local/bin/lbry +addfile "$PACKAGING_DIR/lbry-temp-symlink" usr/bin/lbry # add lbrycrdd and lbrycrd-cli mkdir -p "$PACKAGING_DIR/bins" -wget http://s3.amazonaws.com/files.lbry.io/bins.zip --output-file "$PACKAGING_DIR/bins.zip" -unzip -o "$PACKAGING_DIR/bins.zip" -d "$PACKAGING_DIR/bins/" -addfile "$PACKAGING_DIR/bins/lbrycrdd" usr/local/bin/lbrycrdd -addfile "$PACKAGING_DIR/bins/lbrycrd-cli" usr/local/bin/lbrycrd-cli +wget http://s3.amazonaws.com/files.lbry.io/bins.zip --output-document "$PACKAGING_DIR/bins.zip" +unzip "$PACKAGING_DIR/bins.zip" -d "$PACKAGING_DIR/bins/" +addfile "$PACKAGING_DIR/bins/lbrycrdd" usr/bin/lbrycrdd +addfile "$PACKAGING_DIR/bins/lbrycrd-cli" usr/bin/lbrycrd-cli # add postinstall script cat "$PACKAGING_DIR/postinst_append" >> control/postinst From c58fc0781c44723d61cf04ee1cc750db48ad39c0 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 6 Jul 2016 15:57:00 -0400 Subject: [PATCH 326/462] Call xdg-open properly in jsonrpc_reveal() --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 16dc95c86..62af52e40 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -2243,19 +2243,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_reveal(self, p): """ - Open a folder in finder/file explorer + Reveal a file or directory in file browser Args: - 'path': path to be selected in finder + 'path': path to be selected in file browser Returns: - True, opens finder + True, opens file browser """ path = p['path'] if sys.platform == "darwin": d = threads.deferToThread(subprocess.Popen, ['open', '-R', path]) else: - d = threads.deferToThread(subprocess.Popen, ['xdg-open', '-R', path]) + # No easy way to reveal specific files on Linux, so just open the containing directory + d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.dirname(path)]) + d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d From 74bbde0c34c213b0a6dc48be864fe6771c3cb553 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 19:55:01 -0400 Subject: [PATCH 327/462] fix "launch as root" bug, update package name and maintainer --- packaging/ubuntu/lbry | 2 +- packaging/ubuntu/postinst_append | 29 +++++++----------------- packaging/ubuntu/ubuntu_package_setup.sh | 3 +++ setup.py | 4 ++-- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/packaging/ubuntu/lbry b/packaging/ubuntu/lbry index b22309c45..a139556cf 100755 --- a/packaging/ubuntu/lbry +++ b/packaging/ubuntu/lbry @@ -7,7 +7,7 @@ LBRYCRDDIR="$HOME/.lbrycrd" LBRYCRDCONF="$LBRYCRDDIR/lbrycrd.conf" if [ ! -f "$LBRYCRDDPATHCONF" ]; then - echo "/usr/local/bin/lbrycrdd" > "$LBRYCRDDPATHCONF" + echo "/usr/bin/lbrycrdd" > "$LBRYCRDDPATHCONF" fi if [ ! -f "$LBRYCRDCONF" ]; then diff --git a/packaging/ubuntu/postinst_append b/packaging/ubuntu/postinst_append index e082aad14..48e54f006 100755 --- a/packaging/ubuntu/postinst_append +++ b/packaging/ubuntu/postinst_append @@ -1,28 +1,15 @@ ( -sleep 4 +if hash zenity 2>/dev/null; then + sleep 3 -zenity --question --text="LBRY Installed\n\nRun LBRY now?" --title="LBRY Installed" --ok-label=" Yes" \ - --cancel-label=" No" --icon-name="system-software-install" + zenity --info --icon-name="system-software-install" \ + --text="\ +LBRY Installed\n\nLBRY has been installed.\n\n\ +Please start LBRY by running lbry from the command line or selecting LBRY from the application menu.\n\n\ +If you need help or have any questions, join us on Slack (https://slack.lbry.io) or email hello@lbry.io.\ +" -case $? in - 0) RUN=1 # yes - ;; - 1) RUN=0 # no - ;; - *) RUN=0 # timeout or escape or whatever - ;; -esac - -if [ $RUN = 1 ]; then - /bin/su - "$(logname)" -c 'xdg-open "lbry://lbry"' -else - zenity --info --text="LBRY Installed\n\nNo problem. You can run LBRY later by going to - -lbry://lbry - -in your browser." --title="LBRY Installed" --icon-name="system-software-install" fi - ) & diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 3ea6aa463..59d9ecfc7 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -178,6 +178,9 @@ addfile "$PACKAGING_DIR/bins/lbrycrd-cli" usr/bin/lbrycrd-cli # add postinstall script cat "$PACKAGING_DIR/postinst_append" >> control/postinst +# change package name from lbrynet to lbry +sed -i 's/^Package: lbrynet/Package: lbry/' control/control + # repackage .deb $SUDO chown -R root:root control data tar -czf control.tar.gz -C control . diff --git a/setup.py b/setup.py index ce96e06a8..12dc36dc9 100644 --- a/setup.py +++ b/setup.py @@ -31,8 +31,8 @@ requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', setup(name='lbrynet', description='A fully-decentralized content marketplace', version=__version__, - maintainer='Jimmy Kiselak', - maintainer_email='jimmy@lbry.io', + maintainer='Alex Grintsvayg', + maintainer_email='grin@lbry.io', packages=find_packages(base_dir), install_requires=requires, entry_points={'console_scripts': console_scripts}, From 57f728f40e45be3936890c04fe9be14917c14810 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 22:29:12 -0400 Subject: [PATCH 328/462] desc update --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 12dc36dc9..01346fb88 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] setup(name='lbrynet', - description='A fully-decentralized content marketplace', + description='A decentralized media library and marketplace', version=__version__, maintainer='Alex Grintsvayg', maintainer_email='grin@lbry.io', From 5a703c116f2d835e45fc088593e765289f6dd4e9 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 6 Jul 2016 22:43:27 -0400 Subject: [PATCH 329/462] linux lbrycrdd path --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 62af52e40..4bdb9c08d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -309,7 +309,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: self.wallet_dir = user_data_dir("LBRY") else: - self.lbrycrdd_path = "./lbrycrdd" + self.lbrycrdd_path = "lbrycrdd" if self.wallet_type == "lbrycrd": self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") else: From b727bd20c8a97e528a855ec44954c0669d9b8d70 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 6 Jul 2016 22:48:38 -0400 Subject: [PATCH 330/462] version bump --- lbrynet/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 77a3538e4..5c78f9f31 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.4" +__version__ = "0.3.5" version = tuple(__version__.split('.')) \ No newline at end of file From 00b1881f20af3424ab0c28a6ad46622d8f451395 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Wed, 6 Jul 2016 22:56:10 -0400 Subject: [PATCH 331/462] dont forget to run `bumpversion` instead of manually bumping --- .bumpversion.cfg | 2 +- packaging/ubuntu/lbry.desktop | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 59949225b..9bfee007a 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.4 +current_version = 0.3.5 commit = True tag = True message = Bump version: {current_version} -> {new_version} diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index e918c529f..e2428fdee 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.3.4 +Version=0.3.5 Name=LBRY Comment=The world's first user-owned content marketplace Icon=lbry From 5368c9139970828c6b08861571e0623338613003 Mon Sep 17 00:00:00 2001 From: Alex Grintsvayg Date: Thu, 7 Jul 2016 00:18:29 -0400 Subject: [PATCH 332/462] lbry replaces old lbrynet --- packaging/ubuntu/ubuntu_package_setup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packaging/ubuntu/ubuntu_package_setup.sh b/packaging/ubuntu/ubuntu_package_setup.sh index 59d9ecfc7..9d74901e8 100755 --- a/packaging/ubuntu/ubuntu_package_setup.sh +++ b/packaging/ubuntu/ubuntu_package_setup.sh @@ -180,6 +180,8 @@ cat "$PACKAGING_DIR/postinst_append" >> control/postinst # change package name from lbrynet to lbry sed -i 's/^Package: lbrynet/Package: lbry/' control/control +echo "Conflicts: lbrynet (<< 0.3.5)" >> control/control +echo "Replaces: lbrynet (<< 0.3.5)" >> control/control # repackage .deb $SUDO chown -R root:root control data From 4caee284bc079be0d302c3f48e54ba05c1c6ae38 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Thu, 7 Jul 2016 12:06:07 -0500 Subject: [PATCH 333/462] add slack notifications to travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4032ac442..3f6107a7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,9 @@ matrix: language: generic osx_image: xcode7.3 +notifications: + slack: lbry:8biSXNd1yr9jJ76zyI5KhWZv + cache: directories: - $HOME/.cache/pip From 1b53e8e98cd8c78c1534498141cf3f1282c8be89 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 9 Jul 2016 13:31:07 -0400 Subject: [PATCH 334/462] re-enable lbryum --- lbrynet/conf.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 120 ++++++++++----------- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 2 +- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index d984f0454..9ead15f4d 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -38,7 +38,7 @@ API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRES UI_ADDRESS = "http://%s:%i" % (API_INTERFACE, API_PORT) PROTOCOL_PREFIX = "lbry" -DEFAULT_WALLET = "lbrycrd" +DEFAULT_WALLET = "lbryum" WALLET_TYPES = ["lbryum", "lbrycrd"] DEFAULT_TIMEOUT = 30 DEFAULT_MAX_SEARCH_RESULTS = 25 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 4bdb9c08d..8e2c98cc5 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -145,7 +145,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): isLeaf = True - def __init__(self, root, wallet_type="lbrycrd"): + def __init__(self, root, wallet_type=None): jsonrpc.JSONRPC.__init__(self) reactor.addSystemEventTrigger('before', 'shutdown', self._shutdown) @@ -877,13 +877,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def get_wallet(): - if self.wallet_type in ["lbrycrd", "lbryum"]: #force lbrycrd wallet no matter what while lbryum is down + if self.wallet_type == "lbrycrd": #force lbrycrd wallet no matter what while lbryum is down log.info("Using lbrycrd wallet") d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.lbrycrd_conf, lbrycrdd_path=self.lbrycrdd_path)) - # elif self.wallet_type == "lbryum": - # log.info("Using lbryum wallet") - # d = defer.succeed(LBRYumWallet(self.db_dir)) + elif self.wallet_type == "lbryum": + log.info("Using lbryum wallet") + d = defer.succeed(LBRYumWallet(self.db_dir)) elif self.wallet_type == "ptc": log.info("Using PTC wallet") d = defer.succeed(PTCWallet(self.db_dir)) @@ -918,61 +918,61 @@ class LBRYDaemon(jsonrpc.JSONRPC): return dl - def _check_first_run(self): - def _set_first_run_false(): - log.info("Not first run") - self.first_run = False - self.session_settings['requested_first_run_credits'] = True - f = open(self.daemon_conf, "w") - f.write(json.dumps(self.session_settings)) - f.close() - return 0.0 - - if self.wallet_type == 'lbryum': - d = self.session.wallet.is_first_run() - d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run or not self.requested_first_run_credits - else _set_first_run_false()) - else: - d = defer.succeed(None) - d.addCallback(lambda _: _set_first_run_false()) - return d - - def _do_first_run(self): - def send_request(url, data): - log.info("Requesting first run credits") - r = requests.post(url, json=data) - if r.status_code == 200: - self.requested_first_run_credits = True - self.session_settings['requested_first_run_credits'] = True - f = open(self.daemon_conf, "w") - f.write(json.dumps(self.session_settings)) - f.close() - return r.json()['credits_sent'] - return 0.0 - - def log_error(err): - log.warning("unable to request free credits. %s", err.getErrorMessage()) - return 0.0 - - def request_credits(address): - url = "http://credreq.lbry.io/requestcredits" - data = {"address": address} - d = threads.deferToThread(send_request, url, data) - d.addErrback(log_error) - return d - - self.first_run = True - d = self.session.wallet.get_new_address() - d.addCallback(request_credits) - - return d - - def _show_first_run_result(self, credits_received): - if credits_received != 0.0: - points_string = locale.format_string("%.2f LBC", (round(credits_received, 2),), grouping=True) - self.startup_message = "Thank you for testing the alpha version of LBRY! You have been given %s for free because we love you. Please hang on for a few minutes for the next block to be mined. When you refresh this page and see your credits you're ready to go!." % points_string - else: - self.startup_message = None + # def _check_first_run(self): + # def _set_first_run_false(): + # log.info("Not first run") + # self.first_run = False + # self.session_settings['requested_first_run_credits'] = True + # f = open(self.daemon_conf, "w") + # f.write(json.dumps(self.session_settings)) + # f.close() + # return 0.0 + # + # if self.wallet_type == 'lbryum': + # d = self.session.wallet.is_first_run() + # d.addCallback(lambda is_first_run: self._do_first_run() if is_first_run or not self.requested_first_run_credits + # else _set_first_run_false()) + # else: + # d = defer.succeed(None) + # d.addCallback(lambda _: _set_first_run_false()) + # return d + # + # def _do_first_run(self): + # def send_request(url, data): + # log.info("Requesting first run credits") + # r = requests.post(url, json=data) + # if r.status_code == 200: + # self.requested_first_run_credits = True + # self.session_settings['requested_first_run_credits'] = True + # f = open(self.daemon_conf, "w") + # f.write(json.dumps(self.session_settings)) + # f.close() + # return r.json()['credits_sent'] + # return 0.0 + # + # def log_error(err): + # log.warning("unable to request free credits. %s", err.getErrorMessage()) + # return 0.0 + # + # def request_credits(address): + # url = "http://credreq.lbry.io/requestcredits" + # data = {"address": address} + # d = threads.deferToThread(send_request, url, data) + # d.addErrback(log_error) + # return d + # + # self.first_run = True + # d = self.session.wallet.get_new_address() + # d.addCallback(request_credits) + # + # return d + # + # def _show_first_run_result(self, credits_received): + # if credits_received != 0.0: + # points_string = locale.format_string("%.2f LBC", (round(credits_received, 2),), grouping=True) + # self.startup_message = "Thank you for testing the alpha version of LBRY! You have been given %s for free because we love you. Please hang on for a few minutes for the next block to be mined. When you refresh this page and see your credits you're ready to go!." % points_string + # else: + # self.startup_message = None def _setup_stream_identifier(self): file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter, diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 0fae59410..74906da0b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -183,7 +183,7 @@ class HostedLBRYFile(resource.Resource): class LBRYDaemonServer(object): def _setup_server(self, wallet): self.root = LBRYindex(os.path.join(os.path.join(data_dir, "lbry-ui"), "active")) - self._api = LBRYDaemon(self.root, wallet_type="lbrycrd") + self._api = LBRYDaemon(self.root, wallet_type=wallet) self.root.putChild("view", HostedLBRYFile(self._api)) self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) From f74f075b4eb2d285cec5efd6f0242f4b4bcc8e4c Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Sun, 10 Jul 2016 17:47:36 -0500 Subject: [PATCH 335/462] add tests for BlobRequestHandler and run on travis --- .../install_dependencies_and_run_tests.sh | 4 +- tests/__init__.py | 0 tests/lbrynet/__init__.py | 0 tests/lbrynet/core/__init__.py | 0 tests/lbrynet/core/server/__init__.py | 0 .../core/server/test_BlobRequestHandler.py | 104 ++++++++++++++++++ 6 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/lbrynet/__init__.py create mode 100644 tests/lbrynet/core/__init__.py create mode 100644 tests/lbrynet/core/server/__init__.py create mode 100644 tests/lbrynet/core/server/test_BlobRequestHandler.py diff --git a/packaging/travis/install_dependencies_and_run_tests.sh b/packaging/travis/install_dependencies_and_run_tests.sh index f55ab609b..6014087e1 100755 --- a/packaging/travis/install_dependencies_and_run_tests.sh +++ b/packaging/travis/install_dependencies_and_run_tests.sh @@ -38,8 +38,8 @@ rm get-pip.py pip install -r requirements.txt -pip install nose coverage coveralls pylint -nosetests --with-coverage --cover-package=lbrynet -v -I functional_tests.py tests/ +pip install mock pylint +trial tests # TODO: submit coverage report to coveralls # TODO: as code quality improves, make pylint be more strict diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/__init__.py b/tests/lbrynet/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/core/__init__.py b/tests/lbrynet/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/core/server/__init__.py b/tests/lbrynet/core/server/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/core/server/test_BlobRequestHandler.py b/tests/lbrynet/core/server/test_BlobRequestHandler.py new file mode 100644 index 000000000..4cd04259a --- /dev/null +++ b/tests/lbrynet/core/server/test_BlobRequestHandler.py @@ -0,0 +1,104 @@ +import mock +from twisted.internet import defer +from twisted.trial import unittest + +from lbrynet.core.server import BlobRequestHandler + + +class TestBlobRequestHandlerQueries(unittest.TestCase): + def setUp(self): + self.blob_manager = mock.Mock() + self.payment_rate_manager = mock.Mock() + self.handler = BlobRequestHandler.BlobRequestHandler( + self.blob_manager, None, self.payment_rate_manager) + + def test_empty_response_when_empty_query(self): + self.assertEqual({}, self.successResultOf(self.handler.handle_queries({}))) + + def test_error_set_when_rate_is_missing(self): + query = {'requested_blob': 'blob'} + deferred = self.handler.handle_queries(query) + response = {'incoming_blob': {'error': 'RATE_UNSET'}} + self.assertEqual(response, self.successResultOf(deferred)) + + def test_error_set_when_rate_too_low(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = False + query = { + 'blob_data_payment_rate': 'way_too_low', + 'requested_blob': 'blob' + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_TOO_LOW', + 'incoming_blob': {'error': 'RATE_UNSET'} + } + self.assertEqual(response, self.successResultOf(deferred)) + + def test_response_when_rate_too_low(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = False + query = { + 'blob_data_payment_rate': 'way_too_low', + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_TOO_LOW', + } + self.assertEqual(response, self.successResultOf(deferred)) + + def test_blob_unavailable_when_blob_not_validated(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = True + blob = mock.Mock() + blob.is_validated.return_value = False + self.blob_manager.get_blob.return_value = defer.succeed(blob) + query = { + 'blob_data_payment_rate': 'rate', + 'requested_blob': 'blob' + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_ACCEPTED', + 'incoming_blob': {'error': 'BLOB_UNAVAILABLE'} + } + self.assertEqual(response, self.successResultOf(deferred)) + + def test_blob_unavailable_when_blob_cannot_be_opened(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = True + blob = mock.Mock() + blob.is_validated.return_value = True + blob.open_for_reading.return_value = None + self.blob_manager.get_blob.return_value = defer.succeed(blob) + query = { + 'blob_data_payment_rate': 'rate', + 'requested_blob': 'blob' + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_ACCEPTED', + 'incoming_blob': {'error': 'BLOB_UNAVAILABLE'} + } + self.assertEqual(response, self.successResultOf(deferred)) + + def test_blob_details_are_set_when_all_conditions_are_met(self): + self.payment_rate_manager.accept_rate_blob_data.return_value = True + blob = mock.Mock() + blob.is_validated.return_value = True + blob.open_for_reading.return_value = True + blob.blob_hash = 'DEADBEEF' + blob.length = 42 + self.blob_manager.get_blob.return_value = defer.succeed(blob) + query = { + 'blob_data_payment_rate': 'rate', + 'requested_blob': 'blob' + } + deferred = self.handler.handle_queries(query) + response = { + 'blob_data_payment_rate': 'RATE_ACCEPTED', + 'incoming_blob': { + 'blob_hash': 'DEADBEEF', + 'length': 42 + } + } + self.assertEqual(response, self.successResultOf(deferred)) + + + From 5c05daa0078f9ef8ce8c6fb580e5a301c52e2ff7 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 10 Jul 2016 22:21:50 -0400 Subject: [PATCH 336/462] update import to new file name --- lbrynet/core/LBRYWallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 7f7dd7e88..0bf99f7a5 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -7,7 +7,7 @@ from lbrynet.core.sqlite_helpers import rerun_if_locked from lbrynet.conf import BASE_METADATA_FIELDS, SOURCE_TYPES, OPTIONAL_METADATA_FIELDS from lbryum import SimpleConfig, Network -from lbryum.bitcoin import COIN, TYPE_ADDRESS +from lbryum.lbrycrd import COIN, TYPE_ADDRESS from lbryum.wallet import WalletStorage, Wallet from lbryum.commands import known_commands, Commands from lbryum.transaction import Transaction From 53bf1fe4fd7da700051b8c4a75d953361cc99747 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Sun, 10 Jul 2016 21:56:00 -0500 Subject: [PATCH 337/462] add send_blob tests --- lbrynet/core/server/BlobRequestHandler.py | 3 +- .../core/server/test_BlobRequestHandler.py | 33 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/lbrynet/core/server/BlobRequestHandler.py b/lbrynet/core/server/BlobRequestHandler.py index 2ce3688b4..36f67d797 100644 --- a/lbrynet/core/server/BlobRequestHandler.py +++ b/lbrynet/core/server/BlobRequestHandler.py @@ -140,6 +140,7 @@ class BlobRequestHandler(object): def set_expected_payment(): log.info("Setting expected payment") if self.blob_bytes_uploaded != 0 and self.blob_data_payment_rate is not None: + # TODO: explain why 2**20 self.wallet.add_expected_payment(self.peer, self.currently_uploading.length * 1.0 * self.blob_data_payment_rate / 2**20) @@ -156,4 +157,4 @@ class BlobRequestHandler(object): if reason is not None and isinstance(reason, Failure): log.info("Upload has failed. Reason: %s", reason.getErrorMessage()) - return _send_file() \ No newline at end of file + return _send_file() diff --git a/tests/lbrynet/core/server/test_BlobRequestHandler.py b/tests/lbrynet/core/server/test_BlobRequestHandler.py index 4cd04259a..5c55af574 100644 --- a/tests/lbrynet/core/server/test_BlobRequestHandler.py +++ b/tests/lbrynet/core/server/test_BlobRequestHandler.py @@ -1,7 +1,11 @@ +import StringIO + import mock -from twisted.internet import defer +from twisted.internet import defer, protocol +from twisted.test import proto_helpers from twisted.trial import unittest +from lbrynet.core import Peer from lbrynet.core.server import BlobRequestHandler @@ -13,8 +17,9 @@ class TestBlobRequestHandlerQueries(unittest.TestCase): self.blob_manager, None, self.payment_rate_manager) def test_empty_response_when_empty_query(self): - self.assertEqual({}, self.successResultOf(self.handler.handle_queries({}))) - + self.assertEqual( + {}, self.successResultOf(self.handler.handle_queries({}))) + def test_error_set_when_rate_is_missing(self): query = {'requested_blob': 'blob'} deferred = self.handler.handle_queries(query) @@ -99,6 +104,24 @@ class TestBlobRequestHandlerQueries(unittest.TestCase): } } self.assertEqual(response, self.successResultOf(deferred)) - - + +class TestBlobRequestHandlerSender(unittest.TestCase): + def test_nothing_happens_if_not_currently_uploading(self): + handler = BlobRequestHandler.BlobRequestHandler(None, None, None) + handler.currently_uploading = None + deferred = handler.send_blob_if_requested(None) + self.assertEqual(True, self.successResultOf(deferred)) + + def test_file_is_sent_to_consumer(self): + # TODO: also check that the expected payment values are set + consumer = proto_helpers.StringTransport() + test_file = StringIO.StringIO('test') + handler = BlobRequestHandler.BlobRequestHandler(None, None, None) + handler.peer = mock.create_autospec(Peer.Peer) + handler.currently_uploading = mock.Mock() + handler.read_handle = test_file + handler.send_blob_if_requested(consumer) + while consumer.producer: + consumer.producer.resumeProducing() + self.assertEqual(consumer.value(), 'test') From dfc0ca9525303e720a06dd7c0a8485ae88364eda Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 11 Jul 2016 12:05:27 -0500 Subject: [PATCH 338/462] change slack key and encrypt it --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3f6107a7d..ab972c6bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,8 @@ matrix: osx_image: xcode7.3 notifications: - slack: lbry:8biSXNd1yr9jJ76zyI5KhWZv + slack: + secure: "fAvSDLBZ04nu5sDumJSlQdUkCAE45QpbxCP97M1IUljRKpYk+JrbKi760oZz80Lz0SVNOZHiafN0EA6BE94Lg292cxhTLP8TbVkScTEHeliwqHFnH9vHvrM5ftdMrwNAx0VojlyMO0HzfQBoO2uPbffmuFD8gFdSKmhJQ58E5JD4KvLsdtQCxr3dzoBHXxOpozQKBMsTxV0oqCLT4x7GY+9R0iXB3WMSjxP5bHFxTDIP7l0DvixoftAwWeNOcct/Bfbw1ny221C31hiRq1DN0XO34+EtOoTP7BFFHWX1P3v5CiGnA9oxl/7YERAG9admzSECIICh+MPADll1qEkEwqcqpjfT87sv6wxKfVebIZCX2HLe1aNFplgNoZo3LszAJ23dhVo/8M+xUApmbYR7n7JksO2pPQYWF1mGhq8MEPhJk0JAnCNbeS1Tm1pHrFJh6Vm+bvD92BrrfaZBWi9bIRhFxLQ9SCvqy376/l42GMU41+uDvivXWYWZCm1UR4ibeY5QFUt5nOnd9nB+GDNJt9TkWBXwtvfrKKa9q48CUMKZgUOsLD5lfkENDslWwtGAnGkP1WPDRnimEAeIj3q4dQtJjOE8ZErzTWuDTRj57tdCrMOfR41HPgbeLwSAANUZm8p5Uom79LZi1JoOIlE87K4mYPs45Tz0GlEi+HYSos0=" cache: directories: From 404412a950881439fcf26cebfa7c99d2d35eb355 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 11 Jul 2016 21:57:34 -0400 Subject: [PATCH 339/462] return publish error message --- lbrynet/lbrynet_daemon/LBRYDaemonCLI.py | 2 +- lbrynet/lbrynet_daemon/LBRYPublisher.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py index b146cb1a6..2b34d4301 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py @@ -35,7 +35,7 @@ def main(): if len(args) > 1: if isinstance(args[1], dict): params = args[1] - elif isinstance(args[1], str) or isinstance(args[1], unicode): + elif isinstance(args[1], str, unicode): params = json.loads(args[1]) else: params = None diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index c2ef94d1d..c0658bfec 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -127,11 +127,11 @@ class Publisher(object): log.info(err.getTraceback()) message = "An error occurred publishing %s to %s. Error: %s." if err.check(InsufficientFundsError): - d = defer.succeed(True) error_message = "Insufficient funds" else: - d = defer.succeed(True) error_message = err.getErrorMessage() + log.error(error_message) log.error(message, str(self.file_name), str(self.publish_name), err.getTraceback()) - return d + + return defer.succeed(error_message) From 643b95c0e4e71d554f122e0a70664fbf2798e9fa Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 12 Jul 2016 14:48:13 -0500 Subject: [PATCH 340/462] switch slack notification token --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ab972c6bd..cead1a849 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ matrix: notifications: slack: - secure: "fAvSDLBZ04nu5sDumJSlQdUkCAE45QpbxCP97M1IUljRKpYk+JrbKi760oZz80Lz0SVNOZHiafN0EA6BE94Lg292cxhTLP8TbVkScTEHeliwqHFnH9vHvrM5ftdMrwNAx0VojlyMO0HzfQBoO2uPbffmuFD8gFdSKmhJQ58E5JD4KvLsdtQCxr3dzoBHXxOpozQKBMsTxV0oqCLT4x7GY+9R0iXB3WMSjxP5bHFxTDIP7l0DvixoftAwWeNOcct/Bfbw1ny221C31hiRq1DN0XO34+EtOoTP7BFFHWX1P3v5CiGnA9oxl/7YERAG9admzSECIICh+MPADll1qEkEwqcqpjfT87sv6wxKfVebIZCX2HLe1aNFplgNoZo3LszAJ23dhVo/8M+xUApmbYR7n7JksO2pPQYWF1mGhq8MEPhJk0JAnCNbeS1Tm1pHrFJh6Vm+bvD92BrrfaZBWi9bIRhFxLQ9SCvqy376/l42GMU41+uDvivXWYWZCm1UR4ibeY5QFUt5nOnd9nB+GDNJt9TkWBXwtvfrKKa9q48CUMKZgUOsLD5lfkENDslWwtGAnGkP1WPDRnimEAeIj3q4dQtJjOE8ZErzTWuDTRj57tdCrMOfR41HPgbeLwSAANUZm8p5Uom79LZi1JoOIlE87K4mYPs45Tz0GlEi+HYSos0=" + secure: "Am13HPtpgCMljh0MDVuoFHvQXB8yhf4Kvf/qAeSp5N0vsHGL70CSF9Ahccw8dVPE6mbuak1OGtSUb6/UaErLHkpz3ztaRLkDa9x7CmBB3Kynnh8oO2VbB7b/2ROULqkhF4VZmAnNfwrQrbC3gs8Sybp261Nyc7y4ww15xDYBrk2fyq4ds2DCaJdRxfJUJFonrZ6KXr3fVaXosO6cjuyS8eRodcmrqsT4cCtinjNTD1hGWoH107E4ObSmpVelxQO193KhNJMRiLlEcVkvYUOqIWBtwdGHbNE/6Yeuq1TXgKJ0KeJWAmW3wTfUYNngGXNAsyCnrhul5TKNevNzfIAQZHvRsczYiWPJV6LtohHT0CcUiCXJtvEPOyahEBfwK3etY/xxFqny7N9OEmpdW2sgsEPNPX2LJynJti2rQA9SuAD1ogR3ZpDy/NXoaAZf8PTdPcuNUMULV9PGG7tVrLBecO/W1qO6hdFxwlLdgqGLxAENZgGp++v/DhPk/WvtmHj7iTbRq0nxaTWyX5uKOn2ADH+k/yfutjv6BsQU9xNyPeZEEtuEpc6X6waiYn/8G9vl9PecvKC5H0MgsZ6asAxmg7mZ3VSMFG7mo8ENeOhSZ0Oz6ZTBILL3wFccZA9uJIq7NWmqC9dRiGiuKXBB62No7sINoHg3114e2xYa9qvNmGg=" cache: directories: From 5221631b40c8fe0fa93d9ca53c7863add5422b7f Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 12 Jul 2016 17:30:58 -0400 Subject: [PATCH 341/462] Bump version: 0.3.5 -> 0.3.6 --- .bumpversion.cfg | 2 +- lbrynet/__init__.py | 2 +- packaging/ubuntu/lbry.desktop | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9bfee007a..cb7f8ec7f 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.5 +current_version = 0.3.6 commit = True tag = True message = Bump version: {current_version} -> {new_version} diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 5c78f9f31..f106a1889 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.5" +__version__ = "0.3.6" version = tuple(__version__.split('.')) \ No newline at end of file diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index e2428fdee..53408274c 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.3.5 +Version=0.3.6 Name=LBRY Comment=The world's first user-owned content marketplace Icon=lbry From ee71a03f96bbb5241a3da52f08cf2480b1b847b3 Mon Sep 17 00:00:00 2001 From: kimihiro64 Date: Wed, 13 Jul 2016 22:44:02 -0500 Subject: [PATCH 342/462] Revert to 2 params as isinstance cannot support 3+ (#69) * Revert to 2 params as isinstance cannot support 3+ * Use basestring for maximum efficiency --- lbrynet/lbrynet_daemon/LBRYDaemonCLI.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py index 2b34d4301..0b5d0ba0c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonCLI.py @@ -35,7 +35,7 @@ def main(): if len(args) > 1: if isinstance(args[1], dict): params = args[1] - elif isinstance(args[1], str, unicode): + elif isinstance(args[1], basestring): params = json.loads(args[1]) else: params = None @@ -56,4 +56,4 @@ def main(): if __name__ == '__main__': - main() \ No newline at end of file + main() From 7ffb3ea04220c593ad1dc7da26c29164458ea9d5 Mon Sep 17 00:00:00 2001 From: Harwinder Date: Thu, 14 Jul 2016 18:41:23 +0200 Subject: [PATCH 343/462] Added git to linux install command Some people dont have git installed so adding it to it solves confusion for people new to linux. --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 23491e60c..8bf9f8917 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -23,7 +23,7 @@ You can install LBRY command line by running `curl -sL https://rawgit.com/lbryio On Ubuntu or Mint you can install the prerequisites and lbrynet by running ``` - sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip + sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip git git clone https://github.com/lbryio/lbry.git cd lbry sudo python setup.py install From 1e48f5c8f409f4754497cc8969be11134f78d131 Mon Sep 17 00:00:00 2001 From: Harwinder Date: Thu, 14 Jul 2016 22:25:32 +0200 Subject: [PATCH 344/462] Cant use git if its not installed First install needed packages then execute the task. Cant run the whole code if git isnt installed :) --- RUNNING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RUNNING.md b/RUNNING.md index b9f60acc9..d2f1842b4 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -43,9 +43,9 @@ To stop lbrynet-console, enter the command 'exit'. Note: this process takes upwards of an hour and is not necessary to use lbrynet. ``` +sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev libboost-all-dev libdb-dev libdb++-dev libqt4-dev libprotobuf-dev protobuf-compiler git git clone --depth=1 -b alpha https://github.com/lbryio/lbrycrd.git cd lbrycrd -sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev libboost-all-dev libdb-dev libdb++-dev libqt4-dev libprotobuf-dev protobuf-compiler ./autogen.sh ./configure --with-incompatible-bdb --without-gui From 5aecd02668c68eff15e7cb1640304a6ab3c9595e Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Fri, 15 Jul 2016 11:09:20 -0500 Subject: [PATCH 345/462] Refactor jsonrpc_get. Move parameter handling into its own function and better use the `.get()` function for dictionaries. Early return on the failed checks is more readable. The lambda function in the callback was long and hard to read so moved it out. --- .pylintrc | 2 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 107 +++++++++++++++------------ 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/.pylintrc b/.pylintrc index 5e5aab35c..e49732469 100644 --- a/.pylintrc +++ b/.pylintrc @@ -298,7 +298,7 @@ ignored-classes=twisted.internet,RequestMessage # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. -generated-members= +generated-members=lbrynet.lbrynet_daemon.LBRYDaemon.Parameters [IMPORTS] diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 8e2c98cc5..c5280bfd2 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -138,6 +138,11 @@ OK_CODE = 200 REMOTE_SERVER = "www.google.com" +class Parameters(object): + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + class LBRYDaemon(jsonrpc.JSONRPC): """ LBRYnet daemon, a jsonrpc interface to lbry functions @@ -1651,65 +1656,53 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure) return d + def _process_get_parameters(self, p): + """Extract info from input parameters and fill in default values for `get` call.""" + # TODO: this process can be abstracted s.t. each method + # can spec what parameters it expects and how to set default values + timeout = p.get('timeout', self.download_timeout) + download_directory = p.get('download_directory', self.download_directory) + file_name = p.get('file_name') + stream_info = p.get('stream_info') + sd_hash = get_sd_hash(stream_info) + wait_for_write = p.get('wait_for_write', True) + name = p.get('name') + return Parameters( + timout=timeout, + download_directory=download_directory, + file_name=file_name, + stream_info=stream_info, + sd_hash=sd_hash, + wait_for_write=wait_for_write, + name=name + ) + def jsonrpc_get(self, p): - """ - Download stream from a LBRY uri + """Download stream from a LBRY uri. Args: 'name': name to download, string 'download_directory': optional, path to directory where file will be saved, string 'file_name': optional, a user specified name for the downloaded file 'stream_info': optional, specified stream info overrides name + 'timout': optional Returns: 'stream_hash': hex string 'path': path of download """ - - if 'timeout' not in p.keys(): - timeout = self.download_timeout - else: - timeout = p['timeout'] - - if 'download_directory' not in p.keys(): - download_directory = self.download_directory - else: - download_directory = p['download_directory'] - - if 'file_name' in p.keys(): - file_name = p['file_name'] - else: - file_name = None - - if 'stream_info' in p.keys(): - stream_info = p['stream_info'] - if 'sources' in stream_info.keys(): - sd_hash = stream_info['sources']['lbry_sd_hash'] - else: - sd_hash = stream_info['stream_hash'] - else: - stream_info = None - - if 'wait_for_write' in p.keys(): - wait_for_write = p['wait_for_write'] - else: - wait_for_write = True - - if 'name' in p.keys(): - name = p['name'] - if p['name'] not in self.waiting_on.keys(): - d = self._download_name(name=name, timeout=timeout, download_directory=download_directory, - stream_info=stream_info, file_name=file_name, wait_for_write=wait_for_write) - d.addCallback(lambda l: {'stream_hash': sd_hash, - 'path': os.path.join(self.download_directory, l.file_name)} - if stream_info else - {'stream_hash': l.sd_hash, - 'path': os.path.join(self.download_directory, l.file_name)}) - d.addCallback(lambda message: self._render_response(message, OK_CODE)) - else: - d = server.failure - else: - d = server.failure - + params = self._process_get_parameters(p) + if not params.name: + return server.failure + if params.name in self.waiting_on: + return server.failure + d = self._download_name(name=params.name, + timeout=params.timeout, + download_directory=params.download_directory, + stream_info=params.stream_info, + file_name=params.file_name, + wait_for_write=params.wait_for_write) + d.addCallback(get_output_callback(params)) + d.addCallback(lambda message: self._render_response(message, OK_CODE)) return d def jsonrpc_stop_lbry_file(self, p): @@ -2261,3 +2254,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d + + +def get_sd_hash(stream_info): + if not stream_info: + return None + try: + return stream_info['sources']['lbry_sd_hash'] + except KeyError: + return stream_info.get('stream_hash') + + +def get_output_callback(params): + def callback(l): + return { + 'stream_hash': params.sd_hash if params.stream_info else l.sd_hash, + 'path': os.path.join(params.download_directory, l.file_name) + } + return callback From 563896b126564d6e249cea3486cc193e52db0fb2 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Fri, 15 Jul 2016 11:33:38 -0500 Subject: [PATCH 346/462] fix bug in reveal code --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index c5280bfd2..6fa942b22 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -2249,7 +2249,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = threads.deferToThread(subprocess.Popen, ['open', '-R', path]) else: # No easy way to reveal specific files on Linux, so just open the containing directory - d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.dirname(path)]) + d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.path.dirname(path)]) d.addCallback(lambda _: self._render_response(True, OK_CODE)) From a90029ec50bbb94ec4625a4d9b57f367edd1b7b4 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Fri, 15 Jul 2016 11:37:04 -0500 Subject: [PATCH 347/462] update doc string --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 6fa942b22..0c2ce0303 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1686,6 +1686,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'file_name': optional, a user specified name for the downloaded file 'stream_info': optional, specified stream info overrides name 'timout': optional + 'wait_for_write': optional, defaults to True Returns: 'stream_hash': hex string 'path': path of download From a15d7ca54311f3248427bcf3a1b4eb42d1fc889a Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Fri, 15 Jul 2016 12:42:26 -0500 Subject: [PATCH 348/462] Refactor _download_name Nested functions are the devil, especially ones that use variables from the outer scope. Refactoring _download_name to use a helper class helps make the scoping more explicit and will undoubtably prevent bugs in the future. I think this makes _download_name drastically more readable. Also cleaned up some duplicated code and made download_directory respect the passed in parameter instead of being the default. --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 189 +++++++++++++++------------ 1 file changed, 109 insertions(+), 80 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 0c2ce0303..fad2583bd 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -999,97 +999,37 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None, - file_name=None, stream_info=None, wait_for_write=True): + file_name=None, stream_info=None, wait_for_write=True): """ Add a lbry file to the file manager, start the download, and return the new lbry file. If it already exists in the file manager, return the existing lbry file """ - - if not download_directory: - download_directory = self.download_directory - elif not os.path.isdir(download_directory): - download_directory = self.download_directory - - def _remove_from_wait(r): - del self.waiting_on[name] - return r - - def _setup_stream(stream_info): - if 'sources' in stream_info.keys(): - stream_hash = stream_info['sources']['lbry_sd_hash'] - else: - stream_hash = stream_info['stream_hash'] - - d = self._get_lbry_file_by_sd_hash(stream_hash) - def _add_results(l): - if l: - if os.path.isfile(os.path.join(self.download_directory, l.file_name)): - return defer.succeed((stream_info, l)) - return defer.succeed((stream_info, None)) - d.addCallback(_add_results) - return d - - def _wait_on_lbry_file(f): - if os.path.isfile(os.path.join(self.download_directory, f.file_name)): - written_file = file(os.path.join(self.download_directory, f.file_name)) - written_file.seek(0, os.SEEK_END) - written_bytes = written_file.tell() - written_file.close() - else: - written_bytes = False - - if not written_bytes: - d = defer.succeed(None) - d.addCallback(lambda _: reactor.callLater(1, _wait_on_lbry_file, f)) - return d - else: - return defer.succeed(_disp_file(f)) - - def _disp_file(f): - file_path = os.path.join(self.download_directory, f.file_name) - log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.sd_hash) + " --> " + file_path) - return f - - def _get_stream(stream_info): - def _wait_for_write(): - try: - if os.path.isfile(os.path.join(self.download_directory, self.streams[name].downloader.file_name)): - written_file = file(os.path.join(self.download_directory, self.streams[name].downloader.file_name)) - written_file.seek(0, os.SEEK_END) - written_bytes = written_file.tell() - written_file.close() - else: - written_bytes = False - except: - written_bytes = False - - if not written_bytes: - d = defer.succeed(None) - d.addCallback(lambda _: reactor.callLater(1, _wait_for_write)) - return d - else: - return defer.succeed(None) - - self.streams[name] = GetStream(self.sd_identifier, self.session, self.session.wallet, - self.lbry_file_manager, max_key_fee=self.max_key_fee, - data_rate=self.data_rate, timeout=timeout, - download_directory=download_directory, file_name=file_name) - d = self.streams[name].start(stream_info, name) - if wait_for_write: - d.addCallback(lambda _: _wait_for_write()) - d.addCallback(lambda _: self.streams[name].downloader) - - return d + helper = _DownloadNameHelper( + self, name, timeout, download_directory, file_name, wait_for_write) if not stream_info: self.waiting_on[name] = True d = self._resolve_name(name) else: d = defer.succeed(stream_info) - d.addCallback(_setup_stream) - d.addCallback(lambda (stream_info, lbry_file): _get_stream(stream_info) if not lbry_file else _wait_on_lbry_file(lbry_file)) + d.addCallback(helper._setup_stream) + d.addCallback(helper.wait_or_get_stream) if not stream_info: - d.addCallback(_remove_from_wait) + d.addCallback(helper._remove_from_wait) + return d + + def add_stream(self, name, timeout, download_directory, file_name, stream_info): + """Makes, adds and starts a stream""" + self.streams[name] = GetStream(self.sd_identifier, + self.session, + self.session.wallet, + self.lbry_file_manager, + max_key_fee=self.max_key_fee, + data_rate=self.data_rate, + timeout=timeout, + download_directory=download_directory, + file_name=file_name) + d = self.streams[name].start(stream_info, name) return d def _get_long_count_timestamp(self): @@ -2273,3 +2213,92 @@ def get_output_callback(params): 'path': os.path.join(params.download_directory, l.file_name) } return callback + + +class _DownloadNameHelper(object): + def __init__(self, daemon, name, timeout=DEFAULT_TIMEOUT, download_directory=None, + file_name=None, wait_for_write=True): + self.daemon = daemon + self.name = name + self.timeout = timeout + if not download_directory or not os.path.isdir(download_directory): + self.download_directory = daemon.download_directory + else: + self.download_directory = download_directory + self.file_name = file_name + self.wait_for_write = wait_for_write + + def _setup_stream(self, stream_info): + stream_hash = get_sd_hash(stream_info) + d = self.daemon._get_lbry_file_by_sd_hash(stream_hash) + d.addCallback(self._add_results_callback(stream_info)) + return d + + def _add_results_callback(self, stream_info): + def add_results(l): + if l: + if os.path.isfile(os.path.join(self.download_directory, l.file_name)): + return defer.succeed((stream_info, l)) + return defer.succeed((stream_info, None)) + return add_results + + def wait_or_get_stream(self, args): + stream_info, lbry_file = args + if lbry_file: + return self._get_stream(stream_info) + else: + return self._wait_on_lbry_file(lbry_file) + + def _get_stream(self, stream_info): + d = self.daemon.add_stream( + self.name, self.timeout, self.download_directory, self.file_name, stream_info) + if self.wait_for_write: + d.addCallback(lambda _: self._wait_for_write()) + d.addCallback(lambda _: self.daemon.streams[self.name].downloader) + return d + + def _wait_for_write(self): + file_name = self.daemon.streams[self.name].downloader.file_name + written_bytes = self.get_written_bytes(file_name) + d = defer.succeed(None) + if not written_bytes: + d.addCallback(lambda _: reactor.callLater(1, self._wait_for_write)) + return d + + def _wait_on_lbry_file(self, f): + written_bytes = self.get_written_bytes(f.file_name) + if not written_bytes: + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, self._wait_on_lbry_file, f)) + return d + else: + return defer.succeed(self._disp_file(f)) + + def get_written_bytes(self, file_name): + try: + file_path = os.path.join(self.download_directory, file_name) + if os.path.isfile(file_path): + written_file = file(file_path) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + except Exception: + writen_bytes = False + return written_bytes + + def _disp_file(self, f): + file_path = os.path.join(self.download_directory, f.file_name) + log.info("[%s] Already downloaded: %s --> %s", datetime.now(), f.sd_hash, file_path) + return f + + def _remove_from_wait(self, r): + del self.daemon.waiting_on[self.name] + return r + + + + + + From 8743decd17cf5a8398ab152e911d8b9bb0bb82a3 Mon Sep 17 00:00:00 2001 From: Harwinder Date: Fri, 15 Jul 2016 20:01:25 +0200 Subject: [PATCH 349/462] Need to have git installed. You cant execute the code that follows after if you havent installed git already. --- FAQ.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FAQ.md b/FAQ.md index 6336272da..091acd711 100644 --- a/FAQ.md +++ b/FAQ.md @@ -12,7 +12,7 @@ You can install LBRY command line by running `curl -sL https://rawgit.com/lbryio On Ubuntu or Mint you can install the prerequisites and lbrynet by running - sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip + sudo apt-get install libgmp3-dev build-essential python2.7 python2.7-dev python-pip git git clone https://github.com/lbryio/lbry.git cd lbry sudo python setup.py install From 2dcd2b13befeff131e78c6208726b5b66ae4c722 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 15 Jul 2016 14:09:43 -0400 Subject: [PATCH 350/462] add LBRYMetadata.py --- lbrynet/core/LBRYMetadata.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 lbrynet/core/LBRYMetadata.py diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py new file mode 100644 index 000000000..565a10785 --- /dev/null +++ b/lbrynet/core/LBRYMetadata.py @@ -0,0 +1,28 @@ +import json + +BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type'] +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': []} + + +class Metadata(dict): + def __init__(self, metadata): + dict.__init__(self) + self.metaversion = None + m = metadata.copy() + for version in METADATA_REVISIONS: + for k in METADATA_REVISIONS[version]['required']: + assert k in metadata, "Missing required metadata field: %s" % k + self.update({k: m.pop(k)}) + for k in METADATA_REVISIONS[version]['optional']: + if k in metadata: + self.update({k: m.pop(k)}) + if not len(m): + self.metaversion = version + break + assert m == {}, "Unknown metadata keys: %s" % json.dumps(m.keys()) \ No newline at end of file From 172f275bc70a696f491d62d654304f4402be831f Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Sat, 16 Jul 2016 09:24:27 -0500 Subject: [PATCH 351/462] Refactor _resolve_name. Continue using the delegation/helper pattern for the daemon. --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 86 ++++++++++++++++++---------- 1 file changed, 55 insertions(+), 31 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index fad2583bd..828cbd65f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1042,38 +1042,16 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _resolve_name(self, name, force_refresh=False): - def _cache_stream_info(stream_info): - def _add_txid(txid): - self.name_cache[name]['txid'] = txid - return defer.succeed(None) + """Resolves a name. Checks the cache first before going out to the blockchain. - self.name_cache[name] = {'claim_metadata': stream_info, 'timestamp': self._get_long_count_timestamp()} - d = self.session.wallet.get_txid_for_name(name) - d.addCallback(_add_txid) - d.addCallback(lambda _: self._update_claim_cache()) - d.addCallback(lambda _: self.name_cache[name]['claim_metadata']) - - return d - - if not force_refresh: - if name in self.name_cache.keys(): - if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: - log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) - d = defer.succeed(self.name_cache[name]['claim_metadata']) - else: - log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) - d = self.session.wallet.get_stream_info_for_name(name) - d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) - else: - log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) - d = self.session.wallet.get_stream_info_for_name(name) - d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) - else: - log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) - d = self.session.wallet.get_stream_info_for_name(name) - d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) - - return d + Args: + name: the lbry:// to resolve + force_refresh: if True, always go out to the blockchain to resolve. + """ + if name.startswith('lbry://'): + raise ValueError('name %s should not start with lbry://') + helper = _ResolveNameHelper(self, name, force_refresh) + return helper.get_deferred() def _delete_lbry_file(self, lbry_file, delete_file=True): d = self.lbry_file_manager.delete_lbry_file(lbry_file) @@ -2297,8 +2275,54 @@ class _DownloadNameHelper(object): del self.daemon.waiting_on[self.name] return r +class _ResolveNameHelper(object): + def __init__(self, daemon, name, force_refresh): + self.daemon = daemon + self.name = name + self.force_refresh = force_refresh + def get_deferred(self): + if self.need_fresh_stream(): + log.info("Resolving stream info for lbry://%s", self.name) + d = self.wallet.get_stream_info_for_name(self.name) + d.addCallbacks(self._cache_stream_info, lambda _: defer.fail(UnknownNameError)) + else: + log.info("Returning cached stream info for lbry://%s", self.name) + d = defer.succeed(self.name_data['claim_metadata']) + return d + @property + def name_data(self): + return self.daemon.name_cache[self.name] + @property + def wallet(self): + return self.daemon.session.wallet + def now(self): + return self.daemon._get_long_count_timestamp() + def _add_txid(self, txid): + self.name_data['txid'] = txid + return defer.succeed(None) + + def _cache_stream_info(self, stream_info): + self.daemon.name_cache[self.name] = { + 'claim_metadata': stream_info, + 'timestamp': self.now() + } + d = self.wallet.get_txid_for_name(self.name) + d.addCallback(self._add_txid) + d.addCallback(lambda _: self.daemon._update_claim_cache()) + d.addCallback(lambda _: self.name_data['claim_metadata']) + return d + + def need_fresh_stream(self): + return self.force_refresh or not self.is_in_cache() or self.is_cached_name_expired() + + def is_in_cache(self): + return self.name in self.daemon.name_cache + + def is_cached_name_expired(self): + time_in_cache = self.now() - self.name_data['timestamp'] + return time_in_cache >= self.daemon.cache_time From 8ec10e23048b4b292ba1d6d949150fe9cd3ba23c Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Sun, 17 Jul 2016 13:43:35 -0500 Subject: [PATCH 352/462] emergency fix: force lbrycrd to be the default wallet Ignore save settings for wallet type and use the default wallet (lbrycrd) instead. lbryum can be used but only if specified on the command line. --- lbrynet/conf.py | 4 ++-- lbrynet/lbrynet_daemon/LBRYDaemon.py | 35 +++++++++++++++++++++------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 9ead15f4d..b3ab58819 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -38,7 +38,7 @@ API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRES UI_ADDRESS = "http://%s:%i" % (API_INTERFACE, API_PORT) PROTOCOL_PREFIX = "lbry" -DEFAULT_WALLET = "lbryum" +DEFAULT_WALLET = "lbrycrd" WALLET_TYPES = ["lbryum", "lbrycrd"] DEFAULT_TIMEOUT = 30 DEFAULT_MAX_SEARCH_RESULTS = 25 @@ -49,4 +49,4 @@ DEFAULT_UI_BRANCH = "master" SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih'] BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type'] -OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey'] \ No newline at end of file +OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey'] diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 8e2c98cc5..914f21073 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -159,6 +159,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.git_lbryum_version = None self.ui_version = None self.ip = None + # TODO: this is confusing to set here, and then to be reset below. self.wallet_type = wallet_type self.first_run = None self.log_file = lbrynet_log @@ -264,13 +265,30 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.search_timeout = self.session_settings['search_timeout'] self.download_timeout = self.session_settings['download_timeout'] self.max_search_results = self.session_settings['max_search_results'] - if self.session_settings['wallet_type'] in WALLET_TYPES and not wallet_type: - self.wallet_type = self.session_settings['wallet_type'] - log.info("Using wallet type %s from config" % self.wallet_type) - else: + #### + # + # Ignore the saved wallet type. Some users will have their wallet type + # saved as lbryum and we want wallets to be lbrycrd unless explicitly + # set on the command line to be lbryum. + # + # if self.session_settings['wallet_type'] in WALLET_TYPES and not wallet_type: + # self.wallet_type = self.session_settings['wallet_type'] + # log.info("Using wallet type %s from config" % self.wallet_type) + # else: + # self.wallet_type = wallet_type + # self.session_settings['wallet_type'] = wallet_type + # log.info("Using wallet type %s specified from command line" % self.wallet_type) + # + # Instead, if wallet is not set on the command line, default to the default wallet + # + if wallet_type: + log.info("Using wallet type %s specified from command line", wallet_type) self.wallet_type = wallet_type - self.session_settings['wallet_type'] = wallet_type - log.info("Using wallet type %s specified from command line" % self.wallet_type) + else: + log.info("Using the default wallet type %s", DEFAULT_WALLET) + self.wallet_type = DEFAULT_WALLET + # + #### self.delete_blobs_on_remove = self.session_settings['delete_blobs_on_remove'] self.peer_port = self.session_settings['peer_port'] self.dht_node_port = self.session_settings['dht_node_port'] @@ -877,7 +895,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def get_wallet(): - if self.wallet_type == "lbrycrd": #force lbrycrd wallet no matter what while lbryum is down + if self.wallet_type == "lbrycrd": log.info("Using lbrycrd wallet") d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.lbrycrd_conf, lbrycrdd_path=self.lbrycrdd_path)) @@ -888,7 +906,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info("Using PTC wallet") d = defer.succeed(PTCWallet(self.db_dir)) else: - log.info("Requested unknown wallet '%s', using default lbryum" % self.wallet_type) + # TODO: should fail here. Can't switch to lbrycrd because the wallet_dir, conf and path won't be set + log.info("Requested unknown wallet '%s', using default lbryum", self.wallet_type) d = defer.succeed(LBRYumWallet(self.db_dir)) d.addCallback(lambda wallet: {"wallet": wallet}) From 5ebcc01bcca76b62999bbc0133fdd62b98176123 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Sun, 17 Jul 2016 14:10:11 -0500 Subject: [PATCH 353/462] Bump version: 0.3.6 -> 0.3.7 --- .bumpversion.cfg | 2 +- lbrynet/__init__.py | 2 +- packaging/ubuntu/lbry.desktop | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index cb7f8ec7f..0848536a0 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.6 +current_version = 0.3.7 commit = True tag = True message = Bump version: {current_version} -> {new_version} diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index f106a1889..a0576d5af 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.6" +__version__ = "0.3.7" version = tuple(__version__.split('.')) \ No newline at end of file diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index 53408274c..1b0f41790 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.3.6 +Version=0.3.7 Name=LBRY Comment=The world's first user-owned content marketplace Icon=lbry From 97efd63c99ba50976d2eac9cbbd63a737e7b13f0 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Sun, 17 Jul 2016 19:19:00 -0500 Subject: [PATCH 354/462] deprecate lbry-console script --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 01346fb88..60f800df2 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,7 @@ from setuptools import setup, find_packages base_dir = os.path.abspath(os.path.dirname(__file__)) -console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', - 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', +console_scripts = ['lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', 'lbrynet-stdout-downloader = lbrynet.lbrynet_console.LBRYStdoutDownloader:launch_stdout_downloader', 'lbrynet-create-network = lbrynet.create_network:main', 'lbrynet-launch-node = lbrynet.dht.node:main', From 5817b80c947b506ca042f4a7a9805b0a7849fcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Job=20Evers=E2=80=90Meltzer?= Date: Sun, 17 Jul 2016 20:17:42 -0500 Subject: [PATCH 355/462] script to migrate private keys to lbrycrd (#79) * script to migrate private keys to lbrycrd * handle compressed keys too * validate private key actually gets added * add lbrycrdd and lbrycrd-cli checks * exit if no wallet path * move each imported address into the default account --- scripts/migrate_lbryum_to_lbrycrd.py | 113 +++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 scripts/migrate_lbryum_to_lbrycrd.py diff --git a/scripts/migrate_lbryum_to_lbrycrd.py b/scripts/migrate_lbryum_to_lbrycrd.py new file mode 100644 index 000000000..19391ba75 --- /dev/null +++ b/scripts/migrate_lbryum_to_lbrycrd.py @@ -0,0 +1,113 @@ +import argparse +import hashlib +import json +import subprocess +import sys + +import base58 + +from lbryum import SimpleConfig, Network +from lbryum.wallet import WalletStorage, Wallet +from lbryum.commands import known_commands, Commands +from lbryum import lbrycrd + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--wallet', help='path to lbryum wallet') + args = parser.parse_args() + + ensureCliIsOnPathAndServerIsRunning() + + wallet = getWallet(args.wallet) + addresses = wallet.addresses(True) + for addr in addresses[:-1]: + printBalance(wallet, addr) + saveAddr(wallet, addr) + # on the last one, rescan. Don't rescan early for sake of efficiency + addr = addresses[-1] + printBalance(wallet, addr) + saveAddr(wallet, addr, "true") + + +def ensureCliIsOnPathAndServerIsRunning(): + try: + output = subprocess.check_output(['lbrycrd-cli', 'getinfo']) + except OSError: + print 'Failed to run: lbrycrd-cli needs to be on the PATH' + exit(1) + except subprocess.CalledProcessError: + print 'Failed to run: could not connect to the lbrycrd server.' + print 'Make sure it is running and able to be connected to.' + print 'One way to do this is to run:' + print ' lbrycrdd -server -printtoconsole' + exit(1) + + +def validateAddress(addr): + raw_output = subprocess.check_output( + ['lbrycrd-cli', 'validateaddress', addr]) + output = json.loads(raw_output) + if not output['isvalid']: + raise Exception('Address {} is not valid'.format(addr)) + if not output['ismine']: + raise Exception('Address {} is not yours'.format(addr)) + + +def printBalance(wallet, addr): + balance = getBalance(wallet, addr) + print 'Importing private key for %s with balance %s' % (addr, balance) + + +def getBalance(wallet, addr): + return sum(wallet.get_addr_balance(addr)) + + +def getWallet(path=None): + if not path: + config = SimpleConfig() + path = config.get_wallet_path() + storage = WalletStorage(path) + if not storage.file_exists: + print "Failed to run: No wallet to migrate" + exit(1) + return Wallet(storage) + + +def saveAddr(wallet, addr, rescan="false"): + keys = wallet.get_private_key(addr, None) + assert len(keys) == 1, 'Address {} has {} keys. Expected 1'.format(addr, len(keys)) + key = keys[0] + # copied from lbrycrd.regenerate_key + b = lbrycrd.ASecretToSecret(key) + pkey = b[0:32] + is_compressed = lbrycrd.is_compressed(key) + wif = pkeyToWif(pkey, is_compressed) + subprocess.check_call( + ['lbrycrd-cli', 'importprivkey', wif, "lbryum import", rescan]) + validateAddress(addr) + # during the import the account gets set to the label, but lbry + # needs the address to be in the default account + subprocess.check_call(['lbrycrd-cli', 'setaccount', addr, '""']) + + +def pkeyToWif(pkey, compressed): + # Follow https://en.bitcoin.it/wiki/Wallet_import_format + # to convert from a private key to the wallet import format + prefix = '\x1c' + wif = prefix + pkey + if compressed: + wif += '\x01' + intermediate_checksum = hashlib.sha256(wif).digest() + checksum = hashlib.sha256(intermediate_checksum).digest() + wif = wif + checksum[:4] + return base58.b58encode(wif) + + +def wifToPkey(wif): + pkey = base58.b58decode(wif) + return pkey[1:-4] + + +if __name__ == '__main__': + sys.exit(main()) From a384960a7b7e154e822416c828b693535512932c Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Sun, 17 Jul 2016 19:19:00 -0500 Subject: [PATCH 356/462] deprecate lbry-console script --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 01346fb88..60f800df2 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,7 @@ from setuptools import setup, find_packages base_dir = os.path.abspath(os.path.dirname(__file__)) -console_scripts = ['lbrynet-console = lbrynet.lbrynet_console.LBRYConsole:launch_lbry_console', - 'lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', +console_scripts = ['lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUploader:launch_stdin_uploader', 'lbrynet-stdout-downloader = lbrynet.lbrynet_console.LBRYStdoutDownloader:launch_stdout_downloader', 'lbrynet-create-network = lbrynet.create_network:main', 'lbrynet-launch-node = lbrynet.dht.node:main', From a86b4c4e58c32ddba8d5eb90733786f6c1518f93 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 18 Jul 2016 08:45:14 -0500 Subject: [PATCH 357/462] bug fix: use sys.exit() not exit() --- scripts/migrate_lbryum_to_lbrycrd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/migrate_lbryum_to_lbrycrd.py b/scripts/migrate_lbryum_to_lbrycrd.py index 19391ba75..da49bd7dd 100644 --- a/scripts/migrate_lbryum_to_lbrycrd.py +++ b/scripts/migrate_lbryum_to_lbrycrd.py @@ -35,13 +35,13 @@ def ensureCliIsOnPathAndServerIsRunning(): output = subprocess.check_output(['lbrycrd-cli', 'getinfo']) except OSError: print 'Failed to run: lbrycrd-cli needs to be on the PATH' - exit(1) + sys.exit(1) except subprocess.CalledProcessError: print 'Failed to run: could not connect to the lbrycrd server.' print 'Make sure it is running and able to be connected to.' print 'One way to do this is to run:' print ' lbrycrdd -server -printtoconsole' - exit(1) + sys.exit(1) def validateAddress(addr): @@ -70,7 +70,7 @@ def getWallet(path=None): storage = WalletStorage(path) if not storage.file_exists: print "Failed to run: No wallet to migrate" - exit(1) + sys.exit(1) return Wallet(storage) From 1a5cd9b06241e3b05fa386b96c4432207027ac20 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 18 Jul 2016 09:14:50 -0500 Subject: [PATCH 358/462] Forget about trying to set a label. Setting a label also sets the account, but the LBRY app only looks at the default "" account. Marking the imported keys would have been nice, but it doesn't work. --- scripts/migrate_lbryum_to_lbrycrd.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/migrate_lbryum_to_lbrycrd.py b/scripts/migrate_lbryum_to_lbrycrd.py index da49bd7dd..fdafacd6e 100644 --- a/scripts/migrate_lbryum_to_lbrycrd.py +++ b/scripts/migrate_lbryum_to_lbrycrd.py @@ -84,11 +84,8 @@ def saveAddr(wallet, addr, rescan="false"): is_compressed = lbrycrd.is_compressed(key) wif = pkeyToWif(pkey, is_compressed) subprocess.check_call( - ['lbrycrd-cli', 'importprivkey', wif, "lbryum import", rescan]) + ['lbrycrd-cli', 'importprivkey', wif, "", rescan]) validateAddress(addr) - # during the import the account gets set to the label, but lbry - # needs the address to be in the default account - subprocess.check_call(['lbrycrd-cli', 'setaccount', addr, '""']) def pkeyToWif(pkey, compressed): From 9127a816e3183f93df4db600b52f071010e254c5 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 18 Jul 2016 10:12:49 -0500 Subject: [PATCH 359/462] Update protobuf library Updating to protobuf-v3.0.0b3 because it fixes a bug here https://github.com/google/protobuf/blob/e841bac4fcf47f809e089a70d5f84ac37b3883df/python/google/protobuf/internal/python_message.py#L59 where six.moves is not always available. In particular, the system python on OSX has an old version of six which is without six.moves. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d4423b5db..780abee9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ https://github.com/lbryio/lbryum/tarball/master/#egg=lbryum leveldb==0.193 miniupnpc==1.9 pbkdf2==1.3 -protobuf==3.0.0b2 +protobuf==3.0.0b3 pycrypto==2.6.1 python-bitcoinrpc==0.1 qrcode==5.2.2 From 78b4b3ffa8e06753b6a2237cd90ae8997d99a3ed Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 18 Jul 2016 11:30:21 -0500 Subject: [PATCH 360/462] revert default wallet back to lbryum --- lbrynet/conf.py | 2 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index b3ab58819..6bf8df48c 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -38,7 +38,7 @@ API_CONNECTION_STRING = "http://%s:%i/%s" % (API_INTERFACE, API_PORT, API_ADDRES UI_ADDRESS = "http://%s:%i" % (API_INTERFACE, API_PORT) PROTOCOL_PREFIX = "lbry" -DEFAULT_WALLET = "lbrycrd" +DEFAULT_WALLET = "lbryum" WALLET_TYPES = ["lbryum", "lbrycrd"] DEFAULT_TIMEOUT = 30 DEFAULT_MAX_SEARCH_RESULTS = 25 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 914f21073..aaf504080 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -268,8 +268,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): #### # # Ignore the saved wallet type. Some users will have their wallet type - # saved as lbryum and we want wallets to be lbrycrd unless explicitly - # set on the command line to be lbryum. + # saved as lbrycrd and we want wallets to be lbryum unless explicitly + # set on the command line to be lbrycrd. # # if self.session_settings['wallet_type'] in WALLET_TYPES and not wallet_type: # self.wallet_type = self.session_settings['wallet_type'] From f1b4c6a1ec2e4c63bae8bc89953b40eafb241bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Job=20Evers=E2=80=90Meltzer?= Date: Mon, 18 Jul 2016 14:50:05 -0500 Subject: [PATCH 361/462] Cache pyobjc (#87) * cache pyobjc modules as wheels --- .travis.yml | 1 + packaging/osx/lbry-osx-app/setup_app.sh | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index cead1a849..ca2557860 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ cache: directories: - $HOME/.cache/pip - $HOME/Library/Caches/pip + - $TRAVIS_BUILD_DIR/cache/wheel before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./packaging/travis/setup_osx.sh; fi diff --git a/packaging/osx/lbry-osx-app/setup_app.sh b/packaging/osx/lbry-osx-app/setup_app.sh index 5630a1e3f..ad3ca8449 100755 --- a/packaging/osx/lbry-osx-app/setup_app.sh +++ b/packaging/osx/lbry-osx-app/setup_app.sh @@ -5,9 +5,11 @@ set -o xtrace DEST=`pwd` tmp="${DEST}/build" +ON_TRAVIS=false rm -rf build dist LBRY.app +pip install wheel # the default py2app (v0.9) has a bug that is fixed in the head of /metachris/py2app pip install git+https://github.com/metachris/py2app pip install jsonrpc @@ -23,6 +25,7 @@ if [ -z ${TRAVIS_BUILD_DIR+x} ]; then LBRY="${tmp}/lbry" else # building on travis + ON_TRAVIS=true cd ${TRAVIS_BUILD_DIR} LBRY=${TRAVIS_BUILD_DIR} fi @@ -45,7 +48,25 @@ codesign -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/ codesign --deep -s "${LBRY_DEVELOPER_ID}" -f "${DEST}/dist/LBRYURIHandler.app/Contents/MacOS/LBRYURIHandler" codesign -vvvv "${DEST}/dist/LBRYURIHandler.app" -pip install certifi pyobjc-core pyobjc-framework-Cocoa pyobjc-framework-CFNetwork +pip install certifi +MODULES="pyobjc-core pyobjc-framework-Cocoa pyobjc-framework-CFNetwork" +if [ ${ON_TRAVIS} = true ]; then + WHEEL_DIR="${TRAVIS_BUILD_DIR}/cache/wheel" + mkdir -p "${WHEEL_DIR}" + # mapping from the package name to the + # actual built wheel file is surprisingly + # hard so instead of checking for the existance + # of each wheel, we mark with a file when they've all been + # built and skip when that file exists + if [ ! -f "${WHEEL_DIR}"/finished ]; then + pip wheel -w "${WHEEL_DIR}" ${MODULES} + touch "${WHEEL_DIR}"/finished + fi + pip install "${WHEEL_DIR}"/*.whl +else + pip install $MODULES +fi + # add lbrycrdd as a resource. Following # http://stackoverflow.com/questions/11370012/can-executables-made-with-py2app-include-other-terminal-scripts-and-run-them From 4adec39dcef895ded59b3ef0a1071d8af6e718a6 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Mon, 18 Jul 2016 16:00:52 -0500 Subject: [PATCH 362/462] Bump version: 0.3.7 -> 0.3.8 --- .bumpversion.cfg | 2 +- lbrynet/__init__.py | 2 +- packaging/ubuntu/lbry.desktop | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 0848536a0..b34ee8dd9 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.7 +current_version = 0.3.8 commit = True tag = True message = Bump version: {current_version} -> {new_version} diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index a0576d5af..49bd262cf 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.7" +__version__ = "0.3.8" version = tuple(__version__.split('.')) \ No newline at end of file diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index 1b0f41790..1c28d3553 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.3.7 +Version=0.3.8 Name=LBRY Comment=The world's first user-owned content marketplace Icon=lbry From a54166a27cb1cbabfc516196dfeb2eebb857b6b9 Mon Sep 17 00:00:00 2001 From: Alex Liebowitz Date: Wed, 20 Jul 2016 02:36:55 -0400 Subject: [PATCH 363/462] Add support for file uploads (for Publish page) --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 10 + lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 6 +- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 205 ++++++++++++++++++++ 3 files changed, 219 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index aaf504080..379209634 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -170,6 +170,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.streams = {} self.known_dht_nodes = KNOWN_DHT_NODES self.first_run_after_update = False + self.uploaded_temp_files = [] if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle @@ -708,6 +709,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: return defer.succeed(None) + def _clean_up_temp_files(self): + for path in self.uploaded_temp_files: + try: + os.remove(path) + except OSError: + pass + def _shutdown(self): log.info("Closing lbrynet session") log.info("Status at time of shutdown: " + self.startup_status[0]) @@ -720,6 +728,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): if self.lbry_ui_manager.update_checker.running: self.lbry_ui_manager.update_checker.stop() + self._clean_up_temp_files() + d = self._upload_log(log_type="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) d.addErrback(lambda err: True) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 309570873..dff9784ee 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -12,7 +12,7 @@ from twisted.web import server from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy -from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer +from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer, LBRYDaemonRequest from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, \ DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME @@ -109,7 +109,9 @@ def start(): if args.launchui: d.addCallback(lambda _: webbrowser.open(UI_ADDRESS)) - reactor.listenTCP(API_PORT, server.Site(lbry.root), interface=API_INTERFACE) + lbrynet_server = server.Site(lbry.root) + lbrynet_server.requestFactory = LBRYDaemonRequest + reactor.listenTCP(API_PORT, lbrynet_server, interface=API_INTERFACE) reactor.run() if not args.logtoconsole and not args.quiet: diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 74906da0b..b28520471 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -4,6 +4,10 @@ import shutil import json import sys import mimetypes +import mimetools +import tempfile +import time +import cgi from datetime import datetime from appdirs import user_data_dir @@ -29,6 +33,184 @@ handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, ba log.addHandler(handler) log.setLevel(logging.INFO) +class LBRYDaemonRequest(server.Request): + """ + For LBRY specific request functionality. Currently just provides + handling for large multipart POST requests, taken from here: + http://sammitch.ca/2013/07/handling-large-requests-in-twisted/ + + For multipart POST requests, this populates self.args with temp + file objects instead of strings. Note that these files don't auto-delete + on close because we want to be able to move and rename them. + + """ + + # max amount of memory to allow any ~single~ request argument [ie: POSTed file] + # note: this value seems to be taken with a grain of salt, memory usage may spike + # FAR above this value in some cases. + # eg: set the memory limit to 5 MB, write 2 blocks of 4MB, mem usage will + # have spiked to 8MB before the data is rolled to disk after the + # second write completes. + memorylimit = 1024*1024*100 + + # enable/disable debug logging + do_log = True + + # re-defined only for debug/logging purposes + def gotLength(self, length): + if self.do_log: + print '%f Headers received, Content-Length: %d' % (time.time(), length) + server.Request.gotLength(self, length) + + # re-definition of twisted.web.server.Request.requestreceived, the only difference + # is that self.parse_multipart() is used rather than cgi.parse_multipart() + def requestReceived(self, command, path, version): + from twisted.web.http import parse_qs + if self.do_log: + print '%f Request Received' % time.time() + print self.content + + self.content.seek(0,0) + self.args = {} + self.stack = [] + + self.method, self.uri = command, path + self.clientproto = version + x = self.uri.split(b'?', 1) + + if len(x) == 1: + self.path = self.uri + else: + self.path, argstring = x + self.args = parse_qs(argstring, 1) + + # cache the client and server information, we'll need this later to be + # serialized and sent with the request so CGIs will work remotely + self.client = self.channel.transport.getPeer() + self.host = self.channel.transport.getHost() + + # Argument processing + args = self.args + ctype = self.requestHeaders.getRawHeaders(b'content-type') + if ctype is not None: + ctype = ctype[0] + + if self.method == b"POST" and ctype: + mfd = b'multipart/form-data' + key, pdict = cgi.parse_header(ctype) + if key == b'application/x-www-form-urlencoded': + args.update(parse_qs(self.content.read(), 1)) + elif key == mfd: + try: + self.content.seek(0,0) + args.update(self.parse_multipart(self.content, pdict)) + #args.update(cgi.parse_multipart(self.content, pdict)) + + except KeyError as e: + if e.args[0] == b'content-disposition': + # Parse_multipart can't cope with missing + # content-dispostion headers in multipart/form-data + # parts, so we catch the exception and tell the client + # it was a bad request. + self.channel.transport.write( + b"HTTP/1.1 400 Bad Request\r\n\r\n") + self.channel.transport.loseConnection() + return + raise + + self.content.seek(0, 0) + + self.process() + + # re-definition of cgi.parse_multipart that uses a single temporary file to store + # data rather than storing 2 to 3 copies in various lists. + def parse_multipart(self, fp, pdict): + if self.do_log: + print '%f Parsing Multipart data: ' % time.time() + rewind = fp.tell() #save cursor + fp.seek(0,0) #reset cursor + + boundary = "" + if 'boundary' in pdict: + boundary = pdict['boundary'] + if not cgi.valid_boundary(boundary): + raise ValueError, ('Invalid boundary in multipart form: %r' + % (boundary,)) + + nextpart = "--" + boundary + lastpart = "--" + boundary + "--" + partdict = {} + terminator = "" + + while terminator != lastpart: + c_bytes = -1 + + data = tempfile.NamedTemporaryFile(delete=False) + if terminator: + # At start of next part. Read headers first. + headers = mimetools.Message(fp) + clength = headers.getheader('content-length') + if clength: + try: + c_bytes = int(clength) + except ValueError: + pass + if c_bytes > 0: + data.write(fp.read(c_bytes)) + # Read lines until end of part. + while 1: + line = fp.readline() + if not line: + terminator = lastpart # End outer loop + break + if line[:2] == "--": + terminator = line.strip() + if terminator in (nextpart, lastpart): + break + data.write(line) + # Done with part. + if data.tell() == 0: + continue + if c_bytes < 0: + # if a Content-Length header was not supplied with the MIME part + # then the trailing line break must be removed. + # we have data, read the last 2 bytes + rewind = min(2, data.tell()) + data.seek(-rewind, os.SEEK_END) + line = data.read(2) + if line[-2:] == "\r\n": + data.seek(-2, os.SEEK_END) + data.truncate() + elif line[-1:] == "\n": + data.seek(-1, os.SEEK_END) + data.truncate() + + line = headers['content-disposition'] + if not line: + continue + key, params = cgi.parse_header(line) + if key != 'form-data': + continue + if 'name' in params: + name = params['name'] + # kludge in the filename + if 'filename' in params: + fname_index = name + '_filename' + if fname_index in partdict: + partdict[fname_index].append(params['filename']) + else: + partdict[fname_index] = [params['filename']] + else: + # Unnamed parts are not returned at all. + continue + data.seek(0,0) + if name in partdict: + partdict[name].append(data) + else: + partdict[name] = [data] + + fp.seek(rewind) # Restore cursor + return partdict class LBRYindex(resource.Resource): def __init__(self, ui_dir): @@ -179,12 +361,35 @@ class HostedLBRYFile(resource.Resource): # call.addErrback(lambda err: log.info("Error: " + str(err))) # call.cancel() +class LBRYFileUpload(resource.Resource): + """ + Accepts a file sent via the file upload widget in the web UI, saves + it into a temporary dir, and responds with a JSON string containing + the path of the newly created file. + """ + + def __init__(self, api): + self._api = api + + def render_POST(self, request): + origfilename = request.args['file_filename'][0] + uploaded_file = request.args['file'][0] # Temp file created by request + + # Move to a new temporary dir and restore the original file name + newdirpath = tempfile.mkdtemp() + newpath = os.path.join(newdirpath, origfilename) + shutil.move(uploaded_file.name, newpath) + self._api.uploaded_temp_files.append(newpath) + + return json.dumps(newpath) + class LBRYDaemonServer(object): def _setup_server(self, wallet): self.root = LBRYindex(os.path.join(os.path.join(data_dir, "lbry-ui"), "active")) self._api = LBRYDaemon(self.root, wallet_type=wallet) self.root.putChild("view", HostedLBRYFile(self._api)) + self.root.putChild("upload", LBRYFileUpload(self._api)) self.root.putChild(API_ADDRESS, self._api) return defer.succeed(True) From 5c27b9eb90179400a5375a963c0a86ea93f8db14 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Sat, 16 Jul 2016 01:15:58 -0500 Subject: [PATCH 364/462] use asctime in logging format Instead of manually adding datetime.now() use asctime formatter to do it for us. --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 53 +++++++++++---------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 16 ++++++- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 10 ++-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 2 +- lbrynet/lbrynet_daemon/LBRYPublisher.py | 4 +- 5 files changed, 50 insertions(+), 35 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index ceb8a87a7..888406e09 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -66,6 +66,11 @@ 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.addHandler(handler) log.setLevel(logging.INFO) @@ -466,7 +471,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _announce(): self.announced_startup = True self.startup_status = STARTUP_STAGES[5] - log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") + log.info("Started lbrynet-daemon") if len(self.startup_scripts): log.info("Scheduling scripts") reactor.callLater(3, self._run_scripts) @@ -488,7 +493,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: _announce()) return d - log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") + log.info("Starting lbrynet-daemon") self.internet_connection_checker.start(3600) self.version_checker.start(3600 * 12) @@ -553,14 +558,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): s = socket.create_connection((host, 80), 2) self.connected_to_internet = True except: - log.info("[" + str(datetime.now()) + "] Internet connection not working") + log.info("Internet connection not working") self.connected_to_internet = False def _check_lbrynet_connection(self): def _log_success(): - log.info("[" + str(datetime.now()) + "] lbrynet connectivity test passed") + log.info("lbrynet connectivity test passed") def _log_failure(): - log.info("[" + str(datetime.now()) + "] lbrynet connectivity test failed") + log.info("lbrynet connectivity test failed") wonderfullife_sh = "6f3af0fa3924be98a54766aa2715d22c6c1509c3f7fa32566df4899a41f3530a9f97b2ecb817fa1dcbf1b30553aefaa7" d = download_sd_blob(self.session, wonderfullife_sh, self.session.base_payment_rate_manager) @@ -578,7 +583,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.git_lbryum_version = version return defer.succeed(None) except: - log.info("[" + str(datetime.now()) + "] Failed to get lbryum version from git") + log.info("Failed to get lbryum version from git") self.git_lbryum_version = None return defer.fail(None) @@ -593,7 +598,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.git_lbrynet_version = vr return defer.succeed(None) except: - log.info("[" + str(datetime.now()) + "] Failed to get lbrynet version from git") + log.info("Failed to get lbrynet version from git") self.git_lbrynet_version = None return defer.fail(None) @@ -1091,15 +1096,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d d.addCallback(lambda _: finish_deletion(lbry_file)) - d.addCallback(lambda _: log.info("[" + str(datetime.now()) + "] Delete lbry file")) + d.addCallback(lambda _: log.info("Delete lbry file")) return d def _get_est_cost(self, name): def _check_est(d, name): if isinstance(d.result, float): - log.info("[" + str(datetime.now()) + "] Cost est for lbry://" + name + ": " + str(d.result) + "LBC") + log.info("Cost est for lbry://" + name + ": " + str(d.result) + "LBC") else: - log.info("[" + str(datetime.now()) + "] Timeout estimating cost for lbry://" + name + ", using key fee") + log.info("Timeout estimating cost for lbry://" + name + ", using key fee") d.cancel() return defer.succeed(None) @@ -1294,7 +1299,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): Returns: true if daemon completed startup, otherwise false """ - log.info("[" + str(datetime.now()) + "] is_running: " + str(self.announced_startup)) + log.info("is_running: " + str(self.announced_startup)) if self.announced_startup: return self._render_response(True, OK_CODE) @@ -1332,7 +1337,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: r['message'] = "Catching up with the blockchain" r['progress'] = 0 - log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) + log.info("daemon status: " + str(r)) return self._render_response(r, OK_CODE) def jsonrpc_is_first_run(self): @@ -1345,7 +1350,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): True if first run, otherwise False """ - log.info("[" + str(datetime.now()) + "] Check if is first run") + log.info("Check if is first run") try: d = self.session.wallet.is_first_run() except: @@ -1365,7 +1370,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): Startup message, such as first run notification """ - log.info("[" + str(datetime.now()) + "] Get startup notice") + log.info("Get startup notice") if self.first_run and not self.session.wallet.wallet_balance: return self._render_response(self.startup_message, OK_CODE) @@ -1405,7 +1410,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'lbryum_update_available': lbryum_version < self.git_lbryum_version } - log.info("[" + str(datetime.now()) + "] Get version info: " + json.dumps(msg)) + log.info("Get version info: " + json.dumps(msg)) return self._render_response(msg, OK_CODE) def jsonrpc_get_settings(self): @@ -1433,7 +1438,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'start_lbrycrdd': bool, """ - log.info("[" + str(datetime.now()) + "] Get daemon settings") + log.info("Get daemon settings") return self._render_response(self.session_settings, OK_CODE) def jsonrpc_set_settings(self, p): @@ -1454,7 +1459,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ def _log_settings_change(): - log.info("[" + str(datetime.now()) + "] Set daemon settings to " + json.dumps(self.session_settings)) + log.info("Set daemon settings to " + json.dumps(self.session_settings)) d = self._update_settings(p) d.addErrback(lambda err: log.info(err.getTraceback())) @@ -1497,7 +1502,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): balance, float """ - log.info("[" + str(datetime.now()) + "] Get balance") + log.info("Get balance") return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE) def jsonrpc_stop(self): @@ -1727,7 +1732,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.DeferredList(ds) def _disp(results): - log.info('[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' search results') + log.info('Found ' + str(len(results)) + ' search results') consolidated_results = [] for r in results: t = {} @@ -1741,7 +1746,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return consolidated_results - log.info('[' + str(datetime.now()) + '] Search nametrie: ' + search) + log.info('Search nametrie: ' + search) d = self.session.wallet.get_nametrie() d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) @@ -1829,7 +1834,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return server.failure def _disp(x): - log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + str(x)) + log.info("Abandoned name claim tx " + str(x)) return self._render_response(x, OK_CODE) d = defer.Deferred() @@ -1940,7 +1945,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ def _disp(address): - log.info("[" + str(datetime.now()) + "] Got new wallet address: " + address) + log.info("Got new wallet address: " + address) return defer.succeed(address) d = self.session.wallet.get_new_address() @@ -2132,7 +2137,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): exclude_previous = True if 'message' in p.keys(): - log.info("[" + str(datetime.now()) + "] Upload log message: " + str(p['message'])) + log.info("Upload log message: " + str(p['message'])) if 'force' in p.keys(): force = p['force'] @@ -2287,7 +2292,7 @@ class _DownloadNameHelper(object): def _disp_file(self, f): file_path = os.path.join(self.download_directory, f.file_name) - log.info("[%s] Already downloaded: %s --> %s", datetime.now(), f.sd_hash, file_path) + log.info("Already downloaded: %s --> %s", f.sd_hash, file_path) return f def _remove_from_wait(self, r): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 309570873..cdde77c6b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -25,8 +25,13 @@ if not os.path.isdir(log_dir): os.mkdir(log_dir) lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) + +DEFAULT_FORMAT = "%(asctime)s %(levelname)-8s %(name)s:%(lineno)d: %(message)s" +DEFAULT_FORMATTER = logging.Formatter(DEFAULT_FORMAT) + log = logging.getLogger(__name__) handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) +handler.setFormatter(DEFAULT_FORMATTER) log.addHandler(handler) log.setLevel(logging.INFO) @@ -57,6 +62,13 @@ def stop(): d.callback(None) +def configureConsoleLogger(): + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(DEFAULT_FORMATTER) + logging.getLogger().addHandler(handler) + logging.getLogger().setLevel(level=logging.INFO) + + def start(): parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") parser.add_argument("--wallet", @@ -75,7 +87,7 @@ def start(): args = parser.parse_args() if args.logtoconsole: - logging.basicConfig(level=logging.INFO) + configureConsoleLogger() args = parser.parse_args() @@ -121,4 +133,4 @@ def start(): return if __name__ == "__main__": - start() \ No newline at end of file + start() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 74906da0b..507eb025d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -79,7 +79,7 @@ class LBRYFileStreamer(object): def pauseProducing(self): self._paused = True - log.info("[" + str(datetime.now()) + "] Pausing producer") + log.info("Pausing producer") return defer.succeed(None) def resumeProducing(self): @@ -104,7 +104,7 @@ class LBRYFileStreamer(object): self._request.write(data) self._cursor += 1 - log.info("[" + str(datetime.now()) + "] Wrote range %s-%s/%s, length: %s, readable: %s, depth: %s" % + log.info("Wrote range %s-%s/%s, length: %s, readable: %s, depth: %s" % (start_cur, self._cursor, self._file_size, self._cursor - start_cur, readable_bytes, self._depth)) self._sent_bytes = True @@ -117,12 +117,12 @@ class LBRYFileStreamer(object): self._deferred.addCallback(lambda _: threads.deferToThread(reactor.callLater, self._delay, _check_for_new_data)) return defer.succeed(None) - log.info("[" + str(datetime.now()) + "] Resuming producer") + log.info("Resuming producer") self._paused = False self._deferred.addCallback(lambda _: _check_for_new_data()) def stopProducing(self): - log.info("[" + str(datetime.now()) + "] Stopping producer") + log.info("Stopping producer") self._stopped = True # self._fileObject.close() self._deferred.addErrback(lambda err: err.trap(defer.CancelledError)) @@ -147,7 +147,7 @@ class HostedLBRYFile(resource.Resource): # # range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-') # start, stop = int(range_header[0]), range_header[1] - # log.info("[" + str(datetime.now()) + "] GET range %s-%s" % (start, stop)) + # log.info("GET range %s-%s" % (start, stop)) # path = os.path.join(self._api.download_directory, stream.file_name) # # d = stream.get_total_bytes() diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index c72444c70..9187cc56d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -147,6 +147,6 @@ class GetStream(object): d = _pay_key_fee() self.downloader = downloader self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d.addCallback(lambda _: log.info("[%s] Downloading %s --> %s" % (datetime.now(), self.stream_hash, self.downloader.file_name))) + d.addCallback(lambda _: log.info("Downloading %s --> %s", self.stream_hash, self.downloader.file_name)) d.addCallback(lambda _: self.downloader.start()) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index c0658bfec..bd5c50e4b 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -48,9 +48,7 @@ class Publisher(object): def start(self, name, file_path, bid, metadata, fee=None, sources={}): def _show_result(): - - message = "[%s] Published %s --> lbry://%s txid: %s" % (datetime.now(), self.file_name, self.publish_name, self.txid) - log.info(message) + log.info("Published %s --> lbry://%s txid: %s", self.file_name, self.publish_name, self.txid) return defer.succeed(self.txid) self.publish_name = name From 55f47a2b1b552f6272a7cd62815ea038bd892517 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 17:32:15 -0400 Subject: [PATCH 365/462] lbryum catchup --- lbrynet/core/LBRYWallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 0bf99f7a5..5088dc9d2 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -996,7 +996,7 @@ class LBRYumWallet(LBRYWallet): blockchain_caught_d = defer.Deferred() def check_caught_up(): - local_height = self.network.get_local_height() + local_height = self.network.get_catchup_progress() remote_height = self.network.get_server_height() if remote_height != 0 and remote_height - local_height <= 5: From c232743963d87082581116bbb721095bf9b9d292 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 20:13:44 -0400 Subject: [PATCH 366/462] fix LBRYumWallet._do_send_many --- lbrynet/core/LBRYWallet.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 5088dc9d2..4a8a4772f 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -1122,11 +1122,10 @@ class LBRYumWallet(LBRYWallet): def _do_send_many(self, payments_to_send): log.warning("Doing send many. payments to send: %s", str(payments_to_send)) - outputs = [(TYPE_ADDRESS, address, int(amount*COIN)) for address, amount in payments_to_send.iteritems()] - d = threads.deferToThread(self.wallet.mktx, outputs, None, self.config) - d.addCallback(lambda tx: threads.deferToThread(self.wallet.sendtx, tx)) - d.addCallback(self._save_wallet) - return d + cmd = known_commands['paytomanyandsend'] + func = getattr(self.cmd_runner, cmd.name) + # outputs = [(address, amount) for address, amount in payments_to_send.iteritems()] + return threads.deferToThread(func, payments_to_send.iteritems()) def _get_value_for_name(self, name): cmd = known_commands['getvalueforname'] From 0bee2689d1f276f5f9c86d66ba567fe85f46b766 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 20:17:01 -0400 Subject: [PATCH 367/462] download fixes --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index ceb8a87a7..295d91622 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1599,13 +1599,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): # can spec what parameters it expects and how to set default values timeout = p.get('timeout', self.download_timeout) download_directory = p.get('download_directory', self.download_directory) - file_name = p.get('file_name') - stream_info = p.get('stream_info') + file_name = p.get('file_name', None) + stream_info = p.get('stream_info', None) sd_hash = get_sd_hash(stream_info) wait_for_write = p.get('wait_for_write', True) name = p.get('name') return Parameters( - timout=timeout, + timeout=timeout, download_directory=download_directory, file_name=file_name, stream_info=stream_info, @@ -1633,13 +1633,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): return server.failure if params.name in self.waiting_on: return server.failure + d = self._download_name(name=params.name, timeout=params.timeout, download_directory=params.download_directory, stream_info=params.stream_info, file_name=params.file_name, wait_for_write=params.wait_for_write) - d.addCallback(get_output_callback(params)) + # d.addCallback(get_output_callback(params)) d.addCallback(lambda message: self._render_response(message, OK_CODE)) return d @@ -2263,13 +2264,15 @@ class _DownloadNameHelper(object): return d def _wait_on_lbry_file(self, f): - written_bytes = self.get_written_bytes(f.file_name) - if not written_bytes: - d = defer.succeed(None) - d.addCallback(lambda _: reactor.callLater(1, self._wait_on_lbry_file, f)) - return d - else: - return defer.succeed(self._disp_file(f)) + if f is not None: + written_bytes = self.get_written_bytes(f.file_name) + if written_bytes: + return defer.succeed(self._disp_file(f)) + + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, self._wait_on_lbry_file, f)) + return d + def get_written_bytes(self, file_name): try: From 7e7981855580476f04940fcf59797dd837961443 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 20:24:49 -0400 Subject: [PATCH 368/462] Revert "Merge branch 'master' into lbryum-catchup" This reverts commit a51576b250e58457ed001b9be94d959249fc9d3d, reversing changes made to 0bee2689d1f276f5f9c86d66ba567fe85f46b766. --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 53 ++++++++++----------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 16 +------ lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 10 ++-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 2 +- lbrynet/lbrynet_daemon/LBRYPublisher.py | 4 +- 5 files changed, 35 insertions(+), 50 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 3cd656644..295d91622 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -66,11 +66,6 @@ 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.addHandler(handler) log.setLevel(logging.INFO) @@ -471,7 +466,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _announce(): self.announced_startup = True self.startup_status = STARTUP_STAGES[5] - log.info("Started lbrynet-daemon") + log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") if len(self.startup_scripts): log.info("Scheduling scripts") reactor.callLater(3, self._run_scripts) @@ -493,7 +488,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: _announce()) return d - log.info("Starting lbrynet-daemon") + log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") self.internet_connection_checker.start(3600) self.version_checker.start(3600 * 12) @@ -558,14 +553,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): s = socket.create_connection((host, 80), 2) self.connected_to_internet = True except: - log.info("Internet connection not working") + log.info("[" + str(datetime.now()) + "] Internet connection not working") self.connected_to_internet = False def _check_lbrynet_connection(self): def _log_success(): - log.info("lbrynet connectivity test passed") + log.info("[" + str(datetime.now()) + "] lbrynet connectivity test passed") def _log_failure(): - log.info("lbrynet connectivity test failed") + log.info("[" + str(datetime.now()) + "] lbrynet connectivity test failed") wonderfullife_sh = "6f3af0fa3924be98a54766aa2715d22c6c1509c3f7fa32566df4899a41f3530a9f97b2ecb817fa1dcbf1b30553aefaa7" d = download_sd_blob(self.session, wonderfullife_sh, self.session.base_payment_rate_manager) @@ -583,7 +578,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.git_lbryum_version = version return defer.succeed(None) except: - log.info("Failed to get lbryum version from git") + log.info("[" + str(datetime.now()) + "] Failed to get lbryum version from git") self.git_lbryum_version = None return defer.fail(None) @@ -598,7 +593,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.git_lbrynet_version = vr return defer.succeed(None) except: - log.info("Failed to get lbrynet version from git") + log.info("[" + str(datetime.now()) + "] Failed to get lbrynet version from git") self.git_lbrynet_version = None return defer.fail(None) @@ -1096,15 +1091,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d d.addCallback(lambda _: finish_deletion(lbry_file)) - d.addCallback(lambda _: log.info("Delete lbry file")) + d.addCallback(lambda _: log.info("[" + str(datetime.now()) + "] Delete lbry file")) return d def _get_est_cost(self, name): def _check_est(d, name): if isinstance(d.result, float): - log.info("Cost est for lbry://" + name + ": " + str(d.result) + "LBC") + log.info("[" + str(datetime.now()) + "] Cost est for lbry://" + name + ": " + str(d.result) + "LBC") else: - log.info("Timeout estimating cost for lbry://" + name + ", using key fee") + log.info("[" + str(datetime.now()) + "] Timeout estimating cost for lbry://" + name + ", using key fee") d.cancel() return defer.succeed(None) @@ -1299,7 +1294,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): Returns: true if daemon completed startup, otherwise false """ - log.info("is_running: " + str(self.announced_startup)) + log.info("[" + str(datetime.now()) + "] is_running: " + str(self.announced_startup)) if self.announced_startup: return self._render_response(True, OK_CODE) @@ -1337,7 +1332,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: r['message'] = "Catching up with the blockchain" r['progress'] = 0 - log.info("daemon status: " + str(r)) + log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) return self._render_response(r, OK_CODE) def jsonrpc_is_first_run(self): @@ -1350,7 +1345,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): True if first run, otherwise False """ - log.info("Check if is first run") + log.info("[" + str(datetime.now()) + "] Check if is first run") try: d = self.session.wallet.is_first_run() except: @@ -1370,7 +1365,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): Startup message, such as first run notification """ - log.info("Get startup notice") + log.info("[" + str(datetime.now()) + "] Get startup notice") if self.first_run and not self.session.wallet.wallet_balance: return self._render_response(self.startup_message, OK_CODE) @@ -1410,7 +1405,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'lbryum_update_available': lbryum_version < self.git_lbryum_version } - log.info("Get version info: " + json.dumps(msg)) + log.info("[" + str(datetime.now()) + "] Get version info: " + json.dumps(msg)) return self._render_response(msg, OK_CODE) def jsonrpc_get_settings(self): @@ -1438,7 +1433,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'start_lbrycrdd': bool, """ - log.info("Get daemon settings") + log.info("[" + str(datetime.now()) + "] Get daemon settings") return self._render_response(self.session_settings, OK_CODE) def jsonrpc_set_settings(self, p): @@ -1459,7 +1454,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ def _log_settings_change(): - log.info("Set daemon settings to " + json.dumps(self.session_settings)) + log.info("[" + str(datetime.now()) + "] Set daemon settings to " + json.dumps(self.session_settings)) d = self._update_settings(p) d.addErrback(lambda err: log.info(err.getTraceback())) @@ -1502,7 +1497,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): balance, float """ - log.info("Get balance") + log.info("[" + str(datetime.now()) + "] Get balance") return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE) def jsonrpc_stop(self): @@ -1733,7 +1728,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.DeferredList(ds) def _disp(results): - log.info('Found ' + str(len(results)) + ' search results') + log.info('[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' search results') consolidated_results = [] for r in results: t = {} @@ -1747,7 +1742,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return consolidated_results - log.info('Search nametrie: ' + search) + log.info('[' + str(datetime.now()) + '] Search nametrie: ' + search) d = self.session.wallet.get_nametrie() d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) @@ -1835,7 +1830,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return server.failure def _disp(x): - log.info("Abandoned name claim tx " + str(x)) + log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + str(x)) return self._render_response(x, OK_CODE) d = defer.Deferred() @@ -1946,7 +1941,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ def _disp(address): - log.info("Got new wallet address: " + address) + log.info("[" + str(datetime.now()) + "] Got new wallet address: " + address) return defer.succeed(address) d = self.session.wallet.get_new_address() @@ -2138,7 +2133,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): exclude_previous = True if 'message' in p.keys(): - log.info("Upload log message: " + str(p['message'])) + log.info("[" + str(datetime.now()) + "] Upload log message: " + str(p['message'])) if 'force' in p.keys(): force = p['force'] @@ -2295,7 +2290,7 @@ class _DownloadNameHelper(object): def _disp_file(self, f): file_path = os.path.join(self.download_directory, f.file_name) - log.info("Already downloaded: %s --> %s", f.sd_hash, file_path) + log.info("[%s] Already downloaded: %s --> %s", datetime.now(), f.sd_hash, file_path) return f def _remove_from_wait(self, r): diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index cdde77c6b..309570873 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -25,13 +25,8 @@ if not os.path.isdir(log_dir): os.mkdir(log_dir) lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) - -DEFAULT_FORMAT = "%(asctime)s %(levelname)-8s %(name)s:%(lineno)d: %(message)s" -DEFAULT_FORMATTER = logging.Formatter(DEFAULT_FORMAT) - log = logging.getLogger(__name__) handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) -handler.setFormatter(DEFAULT_FORMATTER) log.addHandler(handler) log.setLevel(logging.INFO) @@ -62,13 +57,6 @@ def stop(): d.callback(None) -def configureConsoleLogger(): - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(DEFAULT_FORMATTER) - logging.getLogger().addHandler(handler) - logging.getLogger().setLevel(level=logging.INFO) - - def start(): parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") parser.add_argument("--wallet", @@ -87,7 +75,7 @@ def start(): args = parser.parse_args() if args.logtoconsole: - configureConsoleLogger() + logging.basicConfig(level=logging.INFO) args = parser.parse_args() @@ -133,4 +121,4 @@ def start(): return if __name__ == "__main__": - start() + start() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 507eb025d..74906da0b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -79,7 +79,7 @@ class LBRYFileStreamer(object): def pauseProducing(self): self._paused = True - log.info("Pausing producer") + log.info("[" + str(datetime.now()) + "] Pausing producer") return defer.succeed(None) def resumeProducing(self): @@ -104,7 +104,7 @@ class LBRYFileStreamer(object): self._request.write(data) self._cursor += 1 - log.info("Wrote range %s-%s/%s, length: %s, readable: %s, depth: %s" % + log.info("[" + str(datetime.now()) + "] Wrote range %s-%s/%s, length: %s, readable: %s, depth: %s" % (start_cur, self._cursor, self._file_size, self._cursor - start_cur, readable_bytes, self._depth)) self._sent_bytes = True @@ -117,12 +117,12 @@ class LBRYFileStreamer(object): self._deferred.addCallback(lambda _: threads.deferToThread(reactor.callLater, self._delay, _check_for_new_data)) return defer.succeed(None) - log.info("Resuming producer") + log.info("[" + str(datetime.now()) + "] Resuming producer") self._paused = False self._deferred.addCallback(lambda _: _check_for_new_data()) def stopProducing(self): - log.info("Stopping producer") + log.info("[" + str(datetime.now()) + "] Stopping producer") self._stopped = True # self._fileObject.close() self._deferred.addErrback(lambda err: err.trap(defer.CancelledError)) @@ -147,7 +147,7 @@ class HostedLBRYFile(resource.Resource): # # range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-') # start, stop = int(range_header[0]), range_header[1] - # log.info("GET range %s-%s" % (start, stop)) + # log.info("[" + str(datetime.now()) + "] GET range %s-%s" % (start, stop)) # path = os.path.join(self._api.download_directory, stream.file_name) # # d = stream.get_total_bytes() diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 9187cc56d..c72444c70 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -147,6 +147,6 @@ class GetStream(object): d = _pay_key_fee() self.downloader = downloader self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d.addCallback(lambda _: log.info("Downloading %s --> %s", self.stream_hash, self.downloader.file_name)) + d.addCallback(lambda _: log.info("[%s] Downloading %s --> %s" % (datetime.now(), self.stream_hash, self.downloader.file_name))) d.addCallback(lambda _: self.downloader.start()) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index bd5c50e4b..c0658bfec 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -48,7 +48,9 @@ class Publisher(object): def start(self, name, file_path, bid, metadata, fee=None, sources={}): def _show_result(): - log.info("Published %s --> lbry://%s txid: %s", self.file_name, self.publish_name, self.txid) + + message = "[%s] Published %s --> lbry://%s txid: %s" % (datetime.now(), self.file_name, self.publish_name, self.txid) + log.info(message) return defer.succeed(self.txid) self.publish_name = name From 5eb7c64ec363656d7b373e7587bca1794555c9ca Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 20:30:57 -0400 Subject: [PATCH 369/462] revert get() refactor --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 386 +++++++++++---------------- 1 file changed, 159 insertions(+), 227 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 295d91622..aaf504080 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -138,11 +138,6 @@ OK_CODE = 200 REMOTE_SERVER = "www.google.com" -class Parameters(object): - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - class LBRYDaemon(jsonrpc.JSONRPC): """ LBRYnet daemon, a jsonrpc interface to lbry functions @@ -1018,37 +1013,97 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None, - file_name=None, stream_info=None, wait_for_write=True): + file_name=None, stream_info=None, wait_for_write=True): """ Add a lbry file to the file manager, start the download, and return the new lbry file. If it already exists in the file manager, return the existing lbry file """ - helper = _DownloadNameHelper( - self, name, timeout, download_directory, file_name, wait_for_write) + + if not download_directory: + download_directory = self.download_directory + elif not os.path.isdir(download_directory): + download_directory = self.download_directory + + def _remove_from_wait(r): + del self.waiting_on[name] + return r + + def _setup_stream(stream_info): + if 'sources' in stream_info.keys(): + stream_hash = stream_info['sources']['lbry_sd_hash'] + else: + stream_hash = stream_info['stream_hash'] + + d = self._get_lbry_file_by_sd_hash(stream_hash) + def _add_results(l): + if l: + if os.path.isfile(os.path.join(self.download_directory, l.file_name)): + return defer.succeed((stream_info, l)) + return defer.succeed((stream_info, None)) + d.addCallback(_add_results) + return d + + def _wait_on_lbry_file(f): + if os.path.isfile(os.path.join(self.download_directory, f.file_name)): + written_file = file(os.path.join(self.download_directory, f.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + + if not written_bytes: + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, _wait_on_lbry_file, f)) + return d + else: + return defer.succeed(_disp_file(f)) + + def _disp_file(f): + file_path = os.path.join(self.download_directory, f.file_name) + log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.sd_hash) + " --> " + file_path) + return f + + def _get_stream(stream_info): + def _wait_for_write(): + try: + if os.path.isfile(os.path.join(self.download_directory, self.streams[name].downloader.file_name)): + written_file = file(os.path.join(self.download_directory, self.streams[name].downloader.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + except: + written_bytes = False + + if not written_bytes: + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, _wait_for_write)) + return d + else: + return defer.succeed(None) + + self.streams[name] = GetStream(self.sd_identifier, self.session, self.session.wallet, + self.lbry_file_manager, max_key_fee=self.max_key_fee, + data_rate=self.data_rate, timeout=timeout, + download_directory=download_directory, file_name=file_name) + d = self.streams[name].start(stream_info, name) + if wait_for_write: + d.addCallback(lambda _: _wait_for_write()) + d.addCallback(lambda _: self.streams[name].downloader) + + return d if not stream_info: self.waiting_on[name] = True d = self._resolve_name(name) else: d = defer.succeed(stream_info) - d.addCallback(helper._setup_stream) - d.addCallback(helper.wait_or_get_stream) + d.addCallback(_setup_stream) + d.addCallback(lambda (stream_info, lbry_file): _get_stream(stream_info) if not lbry_file else _wait_on_lbry_file(lbry_file)) if not stream_info: - d.addCallback(helper._remove_from_wait) - return d - - def add_stream(self, name, timeout, download_directory, file_name, stream_info): - """Makes, adds and starts a stream""" - self.streams[name] = GetStream(self.sd_identifier, - self.session, - self.session.wallet, - self.lbry_file_manager, - max_key_fee=self.max_key_fee, - data_rate=self.data_rate, - timeout=timeout, - download_directory=download_directory, - file_name=file_name) - d = self.streams[name].start(stream_info, name) + d.addCallback(_remove_from_wait) return d def _get_long_count_timestamp(self): @@ -1061,16 +1116,38 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _resolve_name(self, name, force_refresh=False): - """Resolves a name. Checks the cache first before going out to the blockchain. + def _cache_stream_info(stream_info): + def _add_txid(txid): + self.name_cache[name]['txid'] = txid + return defer.succeed(None) - Args: - name: the lbry:// to resolve - force_refresh: if True, always go out to the blockchain to resolve. - """ - if name.startswith('lbry://'): - raise ValueError('name %s should not start with lbry://') - helper = _ResolveNameHelper(self, name, force_refresh) - return helper.get_deferred() + self.name_cache[name] = {'claim_metadata': stream_info, 'timestamp': self._get_long_count_timestamp()} + d = self.session.wallet.get_txid_for_name(name) + d.addCallback(_add_txid) + d.addCallback(lambda _: self._update_claim_cache()) + d.addCallback(lambda _: self.name_cache[name]['claim_metadata']) + + return d + + if not force_refresh: + if name in self.name_cache.keys(): + if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: + log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) + d = defer.succeed(self.name_cache[name]['claim_metadata']) + else: + log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) + else: + log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) + else: + log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) + + return d def _delete_lbry_file(self, lbry_file, delete_file=True): d = self.lbry_file_manager.delete_lbry_file(lbry_file) @@ -1593,55 +1670,65 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure) return d - def _process_get_parameters(self, p): - """Extract info from input parameters and fill in default values for `get` call.""" - # TODO: this process can be abstracted s.t. each method - # can spec what parameters it expects and how to set default values - timeout = p.get('timeout', self.download_timeout) - download_directory = p.get('download_directory', self.download_directory) - file_name = p.get('file_name', None) - stream_info = p.get('stream_info', None) - sd_hash = get_sd_hash(stream_info) - wait_for_write = p.get('wait_for_write', True) - name = p.get('name') - return Parameters( - timeout=timeout, - download_directory=download_directory, - file_name=file_name, - stream_info=stream_info, - sd_hash=sd_hash, - wait_for_write=wait_for_write, - name=name - ) - def jsonrpc_get(self, p): - """Download stream from a LBRY uri. + """ + Download stream from a LBRY uri Args: 'name': name to download, string 'download_directory': optional, path to directory where file will be saved, string 'file_name': optional, a user specified name for the downloaded file 'stream_info': optional, specified stream info overrides name - 'timout': optional - 'wait_for_write': optional, defaults to True Returns: 'stream_hash': hex string 'path': path of download """ - params = self._process_get_parameters(p) - if not params.name: - return server.failure - if params.name in self.waiting_on: - return server.failure - d = self._download_name(name=params.name, - timeout=params.timeout, - download_directory=params.download_directory, - stream_info=params.stream_info, - file_name=params.file_name, - wait_for_write=params.wait_for_write) - # d.addCallback(get_output_callback(params)) - d.addCallback(lambda message: self._render_response(message, OK_CODE)) + if 'timeout' not in p.keys(): + timeout = self.download_timeout + else: + timeout = p['timeout'] + + if 'download_directory' not in p.keys(): + download_directory = self.download_directory + else: + download_directory = p['download_directory'] + + if 'file_name' in p.keys(): + file_name = p['file_name'] + else: + file_name = None + + if 'stream_info' in p.keys(): + stream_info = p['stream_info'] + if 'sources' in stream_info.keys(): + sd_hash = stream_info['sources']['lbry_sd_hash'] + else: + sd_hash = stream_info['stream_hash'] + else: + stream_info = None + + if 'wait_for_write' in p.keys(): + wait_for_write = p['wait_for_write'] + else: + wait_for_write = True + + if 'name' in p.keys(): + name = p['name'] + if p['name'] not in self.waiting_on.keys(): + d = self._download_name(name=name, timeout=timeout, download_directory=download_directory, + stream_info=stream_info, file_name=file_name, wait_for_write=wait_for_write) + d.addCallback(lambda l: {'stream_hash': sd_hash, + 'path': os.path.join(self.download_directory, l.file_name)} + if stream_info else + {'stream_hash': l.sd_hash, + 'path': os.path.join(self.download_directory, l.file_name)}) + d.addCallback(lambda message: self._render_response(message, OK_CODE)) + else: + d = server.failure + else: + d = server.failure + return d def jsonrpc_stop_lbry_file(self, p): @@ -2188,163 +2275,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = threads.deferToThread(subprocess.Popen, ['open', '-R', path]) else: # No easy way to reveal specific files on Linux, so just open the containing directory - d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.path.dirname(path)]) + d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.dirname(path)]) d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d - - -def get_sd_hash(stream_info): - if not stream_info: - return None - try: - return stream_info['sources']['lbry_sd_hash'] - except KeyError: - return stream_info.get('stream_hash') - - -def get_output_callback(params): - def callback(l): - return { - 'stream_hash': params.sd_hash if params.stream_info else l.sd_hash, - 'path': os.path.join(params.download_directory, l.file_name) - } - return callback - - -class _DownloadNameHelper(object): - def __init__(self, daemon, name, timeout=DEFAULT_TIMEOUT, download_directory=None, - file_name=None, wait_for_write=True): - self.daemon = daemon - self.name = name - self.timeout = timeout - if not download_directory or not os.path.isdir(download_directory): - self.download_directory = daemon.download_directory - else: - self.download_directory = download_directory - self.file_name = file_name - self.wait_for_write = wait_for_write - - def _setup_stream(self, stream_info): - stream_hash = get_sd_hash(stream_info) - d = self.daemon._get_lbry_file_by_sd_hash(stream_hash) - d.addCallback(self._add_results_callback(stream_info)) - return d - - def _add_results_callback(self, stream_info): - def add_results(l): - if l: - if os.path.isfile(os.path.join(self.download_directory, l.file_name)): - return defer.succeed((stream_info, l)) - return defer.succeed((stream_info, None)) - return add_results - - def wait_or_get_stream(self, args): - stream_info, lbry_file = args - if lbry_file: - return self._get_stream(stream_info) - else: - return self._wait_on_lbry_file(lbry_file) - - def _get_stream(self, stream_info): - d = self.daemon.add_stream( - self.name, self.timeout, self.download_directory, self.file_name, stream_info) - if self.wait_for_write: - d.addCallback(lambda _: self._wait_for_write()) - d.addCallback(lambda _: self.daemon.streams[self.name].downloader) - return d - - def _wait_for_write(self): - file_name = self.daemon.streams[self.name].downloader.file_name - written_bytes = self.get_written_bytes(file_name) - d = defer.succeed(None) - if not written_bytes: - d.addCallback(lambda _: reactor.callLater(1, self._wait_for_write)) - return d - - def _wait_on_lbry_file(self, f): - if f is not None: - written_bytes = self.get_written_bytes(f.file_name) - if written_bytes: - return defer.succeed(self._disp_file(f)) - - d = defer.succeed(None) - d.addCallback(lambda _: reactor.callLater(1, self._wait_on_lbry_file, f)) - return d - - - def get_written_bytes(self, file_name): - try: - file_path = os.path.join(self.download_directory, file_name) - if os.path.isfile(file_path): - written_file = file(file_path) - written_file.seek(0, os.SEEK_END) - written_bytes = written_file.tell() - written_file.close() - else: - written_bytes = False - except Exception: - writen_bytes = False - return written_bytes - - def _disp_file(self, f): - file_path = os.path.join(self.download_directory, f.file_name) - log.info("[%s] Already downloaded: %s --> %s", datetime.now(), f.sd_hash, file_path) - return f - - def _remove_from_wait(self, r): - del self.daemon.waiting_on[self.name] - return r - -class _ResolveNameHelper(object): - def __init__(self, daemon, name, force_refresh): - self.daemon = daemon - self.name = name - self.force_refresh = force_refresh - - def get_deferred(self): - if self.need_fresh_stream(): - log.info("Resolving stream info for lbry://%s", self.name) - d = self.wallet.get_stream_info_for_name(self.name) - d.addCallbacks(self._cache_stream_info, lambda _: defer.fail(UnknownNameError)) - else: - log.info("Returning cached stream info for lbry://%s", self.name) - d = defer.succeed(self.name_data['claim_metadata']) - return d - - @property - def name_data(self): - return self.daemon.name_cache[self.name] - - @property - def wallet(self): - return self.daemon.session.wallet - - def now(self): - return self.daemon._get_long_count_timestamp() - - def _add_txid(self, txid): - self.name_data['txid'] = txid - return defer.succeed(None) - - def _cache_stream_info(self, stream_info): - self.daemon.name_cache[self.name] = { - 'claim_metadata': stream_info, - 'timestamp': self.now() - } - d = self.wallet.get_txid_for_name(self.name) - d.addCallback(self._add_txid) - d.addCallback(lambda _: self.daemon._update_claim_cache()) - d.addCallback(lambda _: self.name_data['claim_metadata']) - return d - - def need_fresh_stream(self): - return self.force_refresh or not self.is_in_cache() or self.is_cached_name_expired() - - def is_in_cache(self): - return self.name in self.daemon.name_cache - - def is_cached_name_expired(self): - time_in_cache = self.now() - self.name_data['timestamp'] - return time_in_cache >= self.daemon.cache_time From f999073fb401c5bb1e1240c9d4e3178c61d7b0b4 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 20:34:02 -0400 Subject: [PATCH 370/462] revert download refactor to be re-merged after fixes --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 385 +++++++++++---------------- 1 file changed, 160 insertions(+), 225 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 888406e09..b3b234736 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -143,11 +143,6 @@ OK_CODE = 200 REMOTE_SERVER = "www.google.com" -class Parameters(object): - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - class LBRYDaemon(jsonrpc.JSONRPC): """ LBRYnet daemon, a jsonrpc interface to lbry functions @@ -1023,37 +1018,97 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _download_name(self, name, timeout=DEFAULT_TIMEOUT, download_directory=None, - file_name=None, stream_info=None, wait_for_write=True): + file_name=None, stream_info=None, wait_for_write=True): """ Add a lbry file to the file manager, start the download, and return the new lbry file. If it already exists in the file manager, return the existing lbry file """ - helper = _DownloadNameHelper( - self, name, timeout, download_directory, file_name, wait_for_write) + + if not download_directory: + download_directory = self.download_directory + elif not os.path.isdir(download_directory): + download_directory = self.download_directory + + def _remove_from_wait(r): + del self.waiting_on[name] + return r + + def _setup_stream(stream_info): + if 'sources' in stream_info.keys(): + stream_hash = stream_info['sources']['lbry_sd_hash'] + else: + stream_hash = stream_info['stream_hash'] + + d = self._get_lbry_file_by_sd_hash(stream_hash) + def _add_results(l): + if l: + if os.path.isfile(os.path.join(self.download_directory, l.file_name)): + return defer.succeed((stream_info, l)) + return defer.succeed((stream_info, None)) + d.addCallback(_add_results) + return d + + def _wait_on_lbry_file(f): + if os.path.isfile(os.path.join(self.download_directory, f.file_name)): + written_file = file(os.path.join(self.download_directory, f.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + + if not written_bytes: + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, _wait_on_lbry_file, f)) + return d + else: + return defer.succeed(_disp_file(f)) + + def _disp_file(f): + file_path = os.path.join(self.download_directory, f.file_name) + log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.sd_hash) + " --> " + file_path) + return f + + def _get_stream(stream_info): + def _wait_for_write(): + try: + if os.path.isfile(os.path.join(self.download_directory, self.streams[name].downloader.file_name)): + written_file = file(os.path.join(self.download_directory, self.streams[name].downloader.file_name)) + written_file.seek(0, os.SEEK_END) + written_bytes = written_file.tell() + written_file.close() + else: + written_bytes = False + except: + written_bytes = False + + if not written_bytes: + d = defer.succeed(None) + d.addCallback(lambda _: reactor.callLater(1, _wait_for_write)) + return d + else: + return defer.succeed(None) + + self.streams[name] = GetStream(self.sd_identifier, self.session, self.session.wallet, + self.lbry_file_manager, max_key_fee=self.max_key_fee, + data_rate=self.data_rate, timeout=timeout, + download_directory=download_directory, file_name=file_name) + d = self.streams[name].start(stream_info, name) + if wait_for_write: + d.addCallback(lambda _: _wait_for_write()) + d.addCallback(lambda _: self.streams[name].downloader) + + return d if not stream_info: self.waiting_on[name] = True d = self._resolve_name(name) else: d = defer.succeed(stream_info) - d.addCallback(helper._setup_stream) - d.addCallback(helper.wait_or_get_stream) + d.addCallback(_setup_stream) + d.addCallback(lambda (stream_info, lbry_file): _get_stream(stream_info) if not lbry_file else _wait_on_lbry_file(lbry_file)) if not stream_info: - d.addCallback(helper._remove_from_wait) - return d - - def add_stream(self, name, timeout, download_directory, file_name, stream_info): - """Makes, adds and starts a stream""" - self.streams[name] = GetStream(self.sd_identifier, - self.session, - self.session.wallet, - self.lbry_file_manager, - max_key_fee=self.max_key_fee, - data_rate=self.data_rate, - timeout=timeout, - download_directory=download_directory, - file_name=file_name) - d = self.streams[name].start(stream_info, name) + d.addCallback(_remove_from_wait) return d def _get_long_count_timestamp(self): @@ -1066,16 +1121,38 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _resolve_name(self, name, force_refresh=False): - """Resolves a name. Checks the cache first before going out to the blockchain. + def _cache_stream_info(stream_info): + def _add_txid(txid): + self.name_cache[name]['txid'] = txid + return defer.succeed(None) - Args: - name: the lbry:// to resolve - force_refresh: if True, always go out to the blockchain to resolve. - """ - if name.startswith('lbry://'): - raise ValueError('name %s should not start with lbry://') - helper = _ResolveNameHelper(self, name, force_refresh) - return helper.get_deferred() + self.name_cache[name] = {'claim_metadata': stream_info, 'timestamp': self._get_long_count_timestamp()} + d = self.session.wallet.get_txid_for_name(name) + d.addCallback(_add_txid) + d.addCallback(lambda _: self._update_claim_cache()) + d.addCallback(lambda _: self.name_cache[name]['claim_metadata']) + + return d + + if not force_refresh: + if name in self.name_cache.keys(): + if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: + log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) + d = defer.succeed(self.name_cache[name]['claim_metadata']) + else: + log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) + else: + log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) + else: + log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) + d = self.session.wallet.get_stream_info_for_name(name) + d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) + + return d def _delete_lbry_file(self, lbry_file, delete_file=True): d = self.lbry_file_manager.delete_lbry_file(lbry_file) @@ -1598,54 +1675,65 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure) return d - def _process_get_parameters(self, p): - """Extract info from input parameters and fill in default values for `get` call.""" - # TODO: this process can be abstracted s.t. each method - # can spec what parameters it expects and how to set default values - timeout = p.get('timeout', self.download_timeout) - download_directory = p.get('download_directory', self.download_directory) - file_name = p.get('file_name') - stream_info = p.get('stream_info') - sd_hash = get_sd_hash(stream_info) - wait_for_write = p.get('wait_for_write', True) - name = p.get('name') - return Parameters( - timout=timeout, - download_directory=download_directory, - file_name=file_name, - stream_info=stream_info, - sd_hash=sd_hash, - wait_for_write=wait_for_write, - name=name - ) - def jsonrpc_get(self, p): - """Download stream from a LBRY uri. + """ + Download stream from a LBRY uri Args: 'name': name to download, string 'download_directory': optional, path to directory where file will be saved, string 'file_name': optional, a user specified name for the downloaded file 'stream_info': optional, specified stream info overrides name - 'timout': optional - 'wait_for_write': optional, defaults to True Returns: 'stream_hash': hex string 'path': path of download """ - params = self._process_get_parameters(p) - if not params.name: - return server.failure - if params.name in self.waiting_on: - return server.failure - d = self._download_name(name=params.name, - timeout=params.timeout, - download_directory=params.download_directory, - stream_info=params.stream_info, - file_name=params.file_name, - wait_for_write=params.wait_for_write) - d.addCallback(get_output_callback(params)) - d.addCallback(lambda message: self._render_response(message, OK_CODE)) + + if 'timeout' not in p.keys(): + timeout = self.download_timeout + else: + timeout = p['timeout'] + + if 'download_directory' not in p.keys(): + download_directory = self.download_directory + else: + download_directory = p['download_directory'] + + if 'file_name' in p.keys(): + file_name = p['file_name'] + else: + file_name = None + + if 'stream_info' in p.keys(): + stream_info = p['stream_info'] + if 'sources' in stream_info.keys(): + sd_hash = stream_info['sources']['lbry_sd_hash'] + else: + sd_hash = stream_info['stream_hash'] + else: + stream_info = None + + if 'wait_for_write' in p.keys(): + wait_for_write = p['wait_for_write'] + else: + wait_for_write = True + + if 'name' in p.keys(): + name = p['name'] + if p['name'] not in self.waiting_on.keys(): + d = self._download_name(name=name, timeout=timeout, download_directory=download_directory, + stream_info=stream_info, file_name=file_name, wait_for_write=wait_for_write) + d.addCallback(lambda l: {'stream_hash': sd_hash, + 'path': os.path.join(self.download_directory, l.file_name)} + if stream_info else + {'stream_hash': l.sd_hash, + 'path': os.path.join(self.download_directory, l.file_name)}) + d.addCallback(lambda message: self._render_response(message, OK_CODE)) + else: + d = server.failure + else: + d = server.failure + return d def jsonrpc_stop_lbry_file(self, p): @@ -2192,161 +2280,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = threads.deferToThread(subprocess.Popen, ['open', '-R', path]) else: # No easy way to reveal specific files on Linux, so just open the containing directory - d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.path.dirname(path)]) + d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.dirname(path)]) d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d - - -def get_sd_hash(stream_info): - if not stream_info: - return None - try: - return stream_info['sources']['lbry_sd_hash'] - except KeyError: - return stream_info.get('stream_hash') - - -def get_output_callback(params): - def callback(l): - return { - 'stream_hash': params.sd_hash if params.stream_info else l.sd_hash, - 'path': os.path.join(params.download_directory, l.file_name) - } - return callback - - -class _DownloadNameHelper(object): - def __init__(self, daemon, name, timeout=DEFAULT_TIMEOUT, download_directory=None, - file_name=None, wait_for_write=True): - self.daemon = daemon - self.name = name - self.timeout = timeout - if not download_directory or not os.path.isdir(download_directory): - self.download_directory = daemon.download_directory - else: - self.download_directory = download_directory - self.file_name = file_name - self.wait_for_write = wait_for_write - - def _setup_stream(self, stream_info): - stream_hash = get_sd_hash(stream_info) - d = self.daemon._get_lbry_file_by_sd_hash(stream_hash) - d.addCallback(self._add_results_callback(stream_info)) - return d - - def _add_results_callback(self, stream_info): - def add_results(l): - if l: - if os.path.isfile(os.path.join(self.download_directory, l.file_name)): - return defer.succeed((stream_info, l)) - return defer.succeed((stream_info, None)) - return add_results - - def wait_or_get_stream(self, args): - stream_info, lbry_file = args - if lbry_file: - return self._get_stream(stream_info) - else: - return self._wait_on_lbry_file(lbry_file) - - def _get_stream(self, stream_info): - d = self.daemon.add_stream( - self.name, self.timeout, self.download_directory, self.file_name, stream_info) - if self.wait_for_write: - d.addCallback(lambda _: self._wait_for_write()) - d.addCallback(lambda _: self.daemon.streams[self.name].downloader) - return d - - def _wait_for_write(self): - file_name = self.daemon.streams[self.name].downloader.file_name - written_bytes = self.get_written_bytes(file_name) - d = defer.succeed(None) - if not written_bytes: - d.addCallback(lambda _: reactor.callLater(1, self._wait_for_write)) - return d - - def _wait_on_lbry_file(self, f): - written_bytes = self.get_written_bytes(f.file_name) - if not written_bytes: - d = defer.succeed(None) - d.addCallback(lambda _: reactor.callLater(1, self._wait_on_lbry_file, f)) - return d - else: - return defer.succeed(self._disp_file(f)) - - def get_written_bytes(self, file_name): - try: - file_path = os.path.join(self.download_directory, file_name) - if os.path.isfile(file_path): - written_file = file(file_path) - written_file.seek(0, os.SEEK_END) - written_bytes = written_file.tell() - written_file.close() - else: - written_bytes = False - except Exception: - writen_bytes = False - return written_bytes - - def _disp_file(self, f): - file_path = os.path.join(self.download_directory, f.file_name) - log.info("Already downloaded: %s --> %s", f.sd_hash, file_path) - return f - - def _remove_from_wait(self, r): - del self.daemon.waiting_on[self.name] - return r - -class _ResolveNameHelper(object): - def __init__(self, daemon, name, force_refresh): - self.daemon = daemon - self.name = name - self.force_refresh = force_refresh - - def get_deferred(self): - if self.need_fresh_stream(): - log.info("Resolving stream info for lbry://%s", self.name) - d = self.wallet.get_stream_info_for_name(self.name) - d.addCallbacks(self._cache_stream_info, lambda _: defer.fail(UnknownNameError)) - else: - log.info("Returning cached stream info for lbry://%s", self.name) - d = defer.succeed(self.name_data['claim_metadata']) - return d - - @property - def name_data(self): - return self.daemon.name_cache[self.name] - - @property - def wallet(self): - return self.daemon.session.wallet - - def now(self): - return self.daemon._get_long_count_timestamp() - - def _add_txid(self, txid): - self.name_data['txid'] = txid - return defer.succeed(None) - - def _cache_stream_info(self, stream_info): - self.daemon.name_cache[self.name] = { - 'claim_metadata': stream_info, - 'timestamp': self.now() - } - d = self.wallet.get_txid_for_name(self.name) - d.addCallback(self._add_txid) - d.addCallback(lambda _: self.daemon._update_claim_cache()) - d.addCallback(lambda _: self.name_data['claim_metadata']) - return d - - def need_fresh_stream(self): - return self.force_refresh or not self.is_in_cache() or self.is_cached_name_expired() - - def is_in_cache(self): - return self.name in self.daemon.name_cache - - def is_cached_name_expired(self): - time_in_cache = self.now() - self.name_data['timestamp'] - return time_in_cache >= self.daemon.cache_time From 1d31aa9da310fbd9fea4038900786742843fbd6f Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 20:34:41 -0400 Subject: [PATCH 371/462] Bump version: 0.3.8 -> 0.3.9 --- .bumpversion.cfg | 2 +- lbrynet/__init__.py | 2 +- packaging/ubuntu/lbry.desktop | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index b34ee8dd9..cfe8c4436 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.8 +current_version = 0.3.9 commit = True tag = True message = Bump version: {current_version} -> {new_version} diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 49bd262cf..c84517169 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.8" +__version__ = "0.3.9" version = tuple(__version__.split('.')) \ No newline at end of file diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index 1c28d3553..212c67eb9 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.3.8 +Version=0.3.9 Name=LBRY Comment=The world's first user-owned content marketplace Icon=lbry From a22dfb38646fce16157cd5670d400916d7c1e770 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 20:56:56 -0400 Subject: [PATCH 372/462] cleaner logging from https://github.com/lbryio/lbry/pull/94 --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 51 +++++++++++---------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 16 ++++++- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 10 ++-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 2 +- lbrynet/lbrynet_daemon/LBRYPublisher.py | 4 +- 5 files changed, 49 insertions(+), 34 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index aaf504080..b3b234736 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -66,6 +66,11 @@ 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.addHandler(handler) log.setLevel(logging.INFO) @@ -461,7 +466,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _announce(): self.announced_startup = True self.startup_status = STARTUP_STAGES[5] - log.info("[" + str(datetime.now()) + "] Started lbrynet-daemon") + log.info("Started lbrynet-daemon") if len(self.startup_scripts): log.info("Scheduling scripts") reactor.callLater(3, self._run_scripts) @@ -483,7 +488,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: _announce()) return d - log.info("[" + str(datetime.now()) + "] Starting lbrynet-daemon") + log.info("Starting lbrynet-daemon") self.internet_connection_checker.start(3600) self.version_checker.start(3600 * 12) @@ -548,14 +553,14 @@ class LBRYDaemon(jsonrpc.JSONRPC): s = socket.create_connection((host, 80), 2) self.connected_to_internet = True except: - log.info("[" + str(datetime.now()) + "] Internet connection not working") + log.info("Internet connection not working") self.connected_to_internet = False def _check_lbrynet_connection(self): def _log_success(): - log.info("[" + str(datetime.now()) + "] lbrynet connectivity test passed") + log.info("lbrynet connectivity test passed") def _log_failure(): - log.info("[" + str(datetime.now()) + "] lbrynet connectivity test failed") + log.info("lbrynet connectivity test failed") wonderfullife_sh = "6f3af0fa3924be98a54766aa2715d22c6c1509c3f7fa32566df4899a41f3530a9f97b2ecb817fa1dcbf1b30553aefaa7" d = download_sd_blob(self.session, wonderfullife_sh, self.session.base_payment_rate_manager) @@ -573,7 +578,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.git_lbryum_version = version return defer.succeed(None) except: - log.info("[" + str(datetime.now()) + "] Failed to get lbryum version from git") + log.info("Failed to get lbryum version from git") self.git_lbryum_version = None return defer.fail(None) @@ -588,7 +593,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.git_lbrynet_version = vr return defer.succeed(None) except: - log.info("[" + str(datetime.now()) + "] Failed to get lbrynet version from git") + log.info("Failed to get lbrynet version from git") self.git_lbrynet_version = None return defer.fail(None) @@ -1168,15 +1173,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d d.addCallback(lambda _: finish_deletion(lbry_file)) - d.addCallback(lambda _: log.info("[" + str(datetime.now()) + "] Delete lbry file")) + d.addCallback(lambda _: log.info("Delete lbry file")) return d def _get_est_cost(self, name): def _check_est(d, name): if isinstance(d.result, float): - log.info("[" + str(datetime.now()) + "] Cost est for lbry://" + name + ": " + str(d.result) + "LBC") + log.info("Cost est for lbry://" + name + ": " + str(d.result) + "LBC") else: - log.info("[" + str(datetime.now()) + "] Timeout estimating cost for lbry://" + name + ", using key fee") + log.info("Timeout estimating cost for lbry://" + name + ", using key fee") d.cancel() return defer.succeed(None) @@ -1371,7 +1376,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): Returns: true if daemon completed startup, otherwise false """ - log.info("[" + str(datetime.now()) + "] is_running: " + str(self.announced_startup)) + log.info("is_running: " + str(self.announced_startup)) if self.announced_startup: return self._render_response(True, OK_CODE) @@ -1409,7 +1414,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): else: r['message'] = "Catching up with the blockchain" r['progress'] = 0 - log.info("[" + str(datetime.now()) + "] daemon status: " + str(r)) + log.info("daemon status: " + str(r)) return self._render_response(r, OK_CODE) def jsonrpc_is_first_run(self): @@ -1422,7 +1427,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): True if first run, otherwise False """ - log.info("[" + str(datetime.now()) + "] Check if is first run") + log.info("Check if is first run") try: d = self.session.wallet.is_first_run() except: @@ -1442,7 +1447,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): Startup message, such as first run notification """ - log.info("[" + str(datetime.now()) + "] Get startup notice") + log.info("Get startup notice") if self.first_run and not self.session.wallet.wallet_balance: return self._render_response(self.startup_message, OK_CODE) @@ -1482,7 +1487,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'lbryum_update_available': lbryum_version < self.git_lbryum_version } - log.info("[" + str(datetime.now()) + "] Get version info: " + json.dumps(msg)) + log.info("Get version info: " + json.dumps(msg)) return self._render_response(msg, OK_CODE) def jsonrpc_get_settings(self): @@ -1510,7 +1515,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'start_lbrycrdd': bool, """ - log.info("[" + str(datetime.now()) + "] Get daemon settings") + log.info("Get daemon settings") return self._render_response(self.session_settings, OK_CODE) def jsonrpc_set_settings(self, p): @@ -1531,7 +1536,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ def _log_settings_change(): - log.info("[" + str(datetime.now()) + "] Set daemon settings to " + json.dumps(self.session_settings)) + log.info("Set daemon settings to " + json.dumps(self.session_settings)) d = self._update_settings(p) d.addErrback(lambda err: log.info(err.getTraceback())) @@ -1574,7 +1579,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): balance, float """ - log.info("[" + str(datetime.now()) + "] Get balance") + log.info("Get balance") return self._render_response(float(self.session.wallet.wallet_balance), OK_CODE) def jsonrpc_stop(self): @@ -1815,7 +1820,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.DeferredList(ds) def _disp(results): - log.info('[' + str(datetime.now()) + '] Found ' + str(len(results)) + ' search results') + log.info('Found ' + str(len(results)) + ' search results') consolidated_results = [] for r in results: t = {} @@ -1829,7 +1834,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return consolidated_results - log.info('[' + str(datetime.now()) + '] Search nametrie: ' + search) + log.info('Search nametrie: ' + search) d = self.session.wallet.get_nametrie() d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) @@ -1917,7 +1922,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return server.failure def _disp(x): - log.info("[" + str(datetime.now()) + "] Abandoned name claim tx " + str(x)) + log.info("Abandoned name claim tx " + str(x)) return self._render_response(x, OK_CODE) d = defer.Deferred() @@ -2028,7 +2033,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ def _disp(address): - log.info("[" + str(datetime.now()) + "] Got new wallet address: " + address) + log.info("Got new wallet address: " + address) return defer.succeed(address) d = self.session.wallet.get_new_address() @@ -2220,7 +2225,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): exclude_previous = True if 'message' in p.keys(): - log.info("[" + str(datetime.now()) + "] Upload log message: " + str(p['message'])) + log.info("Upload log message: " + str(p['message'])) if 'force' in p.keys(): force = p['force'] diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index 309570873..cdde77c6b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -25,8 +25,13 @@ if not os.path.isdir(log_dir): os.mkdir(log_dir) lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) + +DEFAULT_FORMAT = "%(asctime)s %(levelname)-8s %(name)s:%(lineno)d: %(message)s" +DEFAULT_FORMATTER = logging.Formatter(DEFAULT_FORMAT) + log = logging.getLogger(__name__) handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) +handler.setFormatter(DEFAULT_FORMATTER) log.addHandler(handler) log.setLevel(logging.INFO) @@ -57,6 +62,13 @@ def stop(): d.callback(None) +def configureConsoleLogger(): + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(DEFAULT_FORMATTER) + logging.getLogger().addHandler(handler) + logging.getLogger().setLevel(level=logging.INFO) + + def start(): parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") parser.add_argument("--wallet", @@ -75,7 +87,7 @@ def start(): args = parser.parse_args() if args.logtoconsole: - logging.basicConfig(level=logging.INFO) + configureConsoleLogger() args = parser.parse_args() @@ -121,4 +133,4 @@ def start(): return if __name__ == "__main__": - start() \ No newline at end of file + start() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 74906da0b..507eb025d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -79,7 +79,7 @@ class LBRYFileStreamer(object): def pauseProducing(self): self._paused = True - log.info("[" + str(datetime.now()) + "] Pausing producer") + log.info("Pausing producer") return defer.succeed(None) def resumeProducing(self): @@ -104,7 +104,7 @@ class LBRYFileStreamer(object): self._request.write(data) self._cursor += 1 - log.info("[" + str(datetime.now()) + "] Wrote range %s-%s/%s, length: %s, readable: %s, depth: %s" % + log.info("Wrote range %s-%s/%s, length: %s, readable: %s, depth: %s" % (start_cur, self._cursor, self._file_size, self._cursor - start_cur, readable_bytes, self._depth)) self._sent_bytes = True @@ -117,12 +117,12 @@ class LBRYFileStreamer(object): self._deferred.addCallback(lambda _: threads.deferToThread(reactor.callLater, self._delay, _check_for_new_data)) return defer.succeed(None) - log.info("[" + str(datetime.now()) + "] Resuming producer") + log.info("Resuming producer") self._paused = False self._deferred.addCallback(lambda _: _check_for_new_data()) def stopProducing(self): - log.info("[" + str(datetime.now()) + "] Stopping producer") + log.info("Stopping producer") self._stopped = True # self._fileObject.close() self._deferred.addErrback(lambda err: err.trap(defer.CancelledError)) @@ -147,7 +147,7 @@ class HostedLBRYFile(resource.Resource): # # range_header = request.getAllHeaders()['range'].replace('bytes=', '').split('-') # start, stop = int(range_header[0]), range_header[1] - # log.info("[" + str(datetime.now()) + "] GET range %s-%s" % (start, stop)) + # log.info("GET range %s-%s" % (start, stop)) # path = os.path.join(self._api.download_directory, stream.file_name) # # d = stream.get_total_bytes() diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index c72444c70..9187cc56d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -147,6 +147,6 @@ class GetStream(object): d = _pay_key_fee() self.downloader = downloader self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d.addCallback(lambda _: log.info("[%s] Downloading %s --> %s" % (datetime.now(), self.stream_hash, self.downloader.file_name))) + d.addCallback(lambda _: log.info("Downloading %s --> %s", self.stream_hash, self.downloader.file_name)) d.addCallback(lambda _: self.downloader.start()) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index c0658bfec..bd5c50e4b 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -48,9 +48,7 @@ class Publisher(object): def start(self, name, file_path, bid, metadata, fee=None, sources={}): def _show_result(): - - message = "[%s] Published %s --> lbry://%s txid: %s" % (datetime.now(), self.file_name, self.publish_name, self.txid) - log.info(message) + log.info("Published %s --> lbry://%s txid: %s", self.file_name, self.publish_name, self.txid) return defer.succeed(self.txid) self.publish_name = name From 89892042dd75b433e546d070916655b4c68da8f5 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 20:57:05 -0400 Subject: [PATCH 373/462] remove unused line --- lbrynet/core/LBRYWallet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 4a8a4772f..7f82f6e8f 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -1124,7 +1124,6 @@ class LBRYumWallet(LBRYWallet): log.warning("Doing send many. payments to send: %s", str(payments_to_send)) cmd = known_commands['paytomanyandsend'] func = getattr(self.cmd_runner, cmd.name) - # outputs = [(address, amount) for address, amount in payments_to_send.iteritems()] return threads.deferToThread(func, payments_to_send.iteritems()) def _get_value_for_name(self, name): From 4311b218fb95174c48c3bdad87eea37cc317fcf7 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 22:13:22 -0400 Subject: [PATCH 374/462] fix auto_connect --- lbrynet/core/LBRYWallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 7f82f6e8f..99538cea8 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -924,7 +924,7 @@ class LBRYumWallet(LBRYWallet): network_start_d = defer.Deferred() def setup_network(): - self.config = SimpleConfig() + self.config = SimpleConfig({'auto_connect': True}) self.network = Network(self.config) alert.info("Loading the wallet...") return defer.succeed(self.network.start()) From 4888d7a56732dd757f36348f8b43cbf125b4dfab Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 22:34:16 -0400 Subject: [PATCH 375/462] Bump version: 0.3.9 -> 0.3.10 --- .bumpversion.cfg | 2 +- lbrynet/__init__.py | 2 +- packaging/ubuntu/lbry.desktop | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index cfe8c4436..715cdfe80 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.9 +current_version = 0.3.10 commit = True tag = True message = Bump version: {current_version} -> {new_version} diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index c84517169..969401685 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.9" +__version__ = "0.3.10" version = tuple(__version__.split('.')) \ No newline at end of file diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index 212c67eb9..7adaf1da7 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.3.9 +Version=0.3.10 Name=LBRY Comment=The world's first user-owned content marketplace Icon=lbry From e67f55bd7447cbee730d895b7f35cf53cf69dba5 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 20 Jul 2016 22:40:55 -0400 Subject: [PATCH 376/462] disable debug logging --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 0312bf256..6a1fc7c9b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -54,7 +54,7 @@ class LBRYDaemonRequest(server.Request): memorylimit = 1024*1024*100 # enable/disable debug logging - do_log = True + do_log = False # re-defined only for debug/logging purposes def gotLength(self, length): From ec9159408358c83f9c455845549de9334fde8046 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Thu, 21 Jul 2016 10:41:26 -0500 Subject: [PATCH 377/462] Remove datetime.now() from log statements --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index d8a45a392..d4b9dcb2d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1076,7 +1076,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _disp_file(f): file_path = os.path.join(self.download_directory, f.file_name) - log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.sd_hash) + " --> " + file_path) + log.info("Already downloaded: " + str(f.sd_hash) + " --> " + file_path) return f def _get_stream(stream_info): @@ -1147,18 +1147,18 @@ class LBRYDaemon(jsonrpc.JSONRPC): if not force_refresh: if name in self.name_cache.keys(): if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: - log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) + log.info("Returning cached stream info for lbry://" + name) d = defer.succeed(self.name_cache[name]['claim_metadata']) else: - log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) + log.info("Refreshing stream info for lbry://" + name) d = self.session.wallet.get_stream_info_for_name(name) d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) else: - log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) + log.info("Resolving stream info for lbry://" + name) d = self.session.wallet.get_stream_info_for_name(name) d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) else: - log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) + log.info("Resolving stream info for lbry://" + name) d = self.session.wallet.get_stream_info_for_name(name) d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) From 25d1b8b9e5a35ba7f9b3a2924a93544fbfa041fb Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 21 Jul 2016 16:11:14 -0400 Subject: [PATCH 378/462] move towards better fees and metadata --- lbrynet/conf.py | 9 +++-- lbrynet/core/LBRYFee.py | 39 +++++++++++++++++++ lbrynet/core/LBRYWallet.py | 7 ---- lbrynet/lbrynet_daemon/LBRYDaemon.py | 23 +++++++++-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 10 ++--- .../daemon_scripts/Autofetcher.py | 22 +++++++++++ 6 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 lbrynet/core/LBRYFee.py diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 9ead15f4d..59150a085 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -42,11 +42,14 @@ DEFAULT_WALLET = "lbryum" WALLET_TYPES = ["lbryum", "lbrycrd"] DEFAULT_TIMEOUT = 30 DEFAULT_MAX_SEARCH_RESULTS = 25 -DEFAULT_MAX_KEY_FEE = 100.0 +DEFAULT_MAX_KEY_FEE = {'BTC': {'amount': 0.025}} DEFAULT_SEARCH_TIMEOUT = 3.0 DEFAULT_CACHE_TIME = 3600 DEFAULT_UI_BRANCH = "master" SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih'] -BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type'] -OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey'] \ No newline at end of file + +CURRENCIES = [ + {'BTC': {'type': 'crypto'}}, + {'LBC': {'type': 'crypto'}}, +] \ No newline at end of file diff --git a/lbrynet/core/LBRYFee.py b/lbrynet/core/LBRYFee.py new file mode 100644 index 000000000..09b7dbd39 --- /dev/null +++ b/lbrynet/core/LBRYFee.py @@ -0,0 +1,39 @@ +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/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 0bf99f7a5..0e9e3a360 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -349,13 +349,6 @@ class LBRYWallet(object): value['sources'][k] = sources[k] if value['sources'] == {}: return defer.fail("No source given") - for k in BASE_METADATA_FIELDS: - if k not in metadata: - return defer.fail("Missing required field '%s'" % k) - value[k] = metadata[k] - for k in metadata: - if k not in BASE_METADATA_FIELDS: - value[k] = metadata[k] if fee is not None: if "LBC" in fee: value['fee'] = {'LBC': {'amount': fee['LBC']['amount'], 'address': fee['LBC']['address']}} diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 8e2c98cc5..75aaa0096 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -32,6 +32,8 @@ from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerF from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory from lbrynet.core.Error import UnknownNameError, InsufficientFundsError +from lbrynet.core.LBRYFee import LBRYFee +from lbrynet.core.LBRYMetadata import Metadata from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier @@ -41,8 +43,7 @@ from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ - DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME, \ - BASE_METADATA_FIELDS, OPTIONAL_METADATA_FIELDS, SOURCE_TYPES + DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME, SOURCE_TYPES from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -169,6 +170,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.streams = {} self.known_dht_nodes = KNOWN_DHT_NODES self.first_run_after_update = False + self.last_traded_rate = None if os.name == "nt": from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle @@ -249,6 +251,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.session_settings['last_version'] = self.default_settings['last_version'] self.first_run_after_update = True log.info("First run after update") + log.info("lbrynet %s --> %s" % (self.session_settings['last_version']['lbrynet'], self.default_settings['last_version']['lbrynet'])) + log.info("lbryum %s --> %s" % (self.session_settings['last_version']['lbryum'], self.default_settings['last_version']['lbryum'])) f = open(self.daemon_conf, "w") f.write(json.dumps(self.session_settings)) @@ -337,6 +341,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.internet_connection_checker = LoopingCall(self._check_network_connection) self.version_checker = LoopingCall(self._check_remote_versions) self.connection_problem_checker = LoopingCall(self._check_connection_problems) + self.price_checker = LoopingCall(self._update_exchange) # self.lbrynet_connection_checker = LoopingCall(self._check_lbrynet_connection) self.sd_identifier = StreamDescriptorIdentifier() @@ -470,6 +475,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.internet_connection_checker.start(3600) self.version_checker.start(3600 * 12) self.connection_problem_checker.start(1) + self.price_checker.start(600) if host_ui: self.lbry_ui_manager.update_checker.start(1800, now=False) @@ -590,6 +596,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): if not self.connected_to_internet: self.connection_problem = CONNECTION_PROBLEM_CODES[1] + def _update_exchange(self): + try: + r = requests.get("https://bittrex.com/api/v1.1/public/getticker", {'market': 'BTC-LBC'}) + self.last_traded_rate = float(json.loads(r.text)['result']['Last']) + except: + self.last_traded_rate = None + def _start_server(self): if self.peer_port is not None: @@ -701,6 +714,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connection_problem_checker.stop() if self.lbry_ui_manager.update_checker.running: self.lbry_ui_manager.update_checker.stop() + if self.price_checker.running: + self.price_checker.stop() d = self._upload_log(log_type="close", exclude_previous=False if self.first_run else True) d.addCallback(lambda _: self._stop_server()) @@ -1868,9 +1883,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): name = p['name'] bid = p['bid'] file_path = p['file_path'] - metadata = p['metadata'] + metadata = Metadata(p['metadata']) if 'fee' in p: - fee = p['fee'] + fee = LBRYFee.from_dict(p['fee']) else: fee = None diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index c72444c70..9cfbf07c1 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -11,6 +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.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME @@ -94,14 +95,9 @@ class GetStream(object): if 'description' in self.stream_info: self.description = self.stream_info['description'] if 'fee' in self.stream_info: - if 'LBC' in self.stream_info['fee']: - self.key_fee = float(self.stream_info['fee']['LBC']['amount']) - self.key_fee_address = self.stream_info['fee']['LBC']['address'] - else: - self.key_fee_address = None + self.fee = LBRYFee.from_dict(stream_info['fee']) else: - self.key_fee = None - self.key_fee_address = None + 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) diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py index cd7ed02cb..92ea9f9f1 100644 --- a/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py +++ b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py @@ -2,10 +2,13 @@ import json import logging.handlers import sys import os +import requests +from datetime import datetime from appdirs import user_data_dir from twisted.internet.task import LoopingCall from twisted.internet import reactor +from twisted.internet.threads import deferToThread if sys.platform != "darwin": @@ -39,11 +42,15 @@ class Autofetcher(object): def __init__(self, api): self._api = api self._checker = LoopingCall(self._check_for_new_claims) + self._price_checker = LoopingCall(self._update_price) self.best_block = None + self.last_price = None + self.price_updated = None def start(self): reactor.addSystemEventTrigger('before', 'shutdown', self.stop) self._checker.start(5) + self._price_checker.start(30) def stop(self): log.info("Stopping autofetcher") @@ -59,9 +66,24 @@ class Autofetcher(object): c = self._api.get_claims_for_tx({'txid': t}) if len(c): for i in c: + if 'fee' in json.loads(i['value']): + log.info("Downloading stream for claim txid: %s" % t) self._api.get({'name': t, 'stream_info': json.loads(i['value'])}) + def _update_price(self): + def _check_bittrex(): + try: + r = requests.get("https://bittrex.com/api/v1.1/public/getticker", {'market': 'BTC-LBC'}) + self.last_price = json.loads(r.text)['result']['Last'] + self.price_updated = datetime.now() + log.info("Updated exchange rate, last BTC-LBC trade: %f" % self.last_price) + except: + log.info("Failed to update exchange rate") + self.last_price = None + self.price_updated = datetime.now() + return deferToThread(_check_bittrex) + def run(api): fetcher = Autofetcher(api) From b65402db413fbe9dbc4560091c75245e9fff8a58 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 21 Jul 2016 16:47:47 -0400 Subject: [PATCH 379/462] debug line --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 6a1fc7c9b..3fad1d126 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -68,7 +68,7 @@ class LBRYDaemonRequest(server.Request): from twisted.web.http import parse_qs if self.do_log: print '%f Request Received' % time.time() - print self.content + print self.content self.content.seek(0,0) self.args = {} From 05138e1db1809603134a50d5eb9bf5f5f6d12066 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Fri, 22 Jul 2016 10:58:37 -0500 Subject: [PATCH 380/462] web-ui files are on S3 now --- lbrynet/lbrynet_daemon/LBRYUIManager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index 0f0aa2a53..ca43473fc 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -98,8 +98,8 @@ class LBRYUIManager(object): return d else: log.info("Checking for updates for UI branch: " + branch) - self._git_url = "https://api.github.com/repos/lbryio/lbry-web-ui/git/refs/heads/%s" % branch - self._dist_url = "https://raw.githubusercontent.com/lbryio/lbry-web-ui/%s/dist.zip" % branch + self._git_url = "https://s3.amazonaws.com/lbry-ui/{}/data.json".format(branch) + self._dist_url = "https://s3.amazonaws.com/lbry-ui/{}/dist.zip".format(branch) d = self._up_to_date() d.addCallback(lambda r: self._download_ui() if not r else self._load_ui()) @@ -109,7 +109,7 @@ class LBRYUIManager(object): def _get_git_info(): response = urlopen(self._git_url) data = json.loads(response.read()) - return defer.succeed(data['object']['sha']) + return defer.succeed(data['sha']) def _set_git(version): self.git_version = version.replace('\n', '') @@ -239,4 +239,4 @@ class LBRYUIManager(object): def _load_ui(self): for d in [i[0] for i in os.walk(self.active_dir) if os.path.dirname(i[0]) == self.active_dir]: self.root.putChild(os.path.basename(d), static.File(d)) - return defer.succeed(True) \ No newline at end of file + return defer.succeed(True) From 6bfb33adb275e387ca18916dec56cae3fefdd1eb Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 24 Jul 2016 22:03:32 -0400 Subject: [PATCH 381/462] fix version comparison --- lbrynet/core/utils.py | 24 +++++++++++- lbrynet/lbrynet_daemon/LBRYDaemon.py | 55 +++++++++++++--------------- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/lbrynet/core/utils.py b/lbrynet/core/utils.py index 89b57fb0c..34efb4eb5 100644 --- a/lbrynet/core/utils.py +++ b/lbrynet/core/utils.py @@ -25,4 +25,26 @@ def is_valid_blobhash(blobhash): for l in blobhash: if l not in "0123456789abcdef": return False - return True \ No newline at end of file + return True + + +def version_is_greater_than(version1, version2): + """ + handles differing numbers of subversions, ie 0.3.10 > 0.3.9.9 + """ + + v1, v2 = version1.split("."), version2.split(".") + r = True + if len(v2) > len(v1): + for j in range(len(v2) - len(v1)): + v1.append("0") + elif len(v2) < len(v1): + for j in range(len(v1) - len(v2)): + v2.append("0") + for c in range(len(v1)): + if int(v2[c]) > int(v1[c]): + r = False + break + elif c == len(v1) - 1 and int(v1[c]) == int(v2[c]): + r = False + return r \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 09f8f0db4..38c6b861a 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -5,6 +5,8 @@ import os import subprocess import sys import random + +import pkg_resources import simplejson as json import binascii import logging.handlers @@ -32,7 +34,6 @@ from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerF from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory from lbrynet.core.Error import UnknownNameError, InsufficientFundsError -from lbrynet.core.LBRYFee import LBRYFee from lbrynet.core.LBRYMetadata import Metadata from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory @@ -40,7 +41,7 @@ from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifi from lbrynet.lbrynet_daemon.LBRYUIManager import LBRYUIManager from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher -from lbrynet.core.utils import generate_id +from lbrynet.core.utils import generate_id, version_is_greater_than from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME, SOURCE_TYPES @@ -161,8 +162,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connected_to_internet = True self.connection_problem = None self.query_handlers = {} - self.git_lbrynet_version = None - self.git_lbryum_version = None + self.pip_lbrynet_version = None + self.pip_lbryum_version = None self.ui_version = None self.ip = None # TODO: this is confusing to set here, and then to be reset below. @@ -576,39 +577,33 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _check_remote_versions(self): def _get_lbryum_version(): try: - r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') - version = next(line.split("=")[1].split("#")[0].replace(" ", "") - for line in r if "LBRYUM_VERSION" in line) - version = version.replace("'", "") - log.info("remote lbryum " + str(version) + " > local lbryum " + str(lbryum_version) + " = " + str( - version > lbryum_version)) - self.git_lbryum_version = version + r = pkg_resources.get_distribution("lbryum").version + log.info("Local lbryum: %s" % lbryum_version) + log.info("Available lbryum: %s" % r) + self.pip_lbryum_version = r return defer.succeed(None) except: log.info("Failed to get lbryum version from git") - self.git_lbryum_version = None + self.pip_lbryum_version = None return defer.fail(None) def _get_lbrynet_version(): try: - r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') - vs = next(i for i in r if '__version__ =' in i).split("=")[1].replace(" ", "") - vt = tuple(int(x) for x in vs[1:-1].split('.')) - vr = ".".join([str(x) for x in vt]) - log.info("remote lbrynet " + str(vr) + " > local lbrynet " + str(lbrynet_version) + " = " + str( - vr > lbrynet_version)) - self.git_lbrynet_version = vr + r = pkg_resources.get_distribution("lbrynet").version + log.info("Local lbrynet: %s" % lbrynet_version) + log.info("Available lbrynet: %s" % r) + self.pip_lbrynet_version = r return defer.succeed(None) except: log.info("Failed to get lbrynet version from git") - self.git_lbrynet_version = None + self.pip_lbrynet_version = None return defer.fail(None) d = _get_lbrynet_version() d.addCallback(lambda _: _get_lbryum_version()) def _check_connection_problems(self): - if not self.git_lbrynet_version or not self.git_lbryum_version: + if not self.pip_lbrynet_version or not self.pip_lbryum_version: self.connection_problem = CONNECTION_PROBLEM_CODES[0] elif self.startup_status[0] == 'loading_wallet': @@ -1091,7 +1086,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _disp_file(f): file_path = os.path.join(self.download_directory, f.file_name) - log.info("[" + str(datetime.now()) + "] Already downloaded: " + str(f.sd_hash) + " --> " + file_path) + log.info("Already downloaded: " + str(f.sd_hash) + " --> " + file_path) return f def _get_stream(stream_info): @@ -1162,18 +1157,18 @@ class LBRYDaemon(jsonrpc.JSONRPC): if not force_refresh: if name in self.name_cache.keys(): if (self._get_long_count_timestamp() - self.name_cache[name]['timestamp']) < self.cache_time: - log.info("[" + str(datetime.now()) + "] Returning cached stream info for lbry://" + name) + log.info("Returning cached stream info for lbry://" + name) d = defer.succeed(self.name_cache[name]['claim_metadata']) else: - log.info("[" + str(datetime.now()) + "] Refreshing stream info for lbry://" + name) + log.info("Refreshing stream info for lbry://" + name) d = self.session.wallet.get_stream_info_for_name(name) d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) else: - log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) + log.info("Resolving stream info for lbry://" + name) d = self.session.wallet.get_stream_info_for_name(name) d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) else: - log.info("[" + str(datetime.now()) + "] Resolving stream info for lbry://" + name) + log.info("Resolving stream info for lbry://" + name) d = self.session.wallet.get_stream_info_for_name(name) d.addCallbacks(_cache_stream_info, lambda _: defer.fail(UnknownNameError)) @@ -1506,10 +1501,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'lbrynet_version': lbrynet_version, 'lbryum_version': lbryum_version, 'ui_version': self.ui_version, - 'remote_lbrynet': self.git_lbrynet_version, - 'remote_lbryum': self.git_lbryum_version, - 'lbrynet_update_available': lbrynet_version < self.git_lbrynet_version, - 'lbryum_update_available': lbryum_version < self.git_lbryum_version + 'remote_lbrynet': self.pip_lbrynet_version, + 'remote_lbryum': self.pip_lbryum_version, + 'lbrynet_update_available': version_is_greater_than(self.pip_lbrynet_version, lbrynet_version), + 'lbryum_update_available': version_is_greater_than(self.pip_lbryum_version, lbryum_version), } log.info("Get version info: " + json.dumps(msg)) From 2e2d309d38f29ed2aa11cc51a247c6b954dd2039 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 01:40:26 -0400 Subject: [PATCH 382/462] BTC/USD denominated fees, metadata revisions --- lbrynet/conf.py | 2 + lbrynet/core/LBRYFee.py | 39 --------------- lbrynet/core/LBRYMetadata.py | 64 ++++++++++++++++++++++-- lbrynet/core/LBRYWallet.py | 34 ++++--------- lbrynet/lbrynet_daemon/LBRYDaemon.py | 26 ++++++---- lbrynet/lbrynet_daemon/LBRYDownloader.py | 35 +++++-------- lbrynet/lbrynet_daemon/LBRYPublisher.py | 26 +++------- 7 files changed, 112 insertions(+), 114 deletions(-) delete mode 100644 lbrynet/core/LBRYFee.py 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 From ed393eb75c529e033c6bc66707db2cc869a08a6f Mon Sep 17 00:00:00 2001 From: Job Evers Date: Wed, 20 Jul 2016 10:44:06 -0500 Subject: [PATCH 383/462] Updates the deployment process and the version check associated with it This commit supports steps 1 and 2 in the new workflow: 1. Change the logic in the daemon to check the github api for the latest release that is not a pre release 2. Change travis to mark all releases as pre release 3. When we are ready to stage a release we push a tag to master. Travis builds the packages and releases them 4. We manually check them 5. Remove the pre release mark when we are happy --- .travis.yml | 2 + lbrynet/lbrynet_daemon/LBRYDaemon.py | 39 +++++++++++++++---- tests/lbrynet/lbrynet_daemon/__init__.py | 0 .../lbrynet/lbrynet_daemon/test_LBRYDaemon.py | 38 ++++++++++++++++++ 4 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 tests/lbrynet/lbrynet_daemon/__init__.py create mode 100644 tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py diff --git a/.travis.yml b/.travis.yml index ca2557860..2ccba1e61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,6 +45,7 @@ deploy: - provider: releases file: "${TRAVIS_BUILD_DIR}/`python setup.py --name`_`python setup.py -V`_amd64.deb" skip_cleanup: true + prerelease: true on: tags: true condition: "$TRAVIS_OS_NAME = linux" @@ -54,6 +55,7 @@ deploy: - provider: releases file: "${TRAVIS_BUILD_DIR}/packaging/osx/lbry-osx-app/`python setup.py --name`.`python setup.py -V`.dmg" skip_cleanup: true + prerelease: true on: tags: true condition: "$TRAVIS_OS_NAME = osx" diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index d8a45a392..8811211aa 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -2,6 +2,7 @@ import string import locale import mimetypes import os +import re import subprocess import sys import random @@ -585,13 +586,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _get_lbrynet_version(): try: - r = urlopen("https://raw.githubusercontent.com/lbryio/lbry/master/lbrynet/__init__.py").read().split('\n') - vs = next(i for i in r if '__version__ =' in i).split("=")[1].replace(" ", "") - vt = tuple(int(x) for x in vs[1:-1].split('.')) - vr = ".".join([str(x) for x in vt]) - log.info("remote lbrynet " + str(vr) + " > local lbrynet " + str(lbrynet_version) + " = " + str( - vr > lbrynet_version)) - self.git_lbrynet_version = vr + version = get_lbrynet_version_from_github() + log.info( + "remote lbrynet %s > local lbrynet %s = %s", + version, lbrynet_version, compare_versions(version, lbrynet_version) + ) + self.git_lbrynet_version = version return defer.succeed(None) except: log.info("Failed to get lbrynet version from git") @@ -2284,7 +2284,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): Returns: True, opens file browser """ - path = p['path'] if sys.platform == "darwin": d = threads.deferToThread(subprocess.Popen, ['open', '-R', path]) @@ -2295,3 +2294,27 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d + + +def get_lbrynet_version_from_github(): + """Return the latest released version from github.""" + response = requests.get('https://api.github.com/repos/lbryio/lbry/releases/latest') + release = response.json() + tag = release['tag_name'] + # githubs documentation claims this should never happen, but we'll check just in case + if release['prerelease']: + raise Exception('Release {} is a pre-release'.format(tag)) + return get_version_from_tag(tag) + + +def get_version_from_tag(tag): + match = re.match('v([\d.]+)', tag) + if match: + return match.group(1) + else: + raise Exception('Failed to parse version from tag {}'.format(tag)) + + +def compare_versions(a, b): + """Returns True if version a is more recent than version b""" + return a > b diff --git a/tests/lbrynet/lbrynet_daemon/__init__.py b/tests/lbrynet/lbrynet_daemon/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py b/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py new file mode 100644 index 000000000..104e06c01 --- /dev/null +++ b/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py @@ -0,0 +1,38 @@ +import mock +import requests +from twisted.trial import unittest + +from lbrynet.lbrynet_daemon import LBRYDaemon + + +class MiscTests(unittest.TestCase): + def test_get_lbrynet_version_from_github(self): + response = mock.create_autospec(requests.Response) + # don't need to mock out the entire response from the api + # but at least need 'tag_name' + response.json.return_value = { + "url": "https://api.github.com/repos/lbryio/lbry/releases/3685199", + "assets_url": "https://api.github.com/repos/lbryio/lbry/releases/3685199/assets", + "html_url": "https://github.com/lbryio/lbry/releases/tag/v0.3.8", + "id": 3685199, + "tag_name": "v0.3.8", + "prerelease": False + } + with mock.patch('lbrynet.lbrynet_daemon.LBRYDaemon.requests') as req: + req.get.return_value = response + self.assertEqual('0.3.8', LBRYDaemon.get_lbrynet_version_from_github()) + + def test_error_is_thrown_if_prerelease(self): + response = mock.create_autospec(requests.Response) + response.json.return_value = { + "tag_name": "v0.3.8", + "prerelease": True + } + with mock.patch('lbrynet.lbrynet_daemon.LBRYDaemon.requests') as req: + req.get.return_value = response + with self.assertRaises(Exception): + LBRYDaemon.get_lbrynet_version_from_github() + + def test_error_is_thrown_when_version_cant_be_parsed(self): + with self.assertRaises(Exception): + LBRYDaemon.get_version_from_tag('garbage') From ea5d31b530d48c34a7e674560e428cce477d81f2 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 25 Jul 2016 12:19:19 -0500 Subject: [PATCH 384/462] OCD: alphabetize and group imports I'm not the only one: https://google.github.io/styleguide/pyguide.html?showone=Imports_formatting#Imports_formatting --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 29 +++++++++++++--------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 8811211aa..fceacec15 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,19 +1,23 @@ -import string +import binascii +from datetime import datetime +from decimal import Decimal import locale +import logging.handlers import mimetypes import os +import platform +import random import re +import socket +import string import subprocess import sys -import random -import simplejson as json -import binascii -import logging.handlers -import requests -import base58 -import platform -import socket +from urllib2 import urlopen +from appdirs import user_data_dir +import base58 +import requests +import simplejson as json from twisted.web import server from twisted.internet import defer, threads, error, reactor from twisted.internet.task import LoopingCall @@ -21,11 +25,6 @@ from txjsonrpc import jsonrpclib from txjsonrpc.web import jsonrpc from txjsonrpc.web.jsonrpc import Handler -from datetime import datetime -from decimal import Decimal -from appdirs import user_data_dir -from urllib2 import urlopen - from lbrynet import __version__ as lbrynet_version from lbryum.version import LBRYUM_VERSION as lbryum_version from lbrynet.core.PaymentRateManager import PaymentRateManager @@ -53,8 +52,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") From d0bc383a79fd77f1f4a402dde0a2566329b09716 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 25 Jul 2016 12:25:10 -0500 Subject: [PATCH 385/462] bug fix: version comparison is not lexographic. Use distutils.version to compare version strings. --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 3 ++- tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index fceacec15..bf5cfd317 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1,6 +1,7 @@ import binascii from datetime import datetime from decimal import Decimal +import distutils.version import locale import logging.handlers import mimetypes @@ -2314,4 +2315,4 @@ def get_version_from_tag(tag): def compare_versions(a, b): """Returns True if version a is more recent than version b""" - return a > b + return distutils.version.StrictVersion(a) > distutils.version.StrictVersion(b) diff --git a/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py b/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py index 104e06c01..a0d1c346f 100644 --- a/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py +++ b/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py @@ -36,3 +36,6 @@ class MiscTests(unittest.TestCase): def test_error_is_thrown_when_version_cant_be_parsed(self): with self.assertRaises(Exception): LBRYDaemon.get_version_from_tag('garbage') + + def test_compare_versions_isnot_lexographic(self): + self.assertTrue(LBRYDaemon.compare_versions('0.3.10', '0.3.6')) From 1eae3fe5dc5e330e2751db24d3df13e09163c1ce Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 25 Jul 2016 14:53:07 -0500 Subject: [PATCH 386/462] pylint doesn't like distutils --- packaging/travis/install_dependencies_and_run_tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packaging/travis/install_dependencies_and_run_tests.sh b/packaging/travis/install_dependencies_and_run_tests.sh index 6014087e1..422e74229 100755 --- a/packaging/travis/install_dependencies_and_run_tests.sh +++ b/packaging/travis/install_dependencies_and_run_tests.sh @@ -42,5 +42,6 @@ pip install mock pylint trial tests # TODO: submit coverage report to coveralls +# Ignoring distutils because: https://github.com/PyCQA/pylint/issues/73 # TODO: as code quality improves, make pylint be more strict -pylint -E --disable=inherit-non-class --disable=no-member lbrynet +pylint -E --disable=inherit-non-class --disable=no-member --ignored-modules=distutils lbrynet From 75ef652cb300ab7ad3a1bf6f638fd8b2cfc91e5d Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 17:08:56 -0400 Subject: [PATCH 387/462] add googlefinance to setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 60f800df2..847b69ee1 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ console_scripts = ['lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUp requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure', 'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0', - 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58'] + 'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58', 'googlefinance'] setup(name='lbrynet', description='A decentralized media library and marketplace', From 3814912adf264cd714d07ca3083bb450a8b88550 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 25 Jul 2016 16:09:13 -0500 Subject: [PATCH 388/462] Move to core.utils and expand to check lbryum too --- lbrynet/core/utils.py | 15 +++++++++++-- lbrynet/lbrynet_daemon/LBRYDaemon.py | 22 ++++++++++--------- .../lbrynet/lbrynet_daemon/test_LBRYDaemon.py | 3 --- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/lbrynet/core/utils.py b/lbrynet/core/utils.py index 89b57fb0c..34ea99ba3 100644 --- a/lbrynet/core/utils.py +++ b/lbrynet/core/utils.py @@ -1,6 +1,9 @@ -from lbrynet.core.cryptoutils import get_lbry_hash_obj +import distutils.version import random +from lbrynet.core.cryptoutils import get_lbry_hash_obj + + blobhash_length = get_lbry_hash_obj().digest_size * 2 # digest_size is in bytes, and blob hashes are hex encoded @@ -25,4 +28,12 @@ def is_valid_blobhash(blobhash): for l in blobhash: if l not in "0123456789abcdef": return False - return True \ No newline at end of file + return True + + +def version_is_greater_than(a, b): + """Returns True if version a is more recent than version b""" + try: + return distutils.version.StrictVersion(a) > distutils.version.StrictVersion(b) + except ValueError: + return distutils.version.LooseVersion(a) > distutils.version.LooseVersion(b) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index bf5cfd317..b5d3c81d7 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -39,6 +39,7 @@ from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifi from lbrynet.lbrynet_daemon.LBRYUIManager import LBRYUIManager from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher +from lbrynet.core import utils 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ @@ -573,8 +574,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): version = next(line.split("=")[1].split("#")[0].replace(" ", "") for line in r if "LBRYUM_VERSION" in line) version = version.replace("'", "") - log.info("remote lbryum " + str(version) + " > local lbryum " + str(lbryum_version) + " = " + str( - version > lbryum_version)) + log.info( + "remote lbryum %s > local lbryum %s = %s", + version, lbryum_version, + utils.version_is_greater_than(version, lbryum_version) + ) self.git_lbryum_version = version return defer.succeed(None) except: @@ -587,7 +591,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): version = get_lbrynet_version_from_github() log.info( "remote lbrynet %s > local lbrynet %s = %s", - version, lbrynet_version, compare_versions(version, lbrynet_version) + version, lbrynet_version, + utils.version_is_greater_than(version, lbrynet_version) ) self.git_lbrynet_version = version return defer.succeed(None) @@ -1491,8 +1496,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'ui_version': self.ui_version, 'remote_lbrynet': self.git_lbrynet_version, 'remote_lbryum': self.git_lbryum_version, - 'lbrynet_update_available': lbrynet_version < self.git_lbrynet_version, - 'lbryum_update_available': lbryum_version < self.git_lbryum_version + 'lbrynet_update_available': utils.version_is_greater_than( + self.git_lbrynet_version, lbrynet_version), + 'lbryum_update_available': utils.version_is_greater_than( + self.git_lbryum_version, lbryum_version) } log.info("Get version info: " + json.dumps(msg)) @@ -2311,8 +2318,3 @@ def get_version_from_tag(tag): return match.group(1) else: raise Exception('Failed to parse version from tag {}'.format(tag)) - - -def compare_versions(a, b): - """Returns True if version a is more recent than version b""" - return distutils.version.StrictVersion(a) > distutils.version.StrictVersion(b) diff --git a/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py b/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py index a0d1c346f..104e06c01 100644 --- a/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py +++ b/tests/lbrynet/lbrynet_daemon/test_LBRYDaemon.py @@ -36,6 +36,3 @@ class MiscTests(unittest.TestCase): def test_error_is_thrown_when_version_cant_be_parsed(self): with self.assertRaises(Exception): LBRYDaemon.get_version_from_tag('garbage') - - def test_compare_versions_isnot_lexographic(self): - self.assertTrue(LBRYDaemon.compare_versions('0.3.10', '0.3.6')) From c4a78a149bd3b3f1f021e5d32c6766ce89424a41 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 21:45:42 -0400 Subject: [PATCH 389/462] LBRYFee things -move exchange rate updates to wallet, do them every half hour -add convention checker for fees -pay BTC or USD denominated key fees in converted LBC amount --- lbrynet/core/LBRYMetadata.py | 119 +++++++++++++++-------- lbrynet/core/LBRYWallet.py | 90 +++++++++++++---- lbrynet/lbrynet_daemon/LBRYDownloader.py | 15 +-- 3 files changed, 159 insertions(+), 65 deletions(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index efbb334de..4090a2f2a 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -1,63 +1,105 @@ import requests import json +import time from googlefinance import getQuotes from lbrynet.conf import CURRENCIES +import logging + +log = logging.getLogger(__name__) + +BITTREX_FEE = 0.0025 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', 'ver'], 'optional': ['licence_url']} - CURRENT_METADATA_VERSION = '0.0.2' + +#v0.0.1 fee +FEE_REVISIONS = {'0.0.1': {'required': ['amount', 'address'], 'optional': []}} +CURRENT_FEE_REVISION = '0.0.1' + + 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 __init__(self, fee_dict, rate_dict): + fee = LBRYFeeFormat(fee_dict) - def __call__(self): - return {self.currency_symbol: {'amount': self.amount, 'address': self.address}} + for currency in fee: + self.address = fee[currency]['address'] + if not isinstance(fee[currency]['amount'], float): + self.amount = float(fee[currency]['amount']) + else: + self.amount = fee[currency]['amount'] + self.currency_symbol = currency - def convert(self, amount_only=False): + assert 'BTCLBC' in rate_dict and 'USDBTC' in rate_dict + for fx in rate_dict: + assert int(time.time()) - int(rate_dict[fx]['ts']) < 3600, "%s quote is out of date" % fx + self._USDBTC = {'spot': rate_dict['USDBTC']['spot'], 'ts': rate_dict['USDBTC']['ts']} + self._BTCLBC = {'spot': rate_dict['BTCLBC']['spot'], 'ts': rate_dict['BTCLBC']['ts']} + + def to_lbc(self): + r = None 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) + 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) + r = round(float(self._btc_to_lbc(self._usd_to_btc(self.amount))), 5) + assert r is not None 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 to_usd(self): + r = None + if self.currency_symbol == "USD": + r = round(float(self.amount), 5) + elif self.currency_symbol == "BTC": + r = round(float(self._btc_to_usd(self.amount)), 5) + elif self.currency_symbol == "LBC": + r = round(float(self._btc_to_usd(self._lbc_to_btc(self.amount))), 5) + assert r is not None + return r + + def _usd_to_btc(self, usd): + return self._USDBTC['spot'] * float(usd) + + def _btc_to_usd(self, btc): + return float(btc) / self._USDBTC['spot'] + + def _btc_to_lbc(self, btc): + return float(btc) / self._BTCLBC['spot'] / (1.0 - BITTREX_FEE) + + def _lbc_to_btc(self, lbc): + return self._BTCLBC['spot'] * float(lbc) -def fee_from_dict(fee_dict): - s = fee_dict.keys()[0] - return LBRYFee(s, fee_dict[s]['amount'], fee_dict[s]['address']) +class LBRYFeeFormat(dict): + def __init__(self, fee_dict): + dict.__init__(self) + self.fee_version = None + f = fee_dict.copy() + assert len(fee_dict) == 1 + for currency in fee_dict: + assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency) + self.update({currency: {}}) + + for version in FEE_REVISIONS: + for k in FEE_REVISIONS[version]['required']: + assert k in fee_dict, "Missing required fee field: %s" % k + self[currency].update({k: f.pop(k)}) + for k in FEE_REVISIONS[version]['optional']: + if k in fee_dict: + self[currency].update({k: f.pop(k)}) + if not len(f): + self.fee_version = version + break + assert f == {}, "Unknown fee keys: %s" % json.dumps(f.keys()) class Metadata(dict): @@ -66,9 +108,6 @@ class Metadata(dict): 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" @@ -78,9 +117,13 @@ class Metadata(dict): assert k in metadata, "Missing required metadata field: %s" % k self.update({k: m.pop(k)}) for k in METADATA_REVISIONS[version]['optional']: - if k in metadata: + if k == 'fee': + pass + elif k in metadata: self.update({k: m.pop(k)}) - if not len(m): + if not len(m) or m.keys() == ['fee']: self.metaversion = version break + if 'fee' in m: + self.update({'fee': LBRYFeeFormat(m.pop('fee'))}) assert m == {}, "Unknown metadata keys: %s" % json.dumps(m.keys()) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 278396186..e09bd020d 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -1,17 +1,12 @@ import sys -from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHandler, ILBRYWallet -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 SOURCE_TYPES -from lbrynet.core.LBRYMetadata import Metadata - -from lbryum import SimpleConfig, Network -from lbryum.lbrycrd import COIN, TYPE_ADDRESS -from lbryum.wallet import WalletStorage, Wallet -from lbryum.commands import known_commands, Commands -from lbryum.transaction import Transaction +import datetime +import logging +import json +import subprocess +import socket +import time +import os +import requests from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException from twisted.internet import threads, reactor, defer, task @@ -20,15 +15,24 @@ from twisted.enterprise import adbapi from collections import defaultdict, deque from zope.interface import implements from decimal import Decimal -import datetime -import logging -import json -import subprocess -import socket -import time -import os +from googlefinance import getQuotes + +from lbryum import SimpleConfig, Network +from lbryum.lbrycrd import COIN, TYPE_ADDRESS +from lbryum.wallet import WalletStorage, Wallet +from lbryum.commands import known_commands, Commands +from lbryum.transaction import Transaction + +from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHandler, ILBRYWallet +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 SOURCE_TYPES +from lbrynet.core.LBRYMetadata import Metadata log = logging.getLogger(__name__) +log.setLevel(logging.INFO) alert = logging.getLogger("lbryalert." + __name__) @@ -80,6 +84,50 @@ class LBRYWallet(object): self._batch_count = 20 self._first_run = self._FIRST_RUN_UNKNOWN + self._USDBTC = None + self._BTCLBC = None + self._exchange_rate_updater = task.LoopingCall(self._update_exchange_rates) + + def _usd_to_btc(self): + if self._USDBTC is not None: + if int(time.time()) - int(self._USDBTC['ts']) < 600: + log.info("USDBTC quote is new enough") + return defer.succeed({}) + + log.info("Getting new USDBTC quote") + x = float(getQuotes('CURRENCY:USDBTC')[0]['LastTradePrice']) + return defer.succeed({'USDBTC': {'spot': x, 'ts': int(time.time())}}) + + def _btc_to_lbc(self): + if self._BTCLBC is not None: + if int(time.time()) - int(self._BTCLBC['ts']) < 600: + log.info("BTCLBC quote is new enough") + return defer.succeed({}) + + log.info("Getting new BTCLBC quote") + r = requests.get("https://bittrex.com/api/v1.1/public/getmarkethistory", {'market': 'BTC-LBC', 'count': 50}) + trades = json.loads(r.text)['result'] + vwap = sum([i['Total'] for i in trades]) / sum([i['Quantity'] for i in trades]) + x = (1.0 / float(vwap)) / 0.99975 + + return defer.succeed({'BTCLBC': {'spot': x, 'ts': int(time.time())}}) + + def _set_exchange_rates(self, rates): + if 'USDBTC' in rates: + assert int(time.time()) - int(rates['USDBTC']['ts']) < 3600, "new USDBTC quote is too old" + self._USDBTC = {'spot': rates['USDBTC']['spot'], 'ts': rates['USDBTC']['ts']} + log.info("Updated USDBTC rate: %s" % json.dumps(self._USDBTC)) + if 'BTCLBC' in rates: + assert int(time.time()) - int(rates['BTCLBC']['ts']) < 3600, "new BTCLBC quote is too old" + self._BTCLBC = {'spot': rates['BTCLBC']['spot'], 'ts': rates['BTCLBC']['ts']} + log.info("Updated BTCLBC rate: %s" % json.dumps(self._BTCLBC)) + + def _update_exchange_rates(self): + d = self._usd_to_btc() + d.addCallbacks(self._set_exchange_rates, lambda _: reactor.callLater(30, self._update_exchange_rates)) + d.addCallback(lambda _: self._btc_to_lbc()) + d.addCallbacks(self._set_exchange_rates, lambda _: reactor.callLater(30, self._update_exchange_rates)) + def start(self): def start_manage(): @@ -87,6 +135,8 @@ class LBRYWallet(object): self.manage() return True + self._exchange_rate_updater.start(1800) + d = self._open_db() d.addCallback(lambda _: self._start()) d.addCallback(lambda _: start_manage()) diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 7ae0ac13f..7563f018b 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.LBRYMetadata import Metadata, fee_from_dict +from lbrynet.core.LBRYMetadata import Metadata, LBRYFee from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME @@ -90,9 +90,9 @@ class GetStream(object): 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)) + self.fee = LBRYFee(self.metadata['fee'], {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) + if self.fee.to_lbc() > self.max_key_fee: + log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), self.max_key_fee, self.resolved_name)) return defer.fail(None) def _cause_timeout(): @@ -124,11 +124,12 @@ class GetStream(object): def _start_download(self, downloader): def _pay_key_fee(): if self.fee is not None: - reserved_points = self.wallet.reserve_points(self.fee.address, self.fee.convert(amount_only=True)) + x = self.fee.to_lbc() + reserved_points = self.wallet.reserve_points(self.fee.address, x) if reserved_points is None: return defer.fail(InsufficientFundsError()) - log.info("Key fee: %f --> %s" % (self.key_fee, self.key_fee_address)) - return self.wallet.send_points_to_address(reserved_points, self.key_fee) + log.info("Key fee: %f --> %s" % (x, self.fee.address)) + return self.wallet.send_points_to_address(reserved_points, self.fee.address) return defer.succeed(None) d = _pay_key_fee() From a53e911a0c1141953112bb66be8ac19c901c0867 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 21:48:39 -0400 Subject: [PATCH 390/462] stop looping call when stop() is called --- lbrynet/core/LBRYWallet.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index e09bd020d..aa9df4127 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -149,6 +149,10 @@ class LBRYWallet(object): def stop(self): self.stopped = True + + if self._exchange_rate_updater.running: + self._exchange_rate_updater.stop() + # If self.next_manage_call is None, then manage is currently running or else # start has not been called, so set stopped and do nothing else. if self.next_manage_call is not None: From a5374cb876c1269b019c45d0107e4b6d1b777b87 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 22:17:31 -0400 Subject: [PATCH 391/462] fix currencies and LBRYFeeFormat --- lbrynet/conf.py | 11 +++++------ lbrynet/core/LBRYMetadata.py | 11 +++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index fd5e24f54..cb626d717 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -48,9 +48,8 @@ DEFAULT_CACHE_TIME = 3600 DEFAULT_UI_BRANCH = "master" SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih'] -CURRENCIES = [ - {'BTC': {'type': 'crypto'}}, - {'LBC': {'type': 'crypto'}}, - {'USD': {'type': 'fiat'}}, - -] +CURRENCIES = { + 'BTC': {'type': 'crypto'}, + 'LBC': {'type': 'crypto'}, + 'USD': {'type': 'fiat'}, + } diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index 4090a2f2a..e00833e20 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -88,18 +88,17 @@ class LBRYFeeFormat(dict): for currency in fee_dict: assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency) self.update({currency: {}}) - for version in FEE_REVISIONS: for k in FEE_REVISIONS[version]['required']: - assert k in fee_dict, "Missing required fee field: %s" % k - self[currency].update({k: f.pop(k)}) + assert k in fee_dict[currency], "Missing required fee field: %s" % k + self[currency].update({k: f[currency].pop(k)}) for k in FEE_REVISIONS[version]['optional']: - if k in fee_dict: - self[currency].update({k: f.pop(k)}) + if k in fee_dict[currency]: + self[currency].update({k: f[currency].pop(k)}) if not len(f): self.fee_version = version break - assert f == {}, "Unknown fee keys: %s" % json.dumps(f.keys()) + assert f[currency] == {}, "Unknown fee keys: %s" % json.dumps(f.keys()) class Metadata(dict): From d469ef7bf2e6be79c15079069942d0c478915ec7 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 22:21:38 -0400 Subject: [PATCH 392/462] add googlefinance to requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 780abee9d..800ecf2ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,3 +26,4 @@ unqlite==0.2.0 wsgiref==0.1.2 zope.interface==4.1.3 base58==0.2.2 +googlefinance==0.7 \ No newline at end of file From 49c26a76b49325bffb5a15f434dc82826dbf8337 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 22:28:01 -0400 Subject: [PATCH 393/462] remove fee checking from autofetcher script --- .../daemon_scripts/Autofetcher.py | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py index 92ea9f9f1..cd7ed02cb 100644 --- a/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py +++ b/lbrynet/lbrynet_daemon/daemon_scripts/Autofetcher.py @@ -2,13 +2,10 @@ import json import logging.handlers import sys import os -import requests -from datetime import datetime from appdirs import user_data_dir from twisted.internet.task import LoopingCall from twisted.internet import reactor -from twisted.internet.threads import deferToThread if sys.platform != "darwin": @@ -42,15 +39,11 @@ class Autofetcher(object): def __init__(self, api): self._api = api self._checker = LoopingCall(self._check_for_new_claims) - self._price_checker = LoopingCall(self._update_price) self.best_block = None - self.last_price = None - self.price_updated = None def start(self): reactor.addSystemEventTrigger('before', 'shutdown', self.stop) self._checker.start(5) - self._price_checker.start(30) def stop(self): log.info("Stopping autofetcher") @@ -66,24 +59,9 @@ class Autofetcher(object): c = self._api.get_claims_for_tx({'txid': t}) if len(c): for i in c: - if 'fee' in json.loads(i['value']): - log.info("Downloading stream for claim txid: %s" % t) self._api.get({'name': t, 'stream_info': json.loads(i['value'])}) - def _update_price(self): - def _check_bittrex(): - try: - r = requests.get("https://bittrex.com/api/v1.1/public/getticker", {'market': 'BTC-LBC'}) - self.last_price = json.loads(r.text)['result']['Last'] - self.price_updated = datetime.now() - log.info("Updated exchange rate, last BTC-LBC trade: %f" % self.last_price) - except: - log.info("Failed to update exchange rate") - self.last_price = None - self.price_updated = datetime.now() - return deferToThread(_check_bittrex) - def run(api): fetcher = Autofetcher(api) From 102436274fcb353e3b64fa30138b1f0c68f6f64b Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 22:46:04 -0400 Subject: [PATCH 394/462] USD denominated max_key_fee in settings --- lbrynet/core/Error.py | 4 +++ lbrynet/lbrynet_daemon/LBRYDownloader.py | 33 ++++++++++++++++++------ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lbrynet/core/Error.py b/lbrynet/core/Error.py index b2c4c3360..631c5685c 100644 --- a/lbrynet/core/Error.py +++ b/lbrynet/core/Error.py @@ -22,6 +22,10 @@ class ConnectionClosedBeforeResponseError(Exception): pass +class KeyFeeAboveMaxAllowed(Exception): + pass + + class UnknownNameError(Exception): def __init__(self, name): self.name = name diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 7563f018b..852b0f367 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -8,7 +8,7 @@ from datetime import datetime from twisted.internet import defer from twisted.internet.task import LoopingCall -from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError +from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError, KeyFeeAboveMaxAllowed from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.StreamDescriptor import download_sd_blob from lbrynet.core.LBRYMetadata import Metadata, LBRYFee @@ -124,17 +124,34 @@ class GetStream(object): def _start_download(self, downloader): def _pay_key_fee(): if self.fee is not None: - x = self.fee.to_lbc() - reserved_points = self.wallet.reserve_points(self.fee.address, x) - if reserved_points is None: - return defer.fail(InsufficientFundsError()) - log.info("Key fee: %f --> %s" % (x, self.fee.address)) - return self.wallet.send_points_to_address(reserved_points, self.fee.address) + if isinstance(self.max_key_fee, int): + fee_lbc = self.fee.to_lbc() + if fee_lbc > self.max_key_fee: + return defer.fail(KeyFeeAboveMaxAllowed()) + reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc) + if reserved_points is None: + return defer.fail(InsufficientFundsError()) + log.info("Key fee: %f --> %s" % (fee_lbc, self.fee.address)) + return self.wallet.send_points_to_address(reserved_points, self.fee.address) + else: + assert "USD" in self.max_key_fee + max_fee = LBRYFee(self.max_key_fee, {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) + fee_lbc = self.fee.to_lbc() + if fee_lbc > max_fee.to_lbc(): + return defer.fail(KeyFeeAboveMaxAllowed()) + reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc) + if reserved_points is None: + return defer.fail(InsufficientFundsError()) + log.info("Key fee: %f --> %s" % (fee_lbc, self.fee.address)) + return self.wallet.send_points_to_address(reserved_points, self.fee.address) + + return defer.succeed(None) - d = _pay_key_fee() self.downloader = downloader self.download_path = os.path.join(downloader.download_directory, downloader.file_name) + + d = _pay_key_fee() d.addCallback(lambda _: log.info("Downloading %s --> %s", self.stream_hash, self.downloader.file_name)) d.addCallback(lambda _: self.downloader.start()) From 7e5af11c2de714474c31fad4b65185ba80aa9143 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 23:41:26 -0400 Subject: [PATCH 395/462] fix key fee payment problem --- lbrynet/core/LBRYMetadata.py | 4 +-- lbrynet/lbrynet_daemon/LBRYDownloader.py | 40 ++++++++++-------------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index e00833e20..d5a4e6009 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -73,10 +73,10 @@ class LBRYFee(object): return float(btc) / self._USDBTC['spot'] def _btc_to_lbc(self, btc): - return float(btc) / self._BTCLBC['spot'] / (1.0 - BITTREX_FEE) + return float(btc) * self._BTCLBC['spot'] / (1.0 - BITTREX_FEE) def _lbc_to_btc(self, lbc): - return self._BTCLBC['spot'] * float(lbc) + return self._BTCLBC['spot'] / float(lbc) class LBRYFeeFormat(dict): diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 852b0f367..8d25aa56f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -91,9 +91,15 @@ class GetStream(object): if 'fee' in self.metadata: self.fee = LBRYFee(self.metadata['fee'], {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) - if self.fee.to_lbc() > self.max_key_fee: - log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), self.max_key_fee, self.resolved_name)) - return defer.fail(None) + if isinstance(self.max_key_fee, float): + if self.fee.to_lbc() > self.max_key_fee: + log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), self.max_key_fee, self.resolved_name)) + return defer.fail(KeyFeeAboveMaxAllowed()) + elif isinstance(self.max_key_fee, dict): + max_key = LBRYFee(self.max_key_fee, {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) + if self.fee.to_lbc() > max_key.to_lbc(): + log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), max_key.to_lbc(), self.resolved_name)) + return defer.fail(KeyFeeAboveMaxAllowed()) def _cause_timeout(): self.timeout_counter = self.timeout * 2 @@ -124,27 +130,13 @@ class GetStream(object): def _start_download(self, downloader): def _pay_key_fee(): if self.fee is not None: - if isinstance(self.max_key_fee, int): - fee_lbc = self.fee.to_lbc() - if fee_lbc > self.max_key_fee: - return defer.fail(KeyFeeAboveMaxAllowed()) - reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc) - if reserved_points is None: - return defer.fail(InsufficientFundsError()) - log.info("Key fee: %f --> %s" % (fee_lbc, self.fee.address)) - return self.wallet.send_points_to_address(reserved_points, self.fee.address) - else: - assert "USD" in self.max_key_fee - max_fee = LBRYFee(self.max_key_fee, {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) - fee_lbc = self.fee.to_lbc() - if fee_lbc > max_fee.to_lbc(): - return defer.fail(KeyFeeAboveMaxAllowed()) - reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc) - if reserved_points is None: - return defer.fail(InsufficientFundsError()) - log.info("Key fee: %f --> %s" % (fee_lbc, self.fee.address)) - return self.wallet.send_points_to_address(reserved_points, self.fee.address) - + fee_lbc = self.fee.to_lbc() + reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc) + if reserved_points is None: + return defer.fail(InsufficientFundsError()) + log.info("Key fee: %f --> %s" % (fee_lbc, self.fee.address)) + d = self.wallet.send_points_to_address(reserved_points, self.fee.address) + return d return defer.succeed(None) From ca45f461ae7b11fddb678096067d022ae88ee910 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 23:42:03 -0400 Subject: [PATCH 396/462] spelling --- lbrynet/core/LBRYMetadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index d5a4e6009..4e7f8ad39 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -17,7 +17,7 @@ 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', 'ver'], 'optional': ['licence_url']} +METADATA_REVISIONS['0.0.2'] = {'required': ['nsfw', 'ver'], 'optional': ['license_url']} CURRENT_METADATA_VERSION = '0.0.2' From 9e8827f015b54757127f7eed792885a1853d1d58 Mon Sep 17 00:00:00 2001 From: Jack Date: Mon, 25 Jul 2016 23:46:18 -0400 Subject: [PATCH 397/462] resolve name bug --- lbrynet/core/LBRYMetadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index 4e7f8ad39..133c34482 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -124,5 +124,5 @@ class Metadata(dict): self.metaversion = version break if 'fee' in m: - self.update({'fee': LBRYFeeFormat(m.pop('fee'))}) + self['fee'] = LBRYFeeFormat(m.pop('fee')) assert m == {}, "Unknown metadata keys: %s" % json.dumps(m.keys()) From 927b9afe7cff22ca8246ea66b6eb436150b47359 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Tue, 26 Jul 2016 09:34:00 -0500 Subject: [PATCH 398/462] forgot to add the test file --- .gitignore | 3 +++ tests/lbrynet/core/test_utils.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/lbrynet/core/test_utils.py diff --git a/.gitignore b/.gitignore index 1bd0eea94..249826fac 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ lbrynet.egg-info/PKG-INFO *.decTest .coverage + +# temporary files from the twisted.trial test runner +_trial_temp/ diff --git a/tests/lbrynet/core/test_utils.py b/tests/lbrynet/core/test_utils.py new file mode 100644 index 000000000..9fc14f93a --- /dev/null +++ b/tests/lbrynet/core/test_utils.py @@ -0,0 +1,19 @@ +from lbrynet.core import utils + +from twisted.trial import unittest + + +class CompareVersionTest(unittest.TestCase): + def test_compare_versions_isnot_lexographic(self): + self.assertTrue(utils.version_is_greater_than('0.3.10', '0.3.6')) + + def test_same_versions_return_false(self): + self.assertFalse(utils.version_is_greater_than('1.3.9', '1.3.9')) + + def test_same_release_is_greater_then_beta(self): + self.assertTrue(utils.version_is_greater_than('1.3.9', '1.3.9b1')) + + def test_version_can_have_four_parts(self): + self.assertTrue(utils.version_is_greater_than('1.3.9.1', '1.3.9')) + + From 4a1e8469581bf16f3d4a4eca326a8ff8a02532c7 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Tue, 26 Jul 2016 11:34:13 -0500 Subject: [PATCH 399/462] Remove print line This line is responsible for flooding the console with messages like <_io.BytesIO object at 0x7f9e286e52f0> --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 6a1fc7c9b..1ddaf8d49 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -68,7 +68,6 @@ class LBRYDaemonRequest(server.Request): from twisted.web.http import parse_qs if self.do_log: print '%f Request Received' % time.time() - print self.content self.content.seek(0,0) self.args = {} From 93993e62d6db95f983b015390660060e0a78e3c0 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Mon, 25 Jul 2016 13:04:30 -0500 Subject: [PATCH 400/462] Fix: Log configuration should not happen when a module is imported Instead, move the responsibility to the main program. Also, each module had the same, redundant setup. --- lbrynet/core/log_support.py | 24 ++++++++++++++++++++ lbrynet/lbrynet_daemon/LBRYDaemon.py | 20 +++-------------- lbrynet/lbrynet_daemon/LBRYDaemonControl.py | 25 ++++++--------------- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 5 ++--- lbrynet/lbrynet_daemon/LBRYDownloader.py | 4 +--- lbrynet/lbrynet_daemon/LBRYPublisher.py | 3 --- lbrynet/lbrynet_daemon/LBRYUIManager.py | 5 +---- 7 files changed, 38 insertions(+), 48 deletions(-) create mode 100644 lbrynet/core/log_support.py diff --git a/lbrynet/core/log_support.py b/lbrynet/core/log_support.py new file mode 100644 index 000000000..208157d3d --- /dev/null +++ b/lbrynet/core/log_support.py @@ -0,0 +1,24 @@ +import logging +import logging.handlers +import sys + + +DEFAULT_FORMAT = "%(asctime)s %(levelname)-8s %(name)s:%(lineno)d: %(message)s" +DEFAULT_FORMATTER = logging.Formatter(DEFAULT_FORMAT) + + +def configureConsole(log=None, level=logging.INFO): + """Convenience function to configure a logger that outputs to stdout""" + log = log or logging.getLogger() + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(DEFAULT_FORMATTER) + log.addHandler(handler) + log.setLevel(level=level) + + +def configureFileHandler(file_name, log=None, level=logging.INFO): + log = log or logging.getLogger() + handler = logging.handlers.RotatingFileHandler(file_name, maxBytes=2097152, backupCount=5) + handler.setFormatter(DEFAULT_FORMATTER) + log.addHandler(handler) + log.setLevel(level=level) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 8811211aa..adcd87055 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -56,6 +56,7 @@ from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, log = logging.getLogger(__name__) +# TODO: this code snippet is everywhere. Make it go away if sys.platform != "darwin": log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: @@ -68,25 +69,10 @@ 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.addHandler(handler) -log.setLevel(logging.INFO) - -# if os.path.isfile(lbryum_log): -# f = open(lbryum_log, 'r') -# PREVIOUS_LBRYUM_LOG = len(f.read()) -# f.close() -# else: -# PREVIOUS_LBRYUM_LOG = 0 if os.path.isfile(lbrynet_log): - f = open(lbrynet_log, 'r') - PREVIOUS_LBRYNET_LOG = len(f.read()) - f.close() + with open(lbrynet_log, 'r') as f: + PREVIOUS_LBRYNET_LOG = len(f.read()) else: PREVIOUS_LBRYNET_LOG = 0 diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py index b25f73a95..1bfa62eab 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonControl.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonControl.py @@ -12,10 +12,12 @@ from twisted.web import server from twisted.internet import reactor, defer from jsonrpc.proxy import JSONRPCProxy +from lbrynet.core import log_support from lbrynet.lbrynet_daemon.LBRYDaemonServer import LBRYDaemonServer, LBRYDaemonRequest from lbrynet.conf import API_CONNECTION_STRING, API_INTERFACE, API_ADDRESS, API_PORT, \ DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME +# TODO: stop it! if sys.platform != "darwin": log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: @@ -25,15 +27,8 @@ if not os.path.isdir(log_dir): os.mkdir(log_dir) lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) - -DEFAULT_FORMAT = "%(asctime)s %(levelname)-8s %(name)s:%(lineno)d: %(message)s" -DEFAULT_FORMATTER = logging.Formatter(DEFAULT_FORMAT) - log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) -handler.setFormatter(DEFAULT_FORMATTER) -log.addHandler(handler) -log.setLevel(logging.INFO) + REMOTE_SERVER = "www.google.com" @@ -62,13 +57,6 @@ def stop(): d.callback(None) -def configureConsoleLogger(): - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(DEFAULT_FORMATTER) - logging.getLogger().addHandler(handler) - logging.getLogger().setLevel(level=logging.INFO) - - def start(): parser = argparse.ArgumentParser(description="Launch lbrynet-daemon") parser.add_argument("--wallet", @@ -86,10 +74,11 @@ def start(): parser.set_defaults(branch=False, launchui=True, logtoconsole=False, quiet=False) args = parser.parse_args() - if args.logtoconsole: - configureConsoleLogger() - args = parser.parse_args() + log_support.configureFileHandler(lbrynet_log) + if args.logtoconsole: + log_support.configureConsole() + try: JSONRPCProxy.from_url(API_CONNECTION_STRING).is_running() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index 1ddaf8d49..3f2107079 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -20,6 +20,7 @@ from lbrynet.lbrynet_daemon.LBRYDaemon import LBRYDaemon from lbrynet.conf import API_CONNECTION_STRING, API_ADDRESS, DEFAULT_WALLET, UI_ADDRESS, DEFAULT_UI_BRANCH, LOG_FILE_NAME +# TODO: omg, this code is essentially duplicated in LBRYDaemon if sys.platform != "darwin": data_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") else: @@ -29,9 +30,7 @@ if not os.path.isdir(data_dir): lbrynet_log = os.path.join(data_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) -log.addHandler(handler) -log.setLevel(logging.INFO) + class LBRYDaemonRequest(server.Request): """ diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 9187cc56d..357fb30f1 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -37,9 +37,7 @@ if not os.path.isdir(log_dir): lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) -log.addHandler(handler) -log.setLevel(logging.INFO) + class GetStream(object): def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, data_rate=0.5, diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index bd5c50e4b..2839e46cb 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -24,9 +24,6 @@ if not os.path.isdir(log_dir): lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) -log.addHandler(handler) -log.setLevel(logging.INFO) class Publisher(object): diff --git a/lbrynet/lbrynet_daemon/LBRYUIManager.py b/lbrynet/lbrynet_daemon/LBRYUIManager.py index 0f0aa2a53..346a6e37b 100644 --- a/lbrynet/lbrynet_daemon/LBRYUIManager.py +++ b/lbrynet/lbrynet_daemon/LBRYUIManager.py @@ -25,9 +25,6 @@ if not os.path.isdir(log_dir): lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME) log = logging.getLogger(__name__) -handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5) -log.addHandler(handler) -log.setLevel(logging.INFO) class LBRYUIManager(object): @@ -239,4 +236,4 @@ class LBRYUIManager(object): def _load_ui(self): for d in [i[0] for i in os.walk(self.active_dir) if os.path.dirname(i[0]) == self.active_dir]: self.root.putChild(os.path.basename(d), static.File(d)) - return defer.succeed(True) \ No newline at end of file + return defer.succeed(True) From f8dd3d05dce0f989090c0b726407b56bab06415d Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Sun, 17 Jul 2016 09:00:00 -0500 Subject: [PATCH 401/462] misc bug fixes and code cleanup --- lbrynet/core/LBRYWallet.py | 3 +-- lbrynet/core/StreamDescriptor.py | 4 ++-- lbrynet/core/client/DownloadManager.py | 2 +- lbrynet/lbrynet_daemon/LBRYDownloader.py | 30 +++++++++++++++++------- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 99538cea8..c4391bf88 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -56,7 +56,6 @@ class LBRYWallet(object): _FIRST_RUN_NO = 2 def __init__(self, db_dir): - self.db_dir = db_dir self.db = None self.next_manage_call = None @@ -631,7 +630,7 @@ class LBRYcrdWallet(LBRYWallet): settings = {"username": "rpcuser", "password": "rpcpassword", "rpc_port": 9245} - if os.path.exists(self.wallet_conf): + if self.wallet_conf and os.path.exists(self.wallet_conf): conf = open(self.wallet_conf) for l in conf: if l.startswith("rpcuser="): diff --git a/lbrynet/core/StreamDescriptor.py b/lbrynet/core/StreamDescriptor.py index a965969e8..fd21a4b87 100644 --- a/lbrynet/core/StreamDescriptor.py +++ b/lbrynet/core/StreamDescriptor.py @@ -198,7 +198,7 @@ class StreamDescriptorIdentifier(object): return self._stream_downloader_factories[stream_type] def _get_validator(self, stream_type): - if not stream_type in self._stream_downloader_factories: + if not stream_type in self._sd_info_validators: raise UnknownStreamTypeError(stream_type) return self._sd_info_validators[stream_type] @@ -238,4 +238,4 @@ def download_sd_blob(session, blob_hash, payment_rate_manager): """ downloader = StandaloneBlobDownloader(blob_hash, session.blob_manager, session.peer_finder, session.rate_limiter, payment_rate_manager, session.wallet) - return downloader.download() \ No newline at end of file + return downloader.download() diff --git a/lbrynet/core/client/DownloadManager.py b/lbrynet/core/client/DownloadManager.py index d601833dd..265e090eb 100644 --- a/lbrynet/core/client/DownloadManager.py +++ b/lbrynet/core/client/DownloadManager.py @@ -66,7 +66,7 @@ class DownloadManager(object): def add_blobs_to_download(self, blob_infos): - log.debug("Adding %s to blobs", str(blob_infos)) + log.debug("Adding %s blobs to blobs", len(blob_infos)) def add_blob_to_list(blob, blob_num): self.blobs[blob_num] = blob diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 9187cc56d..e2aa74ead 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -73,6 +73,7 @@ class GetStream(object): def check_status(self): self.timeout_counter += 1 + # TODO: Why is this the stopping condition for the finished callback? if self.download_path: self.checker.stop() self.finished.callback((self.stream_hash, self.download_path)) @@ -108,7 +109,9 @@ class GetStream(object): else: pass - def _cause_timeout(): + def _cause_timeout(err): + log.error(err) + log.debug('Forcing a timeout') self.timeout_counter = self.timeout * 2 def _set_status(x, status): @@ -116,20 +119,29 @@ class GetStream(object): self.code = next(s for s in STREAM_STAGES if s[0] == status) return x + def get_downloader_factory(metadata): + for factory in metadata.factories: + if isinstance(factory, ManagedLBRYFileDownloaderFactory): + return factory, metadata + raise Exception('No suitable factory was found in {}'.format(metadata.factories)) + + def make_downloader(args): + factory, metadata = args + return factory.make_downloader(metadata, + [self.data_rate, True], + self.payment_rate_manager, + download_directory=self.download_directory, + file_name=self.file_name) + self.checker.start(1) self.d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE)) self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) self.d.addCallback(lambda r: _set_status(r, DOWNLOAD_RUNNING_CODE)) - self.d.addCallback(lambda metadata: (next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), - metadata)) - self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, - [self.data_rate, True], - self.payment_rate_manager, - download_directory=self.download_directory, - file_name=self.file_name)) - self.d.addCallbacks(self._start_download, lambda _: _cause_timeout()) + self.d.addCallback(get_downloader_factory) + self.d.addCallback(make_downloader) + self.d.addCallbacks(self._start_download, _cause_timeout) self.d.callback(None) return self.finished From a2eab1577d4c280e8c1136c7cfe868adee77f020 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Tue, 26 Jul 2016 13:46:44 -0500 Subject: [PATCH 402/462] Have TimeoutError hexlify blobs --- lbrynet/dht/node.py | 2 +- lbrynet/dht/protocol.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lbrynet/dht/node.py b/lbrynet/dht/node.py index a3bf56553..ae9000582 100644 --- a/lbrynet/dht/node.py +++ b/lbrynet/dht/node.py @@ -241,7 +241,7 @@ class Node(object): def log_error(err, n): log.error("error storing blob_hash %s at %s", binascii.hexlify(blob_hash), str(n)) - log.error(binascii.hexlify(err.getErrorMessage())) + log.error(err.getErrorMessage()) log.error(err.getTraceback()) def log_success(res): diff --git a/lbrynet/dht/protocol.py b/lbrynet/dht/protocol.py index 1a9f2c262..a7d53bdcb 100644 --- a/lbrynet/dht/protocol.py +++ b/lbrynet/dht/protocol.py @@ -7,6 +7,7 @@ # The docstrings in this module contain epytext markup; API documentation # may be created by processing this file with epydoc: http://epydoc.sf.net +import binascii import time from twisted.internet import protocol, defer @@ -23,6 +24,13 @@ reactor = twisted.internet.reactor class TimeoutError(Exception): """ Raised when a RPC times out """ + def __init__(self, remote_contact_id): + # remote_contact_id is a binary blob so we need to convert it + # into something more readable + msg = 'Timeout connecting to {}'.format(binascii.hexlify(remote_contact_id)) + Exception.__init__(self, msg) + self.remote_contact_id = remote_contact_id + class KademliaProtocol(protocol.DatagramProtocol): """ Implements all low-level network-related functions of a Kademlia node """ From 491d431ea57bae2eb6caea7487c9f110edff1b75 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 17:19:26 -0400 Subject: [PATCH 403/462] remove price updater from daemon --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index fb510a0cc..c6a895f94 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -174,7 +174,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.streams = {} self.known_dht_nodes = KNOWN_DHT_NODES self.first_run_after_update = False - self.last_traded_rate = None self.uploaded_temp_files = [] if os.name == "nt": @@ -363,7 +362,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.internet_connection_checker = LoopingCall(self._check_network_connection) self.version_checker = LoopingCall(self._check_remote_versions) self.connection_problem_checker = LoopingCall(self._check_connection_problems) - self.price_checker = LoopingCall(self._update_exchange) # self.lbrynet_connection_checker = LoopingCall(self._check_lbrynet_connection) self.sd_identifier = StreamDescriptorIdentifier() @@ -497,7 +495,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.internet_connection_checker.start(3600) self.version_checker.start(3600 * 12) self.connection_problem_checker.start(1) - self.price_checker.start(600) if host_ui: self.lbry_ui_manager.update_checker.start(1800, now=False) @@ -612,13 +609,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): if not self.connected_to_internet: self.connection_problem = CONNECTION_PROBLEM_CODES[1] - def _update_exchange(self): - try: - r = requests.get("https://bittrex.com/api/v1.1/public/getticker", {'market': 'BTC-LBC'}) - self.last_traded_rate = float(json.loads(r.text)['result']['Last']) - except: - self.last_traded_rate = None - def _start_server(self): if self.peer_port is not None: @@ -737,8 +727,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connection_problem_checker.stop() if self.lbry_ui_manager.update_checker.running: self.lbry_ui_manager.update_checker.stop() - if self.price_checker.running: - self.price_checker.stop() self._clean_up_temp_files() From 0811ebb52d6b7bdbda6a949c914410f1b2b5e04e Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 17:20:51 -0400 Subject: [PATCH 404/462] fix key payment problems --- lbrynet/core/LBRYMetadata.py | 81 +++++++++++++----------- lbrynet/lbrynet_daemon/LBRYDaemon.py | 19 +++--- lbrynet/lbrynet_daemon/LBRYDownloader.py | 25 ++++---- 3 files changed, 67 insertions(+), 58 deletions(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index 133c34482..e57ee1ad6 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -2,11 +2,13 @@ import requests import json import time +from copy import deepcopy from googlefinance import getQuotes from lbrynet.conf import CURRENCIES import logging log = logging.getLogger(__name__) +log.setLevel(logging.INFO) BITTREX_FEE = 0.0025 @@ -26,23 +28,42 @@ FEE_REVISIONS = {'0.0.1': {'required': ['amount', 'address'], 'optional': []}} CURRENT_FEE_REVISION = '0.0.1' -class LBRYFee(object): - def __init__(self, fee_dict, rate_dict): - fee = LBRYFeeFormat(fee_dict) - - for currency in fee: - self.address = fee[currency]['address'] - if not isinstance(fee[currency]['amount'], float): - self.amount = float(fee[currency]['amount']) - else: - self.amount = fee[currency]['amount'] +class LBRYFeeFormat(dict): + def __init__(self, fee_dict): + dict.__init__(self) + self.fee_version = None + f = deepcopy(fee_dict) + assert len(fee_dict) == 1 + for currency in fee_dict: + assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency) self.currency_symbol = currency + self.update({currency: {}}) + for version in FEE_REVISIONS: + for k in FEE_REVISIONS[version]['required']: + assert k in fee_dict[currency], "Missing required fee field: %s" % k + self[currency].update({k: f[currency].pop(k)}) + for k in FEE_REVISIONS[version]['optional']: + if k in fee_dict[currency]: + self[currency].update({k: f[currency].pop(k)}) + if not len(f): + self.fee_version = version + break + assert f[currency] == {}, "Unknown fee keys: %s" % json.dumps(f.keys()) - assert 'BTCLBC' in rate_dict and 'USDBTC' in rate_dict + self.amount = self[self.currency_symbol]['amount'] if isinstance(self[self.currency_symbol]['amount'], float) else float(self[self.currency_symbol]['amount']) + self.address = self[self.currency_symbol]['address'] + + +class LBRYFee(LBRYFeeFormat): + def __init__(self, fee_dict, rate_dict): + LBRYFeeFormat.__init__(self, fee_dict) + rates = deepcopy(rate_dict) + + assert 'BTCLBC' in rates and 'USDBTC' in rates for fx in rate_dict: - assert int(time.time()) - int(rate_dict[fx]['ts']) < 3600, "%s quote is out of date" % fx - self._USDBTC = {'spot': rate_dict['USDBTC']['spot'], 'ts': rate_dict['USDBTC']['ts']} - self._BTCLBC = {'spot': rate_dict['BTCLBC']['spot'], 'ts': rate_dict['BTCLBC']['ts']} + assert int(time.time()) - int(rates[fx]['ts']) < 3600, "%s quote is out of date" % fx + self._USDBTC = {'spot': rates['USDBTC']['spot'], 'ts': rates['USDBTC']['ts']} + self._BTCLBC = {'spot': rates['BTCLBC']['spot'], 'ts': rates['BTCLBC']['ts']} def to_lbc(self): r = None @@ -67,45 +88,31 @@ class LBRYFee(object): return r def _usd_to_btc(self, usd): + # log.error("usd to btc: " + str(usd)) + # log.error("%f * %f = %f" % (self._USDBTC['spot'], float(usd), self._USDBTC['spot'] * float(usd))) return self._USDBTC['spot'] * float(usd) def _btc_to_usd(self, btc): + # log.error("btc to usd: " + str(btc)) + # log.error("%f / %f = %f" % (float(btc), self._USDBTC['spot'], float(btc) / self._USDBTC['spot'])) return float(btc) / self._USDBTC['spot'] def _btc_to_lbc(self, btc): + # log.error("btc to lbc: " + str(btc)) + # log.error("%f * %f = %f" % (float(btc), self._BTCLBC['spot'], float(btc) * self._BTCLBC['spot'] / (1.0 - BITTREX_FEE))) return float(btc) * self._BTCLBC['spot'] / (1.0 - BITTREX_FEE) def _lbc_to_btc(self, lbc): + # log.error("lbc to btc: " + str(lbc)) + # log.error("%f / %f = %f" % (self._BTCLBC['spot'], float(lbc), self._BTCLBC['spot'] / float(lbc))) return self._BTCLBC['spot'] / float(lbc) -class LBRYFeeFormat(dict): - def __init__(self, fee_dict): - dict.__init__(self) - self.fee_version = None - f = fee_dict.copy() - assert len(fee_dict) == 1 - for currency in fee_dict: - assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency) - self.update({currency: {}}) - for version in FEE_REVISIONS: - for k in FEE_REVISIONS[version]['required']: - assert k in fee_dict[currency], "Missing required fee field: %s" % k - self[currency].update({k: f[currency].pop(k)}) - for k in FEE_REVISIONS[version]['optional']: - if k in fee_dict[currency]: - self[currency].update({k: f[currency].pop(k)}) - if not len(f): - self.fee_version = version - break - assert f[currency] == {}, "Unknown fee keys: %s" % json.dumps(f.keys()) - - class Metadata(dict): def __init__(self, metadata): dict.__init__(self) self.metaversion = None - m = metadata.copy() + m = deepcopy(metadata) assert "sources" in metadata, "No sources given" for source in metadata['sources']: diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index c6a895f94..518dbdce6 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1899,20 +1899,19 @@ class LBRYDaemon(jsonrpc.JSONRPC): file_path = p['file_path'] metadata = p['metadata'] - def _set_address(address): - metadata['fee']['address'] = address + d = defer.succeed(None) + + def _set_address(address, currency): + metadata['fee'][currency]['address'] = address return defer.succeed(None) if 'fee' in p: 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: - d = defer.succeed(None) - + assert len(metadata['fee']) == 1, "Too many fees" + for c in metadata['fee']: + if 'address' not in metadata['fee'][c]: + d = self.session.wallet.get_new_address() + d.addCallback(lambda addr: _set_address(addr, c)) pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 8d25aa56f..00c137869 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -3,6 +3,7 @@ import logging import os import sys +from copy import deepcopy from appdirs import user_data_dir from datetime import datetime from twisted.internet import defer @@ -58,7 +59,7 @@ class GetStream(object): self.sd_identifier = sd_identifier self.stream_hash = None self.max_key_fee = max_key_fee - self.metadata = None + self.stream_info = None self.stream_info_manager = None self.d = defer.Deferred(None) self.timeout = timeout @@ -86,20 +87,23 @@ class GetStream(object): def start(self, stream_info, name): self.resolved_name = name - self.metadata = stream_info - self.stream_hash = self.metadata['sources']['lbry_sd_hash'] + self.stream_info = deepcopy(stream_info) + self.description = self.stream_info['description'] + self.stream_hash = self.stream_info['sources']['lbry_sd_hash'] - if 'fee' in self.metadata: - self.fee = LBRYFee(self.metadata['fee'], {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) + if 'fee' in self.stream_info: + self.fee = LBRYFee(self.stream_info['fee'], {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) if isinstance(self.max_key_fee, float): if self.fee.to_lbc() > self.max_key_fee: log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), self.max_key_fee, self.resolved_name)) return defer.fail(KeyFeeAboveMaxAllowed()) + log.info("Key fee %f below limit of %f, downloading lbry://%s" % (self.fee.to_lbc(), self.max_key_fee, self.resolved_name)) elif isinstance(self.max_key_fee, dict): - max_key = LBRYFee(self.max_key_fee, {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) + max_key = LBRYFee(deepcopy(self.max_key_fee), {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) if self.fee.to_lbc() > max_key.to_lbc(): log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), max_key.to_lbc(), self.resolved_name)) return defer.fail(KeyFeeAboveMaxAllowed()) + log.info("Key fee %f below limit of %f, downloading lbry://%s" % (self.fee.to_lbc(), max_key.to_lbc(), self.resolved_name)) def _cause_timeout(): self.timeout_counter = self.timeout * 2 @@ -130,20 +134,19 @@ class GetStream(object): def _start_download(self, downloader): def _pay_key_fee(): if self.fee is not None: - fee_lbc = self.fee.to_lbc() + fee_lbc = float(self.fee.to_lbc()) reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc) if reserved_points is None: return defer.fail(InsufficientFundsError()) - log.info("Key fee: %f --> %s" % (fee_lbc, self.fee.address)) - d = self.wallet.send_points_to_address(reserved_points, self.fee.address) - return d + return self.wallet.send_points_to_address(reserved_points, fee_lbc) return defer.succeed(None) + d = _pay_key_fee() + self.downloader = downloader self.download_path = os.path.join(downloader.download_directory, downloader.file_name) - d = _pay_key_fee() d.addCallback(lambda _: log.info("Downloading %s --> %s", self.stream_hash, self.downloader.file_name)) d.addCallback(lambda _: self.downloader.start()) From 3d88eb92c190d26693691f779f911fcbf936433e Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 17:21:03 -0400 Subject: [PATCH 405/462] change default max fee to $25 --- lbrynet/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index cb626d717..16a459c03 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -42,7 +42,7 @@ DEFAULT_WALLET = "lbryum" WALLET_TYPES = ["lbryum", "lbrycrd"] DEFAULT_TIMEOUT = 30 DEFAULT_MAX_SEARCH_RESULTS = 25 -DEFAULT_MAX_KEY_FEE = {'BTC': {'amount': 0.025}} +DEFAULT_MAX_KEY_FEE = {'USD': {'amount': 25.0, 'address': ''}} DEFAULT_SEARCH_TIMEOUT = 3.0 DEFAULT_CACHE_TIME = 3600 DEFAULT_UI_BRANCH = "master" From f82cffe882a4694de6c937928bfa791ab4e33f16 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 17:21:41 -0400 Subject: [PATCH 406/462] verbose logging --- lbrynet/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 969401685..8d1db90a5 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -2,7 +2,7 @@ import logging log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) -log.setLevel(logging.ERROR) +log.setLevel(logging.INFO) __version__ = "0.3.10" version = tuple(__version__.split('.')) \ No newline at end of file From d86e709b90263c3d95b2a9b9b96234e1397993a2 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 17:24:59 -0400 Subject: [PATCH 407/462] reduce min blob prices --- lbrynet/conf.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 16a459c03..71df9f8fd 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -10,10 +10,11 @@ MAX_RESPONSE_INFO_SIZE = 2**16 MAX_BLOB_INFOS_TO_REQUEST = 20 BLOBFILES_DIR = ".blobfiles" BLOB_SIZE = 2**21 -MIN_BLOB_DATA_PAYMENT_RATE = .005 # points/megabyte -MIN_BLOB_INFO_PAYMENT_RATE = .02 # points/1000 infos -MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .05 # points/1000 infos -MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .05 # points/1000 infos + +MIN_BLOB_DATA_PAYMENT_RATE = .0005 # points/megabyte +MIN_BLOB_INFO_PAYMENT_RATE = .002 # points/1000 infos +MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .005 # points/1000 infos +MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .005 # points/1000 infos MAX_CONNECTIONS_PER_STREAM = 5 KNOWN_DHT_NODES = [('104.236.42.182', 4000), From 5e1fdf66b0fa8be029203bb3374c18414f17e57a Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 18:18:34 -0400 Subject: [PATCH 408/462] fix download problem --- lbrynet/conf.py | 8 +++--- lbrynet/lbrynet_daemon/LBRYDownloader.py | 31 ++++++++++++++++-------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 71df9f8fd..85a9f6836 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -11,10 +11,10 @@ MAX_BLOB_INFOS_TO_REQUEST = 20 BLOBFILES_DIR = ".blobfiles" BLOB_SIZE = 2**21 -MIN_BLOB_DATA_PAYMENT_RATE = .0005 # points/megabyte -MIN_BLOB_INFO_PAYMENT_RATE = .002 # points/1000 infos -MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .005 # points/1000 infos -MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .005 # points/1000 infos +MIN_BLOB_DATA_PAYMENT_RATE = .005 # points/megabyte +MIN_BLOB_INFO_PAYMENT_RATE = .02 # points/1000 infos +MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .05 # points/1000 infos +MIN_VALUABLE_BLOB_HASH_PAYMENT_RATE = .05 # points/1000 infos MAX_CONNECTIONS_PER_STREAM = 5 KNOWN_DHT_NODES = [('104.236.42.182', 4000), diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 00c137869..909a3f340 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -67,7 +67,7 @@ class GetStream(object): self.download_directory = download_directory self.download_path = None self.downloader = None - self.finished = defer.Deferred() + self.finished = defer.Deferred(None) self.checker = LoopingCall(self.check_status) self.code = STREAM_STAGES[0] @@ -105,7 +105,9 @@ class GetStream(object): return defer.fail(KeyFeeAboveMaxAllowed()) log.info("Key fee %f below limit of %f, downloading lbry://%s" % (self.fee.to_lbc(), max_key.to_lbc(), self.resolved_name)) - def _cause_timeout(): + def _cause_timeout(err): + log.error(err) + log.debug('Forcing a timeout') self.timeout_counter = self.timeout * 2 def _set_status(x, status): @@ -113,20 +115,29 @@ class GetStream(object): self.code = next(s for s in STREAM_STAGES if s[0] == status) return x + def get_downloader_factory(metadata): + for factory in metadata.factories: + if isinstance(factory, ManagedLBRYFileDownloaderFactory): + return factory, metadata + raise Exception('No suitable factory was found in {}'.format(metadata.factories)) + + def make_downloader(args): + factory, metadata = args + return factory.make_downloader(metadata, + [self.data_rate, True], + self.payment_rate_manager, + download_directory=self.download_directory, + file_name=self.file_name) + self.checker.start(1) self.d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE)) self.d.addCallback(lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) self.d.addCallback(lambda r: _set_status(r, DOWNLOAD_RUNNING_CODE)) - self.d.addCallback(lambda metadata: (next(factory for factory in metadata.factories if isinstance(factory, ManagedLBRYFileDownloaderFactory)), - metadata)) - self.d.addCallback(lambda (factory, metadata): factory.make_downloader(metadata, - [self.data_rate, True], - self.payment_rate_manager, - download_directory=self.download_directory, - file_name=self.file_name)) - self.d.addCallbacks(self._start_download, lambda _: _cause_timeout()) + self.d.addCallback(get_downloader_factory) + self.d.addCallback(make_downloader) + self.d.addCallbacks(self._start_download, _cause_timeout) self.d.callback(None) return self.finished From bd6fa35d4a3d734ae5b5f071b5fc57782f568716 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 18:19:40 -0400 Subject: [PATCH 409/462] disable verbose dht error --- lbrynet/dht/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lbrynet/dht/node.py b/lbrynet/dht/node.py index a3bf56553..ff77bdfbf 100644 --- a/lbrynet/dht/node.py +++ b/lbrynet/dht/node.py @@ -240,9 +240,9 @@ class Node(object): known_nodes = {} def log_error(err, n): - log.error("error storing blob_hash %s at %s", binascii.hexlify(blob_hash), str(n)) - log.error(binascii.hexlify(err.getErrorMessage())) - log.error(err.getTraceback()) + log.debug("error storing blob_hash %s at %s", binascii.hexlify(blob_hash), str(n)) + log.debug(binascii.hexlify(err.getErrorMessage())) + log.debug(err.getTraceback()) def log_success(res): log.debug("Response to store request: %s", str(res)) From ee9e8b365c2c6ab5f2a5e19768880ac30b374c44 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 18:42:51 -0400 Subject: [PATCH 410/462] add debug log line --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 518dbdce6..aedd5c5c6 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1902,6 +1902,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d = defer.succeed(None) def _set_address(address, currency): + log.info("Generated new address for key fee: " + str(address)) metadata['fee'][currency]['address'] = address return defer.succeed(None) From fe39901885fb34df525010e16b6be2997bbb1b75 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 20:52:05 -0400 Subject: [PATCH 411/462] update claim when publishing to an already claimed name -also save name_metadata for each unique claim rather than for each name --- lbrynet/core/LBRYWallet.py | 23 ++++++++---------- lbrynet/lbrynet_daemon/LBRYDaemon.py | 31 +++++-------------------- lbrynet/lbrynet_daemon/LBRYPublisher.py | 16 +++++++++---- 3 files changed, 28 insertions(+), 42 deletions(-) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index aa9df4127..b2848693a 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -380,7 +380,7 @@ class LBRYWallet(object): return Failure(InvalidStreamInfoError(name)) m = Metadata(value_dict) if 'txid' in result: - d = self._save_name_metadata(name, m['sources']['lbry_sd_hash'], str(result['txid'])) + d = self._save_name_metadata(name, str(result['txid']), m['sources']['lbry_sd_hash']) d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, m.metaversion))) d.addCallback(lambda _: m) return d @@ -398,7 +398,8 @@ class LBRYWallet(object): d = self._send_name_claim(name, json.dumps(metadata), bid) def _save_metadata(txid): - d = self._save_name_metadata(name, metadata['sources']['lbry_sd_hash'], txid) + log.info("Saving metadata for claim %s" % txid) + d = self._save_name_metadata(name, txid, metadata['sources']['lbry_sd_hash']) d.addCallback(lambda _: txid) return d @@ -443,10 +444,12 @@ class LBRYWallet(object): d.addCallback(self._get_decoded_tx) return d - def update_name(self, name, value, amount): + def update_name(self, name, bid, value, old_txid): d = self._get_value_for_name(name) - d.addCallback(lambda r: (self._update_name(r['txid'], json.dumps(value), amount), r['txid'])) - d.addCallback(lambda (new_txid, old_txid): self._update_name_metadata(name, value['sources']['lbry_sd_hash'], old_txid, new_txid)) + d.addCallback(lambda r: self.abandon_name(r['txid'] if not old_txid else old_txid)) + d.addCallback(lambda r: log.info("Abandon claim tx %s" % str(r))) + d.addCallback(lambda _: self.claim_name(name, bid, value)) + return d def get_name_and_validity_for_sd_hash(self, sd_hash): @@ -556,19 +559,13 @@ class LBRYWallet(object): " txid text, " + " sd_hash text)") - def _save_name_metadata(self, name, sd_hash, txid): - d = self.db.runQuery("select * from name_metadata where txid=?", (txid,)) + def _save_name_metadata(self, name, txid, sd_hash): + d = self.db.runQuery("select * from name_metadata where name=? and txid=? and sd_hash=?", (name, txid, sd_hash)) d.addCallback(lambda r: self.db.runQuery("insert into name_metadata values (?, ?, ?)", (name, txid, sd_hash)) if not len(r) else None) return d - def _update_name_metadata(self, name, sd_hash, old_txid, new_txid): - d = self.db.runQuery("delete * from name_metadata where txid=? and sd_hash=?", (old_txid, sd_hash)) - d.addCallback(lambda _: self.db.runQuery("insert into name_metadata values (?, ?, ?)", (name, new_txid, sd_hash))) - d.addCallback(lambda _: new_txid) - return d - def _get_claim_metadata_for_sd_hash(self, sd_hash): d = self.db.runQuery("select name, txid from name_metadata where sd_hash=?", (sd_hash,)) d.addCallback(lambda r: r[0] if len(r) else None) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index aedd5c5c6..e7b95bdf4 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1899,24 +1899,26 @@ class LBRYDaemon(jsonrpc.JSONRPC): file_path = p['file_path'] metadata = p['metadata'] - d = defer.succeed(None) + update = False def _set_address(address, currency): log.info("Generated new address for key fee: " + str(address)) metadata['fee'][currency]['address'] = address return defer.succeed(None) + d = defer.succeed(None) + if 'fee' in p: metadata['fee'] = p['fee'] assert len(metadata['fee']) == 1, "Too many fees" for c in metadata['fee']: if 'address' not in metadata['fee'][c]: - d = self.session.wallet.get_new_address() + d.addCallback(lambda _: self.session.wallet.get_new_address()) d.addCallback(lambda addr: _set_address(addr, c)) pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) - - d.addCallback(lambda _: pub.start(name, file_path, bid, metadata)) + d.addCallback(lambda _: self._get_lbry_file_by_uri(name)) + d.addCallback(lambda r: pub.start(name, file_path, bid, metadata, r.txid)) d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) @@ -2181,27 +2183,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d - def jsonrpc_update_name(self, p): - """ - Update name claim - - Args: - 'name': the uri of the claim to be updated - 'metadata': new metadata dict - 'amount': bid amount of updated claim - Returns: - txid - """ - - name = p['name'] - metadata = p['metadata'] if isinstance(p['metadata'], dict) else json.loads(p['metadata']) - amount = p['amount'] - - d = self.session.wallet.update_name(name, metadata, amount) - d.addCallback(lambda r: self._render_response(r, OK_CODE)) - - return d - def jsonrpc_log(self, p): """ Log message diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index 10f787209..7ff284577 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -45,7 +45,7 @@ class Publisher(object): self.txid = None self.metadata = {} - def start(self, name, file_path, bid, metadata): + def start(self, name, file_path, bid, metadata, old_txid=None): def _show_result(): log.info("Published %s --> lbry://%s txid: %s", self.file_name, self.publish_name, self.txid) @@ -55,6 +55,7 @@ class Publisher(object): self.file_path = file_path self.bid_amount = bid self.metadata = metadata + self.old_txid = old_txid d = self._check_file_path(self.file_path) d.addCallback(lambda _: create_lbry_file(self.session, self.lbry_file_manager, @@ -102,9 +103,16 @@ class Publisher(object): 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, - Metadata(self.metadata)) + + if self.old_txid: + d = self.wallet.update_name(self.publish_name, + self.bid_amount, + Metadata(self.metadata), + self.old_txid) + else: + d = self.wallet.claim_name(self.publish_name, + self.bid_amount, + Metadata(self.metadata)) def set_tx_hash(txid): self.txid = txid From 3e602bce529ee143fe56c8b30d94037961013794 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 20:58:37 -0400 Subject: [PATCH 412/462] fix publish callback --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index e7b95bdf4..b66147b64 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1918,7 +1918,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) d.addCallback(lambda _: self._get_lbry_file_by_uri(name)) - d.addCallback(lambda r: pub.start(name, file_path, bid, metadata, r.txid)) + d.addCallback(lambda r: pub.start(name, file_path, bid, metadata, None if r is None else r.txid)) d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) From 29b5aef73a8b491c2218bcf0b58e4632bca97d84 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 21:24:58 -0400 Subject: [PATCH 413/462] call restore() after claiming name --- lbrynet/lbrynet_daemon/LBRYPublisher.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index 7ff284577..4d27a6973 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -43,6 +43,7 @@ class Publisher(object): self.verified = False self.lbry_file = None self.txid = None + self.stream_hash = None self.metadata = {} def start(self, name, file_path, bid, metadata, old_txid=None): @@ -63,6 +64,7 @@ class Publisher(object): d.addCallback(self.add_to_lbry_files) d.addCallback(lambda _: self._create_sd_blob()) d.addCallback(lambda _: self._claim_name()) + d.addCallback(lambda _: self.set_status()) d.addCallbacks(lambda _: _show_result(), self._show_publish_error) return d @@ -75,16 +77,15 @@ class Publisher(object): return True return threads.deferToThread(check_file_threaded) - def set_status(self, lbry_file_downloader): + def set_lbry_file(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) - d.addCallback(lambda _: lbry_file_downloader.restore()) - return d + return defer.succeed(None) def add_to_lbry_files(self, stream_hash): + self.stream_hash = 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) + d.addCallback(self.set_lbry_file) return d def _create_sd_blob(self): @@ -99,6 +100,11 @@ class Publisher(object): d.addCallback(set_sd_hash) return d + def set_status(self): + d = self.lbry_file_manager.change_lbry_file_status(self.lbry_file, ManagedLBRYFileDownloader.STATUS_FINISHED) + d.addCallback(lambda _: self.lbry_file.restore()) + return d + 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] From 22f73a081769984f2e637f1e2dfb8a5850aa4e81 Mon Sep 17 00:00:00 2001 From: Jack Date: Tue, 26 Jul 2016 21:46:04 -0400 Subject: [PATCH 414/462] add force parameter to resolve_name --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index b66147b64..8c04cf345 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1671,12 +1671,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): metadata from name claim """ - if 'name' in p.keys(): + if 'force' in p: + force = p['force'] + else: + force = False + + if 'name' in p: name = p['name'] else: return self._render_response(None, BAD_REQUEST) - d = self._resolve_name(name) + d = self._resolve_name(name, force_refresh=force) d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure) return d From 5abeceac1fd39e147caa0955173ed58bc58e1c6c Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jul 2016 01:13:41 -0400 Subject: [PATCH 415/462] abandon previous claim in update --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 11 +++++++++-- lbrynet/lbrynet_daemon/LBRYPublisher.py | 12 +++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 8c04cf345..6ecafb741 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1911,7 +1911,13 @@ class LBRYDaemon(jsonrpc.JSONRPC): metadata['fee'][currency]['address'] = address return defer.succeed(None) - d = defer.succeed(None) + def _delete_data(lbry_file): + txid = lbry_file.txid + d = self._delete_lbry_file(lbry_file, delete_file=False) + d.addCallback(lambda _: txid) + return d + + d = self._resolve_name(name, force_refresh=True) if 'fee' in p: metadata['fee'] = p['fee'] @@ -1923,7 +1929,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) d.addCallback(lambda _: self._get_lbry_file_by_uri(name)) - d.addCallback(lambda r: pub.start(name, file_path, bid, metadata, None if r is None else r.txid)) + d.addCallback(lambda l: None if not l else _delete_data(l)) + d.addCallback(lambda r: pub.start(name, file_path, bid, metadata, r)) 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/LBRYPublisher.py b/lbrynet/lbrynet_daemon/LBRYPublisher.py index 4d27a6973..f27e6e48b 100644 --- a/lbrynet/lbrynet_daemon/LBRYPublisher.py +++ b/lbrynet/lbrynet_daemon/LBRYPublisher.py @@ -46,7 +46,7 @@ class Publisher(object): self.stream_hash = None self.metadata = {} - def start(self, name, file_path, bid, metadata, old_txid=None): + def start(self, name, file_path, bid, metadata, old_txid): def _show_result(): log.info("Published %s --> lbry://%s txid: %s", self.file_name, self.publish_name, self.txid) @@ -111,10 +111,12 @@ class Publisher(object): self.metadata['ver'] = CURRENT_METADATA_VERSION if self.old_txid: - d = self.wallet.update_name(self.publish_name, - self.bid_amount, - Metadata(self.metadata), - self.old_txid) + + d = self.wallet.abandon_name(self.old_txid) + d.addCallback(lambda tx: log.info("Abandoned tx %s" % str(tx))) + d.addCallback(lambda _: self.wallet.claim_name(self.publish_name, + self.bid_amount, + Metadata(self.metadata))) else: d = self.wallet.claim_name(self.publish_name, self.bid_amount, From f3af7a6b77c2dfebff36082fff910aa153ecf0d8 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jul 2016 01:39:23 -0400 Subject: [PATCH 416/462] add errback for unknown name --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 6ecafb741..3e814c401 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1918,6 +1918,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d d = self._resolve_name(name, force_refresh=True) + d.addErrback(lambda _: None) if 'fee' in p: metadata['fee'] = p['fee'] From 2102776be81d1eb5a5a971293f668f35da18c0f6 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jul 2016 01:44:01 -0400 Subject: [PATCH 417/462] another errback --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 3e814c401..237bdadad 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1904,8 +1904,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): file_path = p['file_path'] metadata = p['metadata'] - update = False - def _set_address(address, currency): log.info("Generated new address for key fee: " + str(address)) metadata['fee'][currency]['address'] = address @@ -1930,7 +1928,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet) d.addCallback(lambda _: self._get_lbry_file_by_uri(name)) - d.addCallback(lambda l: None if not l else _delete_data(l)) + d.addCallbacks(lambda l: None if not l else _delete_data(l), lambda _: None) d.addCallback(lambda r: pub.start(name, file_path, bid, metadata, r)) d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) From ef580a7680b5316b240662c38dff58602d9b7a24 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jul 2016 01:51:09 -0400 Subject: [PATCH 418/462] errback for pending of date lbry_file --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 237bdadad..c1206a030 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1311,7 +1311,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): if f.txid: d = self._resolve_name(f.uri) - d.addCallback(_add_to_dict) + d.addCallbacks(_add_to_dict, lambda _: _add_to_dict("Pending confirmation")) else: d = defer.succeed(message) return d From 4604e9cd5488919b359063d5ff9a32bda1e6a82d Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jul 2016 03:00:44 -0400 Subject: [PATCH 419/462] check pending claims after one is made --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 30 ++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index c1206a030..a311555d3 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -172,6 +172,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.session = None self.waiting_on = {} self.streams = {} + self.pending_claims = {} self.known_dht_nodes = KNOWN_DHT_NODES self.first_run_after_update = False self.uploaded_temp_files = [] @@ -362,6 +363,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.internet_connection_checker = LoopingCall(self._check_network_connection) self.version_checker = LoopingCall(self._check_remote_versions) self.connection_problem_checker = LoopingCall(self._check_connection_problems) + self.pending_claim_checker = LoopingCall(self._check_pending_claims) # self.lbrynet_connection_checker = LoopingCall(self._check_lbrynet_connection) self.sd_identifier = StreamDescriptorIdentifier() @@ -609,6 +611,24 @@ class LBRYDaemon(jsonrpc.JSONRPC): if not self.connected_to_internet: self.connection_problem = CONNECTION_PROBLEM_CODES[1] + def _add_to_pending_claims(self, name, txid): + log.info("Adding lbry://%s to pending claims, txid %s" % (name, txid)) + self.pending_claims[name] = txid + return txid + + def _check_pending_claims(self): + def _start_file(name): + d = defer.succeed(self.pending_claims.pop(name)) + d.addCallback(lambda _: self._get_lbry_file("name", name, return_json=False)) + d.addCallback(lambda l: _start_file(l) if l.stopped else "LBRY file was already running") + + for name in self.pending_claims: + log.info("Checking if new claim for lbry://%s is confirmed" % name) + d = self._resolve_name(name, force_refresh=True) + d.addCallback(lambda _: self._get_lbry_file_by_uri(name)) + d.addCallbacks(lambda lbry_file: _start_file(name) if self.pending_claims[name] == lbry_file['txid'] and not isinstance(lbry_file['metadata'], str) else self._add_to_pending_claims(name, self.pending_claims.pop(name)), + self._add_to_pending_claims(name, self.pending_claims.pop(name))) + def _start_server(self): if self.peer_port is not None: @@ -727,6 +747,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connection_problem_checker.stop() if self.lbry_ui_manager.update_checker.running: self.lbry_ui_manager.update_checker.stop() + if self.pending_claim_checker.running: + self.pending_claim_checker.stop() self._clean_up_temp_files() @@ -1915,6 +1937,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: txid) return d + if not self.pending_claim_checker.running: + self.pending_claim_checker.start(30) + d = self._resolve_name(name, force_refresh=True) d.addErrback(lambda _: None) @@ -1930,8 +1955,9 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._get_lbry_file_by_uri(name)) d.addCallbacks(lambda l: None if not l else _delete_data(l), lambda _: None) d.addCallback(lambda r: pub.start(name, file_path, bid, metadata, r)) - d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE), - lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) + d.addCallback(lambda txid: self._add_to_pending_claims(name, txid)) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + d.addErrback(lambda err: self._render_response(err.getTraceback(), BAD_REQUEST)) return d From 36722cbbd89e546ff26eb5cfa16870ccd8510ad9 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jul 2016 03:58:25 -0400 Subject: [PATCH 420/462] Bump version: 0.3.10 -> 0.3.11 --- .bumpversion.cfg | 2 +- lbrynet/__init__.py | 2 +- packaging/ubuntu/lbry.desktop | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 715cdfe80..39e7a0c79 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.10 +current_version = 0.3.11 commit = True tag = True message = Bump version: {current_version} -> {new_version} diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 969401685..f18ed3b6d 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.ERROR) -__version__ = "0.3.10" +__version__ = "0.3.11" version = tuple(__version__.split('.')) \ No newline at end of file diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index 7adaf1da7..53b09e185 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.3.10 +Version=0.3.11 Name=LBRY Comment=The world's first user-owned content marketplace Icon=lbry From 0f602e0e62db636f0ce875635c964845dfa19a7e Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 27 Jul 2016 11:59:57 -0500 Subject: [PATCH 421/462] allow bittrex fee to be changed --- lbrynet/core/LBRYMetadata.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index e57ee1ad6..106fabe1e 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -55,8 +55,9 @@ class LBRYFeeFormat(dict): class LBRYFee(LBRYFeeFormat): - def __init__(self, fee_dict, rate_dict): + def __init__(self, fee_dict, rate_dict, bittrex_fee=None): LBRYFeeFormat.__init__(self, fee_dict) + self.bittrex_fee = BITTREX_FEE if bittrex_fee is None else bittrex_fee rates = deepcopy(rate_dict) assert 'BTCLBC' in rates and 'USDBTC' in rates @@ -88,23 +89,15 @@ class LBRYFee(LBRYFeeFormat): return r def _usd_to_btc(self, usd): - # log.error("usd to btc: " + str(usd)) - # log.error("%f * %f = %f" % (self._USDBTC['spot'], float(usd), self._USDBTC['spot'] * float(usd))) return self._USDBTC['spot'] * float(usd) def _btc_to_usd(self, btc): - # log.error("btc to usd: " + str(btc)) - # log.error("%f / %f = %f" % (float(btc), self._USDBTC['spot'], float(btc) / self._USDBTC['spot'])) return float(btc) / self._USDBTC['spot'] def _btc_to_lbc(self, btc): - # log.error("btc to lbc: " + str(btc)) - # log.error("%f * %f = %f" % (float(btc), self._BTCLBC['spot'], float(btc) * self._BTCLBC['spot'] / (1.0 - BITTREX_FEE))) - return float(btc) * self._BTCLBC['spot'] / (1.0 - BITTREX_FEE) + return float(btc) * self._BTCLBC['spot'] / (1.0 - self.bittrex_fee) def _lbc_to_btc(self, lbc): - # log.error("lbc to btc: " + str(lbc)) - # log.error("%f / %f = %f" % (self._BTCLBC['spot'], float(lbc), self._BTCLBC['spot'] / float(lbc))) return self._BTCLBC['spot'] / float(lbc) From 6a1efedc1fb4ddf89079e6b2c7185448feeb1100 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 27 Jul 2016 12:05:29 -0500 Subject: [PATCH 422/462] some example tests. please expand on these --- tests/lbrynet/core/test_LBRYMetadata.py | 102 ++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 tests/lbrynet/core/test_LBRYMetadata.py diff --git a/tests/lbrynet/core/test_LBRYMetadata.py b/tests/lbrynet/core/test_LBRYMetadata.py new file mode 100644 index 000000000..ac88ffa77 --- /dev/null +++ b/tests/lbrynet/core/test_LBRYMetadata.py @@ -0,0 +1,102 @@ +import mock +from lbrynet.core import LBRYMetadata + +from twisted.trial import unittest + + +class LBRYFeeFormatTest(unittest.TestCase): + def test_fee_created_with_correct_inputs(self): + fee_dict = { + 'USD': { + 'amount': 10, + 'address': None + } + } + fee = LBRYMetadata.LBRYFeeFormat(fee_dict) + self.assertEqual(10, fee['USD']['amount']) + + +class LBRYFeeTest(unittest.TestCase): + def setUp(self): + self.patcher = mock.patch('time.time') + self.time = self.patcher.start() + self.time.return_value = 0 + + def tearDown(self): + self.time.stop() + + def test_fee_converts_to_lbc(self): + fee_dict = { + 'USD': { + 'amount': 10, + 'address': None + } + } + rates = {'BTCLBC': {'spot': 3, 'ts': 2}, 'USDBTC': {'spot': 2, 'ts': 3}} + fee = LBRYMetadata.LBRYFee(fee_dict, rates, 0) + self.assertEqual(60, fee.to_lbc()) + + +class MetadataTest(unittest.TestCase): + def test_assertion_if_source_is_missing(self): + metadata = {} + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + + def test_assertion_if_invalid_source(self): + metadata = { + 'sources': {'garbage': None} + } + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + + def test_assertion_if_missing_v001_field(self): + metadata = { + 'sources': [], + } + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + + def test_version_is_001_if_all_fields_are_present(self): + metadata = { + 'sources': [], + 'title': None, + 'description': None, + 'author': None, + 'language': None, + 'license': None, + 'content-type': None, + } + m = LBRYMetadata.Metadata(metadata) + self.assertEquals('0.0.1', m.metaversion) + + def test_assertion_if_there_is_an_extra_field(self): + metadata = { + 'sources': [], + 'title': None, + 'description': None, + 'author': None, + 'language': None, + 'license': None, + 'content-type': None, + 'extra': None + } + with self.assertRaises(AssertionError): + LBRYMetadata.Metadata(metadata) + + def test_version_is_002_if_all_fields_are_present(self): + metadata = { + 'sources': [], + 'title': None, + 'description': None, + 'author': None, + 'language': None, + 'license': None, + 'content-type': None, + 'nsfw': None, + 'ver': None + } + m = LBRYMetadata.Metadata(metadata) + self.assertEquals('0.0.2', m.metaversion) + + From 900d3ad01d12ca5e39783eaac79802324efb8961 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 27 Jul 2016 13:12:20 -0500 Subject: [PATCH 423/462] Fixes errback not being callable Small code refactoring to make it more obvious what both the callback and errback are doing --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 2408f855b..e5ced6588 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -609,12 +609,27 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._get_lbry_file("name", name, return_json=False)) d.addCallback(lambda l: _start_file(l) if l.stopped else "LBRY file was already running") + def _process_lbry_file(name, lbry_file): + ready_to_start = ( + self.pending_claims[name] == lbry_file['txid'] and + not isinstance(lbry_file['metadata'], str) + ) + if ready_to_start: + _start_file(name) + else: + re_add_to_pending_claims(name) + + def re_add_to_pending_claims(name): + self._add_to_pending_claims(name, self.pending_claims.pop(name)), + for name in self.pending_claims: log.info("Checking if new claim for lbry://%s is confirmed" % name) d = self._resolve_name(name, force_refresh=True) d.addCallback(lambda _: self._get_lbry_file_by_uri(name)) - d.addCallbacks(lambda lbry_file: _start_file(name) if self.pending_claims[name] == lbry_file['txid'] and not isinstance(lbry_file['metadata'], str) else self._add_to_pending_claims(name, self.pending_claims.pop(name)), - self._add_to_pending_claims(name, self.pending_claims.pop(name))) + d.addCallbacks( + lambda lbry_file: _process_lbry_file(name, lbry_file), + lambda _: re_add_to_pending_claims(name) + ) def _start_server(self): if self.peer_port is not None: From 0925171e0795421a2d7be997be2844a07ec7d091 Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Wed, 27 Jul 2016 13:59:26 -0500 Subject: [PATCH 424/462] bug fix: use txid attribute, not key --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index e5ced6588..20f0577f3 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -610,8 +610,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda l: _start_file(l) if l.stopped else "LBRY file was already running") def _process_lbry_file(name, lbry_file): + # lbry_file is an instance of ManagedLBRYFileDownloader or None ready_to_start = ( - self.pending_claims[name] == lbry_file['txid'] and + lbry_file and + self.pending_claims[name] == lbry_file.txid and not isinstance(lbry_file['metadata'], str) ) if ready_to_start: @@ -620,7 +622,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): re_add_to_pending_claims(name) def re_add_to_pending_claims(name): - self._add_to_pending_claims(name, self.pending_claims.pop(name)), + txid = self.pending_claims.pop(name) + self._add_to_pending_claims(name, txid) for name in self.pending_claims: log.info("Checking if new claim for lbry://%s is confirmed" % name) From 7f5560de6dcc5f8ca21eff8f53cdc979c72a2fdd Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jul 2016 15:02:19 -0400 Subject: [PATCH 425/462] comment spacing and removing setLevel --- lbrynet/core/LBRYMetadata.py | 8 +++----- lbrynet/core/LBRYWallet.py | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index e57ee1ad6..5970fba3f 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -8,7 +8,6 @@ from lbrynet.conf import CURRENCIES import logging log = logging.getLogger(__name__) -log.setLevel(logging.INFO) BITTREX_FEE = 0.0025 @@ -16,14 +15,13 @@ 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 +# v0.0.1 metadata METADATA_REVISIONS = {'0.0.1': {'required': BASE_METADATA_FIELDS, 'optional': OPTIONAL_METADATA_FIELDS}} -#v0.0.2 metadata additions +# v0.0.2 metadata additions METADATA_REVISIONS['0.0.2'] = {'required': ['nsfw', 'ver'], 'optional': ['license_url']} CURRENT_METADATA_VERSION = '0.0.2' - -#v0.0.1 fee +# v0.0.1 fee FEE_REVISIONS = {'0.0.1': {'required': ['amount', 'address'], 'optional': []}} CURRENT_FEE_REVISION = '0.0.1' diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 21f08bba1..3e7573a6e 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -32,7 +32,6 @@ from lbrynet.conf import SOURCE_TYPES from lbrynet.core.LBRYMetadata import Metadata log = logging.getLogger(__name__) -log.setLevel(logging.INFO) alert = logging.getLogger("lbryalert." + __name__) From e4ebbd8f6982b699d2bd8eb14d6a0abd28ec0a51 Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jul 2016 15:02:54 -0400 Subject: [PATCH 426/462] neater force param in resolve_name --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 2408f855b..45e7f678b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1680,10 +1680,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): metadata from name claim """ - if 'force' in p: - force = p['force'] - else: - force = False + force = p.get('force', False) if 'name' in p: name = p['name'] From f6d91280d7a8394e0040a024b3340be4e79dbcdd Mon Sep 17 00:00:00 2001 From: Jack Date: Wed, 27 Jul 2016 15:03:03 -0400 Subject: [PATCH 427/462] remove debug print line --- lbrynet/lbrynet_daemon/LBRYDaemonServer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py index ba807835e..3f2107079 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemonServer.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemonServer.py @@ -67,7 +67,6 @@ class LBRYDaemonRequest(server.Request): from twisted.web.http import parse_qs if self.do_log: print '%f Request Received' % time.time() - print self.content self.content.seek(0,0) self.args = {} From 4ade80c8a93124efac90e0110bc67b29cda9aae9 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 02:19:45 -0400 Subject: [PATCH 428/462] clean up metadata and fee --- lbrynet/core/LBRYMetadata.py | 97 +++++++++++++++--------- lbrynet/core/LBRYWallet.py | 2 +- lbrynet/lbrynet_daemon/LBRYDownloader.py | 43 ++++++----- tests/lbrynet/core/test_LBRYMetadata.py | 6 +- 4 files changed, 89 insertions(+), 59 deletions(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index b3d80cb91..beed9bf12 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -26,35 +26,50 @@ FEE_REVISIONS = {'0.0.1': {'required': ['amount', 'address'], 'optional': []}} CURRENT_FEE_REVISION = '0.0.1' -class LBRYFeeFormat(dict): +class LBRYFeeValidator(dict): def __init__(self, fee_dict): dict.__init__(self) - self.fee_version = None - f = deepcopy(fee_dict) assert len(fee_dict) == 1 - for currency in fee_dict: - assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency) - self.currency_symbol = currency - self.update({currency: {}}) - for version in FEE_REVISIONS: - for k in FEE_REVISIONS[version]['required']: - assert k in fee_dict[currency], "Missing required fee field: %s" % k - self[currency].update({k: f[currency].pop(k)}) - for k in FEE_REVISIONS[version]['optional']: - if k in fee_dict[currency]: - self[currency].update({k: f[currency].pop(k)}) - if not len(f): - self.fee_version = version - break - assert f[currency] == {}, "Unknown fee keys: %s" % json.dumps(f.keys()) + self.fee_version = None - self.amount = self[self.currency_symbol]['amount'] if isinstance(self[self.currency_symbol]['amount'], float) else float(self[self.currency_symbol]['amount']) + 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): + if isinstance(self[self.currency_symbol]['amount'], float): + return self[self.currency_symbol]['amount'] + else: + return float(self[self.currency_symbol]['amount']) -class LBRYFee(LBRYFeeFormat): + 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 LBRYFee(LBRYFeeValidator): def __init__(self, fee_dict, rate_dict, bittrex_fee=None): - LBRYFeeFormat.__init__(self, fee_dict) + LBRYFeeValidator.__init__(self, fee_dict) self.bittrex_fee = BITTREX_FEE if bittrex_fee is None else bittrex_fee rates = deepcopy(rate_dict) @@ -102,25 +117,35 @@ class LBRYFee(LBRYFeeFormat): class Metadata(dict): def __init__(self, metadata): dict.__init__(self) - self.metaversion = None - m = deepcopy(metadata) + 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['fee'] = LBRYFeeValidator(metadata.pop('fee')) + 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: - for k in METADATA_REVISIONS[version]['required']: - assert k in metadata, "Missing required metadata field: %s" % k - self.update({k: m.pop(k)}) - for k in METADATA_REVISIONS[version]['optional']: - if k == 'fee': - pass - elif k in metadata: - self.update({k: m.pop(k)}) - if not len(m) or m.keys() == ['fee']: - self.metaversion = version + self._load_revision(version, metadata) + if not metadata: + self.meta_version = version break - if 'fee' in m: - self['fee'] = LBRYFeeFormat(m.pop('fee')) - assert m == {}, "Unknown metadata keys: %s" % json.dumps(m.keys()) + assert metadata == {}, "Unknown metadata keys: %s" % json.dumps(metadata.keys()) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 3e7573a6e..1e567169d 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -379,7 +379,7 @@ class LBRYWallet(object): m = Metadata(value_dict) if 'txid' in result: d = self._save_name_metadata(name, str(result['txid']), m['sources']['lbry_sd_hash']) - d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, m.metaversion))) + d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, m.meta_version))) d.addCallback(lambda _: m) return d elif 'error' in result: diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 0056101c5..8106b1fd4 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -84,26 +84,14 @@ class GetStream(object): self.code = STREAM_STAGES[4] self.finished.callback(False) + def _convert_max_fee(self): + if isinstance(self.max_key_fee, dict): + max_fee = deepcopy(self.max_key_fee) + return LBRYFee(max_fee, {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}).to_lbc() + elif isinstance(self.max_key_fee, float): + return float(self.max_key_fee) + def start(self, stream_info, name): - self.resolved_name = name - self.stream_info = deepcopy(stream_info) - self.description = self.stream_info['description'] - self.stream_hash = self.stream_info['sources']['lbry_sd_hash'] - - if 'fee' in self.stream_info: - self.fee = LBRYFee(self.stream_info['fee'], {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) - if isinstance(self.max_key_fee, float): - if self.fee.to_lbc() > self.max_key_fee: - log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), self.max_key_fee, self.resolved_name)) - return defer.fail(KeyFeeAboveMaxAllowed()) - log.info("Key fee %f below limit of %f, downloading lbry://%s" % (self.fee.to_lbc(), self.max_key_fee, self.resolved_name)) - elif isinstance(self.max_key_fee, dict): - max_key = LBRYFee(deepcopy(self.max_key_fee), {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) - if self.fee.to_lbc() > max_key.to_lbc(): - log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), max_key.to_lbc(), self.resolved_name)) - return defer.fail(KeyFeeAboveMaxAllowed()) - log.info("Key fee %f below limit of %f, downloading lbry://%s" % (self.fee.to_lbc(), max_key.to_lbc(), self.resolved_name)) - def _cause_timeout(err): log.error(err) log.debug('Forcing a timeout') @@ -128,6 +116,23 @@ class GetStream(object): download_directory=self.download_directory, file_name=self.file_name) + self.resolved_name = name + self.stream_info = deepcopy(stream_info) + self.description = self.stream_info['description'] + self.stream_hash = self.stream_info['sources']['lbry_sd_hash'] + + if 'fee' in self.stream_info: + self.fee = LBRYFee(self.stream_info['fee'], {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) + max_key_fee = self._convert_max_fee() + if self.fee.to_lbc() > max_key_fee: + log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), + self.max_key_fee, + self.resolved_name)) + return defer.fail(KeyFeeAboveMaxAllowed()) + log.info("Key fee %f below limit of %f, downloading lbry://%s" % (self.fee.to_lbc(), + max_key_fee, + self.resolved_name)) + self.checker.start(1) self.d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE)) diff --git a/tests/lbrynet/core/test_LBRYMetadata.py b/tests/lbrynet/core/test_LBRYMetadata.py index ac88ffa77..643fc5d31 100644 --- a/tests/lbrynet/core/test_LBRYMetadata.py +++ b/tests/lbrynet/core/test_LBRYMetadata.py @@ -12,7 +12,7 @@ class LBRYFeeFormatTest(unittest.TestCase): 'address': None } } - fee = LBRYMetadata.LBRYFeeFormat(fee_dict) + fee = LBRYMetadata.LBRYFeeValidator(fee_dict) self.assertEqual(10, fee['USD']['amount']) @@ -68,7 +68,7 @@ class MetadataTest(unittest.TestCase): 'content-type': None, } m = LBRYMetadata.Metadata(metadata) - self.assertEquals('0.0.1', m.metaversion) + self.assertEquals('0.0.1', m.meta_version) def test_assertion_if_there_is_an_extra_field(self): metadata = { @@ -97,6 +97,6 @@ class MetadataTest(unittest.TestCase): 'ver': None } m = LBRYMetadata.Metadata(metadata) - self.assertEquals('0.0.2', m.metaversion) + self.assertEquals('0.0.2', m.meta_version) From 7d22bfffa5e98b6144f24988b5cb36e06c515fb6 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 02:30:37 -0400 Subject: [PATCH 429/462] fee tests --- tests/lbrynet/core/test_LBRYMetadata.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/lbrynet/core/test_LBRYMetadata.py b/tests/lbrynet/core/test_LBRYMetadata.py index 643fc5d31..e3910ed1a 100644 --- a/tests/lbrynet/core/test_LBRYMetadata.py +++ b/tests/lbrynet/core/test_LBRYMetadata.py @@ -8,12 +8,12 @@ class LBRYFeeFormatTest(unittest.TestCase): def test_fee_created_with_correct_inputs(self): fee_dict = { 'USD': { - 'amount': 10, - 'address': None + 'amount': 10.0, + 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" } } fee = LBRYMetadata.LBRYFeeValidator(fee_dict) - self.assertEqual(10, fee['USD']['amount']) + self.assertEqual(10.0, fee['USD']['amount']) class LBRYFeeTest(unittest.TestCase): @@ -28,13 +28,13 @@ class LBRYFeeTest(unittest.TestCase): def test_fee_converts_to_lbc(self): fee_dict = { 'USD': { - 'amount': 10, - 'address': None + 'amount': 10.0, + 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" } } - rates = {'BTCLBC': {'spot': 3, 'ts': 2}, 'USDBTC': {'spot': 2, 'ts': 3}} + rates = {'BTCLBC': {'spot': 3.0, 'ts': 2}, 'USDBTC': {'spot': 2.0, 'ts': 3}} fee = LBRYMetadata.LBRYFee(fee_dict, rates, 0) - self.assertEqual(60, fee.to_lbc()) + self.assertEqual(60.0, fee.to_lbc()) class MetadataTest(unittest.TestCase): From 12b2bbfa269425b23891cd4640ddb9ba624f6bf7 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 02:36:07 -0400 Subject: [PATCH 430/462] make sure claimed ver is true --- lbrynet/core/LBRYMetadata.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index beed9bf12..9def7f8cd 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -5,6 +5,7 @@ import time from copy import deepcopy from googlefinance import getQuotes from lbrynet.conf import CURRENCIES +from lbrynet.core import utils import logging log = logging.getLogger(__name__) @@ -147,5 +148,7 @@ class Metadata(dict): 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()) From e058c7101973e690b7ce2b0c9be22594fdcbdb34 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 02:46:46 -0400 Subject: [PATCH 431/462] test data --- tests/lbrynet/core/test_LBRYMetadata.py | 83 +++++++++++++++++-------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/tests/lbrynet/core/test_LBRYMetadata.py b/tests/lbrynet/core/test_LBRYMetadata.py index e3910ed1a..2726f6a6d 100644 --- a/tests/lbrynet/core/test_LBRYMetadata.py +++ b/tests/lbrynet/core/test_LBRYMetadata.py @@ -45,58 +45,89 @@ class MetadataTest(unittest.TestCase): def test_assertion_if_invalid_source(self): metadata = { - 'sources': {'garbage': None} + '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", + 'author': 'Written and directed by Todd Berger', + 'sources': { + 'fake': 'source'}, + '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_assertion_if_missing_v001_field(self): metadata = { - 'sources': [], + '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', + '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' } with self.assertRaises(AssertionError): LBRYMetadata.Metadata(metadata) def test_version_is_001_if_all_fields_are_present(self): metadata = { - 'sources': [], - 'title': None, - 'description': None, - 'author': None, - 'language': None, - 'license': None, - 'content-type': None, + '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", + '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) self.assertEquals('0.0.1', m.meta_version) def test_assertion_if_there_is_an_extra_field(self): metadata = { - 'sources': [], - 'title': None, - 'description': None, - 'author': None, - 'language': None, - 'license': None, - 'content-type': None, - 'extra': None + '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', + 'MYSTERYFIELD': '?' } with self.assertRaises(AssertionError): LBRYMetadata.Metadata(metadata) def test_version_is_002_if_all_fields_are_present(self): metadata = { - 'sources': [], - 'title': None, - 'description': None, - 'author': None, - 'language': None, - 'license': None, - 'content-type': None, - 'nsfw': None, - 'ver': None + '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) self.assertEquals('0.0.2', m.meta_version) + + + From 4223298634ba1e5900ce600e622e30d7141b8331 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 02:49:35 -0400 Subject: [PATCH 432/462] test wrong metadata version --- tests/lbrynet/core/test_LBRYMetadata.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/lbrynet/core/test_LBRYMetadata.py b/tests/lbrynet/core/test_LBRYMetadata.py index 2726f6a6d..e474e824e 100644 --- a/tests/lbrynet/core/test_LBRYMetadata.py +++ b/tests/lbrynet/core/test_LBRYMetadata.py @@ -127,7 +127,23 @@ class MetadataTest(unittest.TestCase): m = LBRYMetadata.Metadata(metadata) self.assertEquals('0.0.2', m.meta_version) - + def test_version_claimed_is_001_but_version_is_002(self): + metadata = { + 'license': 'NASA', + 'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}}, + 'ver': '0.0.1', + '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) From 415495fc1688a41c763023ef0da4808241ae78ee Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 05:30:13 -0400 Subject: [PATCH 433/462] LBRYExchangeRateManager --- lbrynet/core/LBRYMetadata.py | 48 +----- lbrynet/core/LBRYWallet.py | 49 ------ lbrynet/lbrynet_daemon/LBRYDaemon.py | 8 +- lbrynet/lbrynet_daemon/LBRYDownloader.py | 23 +-- .../lbrynet_daemon/LBRYExchangeRateManager.py | 150 ++++++++++++++++++ 5 files changed, 170 insertions(+), 108 deletions(-) create mode 100644 lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index 9def7f8cd..2bf31c9c7 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -32,6 +32,7 @@ class LBRYFeeValidator(dict): dict.__init__(self) assert len(fee_dict) == 1 self.fee_version = None + self.currency_symbol = None fee_to_load = deepcopy(fee_dict) @@ -68,53 +69,6 @@ class LBRYFeeValidator(dict): self[self.currency_symbol].update({k: f[self.currency_symbol].pop(k)}) -class LBRYFee(LBRYFeeValidator): - def __init__(self, fee_dict, rate_dict, bittrex_fee=None): - LBRYFeeValidator.__init__(self, fee_dict) - self.bittrex_fee = BITTREX_FEE if bittrex_fee is None else bittrex_fee - rates = deepcopy(rate_dict) - - assert 'BTCLBC' in rates and 'USDBTC' in rates - for fx in rate_dict: - assert int(time.time()) - int(rates[fx]['ts']) < 3600, "%s quote is out of date" % fx - self._USDBTC = {'spot': rates['USDBTC']['spot'], 'ts': rates['USDBTC']['ts']} - self._BTCLBC = {'spot': rates['BTCLBC']['spot'], 'ts': rates['BTCLBC']['ts']} - - def to_lbc(self): - r = None - 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) - assert r is not None - return r - - def to_usd(self): - r = None - if self.currency_symbol == "USD": - r = round(float(self.amount), 5) - elif self.currency_symbol == "BTC": - r = round(float(self._btc_to_usd(self.amount)), 5) - elif self.currency_symbol == "LBC": - r = round(float(self._btc_to_usd(self._lbc_to_btc(self.amount))), 5) - assert r is not None - return r - - def _usd_to_btc(self, usd): - return self._USDBTC['spot'] * float(usd) - - def _btc_to_usd(self, btc): - return float(btc) / self._USDBTC['spot'] - - def _btc_to_lbc(self, btc): - return float(btc) * self._BTCLBC['spot'] / (1.0 - self.bittrex_fee) - - def _lbc_to_btc(self, lbc): - return self._BTCLBC['spot'] / float(lbc) - - class Metadata(dict): def __init__(self, metadata): dict.__init__(self) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 1e567169d..7d7ab8db7 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -82,50 +82,6 @@ class LBRYWallet(object): self._batch_count = 20 self._first_run = self._FIRST_RUN_UNKNOWN - self._USDBTC = None - self._BTCLBC = None - self._exchange_rate_updater = task.LoopingCall(self._update_exchange_rates) - - def _usd_to_btc(self): - if self._USDBTC is not None: - if int(time.time()) - int(self._USDBTC['ts']) < 600: - log.info("USDBTC quote is new enough") - return defer.succeed({}) - - log.info("Getting new USDBTC quote") - x = float(getQuotes('CURRENCY:USDBTC')[0]['LastTradePrice']) - return defer.succeed({'USDBTC': {'spot': x, 'ts': int(time.time())}}) - - def _btc_to_lbc(self): - if self._BTCLBC is not None: - if int(time.time()) - int(self._BTCLBC['ts']) < 600: - log.info("BTCLBC quote is new enough") - return defer.succeed({}) - - log.info("Getting new BTCLBC quote") - r = requests.get("https://bittrex.com/api/v1.1/public/getmarkethistory", {'market': 'BTC-LBC', 'count': 50}) - trades = json.loads(r.text)['result'] - vwap = sum([i['Total'] for i in trades]) / sum([i['Quantity'] for i in trades]) - x = (1.0 / float(vwap)) / 0.99975 - - return defer.succeed({'BTCLBC': {'spot': x, 'ts': int(time.time())}}) - - def _set_exchange_rates(self, rates): - if 'USDBTC' in rates: - assert int(time.time()) - int(rates['USDBTC']['ts']) < 3600, "new USDBTC quote is too old" - self._USDBTC = {'spot': rates['USDBTC']['spot'], 'ts': rates['USDBTC']['ts']} - log.info("Updated USDBTC rate: %s" % json.dumps(self._USDBTC)) - if 'BTCLBC' in rates: - assert int(time.time()) - int(rates['BTCLBC']['ts']) < 3600, "new BTCLBC quote is too old" - self._BTCLBC = {'spot': rates['BTCLBC']['spot'], 'ts': rates['BTCLBC']['ts']} - log.info("Updated BTCLBC rate: %s" % json.dumps(self._BTCLBC)) - - def _update_exchange_rates(self): - d = self._usd_to_btc() - d.addCallbacks(self._set_exchange_rates, lambda _: reactor.callLater(30, self._update_exchange_rates)) - d.addCallback(lambda _: self._btc_to_lbc()) - d.addCallbacks(self._set_exchange_rates, lambda _: reactor.callLater(30, self._update_exchange_rates)) - def start(self): def start_manage(): @@ -133,8 +89,6 @@ class LBRYWallet(object): self.manage() return True - self._exchange_rate_updater.start(1800) - d = self._open_db() d.addCallback(lambda _: self._start()) d.addCallback(lambda _: start_manage()) @@ -147,9 +101,6 @@ class LBRYWallet(object): def stop(self): self.stopped = True - - if self._exchange_rate_updater.running: - self._exchange_rate_updater.stop() # If self.next_manage_call is None, then manage is currently running or else # start has not been called, so set stopped and do nothing else. diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 601c6ae0f..89b07e504 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -41,6 +41,7 @@ from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifi from lbrynet.lbrynet_daemon.LBRYUIManager import LBRYUIManager from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher +from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager from lbrynet.core import utils from lbrynet.core.utils import generate_id from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings @@ -157,6 +158,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.current_db_revision = 1 self.run_server = True self.session = None + self.exchange_rate_manager = ExchangeRateManager() self.waiting_on = {} self.streams = {} self.pending_claims = {} @@ -484,6 +486,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.internet_connection_checker.start(3600) self.version_checker.start(3600 * 12) self.connection_problem_checker.start(1) + self.exchange_rate_manager.start() + if host_ui: self.lbry_ui_manager.update_checker.start(1800, now=False) @@ -1122,8 +1126,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) self.streams[name] = GetStream(self.sd_identifier, self.session, self.session.wallet, - self.lbry_file_manager, max_key_fee=self.max_key_fee, - data_rate=self.data_rate, timeout=timeout, + self.lbry_file_manager, self.exchange_rate_manager, + max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout, download_directory=download_directory, file_name=file_name) d = self.streams[name].start(stream_info, name) if wait_for_write: diff --git a/lbrynet/lbrynet_daemon/LBRYDownloader.py b/lbrynet/lbrynet_daemon/LBRYDownloader.py index 8106b1fd4..62d9cd33d 100644 --- a/lbrynet/lbrynet_daemon/LBRYDownloader.py +++ b/lbrynet/lbrynet_daemon/LBRYDownloader.py @@ -12,7 +12,7 @@ from twisted.internet.task import LoopingCall from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError, KeyFeeAboveMaxAllowed from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.StreamDescriptor import download_sd_blob -from lbrynet.core.LBRYMetadata import Metadata, LBRYFee +from lbrynet.core.LBRYMetadata import Metadata, LBRYFeeValidator from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME @@ -42,8 +42,8 @@ log = logging.getLogger(__name__) class GetStream(object): - def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, data_rate=0.5, - timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None): + def __init__(self, sd_identifier, session, wallet, lbry_file_manager, exchange_rate_manager, + max_key_fee, data_rate=0.5, timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None): self.wallet = wallet self.resolved_name = None self.description = None @@ -52,6 +52,7 @@ class GetStream(object): self.name = None self.file_name = file_name self.session = session + self.exchange_rate_manager = exchange_rate_manager self.payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager) self.lbry_file_manager = lbry_file_manager self.sd_identifier = sd_identifier @@ -86,8 +87,10 @@ class GetStream(object): def _convert_max_fee(self): if isinstance(self.max_key_fee, dict): - max_fee = deepcopy(self.max_key_fee) - return LBRYFee(max_fee, {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}).to_lbc() + max_fee = LBRYFeeValidator(self.max_key_fee) + if max_fee.currency_symbol == "LBC": + return max_fee.amount + return self.exchange_rate_manager.to_lbc(self.fee).amount elif isinstance(self.max_key_fee, float): return float(self.max_key_fee) @@ -122,14 +125,14 @@ class GetStream(object): self.stream_hash = self.stream_info['sources']['lbry_sd_hash'] if 'fee' in self.stream_info: - self.fee = LBRYFee(self.stream_info['fee'], {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC}) + self.fee = LBRYFeeValidator(self.stream_info['fee']) max_key_fee = self._convert_max_fee() - if self.fee.to_lbc() > max_key_fee: - log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), + if self.exchange_rate_manager.to_lbc(self.fee).amount > max_key_fee: + log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.amount, self.max_key_fee, self.resolved_name)) return defer.fail(KeyFeeAboveMaxAllowed()) - log.info("Key fee %f below limit of %f, downloading lbry://%s" % (self.fee.to_lbc(), + log.info("Key fee %s below limit of %f, downloading lbry://%s" % (json.dumps(self.fee), max_key_fee, self.resolved_name)) @@ -149,7 +152,7 @@ class GetStream(object): def _start_download(self, downloader): def _pay_key_fee(): if self.fee is not None: - fee_lbc = float(self.fee.to_lbc()) + fee_lbc = self.exchange_rate_manager.to_lbc(self.fee).amount reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc) if reserved_points is None: return defer.fail(InsufficientFundsError()) diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py new file mode 100644 index 000000000..2db0d526a --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -0,0 +1,150 @@ +import time +import requests +import logging +import json +import googlefinance +from twisted.internet import defer, reactor +from twisted.internet.task import LoopingCall + +from lbrynet.core.LBRYMetadata import LBRYFeeValidator + +log = logging.getLogger(__name__) + +CURRENCY_PAIRS = ["USDBTC", "BTCLBC"] +BITTREX_FEE = 0.0025 +COINBASE_FEE = 0.0 #add fee + + +class ExchangeRate(object): + def __init__(self, market, spot, ts): + assert int(time.time()) - ts < 600 + self.currency_pair = (market[0:3], market[3:6]) + self.spot = spot + self.ts = ts + + def as_dict(self): + return {'spot': self.spot, 'ts': self.ts} + + +class MarketFeed(object): + def __init__(self, market, name, url, params, fee): + self.market = market + self.name = name + self.url = url + self.params = params + self.fee = fee + self.rate = None + self._updater = LoopingCall(self._update_price) + + def _make_request(self): + r = requests.get(self.url, self.params) + return r.text + + def _handle_response(self, response): + return NotImplementedError + + def _subtract_fee(self, from_amount): + return defer.succeed(from_amount / (1.0 - self.fee)) + + def _save_price(self, price): + log.info("Saving price update %f for %s" % (price, self.market)) + self.rate = ExchangeRate(self.market, price, int(time.time())) + + def _update_price(self): + d = defer.succeed(self._make_request()) + d.addCallback(self._handle_response) + d.addCallback(self._subtract_fee) + d.addCallback(self._save_price) + + def start(self): + if not self._updater.running: + self._updater.start(15) + + def stop(self): + if self._updater.running: + self._updater.stop() + + +class BittrexFeed(MarketFeed): + def __init__(self): + MarketFeed.__init__( + self, + "BTCLBC", + "Bittrex", + "https://bittrex.com/api/v1.1/public/getmarkethistory", + {'market': 'BTC-LBC', 'count': 50}, + BITTREX_FEE + ) + + def _handle_response(self, response): + trades = json.loads(response)['result'] + vwap = sum([i['Total'] for i in trades]) / sum([i['Quantity'] for i in trades]) + return defer.succeed(float(1.0 / vwap)) + + +class GoogleBTCFeed(MarketFeed): + def __init__(self): + MarketFeed.__init__( + self, + "USDBTC", + "Coinbase via Google finance", + None, + None, + COINBASE_FEE + ) + + def _make_request(self): + return googlefinance.getQuotes('CURRENCY:USDBTC')[0] + + def _handle_response(self, response): + return float(response['LastTradePrice']) + + +def get_default_market_feed(currency_pair): + currencies = None + if isinstance(currency_pair, str): + currencies = (currency_pair[0:3], currency_pair[3:6]) + elif isinstance(currency_pair, tuple): + currencies = currency_pair + assert currencies is not None + + if currencies == ("USD", "BTC"): + return GoogleBTCFeed() + elif currencies == ("BTC", "LBC"): + return BittrexFeed() + + +class ExchangeRateManager(object): + def __init__(self): + reactor.addSystemEventTrigger('before', 'shutdown', self.stop) + self.market_feeds = [get_default_market_feed(currency_pair) for currency_pair in CURRENCY_PAIRS] + + def start(self): + log.info("Starting exchange rate manager") + for feed in self.market_feeds: + feed.start() + + def stop(self): + log.info("Stopping exchange rate manager") + for source in self.market_feeds: + source.stop() + + def convert_currency(self, from_currency, to_currency, amount): + log.info("Converting %f %s to %s" % (amount, from_currency, to_currency)) + for market in self.market_feeds: + if market.rate.currency_pair == (from_currency, to_currency): + return amount * market.rate.spot + for market in self.market_feeds: + if market.rate.currency_pair[0] == from_currency: + return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot) + + def fee_dict(self): + return {market: market.rate.as_dict() for market in self.market_feeds} + + def to_lbc(self, fee): + return LBRYFeeValidator({fee.currency_symbol: + { + 'amount': self.convert_currency(fee.currency_symbol, "LBC", fee.amount), + 'address': fee.address + } + }) From 52d954c64ecdaf3a740eb87202797f065422b6b5 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 05:34:20 -0400 Subject: [PATCH 434/462] don't spam the market apis --- lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index 2db0d526a..6fd470622 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -58,7 +58,7 @@ class MarketFeed(object): def start(self): if not self._updater.running: - self._updater.start(15) + self._updater.start(300) def stop(self): if self._updater.running: From 5441379f380473b407994f5e308f042fd171410e Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 05:49:31 -0400 Subject: [PATCH 435/462] fix LBRYExchangeRateManager test --- .../lbrynet_daemon/LBRYExchangeRateManager.py | 48 +++++++++++++++++++ tests/lbrynet/core/test_LBRYMetadata.py | 5 +- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index 6fd470622..b8a3538d0 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -148,3 +148,51 @@ class ExchangeRateManager(object): 'address': fee.address } }) + + +class DummyBTCLBCFeed(MarketFeed): + def __init__(self): + MarketFeed.__init__( + self, + "BTCLBC", + "market name", + "derp.com", + None, + 0.0 + ) + + +class DummyUSDBTCFeed(MarketFeed): + def __init__(self): + MarketFeed.__init__( + self, + "USDBTC", + "market name", + "derp.com", + None, + 0.0 + ) + + +class DummyExchangeRateManager(object): + def __init__(self, rates): + self.market_feeds = [DummyBTCLBCFeed(), DummyUSDBTCFeed()] + for feed in self.market_feeds: + feed.rate = rates[feed.market] + + def convert_currency(self, from_currency, to_currency, amount): + log.info("Converting %f %s to %s" % (amount, from_currency, to_currency)) + for market in self.market_feeds: + if market.rate.currency_pair == (from_currency, to_currency): + return amount * market.rate.spot + for market in self.market_feeds: + if market.rate.currency_pair[0] == from_currency: + return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot) + + def to_lbc(self, fee): + return LBRYFeeValidator({fee.currency_symbol: + { + 'amount': self.convert_currency(fee.currency_symbol, "LBC", fee.amount), + 'address': fee.address + } + }) \ No newline at end of file diff --git a/tests/lbrynet/core/test_LBRYMetadata.py b/tests/lbrynet/core/test_LBRYMetadata.py index e474e824e..6f124e38d 100644 --- a/tests/lbrynet/core/test_LBRYMetadata.py +++ b/tests/lbrynet/core/test_LBRYMetadata.py @@ -1,5 +1,6 @@ import mock from lbrynet.core import LBRYMetadata +from lbrynet.lbrynet_daemon import LBRYExchangeRateManager from twisted.trial import unittest @@ -33,8 +34,8 @@ class LBRYFeeTest(unittest.TestCase): } } rates = {'BTCLBC': {'spot': 3.0, 'ts': 2}, 'USDBTC': {'spot': 2.0, 'ts': 3}} - fee = LBRYMetadata.LBRYFee(fee_dict, rates, 0) - self.assertEqual(60.0, fee.to_lbc()) + manager = LBRYExchangeRateManager.DummyExchangeRateManager(rates) + self.assertEqual(60.0, manager.to_lbc(fee_dict).amount) class MetadataTest(unittest.TestCase): From 6b1d11a04d85e75705a3c06188348b0102074c35 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 05:53:31 -0400 Subject: [PATCH 436/462] fix rate in test --- lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index b8a3538d0..a7bf37ef8 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -178,7 +178,7 @@ class DummyExchangeRateManager(object): def __init__(self, rates): self.market_feeds = [DummyBTCLBCFeed(), DummyUSDBTCFeed()] for feed in self.market_feeds: - feed.rate = rates[feed.market] + feed.rate = ExchangeRate(feed, rates[feed]['spot'], rates[feed]['ts']) def convert_currency(self, from_currency, to_currency, amount): log.info("Converting %f %s to %s" % (amount, from_currency, to_currency)) From 413dab8094ce0bb0909b1ece4465bd3eedb6e179 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 05:59:10 -0400 Subject: [PATCH 437/462] whoops --- lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index a7bf37ef8..e0656cfae 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -178,7 +178,7 @@ class DummyExchangeRateManager(object): def __init__(self, rates): self.market_feeds = [DummyBTCLBCFeed(), DummyUSDBTCFeed()] for feed in self.market_feeds: - feed.rate = ExchangeRate(feed, rates[feed]['spot'], rates[feed]['ts']) + feed.rate = ExchangeRate(feed, rates[feed.market]['spot'], rates[feed.market]['ts']) def convert_currency(self, from_currency, to_currency, amount): log.info("Converting %f %s to %s" % (amount, from_currency, to_currency)) From 52c20f02c7d301577488ee75ef8d780508ba3c5a Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 06:03:47 -0400 Subject: [PATCH 438/462] one more --- lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index e0656cfae..3bf6a30ab 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -178,7 +178,7 @@ class DummyExchangeRateManager(object): def __init__(self, rates): self.market_feeds = [DummyBTCLBCFeed(), DummyUSDBTCFeed()] for feed in self.market_feeds: - feed.rate = ExchangeRate(feed, rates[feed.market]['spot'], rates[feed.market]['ts']) + feed.rate = ExchangeRate(feed.market, rates[feed.market]['spot'], rates[feed.market]['ts']) def convert_currency(self, from_currency, to_currency, amount): log.info("Converting %f %s to %s" % (amount, from_currency, to_currency)) From 4387025c489526f80beb30dda7853d727604af41 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 06:07:33 -0400 Subject: [PATCH 439/462] another still --- lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index 3bf6a30ab..cc924ab7e 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -189,7 +189,8 @@ class DummyExchangeRateManager(object): if market.rate.currency_pair[0] == from_currency: return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot) - def to_lbc(self, fee): + def to_lbc(self, f): + fee = LBRYFeeValidator(f) return LBRYFeeValidator({fee.currency_symbol: { 'amount': self.convert_currency(fee.currency_symbol, "LBC", fee.amount), From 15d276b0bc37e14f160b9e358e8f56b05fd449fc Mon Sep 17 00:00:00 2001 From: Job Evers Date: Thu, 28 Jul 2016 11:43:20 -0500 Subject: [PATCH 440/462] fix case where to and from currencies are the same --- lbrynet/core/LBRYMetadata.py | 11 ++++++++--- lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index 2bf31c9c7..9a0ea49c4 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -43,10 +43,15 @@ class LBRYFeeValidator(dict): self.address = self[self.currency_symbol]['address'] def _get_amount(self): - if isinstance(self[self.currency_symbol]['amount'], float): - return self[self.currency_symbol]['amount'] + amt = self[self.currency_symbol]['amount'] + if isinstance(amt, float): + return amt else: - return float(self[self.currency_symbol]['amount']) + 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 diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index cc924ab7e..b578a2983 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -131,12 +131,15 @@ class ExchangeRateManager(object): def convert_currency(self, from_currency, to_currency, amount): log.info("Converting %f %s to %s" % (amount, from_currency, to_currency)) + if from_currency == to_currency: + return amount for market in self.market_feeds: if market.rate.currency_pair == (from_currency, to_currency): return amount * market.rate.spot for market in self.market_feeds: if market.rate.currency_pair[0] == from_currency: return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot) + raise Exception('Unable to convert {} from {} to {}'.format(amount, from_current, to_currency)) def fee_dict(self): return {market: market.rate.as_dict() for market in self.market_feeds} From 39a7f37f3584a357b117c5905e92e539e42a3642 Mon Sep 17 00:00:00 2001 From: Job Evers Date: Thu, 28 Jul 2016 11:43:47 -0500 Subject: [PATCH 441/462] muck with pending_claims... still not sure this is correct --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 89b07e504..adbbe7ff8 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -608,20 +608,25 @@ class LBRYDaemon(jsonrpc.JSONRPC): return txid def _check_pending_claims(self): - def _start_file(name): + # TODO: this was blatantly copied from jsonrpc_start_lbry_file. Be DRY. + def _start_file(f): + d = self.lbry_file_manager.toggle_lbry_file_running(f) + return defer.succeed("Started LBRY file") + + def _get_and_start_file(name): d = defer.succeed(self.pending_claims.pop(name)) d.addCallback(lambda _: self._get_lbry_file("name", name, return_json=False)) d.addCallback(lambda l: _start_file(l) if l.stopped else "LBRY file was already running") def _process_lbry_file(name, lbry_file): # lbry_file is an instance of ManagedLBRYFileDownloader or None + # TODO: check for sd_hash in addition to txid ready_to_start = ( lbry_file and - self.pending_claims[name] == lbry_file.txid and - not isinstance(lbry_file['metadata'], str) + self.pending_claims[name] == lbry_file.txid ) if ready_to_start: - _start_file(name) + _get_and_start_file(name, lbry_file) else: re_add_to_pending_claims(name) From 67c5d15f51fc3944bfb51c96b073d6db5a49a7db Mon Sep 17 00:00:00 2001 From: Job Evers Date: Thu, 28 Jul 2016 11:46:33 -0500 Subject: [PATCH 442/462] stupid typo --- lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index b578a2983..ca7cfdbc4 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -139,7 +139,7 @@ class ExchangeRateManager(object): for market in self.market_feeds: if market.rate.currency_pair[0] == from_currency: return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot) - raise Exception('Unable to convert {} from {} to {}'.format(amount, from_current, to_currency)) + raise Exception('Unable to convert {} from {} to {}'.format(amount, from_currency, to_currency)) def fee_dict(self): return {market: market.rate.as_dict() for market in self.market_feeds} From 295e427abe686b1b0f1b02f9c6e42b4b6ae1f7ee Mon Sep 17 00:00:00 2001 From: Job Evers-Meltzer Date: Thu, 28 Jul 2016 09:42:16 -0500 Subject: [PATCH 443/462] bug fix: check if fee is in metadata before loading --- lbrynet/core/LBRYMetadata.py | 2 +- tests/lbrynet/core/test_LBRYMetadata.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index 9a0ea49c4..7c9450baf 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -89,7 +89,7 @@ class Metadata(dict): self.update({k: metadata.pop(k)}) for k in METADATA_REVISIONS[version]['optional']: if k == 'fee': - self['fee'] = LBRYFeeValidator(metadata.pop('fee')) + self._load_fee(metadata) elif k in metadata: self.update({k: metadata.pop(k)}) diff --git a/tests/lbrynet/core/test_LBRYMetadata.py b/tests/lbrynet/core/test_LBRYMetadata.py index 6f124e38d..6f3023a1e 100644 --- a/tests/lbrynet/core/test_LBRYMetadata.py +++ b/tests/lbrynet/core/test_LBRYMetadata.py @@ -44,6 +44,21 @@ class MetadataTest(unittest.TestCase): with self.assertRaises(AssertionError): LBRYMetadata.Metadata(metadata) + def test_metadata_works_without_fee(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) + self.assertFalse('key' in m) + def test_assertion_if_invalid_source(self): metadata = { 'license': 'Oscilloscope Laboratories', From 881168dc7a9f43b21b21c442722b1f6e8f20df06 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 13:16:33 -0400 Subject: [PATCH 444/462] fix too many args --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index adbbe7ff8..914d6360b 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -618,6 +618,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda _: self._get_lbry_file("name", name, return_json=False)) d.addCallback(lambda l: _start_file(l) if l.stopped else "LBRY file was already running") + def re_add_to_pending_claims(name): + txid = self.pending_claims.pop(name) + self._add_to_pending_claims(name, txid) + def _process_lbry_file(name, lbry_file): # lbry_file is an instance of ManagedLBRYFileDownloader or None # TODO: check for sd_hash in addition to txid @@ -626,14 +630,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.pending_claims[name] == lbry_file.txid ) if ready_to_start: - _get_and_start_file(name, lbry_file) + _get_and_start_file(name) else: re_add_to_pending_claims(name) - def re_add_to_pending_claims(name): - txid = self.pending_claims.pop(name) - self._add_to_pending_claims(name, txid) - for name in self.pending_claims: log.info("Checking if new claim for lbry://%s is confirmed" % name) d = self._resolve_name(name, force_refresh=True) From 5dd68a7ed4c26fe99c621ec3eb0d0522dd49aa45 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 14:55:17 -0400 Subject: [PATCH 445/462] add get_claim_info, clean up _get_stream_info_from_value --- lbrynet/core/LBRYWallet.py | 61 +++++++++++++++++++++------- lbrynet/lbrynet_daemon/LBRYDaemon.py | 11 +++++ 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/lbrynet/core/LBRYWallet.py b/lbrynet/core/LBRYWallet.py index 7d7ab8db7..60696624b 100644 --- a/lbrynet/core/LBRYWallet.py +++ b/lbrynet/core/LBRYWallet.py @@ -320,26 +320,57 @@ class LBRYWallet(object): return d def _get_stream_info_from_value(self, result, name): - if 'value' in result: - value = result['value'] + def _check_result_fields(r): + for k in ['value', 'txid', 'n', 'height', 'amount']: + assert k in r, "getvalueforname response missing field %s" % k - try: - value_dict = json.loads(value) - except (ValueError, TypeError): - return Failure(InvalidStreamInfoError(name)) - m = Metadata(value_dict) - if 'txid' in result: - d = self._save_name_metadata(name, str(result['txid']), m['sources']['lbry_sd_hash']) - d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, m.meta_version))) - d.addCallback(lambda _: m) - return d - elif 'error' in result: + if 'error' in result: log.warning("Got an error looking up a name: %s", result['error']) return Failure(UnknownNameError(name)) - else: - log.warning("Got an error looking up a name: %s", json.dumps(result)) + + _check_result_fields(result) + + try: + metadata = Metadata(json.loads(result['value'])) + except (ValueError, TypeError): + return Failure(InvalidStreamInfoError(name)) + + d = self._save_name_metadata(name, str(result['txid']), metadata['sources']['lbry_sd_hash']) + d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, metadata.meta_version))) + d.addCallback(lambda _: metadata) + return d + + def _get_claim_info(self, result, name): + def _check_result_fields(r): + for k in ['value', 'txid', 'n', 'height', 'amount']: + assert k in r, "getvalueforname response missing field %s" % k + + def _build_response(m, result): + result['value'] = m + return result + + if 'error' in result: + log.warning("Got an error looking up a name: %s", result['error']) return Failure(UnknownNameError(name)) + _check_result_fields(result) + + try: + metadata = Metadata(json.loads(result['value'])) + except (ValueError, TypeError): + return Failure(InvalidStreamInfoError(name)) + + d = self._save_name_metadata(name, str(result['txid']), metadata['sources']['lbry_sd_hash']) + d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, metadata.meta_version))) + d.addCallback(lambda _: _build_response(metadata, result)) + return d + + def get_claim_info(self, name): + d = self._get_value_for_name(name) + d.addCallback(lambda r: self._get_claim_info(r, name)) + return d + + def claim_name(self, name, bid, m): metadata = Metadata(m) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 914d6360b..b41f4eecf 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1718,6 +1718,17 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure) return d + def jsonrpc_get_claim_info(self, p): + def _convert_amount_to_float(r): + r['amount'] = float(r['amount']) / 10**8 + return r + + name = p['name'] + d = self.session.wallet.get_claim_info(name) + d.addCallback(_convert_amount_to_float) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + def jsonrpc_get(self, p): """ Download stream from a LBRY uri From de4ddd578f0355713e1848d2228fab714835d7ca Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 15:24:20 -0400 Subject: [PATCH 446/462] improve readability of metadata and fee requirements --- lbrynet/core/LBRYMetadata.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index 7c9450baf..b71d0723e 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -12,19 +12,27 @@ log = logging.getLogger(__name__) BITTREX_FEE = 0.0025 +# Metadata version 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', 'ver'], 'optional': ['license_url']} -CURRENT_METADATA_VERSION = '0.0.2' +MV001 = "0.0.1" +MV002 = "0.0.2" +CURRENT_METADATA_VERSION = MV002 -# v0.0.1 fee -FEE_REVISIONS = {'0.0.1': {'required': ['amount', 'address'], 'optional': []}} -CURRENT_FEE_REVISION = '0.0.1' +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': []} class LBRYFeeValidator(dict): From 419aa1d78f01e896609537070761cdd036a8609b Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 16:12:20 -0400 Subject: [PATCH 447/462] fix est data cost / search --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 40 +++++++++++++++---- .../lbrynet_daemon/LBRYExchangeRateManager.py | 33 ++++++++++----- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index b41f4eecf..f32b5c9aa 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -34,7 +34,6 @@ from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerF from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory from lbrynet.core.Error import UnknownNameError, InsufficientFundsError -from lbrynet.core.LBRYMetadata import Metadata from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier @@ -1228,7 +1227,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _add_key_fee(data_cost): d = self._resolve_name(name) - d.addCallback(lambda info: data_cost if 'fee' not in info else data_cost + info['fee']['LBC']['amount']) + d.addCallback(lambda info: self.exchange_rate_manager.to_lbc(info.get('fee', None))) + d.addCallback(lambda fee: data_cost if fee is None else data_cost + fee.amount) return d d = self._resolve_name(name) @@ -1238,8 +1238,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) d.addCallback(lambda metadata: metadata.validator.info_to_show()) d.addCallback(lambda info: int(dict(info)['stream_size']) / 1000000 * self.data_rate) - d.addCallback(_add_key_fee) - d.addErrback(lambda _: _add_key_fee(0.0)) + d.addCallbacks(_add_key_fee, lambda _: _add_key_fee(0.0)) reactor.callLater(self.search_timeout, _check_est, d, name) return d @@ -1405,6 +1404,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) + def _search(self, search): + d = self.session.wallet.get_nametrie() + d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) + return d + def _render_response(self, result, code): return defer.succeed({'result': result, 'code': code}) @@ -1719,6 +1723,15 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def jsonrpc_get_claim_info(self, p): + """ + Resolve claim info from a LBRY uri + + Args: + 'name': name to look up, string, do not include lbry:// prefix + Returns: + txid, amount, value, n, height + """ + def _convert_amount_to_float(r): r['amount'] = float(r['amount']) / 10**8 return r @@ -1839,6 +1852,21 @@ class LBRYDaemon(jsonrpc.JSONRPC): d.addCallback(lambda r: self._render_response(r, OK_CODE)) return d + def jsonrpc_get_est_cost(self, p): + """ + Get estimated cost for a lbry uri + + Args: + 'name': lbry uri + Returns: + estimated cost + """ + + name = p['name'] + d = self._get_est_cost(name) + d.addCallback(lambda r: self._render_response(r, OK_CODE)) + return d + def jsonrpc_search_nametrie(self, p): """ Search the nametrie for claims beginning with search (yes, this is a dumb search, it'll be made better) @@ -1890,8 +1918,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): log.info('Search nametrie: ' + search) - d = self.session.wallet.get_nametrie() - d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) + d = self._search(search) d.addCallback(lambda claims: claims[:self.max_search_results]) d.addCallback(resolve_claims) d.addCallback(_clean) @@ -2337,7 +2364,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): # No easy way to reveal specific files on Linux, so just open the containing directory d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.dirname(path)]) - d.addCallback(lambda _: self._render_response(True, OK_CODE)) return d diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index ca7cfdbc4..52f7c3661 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -145,10 +145,17 @@ class ExchangeRateManager(object): return {market: market.rate.as_dict() for market in self.market_feeds} def to_lbc(self, fee): - return LBRYFeeValidator({fee.currency_symbol: + if fee is None: + return None + if not isinstance(fee, LBRYFeeValidator): + fee_in = LBRYFeeValidator(fee) + else: + fee_in = fee + + return LBRYFeeValidator({fee_in.currency_symbol: { - 'amount': self.convert_currency(fee.currency_symbol, "LBC", fee.amount), - 'address': fee.address + 'amount': self.convert_currency(fee.currency_symbol, "LBC", fee_in.amount), + 'address': fee_in.address } }) @@ -192,11 +199,17 @@ class DummyExchangeRateManager(object): if market.rate.currency_pair[0] == from_currency: return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot) - def to_lbc(self, f): - fee = LBRYFeeValidator(f) - return LBRYFeeValidator({fee.currency_symbol: - { - 'amount': self.convert_currency(fee.currency_symbol, "LBC", fee.amount), - 'address': fee.address - } + def to_lbc(self, fee): + if fee is None: + return None + if not isinstance(fee, LBRYFeeValidator): + fee_in = LBRYFeeValidator(fee) + else: + fee_in = fee + + return LBRYFeeValidator({fee_in.currency_symbol: + { + 'amount': self.convert_currency(fee.currency_symbol, "LBC", fee_in.amount), + 'address': fee_in.address + } }) \ No newline at end of file From 2e3342d20d9bd8aca93e7d9148f2275b6293c2f3 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 16:27:16 -0400 Subject: [PATCH 448/462] fix test --- .../lbrynet_daemon/LBRYExchangeRateManager.py | 12 ++---- .../core/test_LBRYExchangeRateManager.py | 38 +++++++++++++++++++ tests/lbrynet/core/test_LBRYMetadata.py | 36 ------------------ 3 files changed, 42 insertions(+), 44 deletions(-) create mode 100644 tests/lbrynet/core/test_LBRYExchangeRateManager.py diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index 52f7c3661..52424cb3e 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -147,10 +147,8 @@ class ExchangeRateManager(object): def to_lbc(self, fee): if fee is None: return None - if not isinstance(fee, LBRYFeeValidator): - fee_in = LBRYFeeValidator(fee) - else: - fee_in = fee + + fee_in = LBRYFeeValidator(fee) return LBRYFeeValidator({fee_in.currency_symbol: { @@ -202,10 +200,8 @@ class DummyExchangeRateManager(object): def to_lbc(self, fee): if fee is None: return None - if not isinstance(fee, LBRYFeeValidator): - fee_in = LBRYFeeValidator(fee) - else: - fee_in = fee + + fee_in = LBRYFeeValidator(fee) return LBRYFeeValidator({fee_in.currency_symbol: { diff --git a/tests/lbrynet/core/test_LBRYExchangeRateManager.py b/tests/lbrynet/core/test_LBRYExchangeRateManager.py new file mode 100644 index 000000000..2a6457536 --- /dev/null +++ b/tests/lbrynet/core/test_LBRYExchangeRateManager.py @@ -0,0 +1,38 @@ +import mock +from lbrynet.core import LBRYMetadata +from lbrynet.lbrynet_daemon import LBRYExchangeRateManager + +from twisted.trial import unittest + + +class LBRYFeeFormatTest(unittest.TestCase): + def test_fee_created_with_correct_inputs(self): + fee_dict = { + 'USD': { + 'amount': 10.0, + 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" + } + } + fee = LBRYMetadata.LBRYFeeValidator(fee_dict) + self.assertEqual(10.0, fee['USD']['amount']) + + +class LBRYFeeTest(unittest.TestCase): + def setUp(self): + self.patcher = mock.patch('time.time') + self.time = self.patcher.start() + self.time.return_value = 0 + + def tearDown(self): + self.time.stop() + + def test_fee_converts_to_lbc(self): + fee_dict = { + 'USD': { + 'amount': 10.0, + 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" + } + } + rates = {'BTCLBC': {'spot': 3.0, 'ts': 2}, 'USDBTC': {'spot': 2.0, 'ts': 3}} + manager = LBRYExchangeRateManager.DummyExchangeRateManager(rates) + self.assertEqual(60.0, manager.to_lbc(fee_dict).amount) \ No newline at end of file diff --git a/tests/lbrynet/core/test_LBRYMetadata.py b/tests/lbrynet/core/test_LBRYMetadata.py index 6f3023a1e..e5c3255b3 100644 --- a/tests/lbrynet/core/test_LBRYMetadata.py +++ b/tests/lbrynet/core/test_LBRYMetadata.py @@ -1,43 +1,7 @@ -import mock from lbrynet.core import LBRYMetadata -from lbrynet.lbrynet_daemon import LBRYExchangeRateManager - from twisted.trial import unittest -class LBRYFeeFormatTest(unittest.TestCase): - def test_fee_created_with_correct_inputs(self): - fee_dict = { - 'USD': { - 'amount': 10.0, - 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" - } - } - fee = LBRYMetadata.LBRYFeeValidator(fee_dict) - self.assertEqual(10.0, fee['USD']['amount']) - - -class LBRYFeeTest(unittest.TestCase): - def setUp(self): - self.patcher = mock.patch('time.time') - self.time = self.patcher.start() - self.time.return_value = 0 - - def tearDown(self): - self.time.stop() - - def test_fee_converts_to_lbc(self): - fee_dict = { - 'USD': { - 'amount': 10.0, - 'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9" - } - } - rates = {'BTCLBC': {'spot': 3.0, 'ts': 2}, 'USDBTC': {'spot': 2.0, 'ts': 3}} - manager = LBRYExchangeRateManager.DummyExchangeRateManager(rates) - self.assertEqual(60.0, manager.to_lbc(fee_dict).amount) - - class MetadataTest(unittest.TestCase): def test_assertion_if_source_is_missing(self): metadata = {} From 52024986eea2831d1064e57c3808a7286199fdc5 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 16:32:59 -0400 Subject: [PATCH 449/462] derp --- .../lbrynet_daemon/LBRYExchangeRateManager.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py index 52424cb3e..0af938954 100644 --- a/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py +++ b/lbrynet/lbrynet_daemon/LBRYExchangeRateManager.py @@ -147,12 +147,14 @@ class ExchangeRateManager(object): def to_lbc(self, fee): if fee is None: return None - - fee_in = LBRYFeeValidator(fee) + if not isinstance(fee, LBRYFeeValidator): + fee_in = LBRYFeeValidator(fee) + else: + fee_in = fee return LBRYFeeValidator({fee_in.currency_symbol: { - 'amount': self.convert_currency(fee.currency_symbol, "LBC", fee_in.amount), + 'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount), 'address': fee_in.address } }) @@ -200,12 +202,14 @@ class DummyExchangeRateManager(object): def to_lbc(self, fee): if fee is None: return None - - fee_in = LBRYFeeValidator(fee) + if not isinstance(fee, LBRYFeeValidator): + fee_in = LBRYFeeValidator(fee) + else: + fee_in = fee return LBRYFeeValidator({fee_in.currency_symbol: { - 'amount': self.convert_currency(fee.currency_symbol, "LBC", fee_in.amount), + 'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount), 'address': fee_in.address } }) \ No newline at end of file From 68216768539f9504e5bbf8f5f61fe539ca684eda Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 18:12:54 -0400 Subject: [PATCH 450/462] handle decimals in jsonrpc return --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index f32b5c9aa..a89bee25c 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -417,6 +417,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): return server.NOT_DONE_YET def _cbRender(self, result, request, id, version): + def default_decimal(obj): + if isinstance(obj, Decimal): + return float(obj) + if isinstance(result, Handler): result = result.result @@ -428,7 +432,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): result = (result,) # Convert the result (python) to JSON-RPC try: - s = jsonrpclib.dumps(result, version=version) + s = jsonrpclib.dumps(result, version=version, default=default_decimal) except: f = jsonrpclib.Fault(self.FAILURE, "can't serialize output") s = jsonrpclib.dumps(f, version=version) From b83dce2cb32260017045794cd3b5403fe1fc99ca Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 18:48:29 -0400 Subject: [PATCH 451/462] no tricky characters --- lbrynet/core/Error.py | 8 ++++++++ lbrynet/core/LBRYMetadata.py | 7 +++++++ lbrynet/lbrynet_daemon/LBRYDaemon.py | 14 +++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lbrynet/core/Error.py b/lbrynet/core/Error.py index 631c5685c..8146dc169 100644 --- a/lbrynet/core/Error.py +++ b/lbrynet/core/Error.py @@ -34,6 +34,14 @@ class UnknownNameError(Exception): return repr(self.name) +class InvalidNameError(Exception): + def __init__(self, name): + self.name = name + + def __str__(self): + return repr(self.name) + + class UnknownStreamTypeError(Exception): def __init__(self, stream_type): self.stream_type = stream_type diff --git a/lbrynet/core/LBRYMetadata.py b/lbrynet/core/LBRYMetadata.py index b71d0723e..656b55c99 100644 --- a/lbrynet/core/LBRYMetadata.py +++ b/lbrynet/core/LBRYMetadata.py @@ -14,6 +14,7 @@ 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'] @@ -35,6 +36,12 @@ 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) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index a89bee25c..b18833056 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -33,7 +33,7 @@ from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory -from lbrynet.core.Error import UnknownNameError, InsufficientFundsError +from lbrynet.core.Error import UnknownNameError, InsufficientFundsError, InvalidNameError from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier @@ -42,6 +42,7 @@ from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ @@ -1165,6 +1166,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(True) def _resolve_name(self, name, force_refresh=False): + try: + verify_name_characters(name) + except: + log.error("Bad name") + return defer.fail(InvalidNameError("Bad name")) + def _cache_stream_info(stream_info): def _add_txid(txid): self.name_cache[name]['txid'] = txid @@ -1975,6 +1982,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): """ name = p['name'] + try: + verify_name_characters(name) + except: + log.error("Bad name") + return defer.fail(InvalidNameError("Bad name")) bid = p['bid'] file_path = p['file_path'] metadata = p['metadata'] From 33e7e4bee385086b82f94e347b9aa2d163e104b0 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 21:25:37 -0400 Subject: [PATCH 452/462] fancy search --- lbrynet/conf.py | 1 + lbrynet/lbrynet_daemon/LBRYDaemon.py | 60 ++++++++++++++-------------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 85a9f6836..9576cc3b4 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -24,6 +24,7 @@ KNOWN_DHT_NODES = [('104.236.42.182', 4000), POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' #POINTTRADER_SERVER = 'http://127.0.0.1:2424' +SEARCH_SERVER = "http://45.63.4.203:50005" LOG_FILE_NAME = "lbrynet.log" LOG_POST_URL = "https://lbry.io/log-upload" diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index b18833056..09ff51351 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -25,7 +25,7 @@ from twisted.internet import defer, threads, error, reactor from twisted.internet.task import LoopingCall from txjsonrpc import jsonrpclib from txjsonrpc.web import jsonrpc -from txjsonrpc.web.jsonrpc import Handler +from txjsonrpc.web.jsonrpc import Handler, Proxy from lbrynet import __version__ as lbrynet_version from lbryum.version import LBRYUM_VERSION as lbryum_version @@ -47,6 +47,7 @@ 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME, SOURCE_TYPES +from lbrynet.conf import SEARCH_SERVER from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -1228,6 +1229,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def _get_est_cost(self, name): + log.info("Estimating cost for " + name) def _check_est(d, name): if isinstance(d.result, float): log.info("Cost est for lbry://" + name + ": " + str(d.result) + "LBC") @@ -1416,8 +1418,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) def _search(self, search): - d = self.session.wallet.get_nametrie() - d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) + proxy = Proxy(SEARCH_SERVER) + + d = proxy.callRemote('search', search) + # d = se.search(search) + # d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) return d def _render_response(self, result, code): @@ -1897,43 +1902,38 @@ class LBRYDaemon(jsonrpc.JSONRPC): t = [] for i in n: if i[0]: - if i[1][0][0] and i[1][1][0] and i[1][2][0]: - i[1][0][1]['value'] = str(i[1][0][1]['value']) - t.append([i[1][0][1], i[1][1][1], i[1][2][1]]) + tr = {} + tr.update(i[1][0]) + thumb = tr.get('thumbnail', None) + if thumb is None: + tr['thumbnail'] = "img/Free-speech-flag.svg" + tr['cost_est'] = i[1][1] + t.append(tr) return t - def resolve_claims(claims): - ds = [] - for claim in claims: - d1 = defer.succeed(claim) - d2 = self._resolve_name(claim['name']) - d3 = self._get_est_cost(claim['name']) - dl = defer.DeferredList([d1, d2, d3], consumeErrors=True) - ds.append(dl) - return defer.DeferredList(ds) + def get_est_costs(results): + def _get_costs(search_result): + log.info("**" + search_result['name']) + d = self._get_est_cost(search_result['name']) + d.addCallback(lambda p: _save_cost(search_result, p)) + return d - def _disp(results): - log.info('Found ' + str(len(results)) + ' search results') - consolidated_results = [] - for r in results: - t = {} - t.update(r[0]) - if not 'thumbnail' in r[1].keys(): - r[1]['thumbnail'] = "img/Free-speech-flag.svg" - t.update(r[1]) - t['cost_est'] = r[2] - consolidated_results.append(t) - # log.info(str(t)) + def _save_cost(value, cost): + log.info("Save cost") + log.info(value) + log.info(cost) + return [value, cost] - return consolidated_results + log.info("Estimating costs") + dl = defer.DeferredList([_get_costs(r) for r in results], consumeErrors=True) + return dl log.info('Search nametrie: ' + search) d = self._search(search) d.addCallback(lambda claims: claims[:self.max_search_results]) - d.addCallback(resolve_claims) + d.addCallback(get_est_costs) d.addCallback(_clean) - d.addCallback(_disp) d.addCallback(lambda results: self._render_response(results, OK_CODE)) return d From ba2e4df76ba0d95ce8edbde55bce67c3ebf696a6 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 21:31:19 -0400 Subject: [PATCH 453/462] remove debug stuff --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 09ff51351..9c64c1394 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1229,7 +1229,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def _get_est_cost(self, name): - log.info("Estimating cost for " + name) def _check_est(d, name): if isinstance(d.result, float): log.info("Cost est for lbry://" + name + ": " + str(d.result) + "LBC") @@ -1419,11 +1418,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _search(self, search): proxy = Proxy(SEARCH_SERVER) - - d = proxy.callRemote('search', search) - # d = se.search(search) - # d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim]) - return d + return proxy.callRemote('search', search) def _render_response(self, result, code): return defer.succeed({'result': result, 'code': code}) @@ -1919,12 +1914,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): return d def _save_cost(value, cost): - log.info("Save cost") - log.info(value) - log.info(cost) return [value, cost] - log.info("Estimating costs") dl = defer.DeferredList([_get_costs(r) for r in results], consumeErrors=True) return dl From bb3ba53c6ec181c3bb33ddb89ec40329e22c57f9 Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 28 Jul 2016 22:39:34 -0400 Subject: [PATCH 454/462] fix search results --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 9c64c1394..4efc11ec5 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1898,10 +1898,11 @@ class LBRYDaemon(jsonrpc.JSONRPC): for i in n: if i[0]: tr = {} - tr.update(i[1][0]) + tr.update(i[1][0]['value']) thumb = tr.get('thumbnail', None) if thumb is None: tr['thumbnail'] = "img/Free-speech-flag.svg" + tr['name'] = i[1][0]['name'] tr['cost_est'] = i[1][1] t.append(tr) return t From d21e458fe1abba2962a57fe5e76da5a8eda5cfae Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 29 Jul 2016 02:42:58 -0400 Subject: [PATCH 455/462] remove debug line --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 4efc11ec5..bc1cbba63 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1909,7 +1909,6 @@ class LBRYDaemon(jsonrpc.JSONRPC): def get_est_costs(results): def _get_costs(search_result): - log.info("**" + search_result['name']) d = self._get_est_cost(search_result['name']) d.addCallback(lambda p: _save_cost(search_result, p)) return d From a574c33ac693e45e97439c33e5efe60f689e689a Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 29 Jul 2016 21:37:44 -0400 Subject: [PATCH 456/462] multiple search servers --- lbrynet/conf.py | 4 +++- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lbrynet/conf.py b/lbrynet/conf.py index 9576cc3b4..96e8b18dc 100644 --- a/lbrynet/conf.py +++ b/lbrynet/conf.py @@ -24,7 +24,9 @@ KNOWN_DHT_NODES = [('104.236.42.182', 4000), POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424' #POINTTRADER_SERVER = 'http://127.0.0.1:2424' -SEARCH_SERVER = "http://45.63.4.203:50005" +SEARCH_SERVERS = ["http://lighthouse1.lbry.io:50005", + "http://lighthouse2.lbry.io:50005", + "http://lighthouse3.lbry.io:50005"] LOG_FILE_NAME = "lbrynet.log" LOG_POST_URL = "https://lbry.io/log-upload" diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index bc1cbba63..f9b9e4425 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -47,7 +47,7 @@ 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, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \ DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME, SOURCE_TYPES -from lbrynet.conf import SEARCH_SERVER +from lbrynet.conf import SEARCH_SERVERS from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob from lbrynet.core.Session import LBRYSession @@ -1417,7 +1417,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): return defer.succeed(None) def _search(self, search): - proxy = Proxy(SEARCH_SERVER) + proxy = Proxy(random.choice(SEARCH_SERVERS)) return proxy.callRemote('search', search) def _render_response(self, result, code): From 20a9e018f7bf574b965542b714f76ce414b29dd1 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 29 Jul 2016 22:36:30 -0400 Subject: [PATCH 457/462] revert updates to git --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 43 +++++++++++++++++----------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index f9b9e4425..deda28bec 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -148,8 +148,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): self.connected_to_internet = True self.connection_problem = None self.query_handlers = {} - self.pip_lbrynet_version = None - self.pip_lbryum_version = None + self.git_lbrynet_version = None + self.git_lbryum_version = None self.ui_version = None self.ip = None # TODO: this is confusing to set here, and then to be reset below. @@ -569,33 +569,42 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _check_remote_versions(self): def _get_lbryum_version(): try: - r = pkg_resources.get_distribution("lbryum").version - log.info("Local lbryum: %s" % lbryum_version) - log.info("Available lbryum: %s" % r) - self.pip_lbryum_version = r + r = urlopen("https://raw.githubusercontent.com/lbryio/lbryum/master/lib/version.py").read().split('\n') + version = next(line.split("=")[1].split("#")[0].replace(" ", "") + for line in r if "LBRYUM_VERSION" in line) + version = version.replace("'", "") + log.info( + "remote lbryum %s > local lbryum %s = %s", + version, lbryum_version, + utils.version_is_greater_than(version, lbryum_version) + ) + self.git_lbryum_version = version return defer.succeed(None) except: log.info("Failed to get lbryum version from git") - self.pip_lbryum_version = None + self.git_lbryum_version = None return defer.fail(None) def _get_lbrynet_version(): try: - r = pkg_resources.get_distribution("lbrynet").version - log.info("Local lbrynet: %s" % lbrynet_version) - log.info("Available lbrynet: %s" % r) - self.pip_lbrynet_version = r + version = get_lbrynet_version_from_github() + log.info( + "remote lbrynet %s > local lbrynet %s = %s", + version, lbrynet_version, + utils.version_is_greater_than(version, lbrynet_version) + ) + self.git_lbrynet_version = version return defer.succeed(None) except: log.info("Failed to get lbrynet version from git") - self.pip_lbrynet_version = None + self.git_lbrynet_version = None return defer.fail(None) d = _get_lbrynet_version() d.addCallback(lambda _: _get_lbryum_version()) def _check_connection_problems(self): - if not self.pip_lbrynet_version or not self.pip_lbryum_version: + if not self.git_lbrynet_version or not self.git_lbryum_version: self.connection_problem = CONNECTION_PROBLEM_CODES[0] elif self.startup_status[0] == 'loading_wallet': @@ -1537,10 +1546,10 @@ class LBRYDaemon(jsonrpc.JSONRPC): 'lbrynet_version': lbrynet_version, 'lbryum_version': lbryum_version, 'ui_version': self.ui_version, - 'remote_lbrynet': self.pip_lbrynet_version, - 'remote_lbryum': self.pip_lbryum_version, - 'lbrynet_update_available': utils.version_is_greater_than(self.pip_lbrynet_version, lbrynet_version), - 'lbryum_update_available': utils.version_is_greater_than(self.pip_lbryum_version, lbryum_version), + 'remote_lbrynet': self.git_lbrynet_version, + 'remote_lbryum': self.git_lbryum_version, + 'lbrynet_update_available': utils.version_is_greater_than(self.git_lbrynet_version, lbrynet_version), + 'lbryum_update_available': utils.version_is_greater_than(self.git_lbryum_version, lbryum_version), } log.info("Get version info: " + json.dumps(msg)) From 64afb6d3050595958383ab8e21f7a0e40c3ba0cb Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 29 Jul 2016 22:43:50 -0400 Subject: [PATCH 458/462] clean up get_est_costs in search --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index deda28bec..e418477ba 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1917,15 +1917,12 @@ class LBRYDaemon(jsonrpc.JSONRPC): return t def get_est_costs(results): - def _get_costs(search_result): + def _save_cost(search_result): d = self._get_est_cost(search_result['name']) - d.addCallback(lambda p: _save_cost(search_result, p)) + d.addCallback(lambda p: [search_result, p]) return d - def _save_cost(value, cost): - return [value, cost] - - dl = defer.DeferredList([_get_costs(r) for r in results], consumeErrors=True) + dl = defer.DeferredList([_save_cost(r) for r in results], consumeErrors=True) return dl log.info('Search nametrie: ' + search) From 1e1d036590ad45e6de6e836935d07b2b7242dc33 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 29 Jul 2016 22:44:38 -0400 Subject: [PATCH 459/462] clean log line --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index e418477ba..70ebeae97 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1925,7 +1925,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): dl = defer.DeferredList([_save_cost(r) for r in results], consumeErrors=True) return dl - log.info('Search nametrie: ' + search) + log.info('Search: %s' % search) d = self._search(search) d.addCallback(lambda claims: claims[:self.max_search_results]) From cf8592ba12f0dd42ba13bf795b3d42782114e959 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 29 Jul 2016 22:49:50 -0400 Subject: [PATCH 460/462] docstring and todo --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index 70ebeae97..cf0188a1f 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1889,7 +1889,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def jsonrpc_search_nametrie(self, p): """ - Search the nametrie for claims beginning with search (yes, this is a dumb search, it'll be made better) + Search the nametrie for claims Args: 'search': search query, string @@ -1897,6 +1897,8 @@ class LBRYDaemon(jsonrpc.JSONRPC): List of search results """ + # TODO: change this function to "search", and use cached stream size info from the search server + if 'search' in p.keys(): search = p['search'] else: From 1ffcd67c9d2ba85b436d9ee5b5a0e1baa1a4ed29 Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 29 Jul 2016 23:05:01 -0400 Subject: [PATCH 461/462] specify exception --- lbrynet/lbrynet_daemon/LBRYDaemon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py index cf0188a1f..ea9124d57 100644 --- a/lbrynet/lbrynet_daemon/LBRYDaemon.py +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -1178,7 +1178,7 @@ class LBRYDaemon(jsonrpc.JSONRPC): def _resolve_name(self, name, force_refresh=False): try: verify_name_characters(name) - except: + except AssertionError: log.error("Bad name") return defer.fail(InvalidNameError("Bad name")) From bbf7940e4518bf7046b04a0d8ac789ac9ed2245b Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 29 Jul 2016 23:44:04 -0400 Subject: [PATCH 462/462] Bump version: 0.3.11 -> 0.3.12 --- .bumpversion.cfg | 2 +- lbrynet/__init__.py | 2 +- packaging/ubuntu/lbry.desktop | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 39e7a0c79..a7e5c071f 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.3.11 +current_version = 0.3.12 commit = True tag = True message = Bump version: {current_version} -> {new_version} diff --git a/lbrynet/__init__.py b/lbrynet/__init__.py index 7a0017b58..b501b627e 100644 --- a/lbrynet/__init__.py +++ b/lbrynet/__init__.py @@ -4,5 +4,5 @@ log = logging.getLogger(__name__) logging.getLogger(__name__).addHandler(logging.NullHandler()) log.setLevel(logging.INFO) -__version__ = "0.3.11" +__version__ = "0.3.12" version = tuple(__version__.split('.')) \ No newline at end of file diff --git a/packaging/ubuntu/lbry.desktop b/packaging/ubuntu/lbry.desktop index 53b09e185..7005175b3 100644 --- a/packaging/ubuntu/lbry.desktop +++ b/packaging/ubuntu/lbry.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=0.3.11 +Version=0.3.12 Name=LBRY Comment=The world's first user-owned content marketplace Icon=lbry