From 441d3c1220b807007bea811a2198ca3fbdb43f97 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 6 Dec 2015 17:32:17 -0500 Subject: [PATCH] LBRYNet daemon Adds a daemon for lbrynet. Currently commands are limited the following: download_name(name) resolve_name(name) get_downloads stop This allows other programs to easily interact with lbrynet, such as LBRYURIHandler. LBRYURIHandler can be built with py2app, the resulting plist file must be edited in the same way the committed plist file has been edited. When built and installed to the /Applications folder lbry:// domain names will download and open the corresponding file so long as the daemon is running. --- lbrynet/core/client/AutoDownloader.py | 136 +++++++++ lbrynet/lbryfilemanager/LBRYFileManager.py | 5 +- lbrynet/lbrynet_daemon/LBRYDaemon.py | 282 ++++++++++++++++++ .../LBRYURIHandler/LBRYURIHandler.py | 22 ++ .../LBRYURIHandler.app/Contents/Info.plist | 117 ++++++++ .../lbrynet_daemon/LBRYURIHandler/setup.py | 19 ++ lbrynet/lbrynet_daemon/__init__.py | 0 lbrynet/lbrynet_gui/LBRYGui.py | 7 +- 8 files changed, 585 insertions(+), 3 deletions(-) create mode 100644 lbrynet/lbrynet_daemon/LBRYDaemon.py create mode 100644 lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py create mode 100644 lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist create mode 100644 lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py create mode 100644 lbrynet/lbrynet_daemon/__init__.py diff --git a/lbrynet/core/client/AutoDownloader.py b/lbrynet/core/client/AutoDownloader.py index 70edce257..df69f561f 100644 --- a/lbrynet/core/client/AutoDownloader.py +++ b/lbrynet/core/client/AutoDownloader.py @@ -113,6 +113,142 @@ class AutoAddStream(object): return self.downloader +class GetStream(object): + def __init__(self, console, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, + console_on=True, pay_key=True): + self.finished_deferred = defer.Deferred(None) + self.console_on = console_on + self.pay_key = pay_key + self.console = console + self.wallet = wallet + self.resolved_name = None + self.description = None + self.key_fee = None + self.key_fee_address = None + self.name = None + self.session = session + self.payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager) + self.loading_metadata_deferred = defer.Deferred() + self.lbry_file_manager = lbry_file_manager + self.sd_identifier = sd_identifier + self.metadata = None + self.loading_failed = False + self.resolved_name = None + self.description = None + self.key_fee = None + self.key_fee_address = None + self.stream_hash = None + self.max_key_fee = max_key_fee + self.stream_info = None + self.stream_info_manager = None + self.downloader = None + + def start(self, stream_info): + self.stream_info = stream_info + try: + if 'stream_hash' in json.loads(self.stream_info['value']): + self.resolved_name = self.stream_info.get('name', None) + self.description = json.loads(self.stream_info['value']).get('description', None) + try: + if 'key_fee' in json.loads(self.stream_info['value']): + self.key_fee = float(json.loads(self.stream_info['value'])['key_fee']) + except ValueError: + self.key_fee = None + self.key_fee_address = json.loads(self.stream_info['value']).get('key_fee_address', None) + self.stream_hash = json.loads(self.stream_info['value'])['stream_hash'] + else: + raise InvalidStreamInfoError(self.stream_info) + + except: + 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']) + self.key_fee_address = self.stream_info['key_fee_address'] + else: + self.key_fee = None + self.stream_hash = self.stream_info['stream_hash'] + else: + raise InvalidStreamInfoError(self.stream_info) + + # if self.key_fee < self.max_key_fee: + # if self.pay_key: + # if self.console_on: + # self.console.sendLine("Key fee (" + str(self.key_fee) + ") above limit of " + str( + # self.max_key_fee) + ", didn't download lbry://" + str(self.resolved_name)) + # return self.finished_deferred.callback(None) + # else: + # pass + # else: + # pass + + def _get_downloader_for_return(): + return defer.succeed(self.downloader) + + self.loading_metadata_deferred = defer.Deferred(None) + self.loading_metadata_deferred.addCallback( + lambda _: download_sd_blob(self.session, self.stream_hash, self.payment_rate_manager)) + self.loading_metadata_deferred.addCallback(self.sd_identifier.get_metadata_for_sd_blob) + self.loading_metadata_deferred.addCallback(self._handle_metadata) + self.loading_metadata_deferred.addErrback(self._handle_load_canceled) + self.loading_metadata_deferred.addErrback(self._handle_load_failed) + self.loading_metadata_deferred.addCallback(lambda _: self._make_downloader()) + self.loading_metadata_deferred.addCallback(lambda _: self.downloader.start()) + self.loading_metadata_deferred.addErrback(self._handle_download_error) + self.loading_metadata_deferred.addCallback(lambda _: _get_downloader_for_return()) + self.loading_metadata_deferred.callback(None) + + return defer.succeed(None) + + def _pay_key_fee(self): + 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()) + return self.wallet.send_points_to_address(reserved_points, self.key_fee) + if self.console_on: + self.console.sendLine("Sent key fee" + str(self.key_fee_address) + " | " + str(self.key_fee)) + return defer.succeed(None) + + def _handle_load_canceled(self, err): + err.trap(defer.CancelledError) + self.finished_deferred.callback(None) + + def _handle_load_failed(self, err): + self.loading_failed = True + if self.console_on: + self.console.sendLine("handle load failed: " + str(err.getTraceback())) + log.error("An exception occurred attempting to load the stream descriptor: %s", err.getTraceback()) + print 'Load Failed: ', err.getTraceback() + self.finished_deferred.callback(None) + + def _handle_metadata(self, metadata): + self.metadata = metadata + self.factory = self.metadata.factories[0] + return defer.succeed(None) #self._start_download() + + def _handle_download_error(self, err): + if self.console_on: + if err.check(InsufficientFundsError): + self.console.sendLine("Download stopped due to insufficient funds.") + else: + self.console.sendLine( + "Autoaddstream: An unexpected error has caused the download to stop: %s" % err.getTraceback()) + else: + print "Autoaddstream: An unexpected error has caused the download to stop: ", err.getTraceback() + + def _make_downloader(self): + + def _set_downloader(downloader): + self.downloader = downloader + print os.path.join(self.downloader.download_directory, self.downloader.file_name) + return self.downloader + + self.downloader = self.factory.make_downloader(self.metadata, [0.5, True], self.payment_rate_manager) + self.downloader.addCallback(_set_downloader) + return defer.succeed(self.downloader) + + class AutoFetcher(object): def __init__(self, session, lbry_file_manager, lbry_file_metadata_manager, wallet, sd_identifier, autofetcher_conf): self.autofetcher_conf = autofetcher_conf diff --git a/lbrynet/lbryfilemanager/LBRYFileManager.py b/lbrynet/lbryfilemanager/LBRYFileManager.py index f27f6052e..c2e426bfa 100644 --- a/lbrynet/lbryfilemanager/LBRYFileManager.py +++ b/lbrynet/lbryfilemanager/LBRYFileManager.py @@ -31,7 +31,10 @@ class LBRYFileManager(object): self.sd_identifier = sd_identifier self.lbry_files = [] self.sql_db = None - self.download_directory = os.getcwd() + if os.name == 'posix': + self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + else: + self.download_directory = os.getcwd() def setup(self): d = self._open_db() diff --git a/lbrynet/lbrynet_daemon/LBRYDaemon.py b/lbrynet/lbrynet_daemon/LBRYDaemon.py new file mode 100644 index 000000000..cb9e6e54c --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYDaemon.py @@ -0,0 +1,282 @@ +import os +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.core.client.AutoDownloader import GetStream +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.core.StreamDescriptor import StreamDescriptorIdentifier +from lbrynet.core.Session import LBRYSession +from lbrynet.core.PTCWallet import PTCWallet +from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet +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 +from datetime import datetime + + +class LBRYDaemon(xmlrpc.XMLRPC): + """ + LBRYnet daemon + """ + + def setup(self): + def _set_vars(): + self.run_server = True + self.session = None + self.known_dht_nodes = [('104.236.42.182', 4000)] + self.db_dir = os.path.join(os.path.expanduser("~"), ".lbrynet") + self.blobfile_dir = os.path.join(self.db_dir, "blobfiles") + self.peer_port = 3333 + self.dht_node_port = 4444 + 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: + self.download_directory = os.path.join(os.path.expanduser("~"), 'Downloads') + 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.stream_info_manager = TempLBRYFileMetadataManager() + 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 = "./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 = "lbrycrd" + self.lbrycrd_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd") + self.lbrycrd_conf = os.path.join(self.lbrycrd_dir, "lbrycrd.conf") + self.rpc_conn = None + return defer.succeed(None) + + d = defer.Deferred() + d.addCallback(lambda _: _set_vars()) + 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()) + d.addCallback(lambda _: self._setup_lbry_file_manager()) + d.addCallback(lambda _: self._setup_lbry_file_opener()) + d.callback(None) + + return defer.succeed(None) + + def _get_settings(self): + 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): + if lbryid is None: + return self._make_lbryid() + else: + self.lbryid = lbryid + + def _make_lbryid(self): + self.lbryid = generate_id() + d = self.settings.save_lbryid(self.lbryid) + return d + + 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 _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}) + 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 + d = defer.succeed(LBRYcrdWallet(self.db_dir, wallet_dir=self.lbrycrd_dir, wallet_conf=self.lbrycrd_conf, + lbrycrdd_path=lbrycrdd_path)) + else: + d = defer.succeed(PTCWallet(self.db_dir)) + d.addCallback(lambda wallet: {"wallet": wallet}) + return d + + d1 = get_default_data_rate() + d2 = get_wallet() + + def combine_results(results): + r = {} + for success, result in results: + if success is True: + r.update(result) + return r + + def create_session(results): + 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.rpc_conn = self.session.wallet.get_rpc_conn_x() + + dl = defer.DeferredList([d1, d2], fireOnOneErrback=True) + + dl.addCallback(combine_results) + + dl.addCallback(create_session) + + dl.addCallback(lambda _: self.session.setup()) + + return dl + + 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() + print self.lbrycrdd_path + + 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, + 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) + 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, + self.session.blob_manager, self.stream_info_manager, + self.session.wallet) + self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory) + return defer.succeed(True) + + def xmlrpc_getbalance(self): + """ + Get LBC balance + """ + return str(self.session.wallet.wallet_balance) + + def xmlrpc_stop(self): + """ + Stop the reactor + """ + + reactor.stop() + return defer.succeed('Stopping') + + def xmlrpc_resolve_name(self, name): + """ + Resolve stream info from a LBRY uri + """ + + def _disp(info): + print '[' + str(datetime.now()) + ']' + ' Resolved info: ' + str(info) + return info + + d = defer.Deferred() + d.addCallback(lambda _: self.session.wallet.get_stream_info_for_name(name)) + d.addCallback(_disp) + d.callback(None) + return d + + def xmlrpc_get_downloads(self): + """ + Get downloads + """ + + downloads = [{'stream_hash': stream.stream_hash, + 'path': os.path.join(stream.downloader.download_directory, stream.downloader.file_name)} + for stream in self.download_deferreds] + return downloads + + def xmlrpc_download_name(self, name): + """ + Download stream from a LBRY uri + """ + + def _disp(): + try: + stream = self.download_deferreds[-1] + print '[' + str(datetime.now()) + ']' + ' Downloading: ' + str(stream.stream_hash) + return defer.succeed(None) + except: + pass + + stream = GetStream(None, self.sd_identifier, self.session, self.session.wallet, + self.lbry_file_manager, 25.0, console_on=False, pay_key=True) + self.download_deferreds.append(stream) + + d = defer.Deferred() + d.addCallback(lambda _: self.session.wallet.get_stream_info_for_name(name)) + d.addCallback(lambda stream_info: stream.start(stream_info)) + d.addCallback(lambda _: _disp()) + d.callback(None) + msg = {'ts': datetime.now(),'name': name} + return defer.succeed(str(msg)) + +def main(): + daemon = LBRYDaemon() + daemon.setup() + reactor.listenTCP(7080, server.Site(daemon)) + reactor.run() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py b/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py new file mode 100644 index 000000000..2e1faa479 --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYURIHandler/LBRYURIHandler.py @@ -0,0 +1,22 @@ +import os +import webbrowser +import xmlrpclib, sys + + +def main(args): + if len(args) == 0: + args.append('lbry://economicsman') + + daemon = xmlrpclib.ServerProxy('http://localhost:7080/') + + if len(args) > 1: + print 'Too many args', args + else: + resolved = daemon.resolve_name(str(args[0])[7:]) + daemon.download_name(str(args[0])[7:]) + path = [h for h in daemon.get_downloads() if h['stream_hash'] == resolved['stream_hash']][0]['path'] + webbrowser.open('file://' + path) + + +if __name__ == "__main__": + main(sys.argv[1:]) \ No newline at end of file diff --git a/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist b/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist new file mode 100644 index 000000000..4f5993ff1 --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYURIHandler/dist/LBRYURIHandler.app/Contents/Info.plist @@ -0,0 +1,117 @@ + + + + + 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 new file mode 100644 index 000000000..fbb83f5dc --- /dev/null +++ b/lbrynet/lbrynet_daemon/LBRYURIHandler/setup.py @@ -0,0 +1,19 @@ +""" +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/__init__.py b/lbrynet/lbrynet_daemon/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lbrynet/lbrynet_gui/LBRYGui.py b/lbrynet/lbrynet_gui/LBRYGui.py index fad1d0fb0..1926d80fe 100644 --- a/lbrynet/lbrynet_gui/LBRYGui.py +++ b/lbrynet/lbrynet_gui/LBRYGui.py @@ -57,7 +57,8 @@ class LBRYDownloader(object): if os.name == "nt": self.lbrycrdd_path = "lbrycrdd.exe" else: - self.lbrycrdd_path = "./lbrycrdd" + self.lbrycrdd_path = None + self.default_lbrycrdd_path = "./lbrycrdd" self.delete_blobs_on_remove = True self.blob_request_payment_rate_manager = None @@ -123,7 +124,7 @@ class LBRYDownloader(object): if not os.path.exists(lbrycrdd_path_conf_path): return "" lbrycrdd_path_conf = open(lbrycrdd_path_conf_path) - lines = lbrycrdd_path_conf.readline() + lines = lbrycrdd_path_conf.readlines() return lines d = threads.deferToThread(get_lbrycrdd_path_conf_file) @@ -293,6 +294,8 @@ class LBRYDownloader(object): lbrycrdd_path = None if self.start_lbrycrdd is True: lbrycrdd_path = self.lbrycrdd_path + if not lbrycrdd_path: + lbrycrdd_path = self.default_lbrycrdd_path wallet = LBRYcrdWallet(self.db_dir, wallet_dir=self.wallet_dir, wallet_conf=self.wallet_conf, lbrycrdd_path=lbrycrdd_path)