Downloader options in its own class, show options in gui downloader

Put stream downloader options into its own class, and make stream
downloader options global to the stream type rather than specific
to each factory.

Show downloader options in the lbrynet-downloader-gui.

Make a class for downloader option choices, so that the descriptions
can be displayed.

In the console, if there are multiple choices for the download
option, make it a list selected by its index.

Make sure that the ConnectionManager closes properly when some of
the connections fail to open (e.g. due to a host being down)
This commit is contained in:
Jimmy Kiselak 2015-08-27 15:41:17 -04:00
parent f4b3187494
commit c8b2b7b279
18 changed files with 583 additions and 231 deletions

View file

@ -1,6 +1,16 @@
class DownloadChoice(object):
def __init__(self, value, short_description, long_description, bool_options_description=None):
self.value = value
self.short_description = short_description
self.long_description = long_description
self.bool_options_description = bool_options_description
class DownloadOption(object): class DownloadOption(object):
def __init__(self, option_types, long_description, short_description, default): def __init__(self, option_types, long_description, short_description, default_value,
default_value_description):
self.option_types = option_types self.option_types = option_types
self.long_description = long_description self.long_description = long_description
self.short_description = short_description self.short_description = short_description
self.default = default self.default_value = default_value
self.default_value_description = default_value_description

View file

@ -110,9 +110,10 @@ class StreamDescriptorIdentifier(object):
""" """
def __init__(self): def __init__(self):
self._sd_info_validators = {} # {stream_type: IStreamDescriptorValidator} self._sd_info_validators = {} # {stream_type: IStreamDescriptorValidator}
self._stream_options = {} # {stream_type: IStreamOptions}
self._stream_downloader_factories = defaultdict(list) # {stream_type: [IStreamDownloaderFactory]} self._stream_downloader_factories = defaultdict(list) # {stream_type: [IStreamDownloaderFactory]}
def add_stream_info_validator(self, stream_type, sd_info_validator): def add_stream_type(self, stream_type, sd_info_validator, stream_options):
""" """
This is how the StreamDescriptorIdentifier learns about new types of stream descriptors. This is how the StreamDescriptorIdentifier learns about new types of stream descriptors.
@ -126,9 +127,13 @@ class StreamDescriptorIdentifier(object):
will then be called. If the validation step fails, an exception will be thrown, preventing the stream will then be called. If the validation step fails, an exception will be thrown, preventing the stream
descriptor from being further processed. descriptor from being further processed.
@param stream_options: A class implementing the IStreamOptions interface. This class's constructor will be
passed the sd_info_validator object containing the raw metadata from the stream descriptor file.
@return: None @return: None
""" """
self._sd_info_validators[stream_type] = sd_info_validator self._sd_info_validators[stream_type] = sd_info_validator
self._stream_options[stream_type] = stream_options
def add_stream_downloader_factory(self, stream_type, factory): def add_stream_downloader_factory(self, stream_type, factory):
""" """
@ -167,14 +172,23 @@ class StreamDescriptorIdentifier(object):
assert stream_type in self._sd_info_validators, "Unrecognized stream type: " + str(stream_type) assert stream_type in self._sd_info_validators, "Unrecognized stream type: " + str(stream_type)
return self._sd_info_validators[stream_type] return self._sd_info_validators[stream_type]
def _get_options(self, stream_type):
assert stream_type in self._sd_info_validators, "Unrecognized stream type: " + str(stream_type)
return self._stream_options[stream_type]
def _return_info_and_factories(self, sd_info): def _return_info_and_factories(self, sd_info):
assert 'stream_type' in sd_info, 'Invalid stream descriptor. No stream_type parameter.' assert 'stream_type' in sd_info, 'Invalid stream descriptor. No stream_type parameter.'
stream_type = sd_info['stream_type'] stream_type = sd_info['stream_type']
factories = self._get_factories(stream_type)
validator = self._get_validator(stream_type)(sd_info) validator = self._get_validator(stream_type)(sd_info)
factories = [f for f in self._get_factories(stream_type) if f.can_download(validator)]
d = validator.validate() d = validator.validate()
d.addCallback(lambda _: (validator, factories)) def get_options():
options = self._get_options(stream_type)
return validator, options, factories
d.addCallback(lambda _: get_options())
return d return d

View file

