diff --git a/lbrynet/analytics.py b/lbrynet/analytics.py index 513e2cbc8..ae1e55120 100644 --- a/lbrynet/analytics.py +++ b/lbrynet/analytics.py @@ -18,6 +18,7 @@ HEARTBEAT = 'Heartbeat' CLAIM_ACTION = 'Claim Action' # publish/create/update/abandon NEW_CHANNEL = 'New Channel' CREDITS_SENT = 'Credits Sent' +NEW_DOWNLOAD_STAT = 'Download' BLOB_BYTES_UPLOADED = 'Blob Bytes Uploaded' @@ -41,6 +42,32 @@ class Manager: return cls(api) # Things We Track + def send_new_download_start(self, download_id, name, claim_dict): + self._send_new_download_stats("start", download_id, name, claim_dict) + + def send_new_download_success(self, download_id, name, claim_dict): + self._send_new_download_stats("success", download_id, name, claim_dict) + + def send_new_download_fail(self, download_id, name, claim_dict, e): + self._send_new_download_stats("failure", download_id, name, claim_dict, { + 'name': type(e).__name__ if hasattr(type(e), "__name__") else str(type(e)), + 'message': e.message, + }) + + def _send_new_download_stats(self, action, download_id, name, claim_dict, e=None): + self.analytics_api.track({ + 'userId': 'lbry', # required, see https://segment.com/docs/sources/server/http/#track + 'event': NEW_DOWNLOAD_STAT, + 'properties': self._event_properties({ + 'download_id': download_id, + 'name': name, + 'sd_hash': None if not claim_dict else claim_dict.source_hash, + 'action': action, + 'error': e, + }), + 'context': self.context, + 'timestamp': utils.isonow(), + }) def send_server_startup(self): self.analytics_api.track(self._event(SERVER_STARTUP)) @@ -185,26 +212,18 @@ class Manager: @staticmethod def _make_context(platform, wallet): + # see https://segment.com/docs/spec/common/#context + # they say they'll ignore fields outside the spec, but evidently they don't context = { 'app': { - 'name': 'lbrynet', 'version': platform['lbrynet_version'], - 'python_version': platform['python_version'], 'build': platform['build'], - 'wallet': { - 'name': wallet, - 'version': platform['lbrynet_version'] - }, }, # TODO: expand os info to give linux/osx specific info 'os': { 'name': platform['os_system'], 'version': platform['os_release'] }, - 'library': { - 'name': 'lbrynet-analytics', - 'version': '1.0.0' - }, } if 'desktop' in platform and 'distro' in platform: context['os']['desktop'] = platform['desktop'] diff --git a/lbrynet/core/Error.py b/lbrynet/core/Error.py index 9e66a5005..16e019a97 100644 --- a/lbrynet/core/Error.py +++ b/lbrynet/core/Error.py @@ -49,6 +49,20 @@ class InsufficientFundsError(RPCError): code = -310 +class CurrencyConversionError(Exception): + pass + + +class FileOpenError(ValueError): + # this extends ValueError because it is replacing a ValueError in EncryptedFileDownloader + # and I don't know where it might get caught upstream + pass + + +class ResolveError(Exception): + pass + + class ConnectionClosedBeforeResponseError(Exception): pass diff --git a/lbrynet/daemon/Daemon.py b/lbrynet/daemon/Daemon.py index 9526fc460..84dc631aa 100644 --- a/lbrynet/daemon/Daemon.py +++ b/lbrynet/daemon/Daemon.py @@ -40,6 +40,7 @@ from lbrynet.core.StreamDescriptor import download_sd_blob from lbrynet.core.Error import InsufficientFundsError, UnknownNameError from lbrynet.core.Error import DownloadDataTimeout, DownloadSDTimeout from lbrynet.core.Error import NullFundsError, NegativeFundsError +from lbrynet.core.Error import ResolveError from lbrynet.dht.error import TimeoutError from lbrynet.core.Peer import Peer from lbrynet.core.SinglePeerDownloader import SinglePeerDownloader @@ -339,12 +340,14 @@ class Daemon(AuthJSONRPCServer): def _download_finished(download_id, name, claim_dict): report = yield self._get_stream_analytics_report(claim_dict) self.analytics_manager.send_download_finished(download_id, name, report, claim_dict) + self.analytics_manager.send_new_download_success(download_id, name, claim_dict) @defer.inlineCallbacks def _download_failed(error, download_id, name, claim_dict): report = yield self._get_stream_analytics_report(claim_dict) self.analytics_manager.send_download_errored(error, download_id, name, claim_dict, report) + self.analytics_manager.send_new_download_fail(download_id, name, claim_dict, error) if sd_hash in self.streams: downloader = self.streams[sd_hash] @@ -353,6 +356,7 @@ class Daemon(AuthJSONRPCServer): else: download_id = utils.random_string() self.analytics_manager.send_download_started(download_id, name, claim_dict) + self.analytics_manager.send_new_download_start(download_id, name, claim_dict) self.streams[sd_hash] = GetStream( self.sd_identifier, self.wallet_manager, self.exchange_rate_manager, self.blob_manager, self.dht_node.peer_finder, self.rate_limiter, self.payment_rate_manager, self.storage, @@ -1851,7 +1855,7 @@ class Daemon(AuthJSONRPCServer): resolved = resolved if 'value' in resolved else resolved.get('claim') if not resolved: - raise Exception( + raise ResolveError( "Failed to resolve stream at lbry://{}".format(uri.replace("lbry://", "")) ) diff --git a/lbrynet/daemon/ExchangeRateManager.py b/lbrynet/daemon/ExchangeRateManager.py index 527d9eb91..ec923de77 100644 --- a/lbrynet/daemon/ExchangeRateManager.py +++ b/lbrynet/daemon/ExchangeRateManager.py @@ -6,7 +6,7 @@ import treq from twisted.internet import defer from twisted.internet.task import LoopingCall -from lbrynet.core.Error import InvalidExchangeRateResponse +from lbrynet.core.Error import InvalidExchangeRateResponse, CurrencyConversionError log = logging.getLogger(__name__) @@ -233,7 +233,7 @@ class ExchangeRateManager: market.rate.currency_pair[0] == from_currency): return self.convert_currency( market.rate.currency_pair[1], to_currency, amount * market.rate.spot) - raise Exception( + raise CurrencyConversionError( 'Unable to convert {} from {} to {}'.format(amount, from_currency, to_currency)) def fee_dict(self): diff --git a/lbrynet/lbry_file/client/EncryptedFileDownloader.py b/lbrynet/lbry_file/client/EncryptedFileDownloader.py index 797e1c9b1..7869768ed 100644 --- a/lbrynet/lbry_file/client/EncryptedFileDownloader.py +++ b/lbrynet/lbry_file/client/EncryptedFileDownloader.py @@ -6,6 +6,7 @@ from binascii import hexlify, unhexlify from lbrynet.core.StreamDescriptor import save_sd_info from lbrynet.cryptstream.client.CryptStreamDownloader import CryptStreamDownloader from lbrynet.core.client.StreamProgressManager import FullStreamProgressManager +from lbrynet.core.Error import FileOpenError from lbrynet.lbry_file.client.EncryptedFileMetadataHandler import EncryptedFileMetadataHandler from twisted.internet import defer, threads @@ -153,7 +154,7 @@ class EncryptedFileSaver(EncryptedFileDownloader): self.file_written_to = file_written_to except IOError: log.error(traceback.format_exc()) - raise ValueError( + raise FileOpenError( "Failed to open %s. Make sure you have permission to save files to that" " location." % file_written_to )