@ -7,49 +7,55 @@ from lbrynet.core.client.ClientProtocol import ClientProtocolFactory
from lbrynet.core.Error import InsufficientFundsError from lbrynet.core.Error import InsufficientFundsError
class PeerConnectionHandler(object):
def __init__(self, request_creators, factory):
self.request_creators = request_creators
self.factory = factory
self.connection = None
class ConnectionManager(object): class ConnectionManager(object):
implements(interfaces.IConnectionManager) implements(interfaces.IConnectionManager)
def __init__(self, downloader, rate_limiter, primary_request_creators, secondary_request_creators): def __init__(self, downloader, rate_limiter, primary_request_creators, secondary_request_creators):
self.downloader = downloader self.downloader = downloader
self.rate_limiter = rate_limiter self.rate_limiter = rate_limiter
self.primary_request_creators = primary_request_creators self._primary_request_creators = primary_request_creators
self.secondary_request_creators = secondary_request_creators self._secondary_request_creators = secondary_request_creators
self.peer_connections = {} # {Peer: {'connection': connection, self._peer_connections = {} # {Peer: PeerConnectionHandler}
# 'request_creators': [IRequestCreator if using this connection]}} self._connections_closing = {} # {Peer: deferred (fired when the connection is closed)}
self.connections_closing = {} # {Peer: deferred (fired when the connection is closed)} self._next_manage_call = None
self.next_manage_call = None
def start(self): def start(self):
from twisted.internet import reactor from twisted.internet import reactor
if self.next_manage_call is not None and self.next_manage_call.active() is True: if self._next_manage_call is not None and self._next_manage_call.active() is True:
self.next_manage_call.cancel() self._next_manage_call.cancel()
self.next_manage_call = reactor.callLater(0, self._manage) self._next_manage_call = reactor.callLater(0, self._manage)
return defer.succeed(True) return defer.succeed(True)
def stop(self): def stop(self):
if self.next_manage_call is not None and self.next_manage_call.active() is True: if self._next_manage_call is not None and self._next_manage_call.active() is True:
self.next_manage_call.cancel() self._next_manage_call.cancel()
self.next_manage_call = None self._next_manage_call = None
closing_deferreds = [] closing_deferreds = []
for peer in self.peer_connections.keys(): for peer in self._peer_connections.keys():
def close_connection(p): def close_connection(p):
logging.info("Abruptly closing a connection to %s due to downloading being paused", logging.info("Abruptly closing a connection to %s due to downloading being paused",
str(p)) str(p))
if self.peer_connections[p]['factory'].p is not None: if self._peer_connections[p].factory.p is not None:
d = self.peer_connections[p]['factory'].p.cancel_requests() d = self._peer_connections[p].factory.p.cancel_requests()
else: else:
d = defer.succeed(True) d = defer.succeed(True)
def disconnect_peer(): def disconnect_peer():
self.peer_connections[p]['connection'].disconnect()
if p in self.peer_connections:
del self.peer_connections[p]
d = defer.Deferred() d = defer.Deferred()
self.connections_closing[p] = d self._connections_closing[p] = d
self._peer_connections[p].connection.disconnect()
if p in self._peer_connections:
del self._peer_connections[p]
return d return d
d.addBoth(lambda _: disconnect_peer()) d.addBoth(lambda _: disconnect_peer())
@ -62,7 +68,7 @@ class ConnectionManager(object):
logging.debug("Trying to get the next request for peer %s", str(peer)) logging.debug("Trying to get the next request for peer %s", str(peer))
if not peer in self.peer_connections: if not peer in self._peer_connections:
logging.debug("The peer has already been told to shut down.") logging.debug("The peer has already been told to shut down.")
return defer.succeed(False) return defer.succeed(False)
@ -75,11 +81,11 @@ class ConnectionManager(object):
def check_if_request_sent(request_sent, request_creator): def check_if_request_sent(request_sent, request_creator):
if request_sent is False: if request_sent is False:
if request_creator in self.peer_connections[peer]['request_creators']: if request_creator in self._peer_connections[peer].request_creators:
self.peer_connections[peer]['request_creators'].remove(request_creator) self._peer_connections[peer].request_creators.remove(request_creator)
else: else:
if not request_creator in self.peer_connections[peer]['request_creators']: if not request_creator in self._peer_connections[peer].request_creators:
self.peer_connections[peer]['request_creators'].append(request_creator) self._peer_connections[peer].request_creators.append(request_creator)
return request_sent return request_sent
def check_requests(requests): def check_requests(requests):
@ -89,7 +95,7 @@ class ConnectionManager(object):
def get_secondary_requests_if_necessary(have_request): def get_secondary_requests_if_necessary(have_request):
if have_request is True: if have_request is True:
ds = [] ds = []
for s_r_c in self.secondary_request_creators: for s_r_c in self._secondary_request_creators:
d = s_r_c.send_next_request(peer, protocol) d = s_r_c.send_next_request(peer, protocol)
ds.append(d) ds.append(d)
dl = defer.DeferredList(ds) dl = defer.DeferredList(ds)
@ -100,7 +106,7 @@ class ConnectionManager(object):
ds = [] ds = []
for p_r_c in self.primary_request_creators: for p_r_c in self._primary_request_creators:
d = p_r_c.send_next_request(peer, protocol) d = p_r_c.send_next_request(peer, protocol)
d.addErrback(handle_error) d.addErrback(handle_error)
d.addCallback(check_if_request_sent, p_r_c) d.addCallback(check_if_request_sent, p_r_c)
@ -112,11 +118,11 @@ class ConnectionManager(object):
return dl return dl
def protocol_disconnected(self, peer, protocol): def protocol_disconnected(self, peer, protocol):
if peer in self.peer_connections: if peer in self._peer_connections:
del self.peer_connections[peer] del self._peer_connections[peer]
if peer in self.connections_closing: if peer in self._connections_closing:
d = self.connections_closing[peer] d = self._connections_closing[peer]
del self.connections_closing[peer] del self._connections_closing[peer]
d.callback(True) d.callback(True)
def _rank_request_creator_connections(self): def _rank_request_creator_connections(self):
@ -125,9 +131,9 @@ class ConnectionManager(object):
connections open that it likes connections open that it likes
""" """
def count_peers(request_creator): def count_peers(request_creator):
return len([p for p in self.peer_connections.itervalues() if request_creator in p['request_creators']]) return len([p for p in self._peer_connections.itervalues() if request_creator in p.request_creators])
return sorted(self.primary_request_creators, key=count_peers) return sorted(self._primary_request_creators, key=count_peers)
def _connect_to_peer(self, peer): def _connect_to_peer(self, peer):
@ -136,10 +142,10 @@ class ConnectionManager(object):
if peer is not None: if peer is not None:
logging.debug("Trying to connect to %s", str(peer)) logging.debug("Trying to connect to %s", str(peer))
factory = ClientProtocolFactory(peer, self.rate_limiter, self) factory = ClientProtocolFactory(peer, self.rate_limiter, self)
self._peer_connections[peer] = PeerConnectionHandler(self._primary_request_creators[:],
factory)
connection = reactor.connectTCP(peer.host, peer.port, factory) connection = reactor.connectTCP(peer.host, peer.port, factory)
self.peer_connections[peer] = {'connection': connection, self._peer_connections[peer].connection = connection
'request_creators': self.primary_request_creators[:],
'factory': factory}
def _manage(self): def _manage(self):
@ -162,16 +168,16 @@ class ConnectionManager(object):
if peers is None: if peers is None:
return None return None
for peer in peers: for peer in peers:
if not peer in self.peer_connections: if not peer in self._peer_connections:
logging.debug("Got a good peer. Returning peer %s", str(peer)) logging.debug("Got a good peer. Returning peer %s", str(peer))
return peer return peer
logging.debug("Couldn't find a good peer to connect to") logging.debug("Couldn't find a good peer to connect to")
return None return None
if len(self.peer_connections) < MAX_CONNECTIONS_PER_STREAM: if len(self._peer_connections) < MAX_CONNECTIONS_PER_STREAM:
ordered_request_creators = self._rank_request_creator_connections() ordered_request_creators = self._rank_request_creator_connections()
d = get_new_peers(ordered_request_creators) d = get_new_peers(ordered_request_creators)
d.addCallback(pick_best_peer) d.addCallback(pick_best_peer)
d.addCallback(self._connect_to_peer) d.addCallback(self._connect_to_peer)
self.next_manage_call = reactor.callLater(1, self._manage) self._next_manage_call = reactor.callLater(1, self._manage)

View file

@ -445,10 +445,7 @@ class IQueryHandlerFactory(Interface):
""" """
class IStreamDownloaderFactory(Interface): class IStreamDownloaderOptions(Interface):
"""
Construct IStreamDownloaders and provide options that will be passed to those IStreamDownloaders.
"""
def get_downloader_options(self, sd_validator, payment_rate_manager): def get_downloader_options(self, sd_validator, payment_rate_manager):
""" """
Return the list of options that can be used to modify IStreamDownloader behavior Return the list of options that can be used to modify IStreamDownloader behavior
@ -459,8 +456,28 @@ class IStreamDownloaderFactory(Interface):
@param payment_rate_manager: The payment rate manager currently in effect for the downloader @param payment_rate_manager: The payment rate manager currently in effect for the downloader
@type payment_rate_manager: PaymentRateManager @type payment_rate_manager: PaymentRateManager
@return: [(option_description, default)] @return: [DownloadOption]
@rtype: [(string, string)] @rtype: [DownloadOption]
"""
class IStreamDownloaderFactory(Interface):
"""
Construct IStreamDownloaders and provide options that will be passed to those IStreamDownloaders.
"""
def can_download(self, sd_validator, payment_rate_manager):
"""
Decide whether the downloaders created by this factory can download the stream described by sd_validator
@param sd_validator: object containing stream metadata
@type sd_validator: object which implements IStreamDescriptorValidator interface
@param payment_rate_manager: The payment rate manager currently in effect for the downloader
@type payment_rate_manager: PaymentRateManager
@return: True if the downloaders can download the stream, False otherwise
@rtype: bool
""" """
def make_downloader(self, sd_validator, options, payment_rate_manager): def make_downloader(self, sd_validator, options, payment_rate_manager):
@ -470,10 +487,10 @@ class IStreamDownloaderFactory(Interface):
@param sd_validator: object containing stream metadata which will be given to the IStreamDownloader @param sd_validator: object containing stream metadata which will be given to the IStreamDownloader
@type sd_validator: object which implements IStreamDescriptorValidator interface @type sd_validator: object which implements IStreamDescriptorValidator interface
@param options: a list of strings that will be used by the IStreamDownloaderFactory to @param options: a list of values that will be used by the IStreamDownloaderFactory to
construct the IStreamDownloader. the options are in the same order as they were given construct the IStreamDownloader. the options are in the same order as they were given
by get_downloader_options. by get_downloader_options.
@type options: [string] @type options: [Object]
@param payment_rate_manager: the PaymentRateManager which the IStreamDownloader should use. @param payment_rate_manager: the PaymentRateManager which the IStreamDownloader should use.
@type payment_rate_manager: PaymentRateManager @type payment_rate_manager: PaymentRateManager

View file

@ -3,7 +3,6 @@ import binascii
from zope.interface import implements from zope.interface import implements
from lbrynet.core.DownloadOption import DownloadOption
from lbrynet.lbryfile.StreamDescriptor import save_sd_info from lbrynet.lbryfile.StreamDescriptor import save_sd_info
from lbrynet.cryptstream.client.CryptStreamDownloader import CryptStreamDownloader from lbrynet.cryptstream.client.CryptStreamDownloader import CryptStreamDownloader
from lbrynet.core.client.StreamProgressManager import FullStreamProgressManager from lbrynet.core.client.StreamProgressManager import FullStreamProgressManager
@ -11,6 +10,7 @@ from lbrynet.interfaces import IStreamDownloaderFactory
from lbrynet.lbryfile.client.LBRYFileMetadataHandler import LBRYFileMetadataHandler from lbrynet.lbryfile.client.LBRYFileMetadataHandler import LBRYFileMetadataHandler
import os import os
from twisted.internet import defer, threads, reactor from twisted.internet import defer, threads, reactor
from distutils.spawn import find_executable
class LBRYFileDownloader(CryptStreamDownloader): class LBRYFileDownloader(CryptStreamDownloader):
@ -94,26 +94,11 @@ class LBRYFileDownloaderFactory(object):
self.stream_info_manager = stream_info_manager self.stream_info_manager = stream_info_manager
self.wallet = wallet self.wallet = wallet
def get_downloader_options(self, sd_validator, payment_rate_manager): def can_download(self, sd_validator):
options = [ return True
DownloadOption(
[float, None],
"rate which will be paid for data (None means use application default)",
"data payment rate",
None
),
DownloadOption(
[bool],
"allow reuploading data downloaded for this file",
"allow upload",
True
),
]
return options
def make_downloader(self, sd_validator, options, payment_rate_manager, **kwargs): def make_downloader(self, sd_validator, options, payment_rate_manager, **kwargs):
if options[0] is not None: payment_rate_manager.min_blob_data_payment_rate = options[0]
payment_rate_manager.float(options[0])
upload_allowed = options[1] upload_allowed = options[1]
def create_downloader(stream_hash): def create_downloader(stream_hash):
@ -276,6 +261,9 @@ class LBRYFileOpener(LBRYFileDownloader):
class LBRYFileOpenerFactory(LBRYFileDownloaderFactory): class LBRYFileOpenerFactory(LBRYFileDownloaderFactory):
def can_download(self, sd_validator):
return bool(find_executable('vlc'))
def _make_downloader(self, stream_hash, payment_rate_manager, stream_info, upload_allowed): def _make_downloader(self, stream_hash, payment_rate_manager, stream_info, upload_allowed):
return LBRYFileOpener(stream_hash, self.peer_finder, self.rate_limiter, self.blob_manager, return LBRYFileOpener(stream_hash, self.peer_finder, self.rate_limiter, self.blob_manager,
self.stream_info_manager, payment_rate_manager, self.wallet, upload_allowed) self.stream_info_manager, payment_rate_manager, self.wallet, upload_allowed)

View file

@ -0,0 +1,55 @@
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType, LBRYFileStreamDescriptorValidator
from lbrynet.core.DownloadOption import DownloadOption, DownloadChoice
def add_lbry_file_to_sd_identifier(sd_identifier):
sd_identifier.add_stream_type(LBRYFileStreamType, LBRYFileStreamDescriptorValidator, LBRYFileOptions())
class LBRYFileOptions(object):
def __init__(self):
pass
def get_downloader_options(self, sd_validator, payment_rate_manager):
prm = payment_rate_manager
def get_default_data_rate_description():
if prm.min_blob_data_payment_rate is None:
return "Application default (%s LBC/MB)" % str(prm.base.min_blob_data_payment_rate)
else:
return "%f LBC/MB" % prm.min_blob_data_payment_rate
rate_choices = []
rate_choices.append(DownloadChoice(prm.min_blob_data_payment_rate,
"No change - %s" % get_default_data_rate_description(),
"No change - %s" % get_default_data_rate_description()))
if prm.min_blob_data_payment_rate is not None:
rate_choices.append(DownloadChoice(None,
"Application default (%s LBC/MB)" % str(prm.base.min_blob_data_payment_rate),
"Application default (%s LBC/MB)" % str(prm.base.min_blob_data_payment_rate)))
rate_choices.append(DownloadChoice(float,
"Enter rate in LBC/MB",
"Enter rate in LBC/MB"))
options = [
DownloadOption(
rate_choices,
"Rate which will be paid for data",
"data payment rate",
prm.min_blob_data_payment_rate,
get_default_data_rate_description()
),
DownloadOption(
[
DownloadChoice(bool,
None,
None,
bool_options_description=("Allow", "Disallow")),
],
"Allow reuploading data downloaded for this file",
"allow upload",
True,
"Allow"
),
]
return options

View file

@ -2,7 +2,6 @@
Download LBRY Files from LBRYnet and save them to disk. Download LBRY Files from LBRYnet and save them to disk.
""" """
from lbrynet.core.DownloadOption import DownloadOption
from zope.interface import implements from zope.interface import implements
from lbrynet.core.client.StreamProgressManager import FullStreamProgressManager from lbrynet.core.client.StreamProgressManager import FullStreamProgressManager
from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaver, LBRYFileDownloader from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaver, LBRYFileDownloader
@ -117,22 +116,8 @@ class ManagedLBRYFileDownloaderFactory(object):
def __init__(self, lbry_file_manager): def __init__(self, lbry_file_manager):
self.lbry_file_manager = lbry_file_manager self.lbry_file_manager = lbry_file_manager
def get_downloader_options(self, sd_validator, payment_rate_manager): def can_download(self, sd_validator):
options = [ return True
DownloadOption(
[float, None],
"rate which will be paid for data (None means use application default)",
"data payment rate",
None
),
DownloadOption(
[bool],
"allow reuploading data downloaded for this file",
"allow upload",
True
),
]
return options
def make_downloader(self, sd_validator, options, payment_rate_manager): def make_downloader(self, sd_validator, options, payment_rate_manager):
data_rate = options[0] data_rate = options[0]

View file

@ -7,7 +7,6 @@ import json
import leveldb import leveldb
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamDescriptorValidator
import os import os
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory
@ -98,7 +97,6 @@ class LBRYFileManager(object):
def _add_to_sd_identifier(self): def _add_to_sd_identifier(self):
downloader_factory = ManagedLBRYFileDownloaderFactory(self) downloader_factory = ManagedLBRYFileDownloaderFactory(self)
self.sd_identifier.add_stream_info_validator(LBRYFileStreamType, LBRYFileStreamDescriptorValidator)
self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory) self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, downloader_factory)
def _start_lbry_files(self): def _start_lbry_files(self):

View file

@ -1,15 +1,12 @@
from lbrynet.core.StreamDescriptor import BlobStreamDescriptorWriter from lbrynet.core.StreamDescriptor import BlobStreamDescriptorWriter
from lbrynet.lbrylive.StreamDescriptor import get_sd_info, LiveStreamType, LBRYLiveStreamDescriptorValidator from lbrynet.lbrylive.StreamDescriptor import get_sd_info
from lbrynet.cryptstream.CryptStreamCreator import CryptStreamCreator from lbrynet.cryptstream.CryptStreamCreator import CryptStreamCreator
from lbrynet.lbrylive.LiveBlob import LiveStreamBlobMaker from lbrynet.lbrylive.LiveBlob import LiveStreamBlobMaker
from lbrynet.lbrylive.PaymentRateManager import BaseLiveStreamPaymentRateManager
from lbrynet.core.cryptoutils import get_lbry_hash_obj, get_pub_key, sign_with_pass_phrase from lbrynet.core.cryptoutils import get_lbry_hash_obj, get_pub_key, sign_with_pass_phrase
from Crypto import Random from Crypto import Random
import binascii import binascii
import logging import logging
from lbrynet.conf import CRYPTSD_FILE_EXTENSION from lbrynet.conf import CRYPTSD_FILE_EXTENSION
from lbrynet.conf import MIN_BLOB_INFO_PAYMENT_RATE
from lbrynet.lbrylive.client.LiveStreamDownloader import FullLiveStreamDownloaderFactory
from twisted.internet import interfaces, defer from twisted.internet import interfaces, defer
from twisted.protocols.basic import FileSender from twisted.protocols.basic import FileSender
from zope.interface import implements from zope.interface import implements
@ -173,17 +170,4 @@ class StdinStreamProducer(object):
self.consumer.write(data) self.consumer.write(data)
def childConnectionLost(self, fd, reason): def childConnectionLost(self, fd, reason):
self.stopProducing() self.stopProducing()
def add_live_stream_to_sd_identifier(session, stream_info_manager, sd_identifier):
downloader_factory = FullLiveStreamDownloaderFactory(session.peer_finder,
session.rate_limiter,
session.blob_manager,
stream_info_manager,
session.wallet,
BaseLiveStreamPaymentRateManager(
MIN_BLOB_INFO_PAYMENT_RATE
))
sd_identifier.add_stream_info_validator(LiveStreamType, LBRYLiveStreamDescriptorValidator)
sd_identifier.add_stream_downloader_factory(LiveStreamType, downloader_factory)

View file

@ -1,5 +1,4 @@
import binascii import binascii
from lbrynet.core.DownloadOption import DownloadOption
from lbrynet.cryptstream.client.CryptStreamDownloader import CryptStreamDownloader from lbrynet.cryptstream.client.CryptStreamDownloader import CryptStreamDownloader
from zope.interface import implements from zope.interface import implements
from lbrynet.lbrylive.client.LiveStreamMetadataHandler import LiveStreamMetadataHandler from lbrynet.lbrylive.client.LiveStreamMetadataHandler import LiveStreamMetadataHandler
@ -9,6 +8,9 @@ from lbrynet.lbrylive.StreamDescriptor import save_sd_info
from lbrynet.lbrylive.PaymentRateManager import LiveStreamPaymentRateManager from lbrynet.lbrylive.PaymentRateManager import LiveStreamPaymentRateManager
from twisted.internet import defer, threads # , process from twisted.internet import defer, threads # , process
from lbrynet.interfaces import IStreamDownloaderFactory from lbrynet.interfaces import IStreamDownloaderFactory
from lbrynet.lbrylive.PaymentRateManager import BaseLiveStreamPaymentRateManager
from lbrynet.conf import MIN_BLOB_INFO_PAYMENT_RATE
from lbrynet.lbrylive.StreamDescriptor import LiveStreamType
class LiveStreamDownloader(CryptStreamDownloader): class LiveStreamDownloader(CryptStreamDownloader):
@ -138,28 +140,8 @@ class FullLiveStreamDownloaderFactory(object):
self.wallet = wallet self.wallet = wallet
self.default_payment_rate_manager = default_payment_rate_manager self.default_payment_rate_manager = default_payment_rate_manager
def get_downloader_options(self, sd_validator, payment_rate_manager): def can_download(self, sd_validator):
options = [ return True
DownloadOption(
[float, None],
"rate which will be paid for data (None means use application default)",
"data payment rate",
None
),
DownloadOption(
[float, None],
"rate which will be paid for metadata (None means use application default)",
"metadata payment rate",
None
),
DownloadOption(
[bool],
"allow reuploading data downloaded for this file",
"allow upload",
True
),
]
return options
def make_downloader(self, sd_validator, options, payment_rate_manager): def make_downloader(self, sd_validator, options, payment_rate_manager):
# TODO: check options for payment rate manager parameters # TODO: check options for payment rate manager parameters
@ -177,4 +159,15 @@ class FullLiveStreamDownloaderFactory(object):
return d return d
d.addCallback(create_downloader) d.addCallback(create_downloader)
return d return d
def add_full_live_stream_downloader_to_sd_identifier(session, stream_info_manager, sd_identifier,
base_live_stream_payment_rate_manager):
downloader_factory = FullLiveStreamDownloaderFactory(session.peer_finder,
session.rate_limiter,
session.blob_manager,
stream_info_manager,
session.wallet,
base_live_stream_payment_rate_manager)
sd_identifier.add_stream_downloader_factory(LiveStreamType, downloader_factory)

View file

@ -0,0 +1,73 @@
from lbrynet.lbrylive.StreamDescriptor import LiveStreamType, LBRYLiveStreamDescriptorValidator
from lbrynet.core.DownloadOption import DownloadOption, DownloadChoice
def add_live_stream_to_sd_identifier(sd_identifier, base_live_stream_payment_rate_manager):
sd_identifier.add_stream_type(LiveStreamType, LBRYLiveStreamDescriptorValidator,
LiveStreamOptions(base_live_stream_payment_rate_manager))
class LiveStreamOptions(object):
def __init__(self, base_live_stream_payment_rate_manager):
self.base_live_stream_prm = base_live_stream_payment_rate_manager
def get_downloader_options(self, sd_validator, payment_rate_manager):
prm = payment_rate_manager
def get_default_data_rate_description():
if prm.min_blob_data_payment_rate is None:
return "Application default (%s LBC/MB)" % str(prm.base.min_blob_data_payment_rate)
else:
return "%f LBC/MB" % prm.min_blob_data_payment_rate
options = [
DownloadOption(
[
DownloadChoice(None,
"No change",
"No change"),
DownloadChoice(None,
"Application default (%s LBC/MB)" % str(prm.base.min_blob_data_payment_rate),
"Default (%s LBC/MB)" % str(prm.base.min_blob_data_payment_rate)),
DownloadChoice(float,
"Rate in LBC/MB",
"Rate in LBC/MB")
],
"rate which will be paid for data",
"data payment rate",
prm.min_blob_data_payment_rate,
get_default_data_rate_description()
),
DownloadOption(
[
DownloadChoice(None,
"No change",
"No change"),
DownloadChoice(None,
"Application default (%s LBC/MB)" % str(self.base_live_stream_prm.min_live_blob_info_payment_rate),
"Default (%s LBC/MB)" % str(self.base_live_stream_prm.min_live_blob_info_payment_rate)),
DownloadChoice(float,
"Rate in LBC/MB",
"Rate in LBC/MB")
],
"rate which will be paid for metadata",
"metadata payment rate",
None,
"Application default (%s LBC/MB)" % str(self.base_live_stream_prm.min_live_blob_info_payment_rate)
),
DownloadOption(
[
DownloadChoice(True,
"Allow reuploading data downloaded for this file",
"Allow reuploading"),
DownloadChoice(False,
"Disallow reuploading data downloaded for this file",
"Disallow reuploading")
],
"allow reuploading data downloaded for this file",
"allow upload",
True,
"Allow"
),
]
return options

View file

@ -13,6 +13,10 @@ class InvalidChoiceError(Exception):
pass pass
class InvalidValueError(Exception):
pass
class ControlHandlerFactory(object): class ControlHandlerFactory(object):
implements(IControlHandlerFactory) implements(IControlHandlerFactory)
@ -235,9 +239,11 @@ class AddStream(ControlHandler):
self.factories = None self.factories = None
self.factory = None self.factory = None
self.info_validator = None self.info_validator = None
self.options = None
self.options_left = [] self.options_left = []
self.options_chosen = [] self.options_chosen = []
self.current_option = None self.current_option = None
self.current_choice = None
self.downloader = None self.downloader = None
self.got_options_response = False self.got_options_response = False
self.loading_failed = False self.loading_failed = False
@ -274,18 +280,31 @@ class AddStream(ControlHandler):
return False, defer.succeed(self._show_factory_choices()) return False, defer.succeed(self._show_factory_choices())
if self.got_options_response is False: if self.got_options_response is False:
self.got_options_response = True self.got_options_response = True
if line == 'y' or line == 'Y': if line == 'y' or line == 'Y' and self.options_left:
if self.options_left: return False, defer.succeed(self._get_next_option_prompt())
return False, defer.succeed(self._get_next_option_prompt()) else:
self.options_chosen = [option.default for option in self.options_left] self.options_chosen = [option.default_value for option in self.options_left]
self.options_left = [] self.options_left = []
return False, defer.succeed(self.line_prompt3) return False, defer.succeed(self.line_prompt3)
if self.current_option is not None: if self.current_option is not None:
try: if self.current_choice is None:
choice = self._get_choice_from_input(line) try:
except InvalidChoiceError: self.current_choice = self._get_choice_from_input(line)
return False, defer.succeed(self._get_next_option_prompt(invalid_response=True)) except InvalidChoiceError:
self.options_chosen.append(choice) return False, defer.succeed(self._get_next_option_prompt(invalid_choice=True))
choice = self.current_option.option_types[self.current_choice]
if choice.value == float or choice.value == bool:
return False, defer.succeed(self._get_choice_value_prompt())
else:
value = choice.value
else:
try:
value = self._get_value_for_choice(line)
except InvalidValueError:
return False, defer.succeed(self._get_choice_value_prompt(invalid_value=True))
self.options_chosen.append(value)
self.current_choice = None
self.current_option = None
self.options_left = self.options_left[1:] self.options_left = self.options_left[1:]
if self.options_left: if self.options_left:
return False, defer.succeed(self._get_next_option_prompt()) return False, defer.succeed(self._get_next_option_prompt())
@ -299,23 +318,13 @@ class AddStream(ControlHandler):
return True, d return True, d
def _get_choice_from_input(self, line): def _get_choice_from_input(self, line):
if line == "": try:
return self.current_option.default choice_num = int(line)
for option_type in self.current_option.option_types: except ValueError:
if option_type == float: raise InvalidChoiceError()
try: if 0 <= choice_num < len(self.current_option.option_types):
return float(line) return choice_num
except ValueError: raise InvalidChoiceError()
pass
if option_type is None:
if line.lower() == "none":
return None
if option_type == bool:
if line.lower() == "true" or line.lower() == "t":
return True
if line.lower() == "false" or line.lower() == "f":
return False
raise InvalidChoiceError(line)
def _load_info_and_factories(self, sd_file): def _load_info_and_factories(self, sd_file):
return defer.fail(NotImplementedError()) return defer.fail(NotImplementedError())
@ -333,7 +342,7 @@ class AddStream(ControlHandler):
def _choose_factory(self, info_and_factories): def _choose_factory(self, info_and_factories):
self.loading_info_and_factories_deferred = None self.loading_info_and_factories_deferred = None
self.info_validator, self.factories = info_and_factories self.info_validator, self.options, self.factories = info_and_factories
if len(self.factories) == 1: if len(self.factories) == 1:
self.factory = self.factories[0] self.factory = self.factories[0]
return self._show_info_and_options() return self._show_info_and_options()
@ -346,38 +355,71 @@ class AddStream(ControlHandler):
return str(prompt) return str(prompt)
def _show_info_and_options(self): def _show_info_and_options(self):
self.options_left = self.factory.get_downloader_options(self.info_validator, self.options_left = self.options.get_downloader_options(self.info_validator,
self.payment_rate_manager) self.payment_rate_manager)
prompt = "Stream info:\n" prompt = "Stream info:\n"
for info_line in self.info_validator.info_to_show(): for info_line in self.info_validator.info_to_show():
prompt += info_line[0] + ": " + info_line[1] + "\n" prompt += info_line[0] + ": " + info_line[1] + "\n"
prompt += "\nOptions:\n" prompt += "\nOptions:\n"
for option in self.options_left: for option in self.options_left:
prompt += option.long_description + ": " + str(option.default) + "\n" prompt += option.long_description + ": " + str(option.default_value_description) + "\n"
prompt += "\nModify options? (y/n)" prompt += "\nModify options? (y/n)"
return str(prompt) return str(prompt)
def _get_option_type_description(self, option_type): def _get_list_of_option_types(self):
if option_type == float: options_string = ""
return "floating point number (e.g. 1.0)" for i, option_type in enumerate(self.current_option.option_types):
if option_type == bool: options_string += "[%s] %s\n" % (str(i), option_type.long_description)
return "True or False" options_string += "Enter choice:"
if option_type is None: return options_string
return "None"
def _get_next_option_prompt(self, invalid_response=False): def _get_choice_value_prompt(self, invalid_value=False):
assert len(self.options_left), "Something went wrong. There were no options left" choice = self.current_option.option_types[self.current_choice]
choice = self.options_left[0]
choice_string = "" choice_string = ""
if invalid_response is True: if invalid_value is True:
"Invalid value entered. Try again.\n"
if choice.short_description is not None:
choice_string += choice.short_description + "\n"
if choice.value == float:
choice_string += "Enter floating point number (e.g. 1.0):"
elif choice.value == bool:
true_string = "Yes"
false_string = "No"
if choice.bool_options_description is not None:
true_string, false_string = choice.bool_options_description
choice_string += "[0] %s\n[1] %s\nEnter choice:" % (true_string, false_string)
else:
NotImplementedError()
return choice_string
def _get_value_for_choice(self, input):
choice = self.current_option.option_types[self.current_choice]
if choice.value == float:
try:
return float(input)
except ValueError:
raise InvalidValueError()
elif choice.value == bool:
if input == "0":
return True
elif input == "1":
return False
raise InvalidValueError()
raise NotImplementedError()
def _get_next_option_prompt(self, invalid_choice=False):
assert len(self.options_left), "Something went wrong. There were no options left"
self.current_option = self.options_left[0]
choice_string = ""
if invalid_choice is True:
choice_string += "Invalid response entered. Try again.\n" choice_string += "Invalid response entered. Try again.\n"
choice_string += choice.long_description + "\n"
choice_string += "Valid inputs:\n" choice_string += self.current_option.long_description + "\n"
for option_type in choice.option_types: if len(self.current_option.option_types) > 1:
choice_string += "\t" + self._get_option_type_description(option_type) + "\n" choice_string += self._get_list_of_option_types()
choice_string += "Leave blank for default (" + str(choice.default) + ")\n" elif len(self.current_option.option_types) == 1:
choice_string += "Enter choice:" self.current_choice = 0
self.current_option = choice choice_string += self._get_choice_value_prompt()
return choice_string return choice_string
def _start_download(self): def _start_download(self):

View file

@ -15,6 +15,7 @@ from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerF
from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory
from lbrynet.core.server.ServerProtocol import ServerProtocolFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory
from lbrynet.core.PTCWallet import PTCWallet from lbrynet.core.PTCWallet import PTCWallet
from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier
from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileOpenerFactory from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileOpenerFactory
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType
from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager
@ -77,6 +78,7 @@ class LBRYConsole():
d = threads.deferToThread(self._create_directory) d = threads.deferToThread(self._create_directory)
d.addCallback(lambda _: self._get_settings()) d.addCallback(lambda _: self._get_settings())
d.addCallback(lambda _: self._get_session()) d.addCallback(lambda _: self._get_session())
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_manager())
d.addCallback(lambda _: self._setup_lbry_file_opener()) d.addCallback(lambda _: self._setup_lbry_file_opener())
d.addCallback(lambda _: self._setup_control_handlers()) d.addCallback(lambda _: self._setup_control_handlers())

View file

@ -17,7 +17,8 @@ from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier
from lbrynet.core.PaymentRateManager import PaymentRateManager from lbrynet.core.PaymentRateManager import PaymentRateManager
from lbrynet.lbryfile.LBRYFileMetadataManager import TempLBRYFileMetadataManager from lbrynet.lbryfile.LBRYFileMetadataManager import TempLBRYFileMetadataManager
from lbrynet.core import StreamDescriptor from lbrynet.core import StreamDescriptor
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType, LBRYFileStreamDescriptorValidator from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType
import requests import requests
@ -101,7 +102,7 @@ class LBRYDownloader(object):
return defer.succeed(True) return defer.succeed(True)
def _setup_stream_identifier(self): def _setup_stream_identifier(self):
self.sd_identifier.add_stream_info_validator(LBRYFileStreamType, LBRYFileStreamDescriptorValidator) add_lbry_file_to_sd_identifier(self.sd_identifier)
file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter, file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter,
self.session.blob_manager, self.stream_info_manager, self.session.blob_manager, self.stream_info_manager,
self.session.wallet, self.download_directory) self.session.wallet, self.download_directory)
@ -184,7 +185,7 @@ class LBRYDownloader(object):
return stream_name, stream_size return stream_name, stream_size
def choose_download_factory(info_and_factories): def choose_download_factory(info_and_factories):
info_validator, factories = info_and_factories info_validator, options, factories = info_and_factories
stream_name, stream_size = get_info_from_validator(info_validator) stream_name, stream_size = get_info_from_validator(info_validator)
if isinstance(stream_size, (int, long)): if isinstance(stream_size, (int, long)):
price = payment_rate_manager.get_effective_min_blob_data_payment_rate() price = payment_rate_manager.get_effective_min_blob_data_payment_rate()
@ -192,26 +193,30 @@ class LBRYDownloader(object):
else: else:
estimated_cost = "unknown" estimated_cost = "unknown"
stream_frame.show_stream_metadata(stream_name, stream_size, estimated_cost) stream_frame.show_stream_metadata(stream_name, stream_size)
available_options = options.get_downloader_options(info_validator, payment_rate_manager)
stream_frame.show_download_options(available_options)
get_downloader_d = defer.Deferred() get_downloader_d = defer.Deferred()
def create_downloader(f): def create_downloader(f, chosen_options):
def fire_get_downloader_d(downloader): def fire_get_downloader_d(downloader):
if not get_downloader_d.called: if not get_downloader_d.called:
get_downloader_d.callback(downloader) get_downloader_d.callback(downloader)
stream_frame.disable_download_buttons() stream_frame.disable_download_buttons()
download_options = [o.default for o in f.get_downloader_options(info_validator, payment_rate_manager)] d = f.make_downloader(info_validator, chosen_options,
d = f.make_downloader(info_validator, download_options,
payment_rate_manager) payment_rate_manager)
d.addCallback(fire_get_downloader_d) d.addCallback(fire_get_downloader_d)
for factory in factories: for factory in factories:
def choose_factory(f=factory): def choose_factory(f=factory):
create_downloader(f) chosen_options = stream_frame.get_chosen_options()
create_downloader(f, chosen_options)
stream_frame.add_download_factory(factory, choose_factory) stream_frame.add_download_factory(factory, choose_factory)
@ -301,9 +306,9 @@ class StreamFrame(object):
self.uri_label.grid(row=0, column=0, sticky=tk.W) self.uri_label.grid(row=0, column=0, sticky=tk.W)
if os.name == "nt": if os.name == "nt":
close_cursor = "" self.button_cursor = ""
else: else:
close_cursor = "hand1" self.button_cursor = "hand1"
close_file_name = "close2.gif" close_file_name = "close2.gif"
try: try:
@ -316,7 +321,7 @@ class StreamFrame(object):
file=close_file file=close_file
) )
self.close_button = ttk.Button( self.close_button = ttk.Button(
self.stream_frame_header, command=self.cancel, style="Stop.TButton", cursor=close_cursor 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.config(image=self.close_picture)
self.close_button.grid(row=0, column=1, sticky=tk.E + tk.N) self.close_button.grid(row=0, column=1, sticky=tk.E + tk.N)
@ -334,26 +339,50 @@ class StreamFrame(object):
self.stream_frame_body.grid_columnconfigure(0, weight=1) self.stream_frame_body.grid_columnconfigure(0, weight=1)
self.info_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame") self.metadata_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
self.info_frame.grid(sticky=tk.W + tk.E, row=1) self.metadata_frame.grid(sticky=tk.W + tk.E, row=1)
self.info_frame.grid_columnconfigure(0, weight=1)
self.metadata_frame = ttk.Frame(self.info_frame, style="E.TFrame")
self.metadata_frame.grid(sticky=tk.W + tk.E)
self.metadata_frame.grid_columnconfigure(0, weight=1) self.metadata_frame.grid_columnconfigure(0, weight=1)
self.outer_button_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame") self.options_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
self.outer_button_frame.grid(sticky=tk.W + tk.E, row=2)
self.button_frame = ttk.Frame(self.outer_button_frame, style="E.TFrame") self.outer_button_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
self.button_frame.pack(side=tk.TOP) self.outer_button_frame.grid(sticky=tk.W + tk.E, row=4)
show_options_picture_file_name = "show_options.gif"
try:
show_options_picture_file = os.path.join(os.path.dirname(__file__),
show_options_picture_file_name)
except NameError:
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)
self.show_options_picture = tk.PhotoImage(
file=show_options_picture_file
)
hide_options_picture_file_name = "hide_options.gif"
try:
hide_options_picture_file = os.path.join(os.path.dirname(__file__),
hide_options_picture_file_name)
except NameError:
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)
self.hide_options_picture = tk.PhotoImage(
file=hide_options_picture_file
)
self.show_options_button = None
self.status_label = None self.status_label = None
self.name_label = None self.name_label = None
self.bytes_downloaded_label = None self.bytes_downloaded_label = None
self.bytes_outputted_label = None self.bytes_outputted_label = None
self.button_frame = None
self.download_buttons = [] self.download_buttons = []
self.option_frames = []
self.name_font = None self.name_font = None
self.description_label = None self.description_label = None
self.file_name_frame = None self.file_name_frame = None
@ -416,7 +445,7 @@ class StreamFrame(object):
return "%.1f %s" % (round((stream_size * 1.0 / factor), 1), units) return "%.1f %s" % (round((stream_size * 1.0 / factor), 1), units)
return stream_size return stream_size
def show_stream_metadata(self, stream_name, stream_size, estimated_cost): def show_stream_metadata(self, stream_name, stream_size):
if self.status_label is not None: if self.status_label is not None:
self.status_label.destroy() self.status_label.destroy()
@ -436,19 +465,6 @@ class StreamFrame(object):
) )
file_name_label.grid(row=0, column=3) file_name_label.grid(row=0, column=3)
self.outer_button_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
self.outer_button_frame.grid(sticky=tk.W + tk.E, row=2)
self.cost_frame = ttk.Frame(self.outer_button_frame, style="F.TFrame")
self.cost_frame.grid(row=0, column=0, sticky=tk.W+tk.N, pady=(0, 12))
self.cost_label = ttk.Label(
self.cost_frame,
text=locale.format_string("%.2f LBC", (round(estimated_cost, 2),), grouping=True),
foreground="red"
)
self.cost_label.grid(row=0, column=1, padx=(1, 0))
self.button_frame = ttk.Frame(self.outer_button_frame, style="E.TFrame") self.button_frame = ttk.Frame(self.outer_button_frame, style="E.TFrame")
self.button_frame.grid(row=0, column=1) self.button_frame.grid(row=0, column=1)
@ -457,13 +473,9 @@ class StreamFrame(object):
self.outer_button_frame.grid_columnconfigure(2, weight=1, uniform="buttons") self.outer_button_frame.grid_columnconfigure(2, weight=1, uniform="buttons")
def add_download_factory(self, factory, download_func): def add_download_factory(self, factory, download_func):
if os.name == "nt":
button_cursor = ""
else:
button_cursor = "hand1"
download_button = ttk.Button( download_button = ttk.Button(
self.button_frame, text=factory.get_description(), command=download_func, self.button_frame, text=factory.get_description(), command=download_func,
style='LBRY.TButton', cursor=button_cursor style='LBRY.TButton', cursor=self.button_cursor
) )
self.download_buttons.append(download_button) self.download_buttons.append(download_button)
download_button.grid(row=0, column=len(self.download_buttons) - 1, padx=5, pady=(1, 2)) download_button.grid(row=0, column=len(self.download_buttons) - 1, padx=5, pady=(1, 2))
@ -477,11 +489,160 @@ class StreamFrame(object):
download_button.destroy() download_button.destroy()
self.download_buttons = [] 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, bytes_left_to_output, points_paid, def show_progress(self, total_bytes, bytes_left_to_download, bytes_left_to_output, points_paid,
points_remaining): points_remaining):
if self.bytes_outputted_label is None: if self.bytes_outputted_label is None:
self.remove_download_buttons() self.remove_download_buttons()
self.button_frame.destroy() self.button_frame.destroy()
for option, frame in self.option_frames:
frame.destroy()
self.options_frame.destroy()
self.show_options_button.destroy()
self.cost_frame = ttk.Frame(self.outer_button_frame, style="F.TFrame")
self.cost_frame.grid(row=0, column=0, sticky=tk.W+tk.N, pady=(0, 12))
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="") self.outer_button_frame.grid_columnconfigure(2, weight=0, uniform="")
self.bytes_outputted_label = ttk.Label( self.bytes_outputted_label = ttk.Label(
@ -667,6 +828,7 @@ class App(object):
ttk.Style().configure("Lookup.LBRY.TButton", padding=lookup_button_padding) 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("Stop.TButton", padding=1, background="#FFFFFF", relief="flat", borderwidth=0)
ttk.Style().configure("TEntry", padding=11) ttk.Style().configure("TEntry", padding=11)
ttk.Style().configure("Float.TEntry", padding=2)
#ttk.Style().configure("A.TFrame", background="red") #ttk.Style().configure("A.TFrame", background="red")
#ttk.Style().configure("B.TFrame", background="green") #ttk.Style().configure("B.TFrame", background="green")
#ttk.Style().configure("B2.TFrame", background="#80FF80") #ttk.Style().configure("B2.TFrame", background="#80FF80")
@ -674,6 +836,8 @@ class App(object):
#ttk.Style().configure("D.TFrame", background="blue") #ttk.Style().configure("D.TFrame", background="blue")
#ttk.Style().configure("E.TFrame", background="yellow") #ttk.Style().configure("E.TFrame", background="yellow")
#ttk.Style().configure("F.TFrame", background="#808080") #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", background="#104639", orient="horizontal", thickness=5)
#ttk.Style().configure("LBRY.TProgressbar") #ttk.Style().configure("LBRY.TProgressbar")
#ttk.Style().layout("Horizontal.LBRY.TProgressbar", ttk.Style().layout("Horizontal.TProgressbar")) #ttk.Style().layout("Horizontal.LBRY.TProgressbar", ttk.Style().layout("Horizontal.TProgressbar"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

View file

@ -36,6 +36,8 @@ setup(name='lbrynet',
'lbrynet/lbrynet_downloader_gui/lbry-dark-icon.xbm', 'lbrynet/lbrynet_downloader_gui/lbry-dark-icon.xbm',
'lbrynet/lbrynet_downloader_gui/lbry-dark-icon.ico', 'lbrynet/lbrynet_downloader_gui/lbry-dark-icon.ico',
'lbrynet/lbrynet_downloader_gui/drop_down.gif', 'lbrynet/lbrynet_downloader_gui/drop_down.gif',
'lbrynet/lbrynet_downloader_gui/show_options.gif',
'lbrynet/lbrynet_downloader_gui/hide_options.gif',
] ]
) )
] ]

View file

@ -9,7 +9,7 @@ from Crypto import Random
from Crypto.Hash import MD5 from Crypto.Hash import MD5
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE
from lbrynet.conf import MIN_BLOB_INFO_PAYMENT_RATE from lbrynet.conf import MIN_BLOB_INFO_PAYMENT_RATE
from lbrynet.lbrylive.LiveStreamCreator import FileLiveStreamCreator, add_live_stream_to_sd_identifier from lbrynet.lbrylive.LiveStreamCreator import FileLiveStreamCreator
from lbrynet.lbrylive.PaymentRateManager import BaseLiveStreamPaymentRateManager from lbrynet.lbrylive.PaymentRateManager import BaseLiveStreamPaymentRateManager
from lbrynet.lbrylive.PaymentRateManager import LiveStreamPaymentRateManager from lbrynet.lbrylive.PaymentRateManager import LiveStreamPaymentRateManager
from lbrynet.lbrylive.LiveStreamMetadataManager import DBLiveStreamMetadataManager from lbrynet.lbrylive.LiveStreamMetadataManager import DBLiveStreamMetadataManager
@ -24,6 +24,7 @@ from lbrynet.core.StreamDescriptor import BlobStreamDescriptorWriter
from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier
from lbrynet.core.StreamDescriptor import download_sd_blob from lbrynet.core.StreamDescriptor import download_sd_blob
from lbrynet.lbryfilemanager.LBRYFileCreator import create_lbry_file from lbrynet.lbryfilemanager.LBRYFileCreator import create_lbry_file
from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier
from lbrynet.lbryfile.StreamDescriptor import get_sd_info from lbrynet.lbryfile.StreamDescriptor import get_sd_info
from twisted.internet import defer, threads, task from twisted.internet import defer, threads, task
from twisted.trial.unittest import TestCase from twisted.trial.unittest import TestCase
@ -35,6 +36,8 @@ from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerF
from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory
from lbrynet.core.server.ServerProtocol import ServerProtocolFactory from lbrynet.core.server.ServerProtocol import ServerProtocolFactory
from lbrynet.lbrylive.server.LiveBlobInfoQueryHandler import CryptBlobInfoQueryHandlerFactory from lbrynet.lbrylive.server.LiveBlobInfoQueryHandler import CryptBlobInfoQueryHandlerFactory
from lbrynet.lbrylive.client.LiveStreamOptions import add_live_stream_to_sd_identifier
from lbrynet.lbrylive.client.LiveStreamDownloader import add_full_live_stream_downloader_to_sd_identifier
log_format = "%(funcName)s(): %(message)s" log_format = "%(funcName)s(): %(message)s"
@ -248,6 +251,7 @@ def start_lbry_uploader(sd_hash_queue, kill_event, dead_event):
def start_all(): def start_all():
d = session.setup() d = session.setup()
d.addCallback(lambda _: add_lbry_file_to_sd_identifier(sd_identifier))
d.addCallback(lambda _: lbry_file_manager.setup()) d.addCallback(lambda _: lbry_file_manager.setup())
d.addCallback(lambda _: start_server()) d.addCallback(lambda _: start_server())
d.addCallback(lambda _: create_stream()) d.addCallback(lambda _: create_stream())
@ -437,7 +441,12 @@ def start_live_server(sd_hash_queue, kill_event, dead_event):
return d return d
def enable_live_stream(): def enable_live_stream():
return add_live_stream_to_sd_identifier(session, stream_info_manager, sd_identifier) base_live_stream_payment_rate_manager = BaseLiveStreamPaymentRateManager(
MIN_BLOB_INFO_PAYMENT_RATE
)
add_live_stream_to_sd_identifier(sd_identifier, base_live_stream_payment_rate_manager)
add_full_live_stream_downloader_to_sd_identifier(session, stream_info_manager, sd_identifier,
base_live_stream_payment_rate_manager)
def run_server(): def run_server():
d = session.setup() d = session.setup()
@ -670,9 +679,9 @@ class TestTransfer(TestCase):
self.lbry_file_manager = LBRYFileManager(self.session, self.stream_info_manager, sd_identifier) self.lbry_file_manager = LBRYFileManager(self.session, self.stream_info_manager, sd_identifier)
def make_downloader(info_and_factories, prm): def make_downloader(info_and_factories, prm):
info_validator, factories = info_and_factories info_validator, options, factories = info_and_factories
options = [o.default for o in factories[0].get_downloader_options(info_validator, prm)] chosen_options = [o.default_value for o in options.get_downloader_options(info_validator, prm)]
return factories[0].make_downloader(info_validator, options, prm) return factories[0].make_downloader(info_validator, chosen_options, prm)
def download_file(sd_hash): def download_file(sd_hash):
prm = PaymentRateManager(self.session.base_payment_rate_manager) prm = PaymentRateManager(self.session.base_payment_rate_manager)
@ -693,6 +702,7 @@ class TestTransfer(TestCase):
logging.debug("Starting the transfer") logging.debug("Starting the transfer")
d = self.session.setup() d = self.session.setup()
d.addCallback(lambda _: add_lbry_file_to_sd_identifier(sd_identifier))
d.addCallback(lambda _: self.lbry_file_manager.setup()) d.addCallback(lambda _: self.lbry_file_manager.setup())
d.addCallback(lambda _: download_file(sd_hash)) d.addCallback(lambda _: download_file(sd_hash))
d.addCallback(lambda _: check_md5_sum()) d.addCallback(lambda _: check_md5_sum())
@ -750,9 +760,9 @@ class TestTransfer(TestCase):
d = self.wait_for_hash_from_queue(sd_hash_queue) d = self.wait_for_hash_from_queue(sd_hash_queue)
def create_downloader(info_and_factories, prm): def create_downloader(info_and_factories, prm):
info_validator, factories = info_and_factories info_validator, options, factories = info_and_factories
options = [o.default for o in factories[0].get_downloader_options(info_validator, prm)] chosen_options = [o.default_value for o in options.get_downloader_options(info_validator, prm)]
return factories[0].make_downloader(info_validator, options, prm) return factories[0].make_downloader(info_validator, chosen_options, prm)
def start_lbry_file(lbry_file): def start_lbry_file(lbry_file):
lbry_file = lbry_file lbry_file = lbry_file
@ -776,7 +786,14 @@ class TestTransfer(TestCase):
return d return d
def enable_live_stream(): def enable_live_stream():
return add_live_stream_to_sd_identifier(self.session, self.stream_info_manager, sd_identifier) base_live_stream_payment_rate_manager = BaseLiveStreamPaymentRateManager(
MIN_BLOB_INFO_PAYMENT_RATE
)
add_live_stream_to_sd_identifier(sd_identifier,
base_live_stream_payment_rate_manager)
add_full_live_stream_downloader_to_sd_identifier(self.session, self.stream_info_manager,
sd_identifier,
base_live_stream_payment_rate_manager)
d.addCallback(do_download) d.addCallback(do_download)
@ -941,6 +958,7 @@ class TestStreamify(TestCase):
d = self.session.setup() d = self.session.setup()
d.addCallback(lambda _: self.stream_info_manager.setup()) d.addCallback(lambda _: self.stream_info_manager.setup())
d.addCallback(lambda _: add_lbry_file_to_sd_identifier(sd_identifier))
d.addCallback(lambda _: self.lbry_file_manager.setup()) d.addCallback(lambda _: self.lbry_file_manager.setup())
def verify_equal(sd_info): def verify_equal(sd_info):
@ -1017,6 +1035,7 @@ class TestStreamify(TestCase):
d = self.session.setup() d = self.session.setup()
d.addCallback(lambda _: self.stream_info_manager.setup()) d.addCallback(lambda _: self.stream_info_manager.setup())
d.addCallback(lambda _: add_lbry_file_to_sd_identifier(sd_identifier))
d.addCallback(lambda _: self.lbry_file_manager.setup()) d.addCallback(lambda _: self.lbry_file_manager.setup())
d.addCallback(lambda _: create_stream()) d.addCallback(lambda _: create_stream())
d.addCallback(combine_stream) d.addCallback(combine_stream)