forked from LBRYCommunity/lbry-sdk
c8b2b7b279
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)
1038 lines
No EOL
41 KiB
Python
1038 lines
No EOL
41 KiB
Python
import Tkinter as tk
|
|
import ttk
|
|
import tkFont
|
|
import tkMessageBox
|
|
import logging
|
|
from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory
|
|
from twisted.internet import tksupport, reactor, defer, task, threads
|
|
import sys
|
|
import os
|
|
import locale
|
|
import binascii
|
|
from Crypto import Random
|
|
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE
|
|
from lbrynet.core.Session import LBRYSession
|
|
from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet
|
|
from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier
|
|
from lbrynet.core.PaymentRateManager import PaymentRateManager
|
|
from lbrynet.lbryfile.LBRYFileMetadataManager import TempLBRYFileMetadataManager
|
|
from lbrynet.core import StreamDescriptor
|
|
from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier
|
|
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType
|
|
import requests
|
|
|
|
|
|
class LBRYDownloader(object):
|
|
def __init__(self):
|
|
self.session = None
|
|
self.known_dht_nodes = [('104.236.42.182', 4000)]
|
|
self.conf_dir = os.path.join(os.path.expanduser("~"), ".lbrydownloader")
|
|
self.data_dir = os.path.join(self.conf_dir, "blobfiles")
|
|
self.wallet_dir = os.path.join(os.path.expanduser("~"), ".lbrycrd")
|
|
self.wallet_conf = os.path.join(self.wallet_dir, "lbrycrd.conf")
|
|
self.first_run = False
|
|
if os.name == "nt":
|
|
from lbrynet.winhelpers.knownpaths import get_path, FOLDERID, UserHandle
|
|
self.download_directory = get_path(FOLDERID.Downloads, UserHandle.current)
|
|
else:
|
|
self.download_directory = os.getcwd()
|
|
self.wallet_user = None
|
|
self.wallet_password = None
|
|
self.sd_identifier = StreamDescriptorIdentifier()
|
|
self.wallet_rpc_port = 8332
|
|
self.download_deferreds = []
|
|
self.stream_frames = []
|
|
|
|
def start(self):
|
|
d = threads.deferToThread(self._create_directory)
|
|
d.addCallback(lambda _: self._get_session())
|
|
d.addCallback(lambda _: self._setup_stream_info_manager())
|
|
d.addCallback(lambda _: self._setup_stream_identifier())
|
|
return d
|
|
|
|
def stop(self):
|
|
dl = defer.DeferredList(self.download_deferreds)
|
|
for stream_frame in self.stream_frames:
|
|
stream_frame.cancel_func()
|
|
if self.session is not None:
|
|
dl.addBoth(lambda _: self.session.shut_down())
|
|
return dl
|
|
|
|
def get_new_address(self):
|
|
return self.session.wallet.get_new_address()
|
|
|
|
def _create_directory(self):
|
|
if not os.path.exists(self.conf_dir):
|
|
os.makedirs(self.conf_dir)
|
|
logging.debug("Created the configuration directory: %s", str(self.conf_dir))
|
|
if not os.path.exists(self.data_dir):
|
|
os.makedirs(self.data_dir)
|
|
logging.debug("Created the data directory: %s", str(self.data_dir))
|
|
if not os.path.exists(self.wallet_dir):
|
|
os.makedirs(self.wallet_dir)
|
|
if not os.path.exists(self.wallet_conf):
|
|
lbrycrd_conf = open(self.wallet_conf, mode='w')
|
|
self.wallet_user = "rpcuser"
|
|
lbrycrd_conf.write("rpcuser=%s\n" % self.wallet_user)
|
|
self.wallet_password = binascii.hexlify(Random.new().read(20))
|
|
lbrycrd_conf.write("rpcpassword=%s\n" % self.wallet_password)
|
|
lbrycrd_conf.write("server=1\n")
|
|
lbrycrd_conf.close()
|
|
self.first_run = True
|
|
else:
|
|
lbrycrd_conf = open(self.wallet_conf)
|
|
for l in lbrycrd_conf:
|
|
if l.startswith("rpcuser="):
|
|
self.wallet_user = l[8:-1]
|
|
if l.startswith("rpcpassword="):
|
|
self.wallet_password = l[12:-1]
|
|
if l.startswith("rpcport="):
|
|
self.wallet_rpc_port = int(l[8:-1])
|
|
|
|
def _get_session(self):
|
|
wallet = LBRYcrdWallet(self.wallet_user, self.wallet_password, "127.0.0.1", self.wallet_rpc_port,
|
|
start_lbrycrdd=True, wallet_dir=self.wallet_dir, wallet_conf=self.wallet_conf)
|
|
self.session = LBRYSession(MIN_BLOB_DATA_PAYMENT_RATE, db_dir=self.conf_dir, blob_dir=self.data_dir,
|
|
use_upnp=False, wallet=wallet,
|
|
known_dht_nodes=self.known_dht_nodes, dht_node_port=4446)
|
|
return self.session.setup()
|
|
|
|
def _setup_stream_info_manager(self):
|
|
self.stream_info_manager = TempLBRYFileMetadataManager()
|
|
return defer.succeed(True)
|
|
|
|
def _setup_stream_identifier(self):
|
|
add_lbry_file_to_sd_identifier(self.sd_identifier)
|
|
file_saver_factory = LBRYFileSaverFactory(self.session.peer_finder, self.session.rate_limiter,
|
|
self.session.blob_manager, self.stream_info_manager,
|
|
self.session.wallet, self.download_directory)
|
|
self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_saver_factory)
|
|
file_opener_factory = LBRYFileOpenerFactory(self.session.peer_finder, self.session.rate_limiter,
|
|
self.session.blob_manager, self.stream_info_manager,
|
|
self.session.wallet)
|
|
self.sd_identifier.add_stream_downloader_factory(LBRYFileStreamType, file_opener_factory)
|
|
|
|
def do_first_run(self):
|
|
if self.first_run is True:
|
|
d = self.session.wallet.get_new_address()
|
|
|
|
def send_request(url, data):
|
|
r = requests.post(url, json=data)
|
|
if r.status_code == 200:
|
|
return r.json()['credits_sent']
|
|
return 0.0
|
|
|
|
def log_error(err):
|
|
logging.warning("unable to request free credits. %s", err.getErrorMessage())
|
|
return 0.0
|
|
|
|
def request_credits(address):
|
|
url = "http://credreq.lbry.io/requestcredits"
|
|
data = {"address": address}
|
|
d = threads.deferToThread(send_request, url, data)
|
|
d.addErrback(log_error)
|
|
return d
|
|
|
|
d.addCallback(request_credits)
|
|
return d
|
|
return defer.succeed(0.0)
|
|
|
|
def _resolve_name(self, uri):
|
|
return self.session.wallet.get_stream_info_for_name(uri)
|
|
|
|
def download_stream(self, stream_frame, uri):
|
|
resolve_d = self._resolve_name(uri)
|
|
|
|
stream_frame.show_metadata_status("resolving name...")
|
|
|
|
stream_frame.cancel_func = resolve_d.cancel
|
|
payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager)
|
|
|
|
def update_stream_name(value):
|
|
if 'name' in value:
|
|
stream_frame.show_name(value['name'])
|
|
if 'description' in value:
|
|
stream_frame.show_description(value['description'])
|
|
return value
|
|
|
|
def get_sd_hash(value):
|
|
if 'stream_hash' in value:
|
|
return value['stream_hash']
|
|
raise ValueError("Invalid stream")
|
|
|
|
def get_sd_blob(sd_hash):
|
|
stream_frame.show_metadata_status("name resolved, fetching metadata...")
|
|
get_sd_d = StreamDescriptor.download_sd_blob(self.session, sd_hash,
|
|
payment_rate_manager)
|
|
get_sd_d.addCallback(self.sd_identifier.get_info_and_factories_for_sd_blob)
|
|
get_sd_d.addCallbacks(choose_download_factory, bad_sd_blob)
|
|
return get_sd_d
|
|
|
|
def get_info_from_validator(info_validator):
|
|
stream_name = None
|
|
stream_size = None
|
|
for field, val in info_validator.info_to_show():
|
|
if field == "suggested_file_name":
|
|
stream_name = val
|
|
elif field == "stream_name" and stream_name is None:
|
|
stream_name = val
|
|
elif field == "stream_size":
|
|
stream_size = int(val)
|
|
if stream_size is None:
|
|
stream_size = "unknown"
|
|
if stream_name is None:
|
|
stream_name = "unknown"
|
|
return stream_name, stream_size
|
|
|
|
def choose_download_factory(info_and_factories):
|
|
info_validator, options, factories = info_and_factories
|
|
stream_name, stream_size = get_info_from_validator(info_validator)
|
|
if isinstance(stream_size, (int, long)):
|
|
price = payment_rate_manager.get_effective_min_blob_data_payment_rate()
|
|
estimated_cost = stream_size * 1.0 / 2**20 * price
|
|
else:
|
|
estimated_cost = "unknown"
|
|
|
|
stream_frame.show_stream_metadata(stream_name, stream_size)
|
|
|
|
available_options = options.get_downloader_options(info_validator, payment_rate_manager)
|
|
|
|
stream_frame.show_download_options(available_options)
|
|
|
|
get_downloader_d = defer.Deferred()
|
|
|
|
def create_downloader(f, chosen_options):
|
|
|
|
def fire_get_downloader_d(downloader):
|
|
if not get_downloader_d.called:
|
|
get_downloader_d.callback(downloader)
|
|
|
|
stream_frame.disable_download_buttons()
|
|
d = f.make_downloader(info_validator, chosen_options,
|
|
payment_rate_manager)
|
|
d.addCallback(fire_get_downloader_d)
|
|
|
|
for factory in factories:
|
|
|
|
def choose_factory(f=factory):
|
|
chosen_options = stream_frame.get_chosen_options()
|
|
create_downloader(f, chosen_options)
|
|
|
|
stream_frame.add_download_factory(factory, choose_factory)
|
|
|
|
get_downloader_d.addCallback(start_download)
|
|
|
|
return get_downloader_d
|
|
|
|
def show_stream_status(downloader):
|
|
total_bytes = downloader.get_total_bytes()
|
|
bytes_left_to_download = downloader.get_bytes_left_to_download()
|
|
bytes_left_to_output = downloader.get_bytes_left_to_output()
|
|
points_paid = payment_rate_manager.points_paid
|
|
payment_rate = payment_rate_manager.get_effective_min_blob_data_payment_rate()
|
|
points_remaining = 1.0 * bytes_left_to_download * payment_rate / 2**20
|
|
stream_frame.show_progress(total_bytes, bytes_left_to_download, bytes_left_to_output,
|
|
points_paid, points_remaining)
|
|
|
|
def show_finished(arg, downloader):
|
|
show_stream_status(downloader)
|
|
stream_frame.show_download_done(payment_rate_manager.points_paid)
|
|
return arg
|
|
|
|
def start_download(downloader):
|
|
l = task.LoopingCall(show_stream_status, downloader)
|
|
l.start(1)
|
|
d = downloader.start()
|
|
stream_frame.cancel_func = downloader.stop
|
|
|
|
def stop_looping_call(arg):
|
|
l.stop()
|
|
stream_frame.cancel_func = resolve_d.cancel
|
|
return arg
|
|
|
|
d.addBoth(stop_looping_call)
|
|
d.addCallback(show_finished, downloader)
|
|
return d
|
|
|
|
def lookup_failed(err):
|
|
stream_frame.show_metadata_status("name lookup failed")
|
|
return err
|
|
|
|
def bad_sd_blob(err):
|
|
stream_frame.show_metadata_status("Unknown type or badly formed metadata")
|
|
return err
|
|
|
|
resolve_d.addCallback(update_stream_name)
|
|
resolve_d.addCallback(get_sd_hash)
|
|
resolve_d.addCallbacks(get_sd_blob, lookup_failed)
|
|
|
|
def show_err(err):
|
|
tkMessageBox.showerror(title="Download Error", message=err.getErrorMessage())
|
|
logging.error(err.getErrorMessage())
|
|
stream_frame.show_download_done(payment_rate_manager.points_paid)
|
|
|
|
resolve_d.addErrback(lambda err: err.trap(defer.CancelledError))
|
|
resolve_d.addErrback(show_err)
|
|
self._add_download_deferred(resolve_d, stream_frame)
|
|
|
|
def _add_download_deferred(self, d, stream_frame):
|
|
self.download_deferreds.append(d)
|
|
self.stream_frames.append(stream_frame)
|
|
|
|
def remove_from_list():
|
|
self.download_deferreds.remove(d)
|
|
self.stream_frames.remove(stream_frame)
|
|
|
|
d.addBoth(lambda _: remove_from_list())
|
|
|
|
|
|
class StreamFrame(object):
|
|
def __init__(self, app, uri):
|
|
self.app = app
|
|
self.uri = uri
|
|
self.cancel_func = None
|
|
|
|
self.stream_frame = ttk.Frame(self.app.streams_frame, style="B.TFrame")
|
|
|
|
self.stream_frame.pack(fill=tk.X, side=tk.BOTTOM, pady=(30, 0))
|
|
|
|
self.stream_frame_header = ttk.Frame(self.stream_frame, style="C.TFrame")
|
|
self.stream_frame_header.grid(sticky=tk.E + tk.W)
|
|
|
|
self.uri_font = tkFont.Font(size=8)
|
|
self.uri_label = ttk.Label(
|
|
self.stream_frame_header, text=self.uri, font=self.uri_font, foreground="#666666"
|
|
)
|
|
self.uri_label.grid(row=0, column=0, sticky=tk.W)
|
|
|
|
if os.name == "nt":
|
|
self.button_cursor = ""
|
|
else:
|
|
self.button_cursor = "hand1"
|
|
|
|
close_file_name = "close2.gif"
|
|
try:
|
|
close_file = os.path.join(os.path.dirname(__file__), close_file_name)
|
|
except NameError:
|
|
close_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), "lbrynet",
|
|
"lbrynet_downloader_gui", close_file_name)
|
|
|
|
self.close_picture = tk.PhotoImage(
|
|
file=close_file
|
|
)
|
|
self.close_button = ttk.Button(
|
|
self.stream_frame_header, command=self.cancel, style="Stop.TButton", cursor=self.button_cursor
|
|
)
|
|
self.close_button.config(image=self.close_picture)
|
|
self.close_button.grid(row=0, column=1, sticky=tk.E + tk.N)
|
|
|
|
self.stream_frame_header.grid_columnconfigure(0, weight=1)
|
|
|
|
self.stream_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
self.stream_frame_body = ttk.Frame(self.stream_frame, style="C.TFrame")
|
|
self.stream_frame_body.grid(row=1, column=0, sticky=tk.E + tk.W)
|
|
|
|
self.name_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
|
|
self.name_frame.grid(sticky=tk.W + tk.E)
|
|
self.name_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
self.stream_frame_body.grid_columnconfigure(0, weight=1)
|
|
|
|
self.metadata_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
|
|
self.metadata_frame.grid(sticky=tk.W + tk.E, row=1)
|
|
self.metadata_frame.grid_columnconfigure(0, weight=1)
|
|
|
|
self.options_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
|
|
|
|
self.outer_button_frame = ttk.Frame(self.stream_frame_body, style="D.TFrame")
|
|
self.outer_button_frame.grid(sticky=tk.W + tk.E, row=4)
|
|
|
|
show_options_picture_file_name = "show_options.gif"
|
|
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.name_label = None
|
|
self.bytes_downloaded_label = None
|
|
self.bytes_outputted_label = None
|
|
self.button_frame = None
|
|
self.download_buttons = []
|
|
self.option_frames = []
|
|
self.name_font = None
|
|
self.description_label = None
|
|
self.file_name_frame = None
|
|
self.cost_frame = None
|
|
self.cost_description = None
|
|
self.remaining_cost_description = None
|
|
self.cost_label = None
|
|
self.remaining_cost_label = None
|
|
|
|
def cancel(self):
|
|
if self.cancel_func is not None:
|
|
self.cancel_func()
|
|
self.stream_frame.destroy()
|
|
self.app.stream_removed()
|
|
|
|
def show_name(self, name):
|
|
self.name_font = tkFont.Font(size=16)
|
|
self.name_label = ttk.Label(
|
|
self.name_frame, text=name, font=self.name_font
|
|
)
|
|
self.name_label.grid(row=0, column=0, sticky=tk.W)
|
|
|
|
def show_description(self, description):
|
|
if os.name == "nt":
|
|
wraplength = 580
|
|
else:
|
|
wraplength = 600
|
|
self.description_label = ttk.Label(
|
|
self.name_frame, text=description, wraplength=wraplength
|
|
)
|
|
self.description_label.grid(row=1, column=0, sticky=tk.W)
|
|
|
|
def show_metadata_status(self, value):
|
|
if self.status_label is None:
|
|
self.status_label = ttk.Label(
|
|
self.metadata_frame, text=value
|
|
)
|
|
self.status_label.grid()
|
|
self.metadata_frame.grid_columnconfigure(0, weight=1)
|
|
else:
|
|
self.status_label.config(text=value)
|
|
|
|
@staticmethod
|
|
def get_formatted_stream_size(stream_size):
|
|
if isinstance(stream_size, (int, long)):
|
|
if stream_size >= 2**40:
|
|
units = "TB"
|
|
factor = 2**40
|
|
elif stream_size >= 2**30:
|
|
units = "GB"
|
|
factor = 2**30
|
|
elif stream_size >= 2**20:
|
|
units = "MB"
|
|
factor = 2**20
|
|
elif stream_size >= 2**10:
|
|
units = "KB"
|
|
factor = 2**10
|
|
else:
|
|
return str(stream_size) + " B"
|
|
return "%.1f %s" % (round((stream_size * 1.0 / factor), 1), units)
|
|
return stream_size
|
|
|
|
def show_stream_metadata(self, stream_name, stream_size):
|
|
if self.status_label is not None:
|
|
self.status_label.destroy()
|
|
|
|
self.file_name_frame = ttk.Frame(self.metadata_frame, style="F.TFrame")
|
|
self.file_name_frame.grid(row=0, column=0, sticky=tk.W)
|
|
self.metadata_frame.grid_columnconfigure(0, weight=1, uniform="metadata")
|
|
|
|
file_size_label = ttk.Label(
|
|
self.file_name_frame,
|
|
text=self.get_formatted_stream_size(stream_size)
|
|
)
|
|
file_size_label.grid(row=0, column=2)
|
|
|
|
file_name_label = ttk.Label(
|
|
self.file_name_frame,
|
|
text=" - " + stream_name,
|
|
)
|
|
file_name_label.grid(row=0, column=3)
|
|
|
|
self.button_frame = ttk.Frame(self.outer_button_frame, style="E.TFrame")
|
|
self.button_frame.grid(row=0, column=1)
|
|
|
|
self.outer_button_frame.grid_columnconfigure(0, weight=1, uniform="buttons")
|
|
self.outer_button_frame.grid_columnconfigure(1, weight=2, uniform="buttons1")
|
|
self.outer_button_frame.grid_columnconfigure(2, weight=1, uniform="buttons")
|
|
|
|
def add_download_factory(self, factory, download_func):
|
|
download_button = ttk.Button(
|
|
self.button_frame, text=factory.get_description(), command=download_func,
|
|
style='LBRY.TButton', cursor=self.button_cursor
|
|
)
|
|
self.download_buttons.append(download_button)
|
|
download_button.grid(row=0, column=len(self.download_buttons) - 1, padx=5, pady=(1, 2))
|
|
|
|
def disable_download_buttons(self):
|
|
for download_button in self.download_buttons:
|
|
download_button.config(state=tk.DISABLED)
|
|
|
|
def remove_download_buttons(self):
|
|
for download_button in self.download_buttons:
|
|
download_button.destroy()
|
|
self.download_buttons = []
|
|
|
|
def get_option_widget(self, option_type, option_frame):
|
|
if option_type.value == float:
|
|
entry_frame = ttk.Frame(
|
|
option_frame,
|
|
style="H.TFrame"
|
|
)
|
|
entry_frame.grid()
|
|
col = 0
|
|
if option_type.short_description is not None:
|
|
entry_label = ttk.Label(
|
|
entry_frame,
|
|
#text=option_type.short_description
|
|
text=""
|
|
)
|
|
entry_label.grid(row=0, column=0, sticky=tk.W)
|
|
col = 1
|
|
entry = ttk.Entry(
|
|
entry_frame,
|
|
width=10,
|
|
style="Float.TEntry"
|
|
)
|
|
entry_frame.entry = entry
|
|
entry.grid(row=0, column=col, sticky=tk.W)
|
|
return entry_frame
|
|
if option_type.value == bool:
|
|
bool_frame = ttk.Frame(
|
|
option_frame,
|
|
style="H.TFrame"
|
|
)
|
|
bool_frame.chosen_value = tk.BooleanVar()
|
|
true_text = "True"
|
|
false_text = "False"
|
|
if option_type.bool_options_description is not None:
|
|
true_text, false_text = option_type.bool_options_description
|
|
true_radio_button = ttk.Radiobutton(
|
|
bool_frame, text=true_text, variable=bool_frame.chosen_value, value=True
|
|
)
|
|
true_radio_button.grid(row=0, sticky=tk.W)
|
|
false_radio_button = ttk.Radiobutton(
|
|
bool_frame, text=false_text, variable=bool_frame.chosen_value, value=False
|
|
)
|
|
false_radio_button.grid(row=1, sticky=tk.W)
|
|
return bool_frame
|
|
label = ttk.Label(
|
|
option_frame,
|
|
text=""
|
|
)
|
|
return label
|
|
|
|
def show_download_options(self, options):
|
|
left_padding = 20
|
|
for option in options:
|
|
f = ttk.Frame(
|
|
self.options_frame,
|
|
style="E.TFrame"
|
|
)
|
|
f.grid(sticky=tk.W + tk.E, padx=left_padding)
|
|
self.option_frames.append((option, f))
|
|
description_label = ttk.Label(
|
|
f,
|
|
text=option.long_description
|
|
)
|
|
description_label.grid(row=0, sticky=tk.W)
|
|
if len(option.option_types) > 1:
|
|
f.chosen_type = tk.IntVar()
|
|
choices_frame = ttk.Frame(
|
|
f,
|
|
style="F.TFrame"
|
|
)
|
|
f.choices_frame = choices_frame
|
|
choices_frame.grid(row=1, sticky=tk.W, padx=left_padding)
|
|
choices_frame.choices = []
|
|
for i, option_type in enumerate(option.option_types):
|
|
choice_frame = ttk.Frame(
|
|
choices_frame,
|
|
style="G.TFrame"
|
|
)
|
|
choice_frame.grid(sticky=tk.W)
|
|
option_text = ""
|
|
if option_type.short_description is not None:
|
|
option_text = option_type.short_description
|
|
option_radio_button = ttk.Radiobutton(
|
|
choice_frame, text=option_text, variable=f.chosen_type, value=i
|
|
)
|
|
option_radio_button.grid(row=0, column=0, sticky=tk.W)
|
|
option_widget = self.get_option_widget(option_type, choice_frame)
|
|
option_widget.grid(row=0, column=1, sticky=tk.W)
|
|
choices_frame.choices.append(option_widget)
|
|
if i == 0:
|
|
option_radio_button.invoke()
|
|
else:
|
|
choice_frame = ttk.Frame(
|
|
f,
|
|
style="F.TFrame"
|
|
)
|
|
choice_frame.grid(sticky=tk.W, padx=left_padding)
|
|
option_widget = self.get_option_widget(option.option_types[0], choice_frame)
|
|
option_widget.grid(row=0, column=0, sticky=tk.W)
|
|
f.option_widget = option_widget
|
|
self.show_options_button = ttk.Button(
|
|
self.stream_frame_body, command=self._toggle_show_options, style="Stop.TButton",
|
|
cursor=self.button_cursor
|
|
)
|
|
self.show_options_button.config(image=self.show_options_picture)
|
|
self.show_options_button.grid(sticky=tk.W, row=2, column=0)
|
|
|
|
def _get_chosen_option(self, option_type, option_widget):
|
|
if option_type.value == float:
|
|
return float(option_widget.entry.get())
|
|
if option_type.value == bool:
|
|
return option_widget.chosen_value.get()
|
|
return option_type.value
|
|
|
|
def get_chosen_options(self):
|
|
chosen_options = []
|
|
for o, f in self.option_frames:
|
|
if len(o.option_types) > 1:
|
|
chosen_index = f.chosen_type.get()
|
|
option_type = o.option_types[chosen_index]
|
|
option_widget = f.choices_frame.choices[chosen_index]
|
|
chosen_options.append(self._get_chosen_option(option_type, option_widget))
|
|
else:
|
|
option_type = o.option_types[0]
|
|
option_widget = f.option_widget
|
|
chosen_options.append(self._get_chosen_option(option_type, option_widget))
|
|
return chosen_options
|
|
|
|
def _toggle_show_options(self):
|
|
if self.options_frame.winfo_ismapped():
|
|
self.show_options_button.config(image=self.show_options_picture)
|
|
self.options_frame.grid_forget()
|
|
else:
|
|
self.show_options_button.config(image=self.hide_options_picture)
|
|
self.options_frame.grid(sticky=tk.W + tk.E, row=3)
|
|
|
|
def show_progress(self, total_bytes, bytes_left_to_download, bytes_left_to_output, points_paid,
|
|
points_remaining):
|
|
if self.bytes_outputted_label is None:
|
|
self.remove_download_buttons()
|
|
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.bytes_outputted_label = ttk.Label(
|
|
self.file_name_frame,
|
|
text=""
|
|
)
|
|
self.bytes_outputted_label.grid(row=0, column=0)
|
|
|
|
self.bytes_downloaded_label = ttk.Label(
|
|
self.file_name_frame,
|
|
text=""
|
|
)
|
|
self.bytes_downloaded_label.grid(row=0, column=1)
|
|
|
|
if self.bytes_outputted_label.winfo_exists():
|
|
self.bytes_outputted_label.config(
|
|
text=self.get_formatted_stream_size(total_bytes - bytes_left_to_output) + " / "
|
|
)
|
|
if self.bytes_downloaded_label.winfo_exists():
|
|
self.bytes_downloaded_label.config(
|
|
text=self.get_formatted_stream_size(total_bytes - bytes_left_to_download) + " / "
|
|
)
|
|
if self.cost_label.winfo_exists():
|
|
total_points = points_remaining + points_paid
|
|
self.cost_label.config(text=locale.format_string("%.2f/%.2f LBC",
|
|
(round(points_paid, 2), round(total_points, 2)),
|
|
grouping=True))
|
|
|
|
def show_download_done(self, total_points_paid):
|
|
if self.bytes_outputted_label is not None and self.bytes_outputted_label.winfo_exists():
|
|
self.bytes_outputted_label.destroy()
|
|
if self.bytes_downloaded_label is not None and self.bytes_downloaded_label.winfo_exists():
|
|
self.bytes_downloaded_label.destroy()
|
|
if self.cost_label is not None and self.cost_label.winfo_exists():
|
|
self.cost_label.config(text=locale.format_string("%.2f LBC",
|
|
(round(total_points_paid, 2),),
|
|
grouping=True))
|
|
|
|
|
|
class AddressWindow(object):
|
|
def __init__(self, root, address):
|
|
self.root = root
|
|
self.address = address
|
|
|
|
def show(self):
|
|
window = tk.Toplevel(self.root, background="#FFFFFF")
|
|
window.transient(self.root)
|
|
window.wm_title("New address")
|
|
window.protocol("WM_DELETE_WINDOW", window.destroy)
|
|
window.resizable(0, 0)
|
|
|
|
text_box = tk.Text(window, width=35, height=1, relief=tk.FLAT, borderwidth=0,
|
|
highlightthickness=0)
|
|
text_box.insert(tk.END, self.address)
|
|
text_box.grid(row=0, padx=10, pady=5, columnspan=2)
|
|
text_box.config(state='normal')
|
|
|
|
def copy_to_clipboard():
|
|
self.root.clipboard_clear()
|
|
self.root.clipboard_append(text_box.get('1.0', 'end-1c'))
|
|
|
|
def copy_command():
|
|
text_box.event_generate("<Control-c>")
|
|
|
|
copy_menu = tk.Menu(
|
|
self.root, tearoff=0
|
|
)
|
|
copy_menu.add_command(label=" Copy ", command=copy_command)
|
|
|
|
def popup(event):
|
|
if text_box.tag_ranges("sel"):
|
|
copy_menu.tk_popup(event.x_root, event.y_root)
|
|
|
|
text_box.bind("<Button-3>", popup)
|
|
|
|
copy_button = ttk.Button(
|
|
window, text="Copy", command=copy_to_clipboard, style="LBRY.TButton"
|
|
)
|
|
copy_button.grid(row=1, column=0, pady=(0, 5), padx=5, sticky=tk.E)
|
|
|
|
done_button = ttk.Button(
|
|
window, text="OK", command=window.destroy, style="LBRY.TButton"
|
|
)
|
|
done_button.grid(row=1, column=1, pady=(0, 5), padx=5, sticky=tk.W)
|
|
window.focus_set()
|
|
|
|
|
|
class WelcomeWindow(object):
|
|
def __init__(self, root, points_sent):
|
|
self.root = root
|
|
self.points_sent = points_sent
|
|
|
|
def show(self):
|
|
window = tk.Toplevel(self.root, background="#FFFFFF")
|
|
window.transient(self.root)
|
|
window.wm_title("Welcome to LBRY")
|
|
window.protocol("WM_DELETE_WINDOW", window.destroy)
|
|
window.resizable(0, 0)
|
|
|
|
text_box = tk.Text(window, width=45, height=3, relief=tk.FLAT, borderwidth=0,
|
|
highlightthickness=0)
|
|
|
|
points_string = locale.format_string("%.2f LBC", (round(self.points_sent, 2),),
|
|
grouping=True)
|
|
|
|
text_box.insert(tk.END, "Thank you for using LBRY! You have been\n"
|
|
"given %s for free because we love\n"
|
|
"you. Please give them 60 seconds to show up." % points_string)
|
|
text_box.grid(row=0, padx=10, pady=5, columnspan=2)
|
|
text_box.config(state='normal')
|
|
|
|
window.focus_set()
|
|
|
|
|
|
class App(object):
|
|
def __init__(self):
|
|
self.master = None
|
|
self.downloader = None
|
|
self.wallet_balance_check = None
|
|
self.streams_frame = None
|
|
|
|
def start(self):
|
|
|
|
d = defer.maybeDeferred(self._start_root)
|
|
d.addCallback(lambda _: self._draw_main())
|
|
d.addCallback(lambda _: self._start_downloader())
|
|
d.addCallback(lambda _: self._start_checking_wallet_balance())
|
|
d.addCallback(lambda _: self._enable_lookup())
|
|
|
|
def show_error_and_stop(err):
|
|
logging.error(err.getErrorMessage())
|
|
tkMessageBox.showerror(title="Start Error", message=err.getErrorMessage())
|
|
return self.stop()
|
|
|
|
d.addErrback(show_error_and_stop)
|
|
return d
|
|
|
|
def stop(self):
|
|
|
|
def log_error(err):
|
|
logging.error(err.getErrorMessage())
|
|
|
|
if self.downloader is not None:
|
|
d = self.downloader.stop()
|
|
else:
|
|
d = defer.succeed(True)
|
|
d.addErrback(log_error)
|
|
d.addCallback(lambda _: self._stop_checking_wallet_balance())
|
|
d.addErrback(log_error)
|
|
d.addCallback(lambda _: reactor.stop())
|
|
d.addErrback(log_error)
|
|
return d
|
|
|
|
def _start_root(self):
|
|
if os.name == "nt":
|
|
button_foreground = "#104639"
|
|
lookup_button_padding = 10
|
|
else:
|
|
button_foreground = "#FFFFFF"
|
|
lookup_button_padding = 11
|
|
|
|
root = tk.Tk()
|
|
root.resizable(0, 0)
|
|
root.wm_title("LBRY")
|
|
|
|
tksupport.install(root)
|
|
|
|
if os.name == "nt":
|
|
root.iconbitmap(os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),
|
|
"lbrynet", "lbrynet_downloader_gui", "lbry-dark-icon.ico"))
|
|
else:
|
|
root.wm_iconbitmap("@" + os.path.join(os.path.dirname(__file__), "lbry-dark-icon.xbm"))
|
|
|
|
root.button_font = tkFont.Font(size=9)
|
|
|
|
ttk.Style().configure(".", background="#FFFFFF")
|
|
ttk.Style().configure("LBRY.TButton", background="#104639", foreground=button_foreground,
|
|
borderwidth=1, relief="solid", font=root.button_font)
|
|
ttk.Style().map("LBRY.TButton",
|
|
background=[('pressed', "#104639"),
|
|
('active', "#104639")])
|
|
#ttk.Style().configure("LBRY.TButton.border", background="#808080")
|
|
ttk.Style().configure("Lookup.LBRY.TButton", padding=lookup_button_padding)
|
|
ttk.Style().configure("Stop.TButton", padding=1, background="#FFFFFF", relief="flat", borderwidth=0)
|
|
ttk.Style().configure("TEntry", padding=11)
|
|
ttk.Style().configure("Float.TEntry", padding=2)
|
|
#ttk.Style().configure("A.TFrame", background="red")
|
|
#ttk.Style().configure("B.TFrame", background="green")
|
|
#ttk.Style().configure("B2.TFrame", background="#80FF80")
|
|
#ttk.Style().configure("C.TFrame", background="orange")
|
|
#ttk.Style().configure("D.TFrame", background="blue")
|
|
#ttk.Style().configure("E.TFrame", background="yellow")
|
|
#ttk.Style().configure("F.TFrame", background="#808080")
|
|
#ttk.Style().configure("G.TFrame", background="#FF80FF")
|
|
#ttk.Style().configure("H.TFrame", background="#0080FF")
|
|
#ttk.Style().configure("LBRY.TProgressbar", background="#104639", orient="horizontal", thickness=5)
|
|
#ttk.Style().configure("LBRY.TProgressbar")
|
|
#ttk.Style().layout("Horizontal.LBRY.TProgressbar", ttk.Style().layout("Horizontal.TProgressbar"))
|
|
|
|
root.configure(background="#FFFFFF")
|
|
|
|
root.protocol("WM_DELETE_WINDOW", self.stop)
|
|
|
|
self.master = root
|
|
|
|
def _draw_main(self):
|
|
self.frame = ttk.Frame(self.master, style="A.TFrame")
|
|
self.frame.grid(padx=20, pady=20)
|
|
|
|
logo_file_name = "lbry-dark-242x80.gif"
|
|
try:
|
|
logo_file = os.path.join(os.path.dirname(__file__), logo_file_name)
|
|
except NameError:
|
|
logo_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), "lbrynet",
|
|
"lbrynet_downloader_gui", logo_file_name)
|
|
|
|
self.logo_picture = tk.PhotoImage(file=logo_file)
|
|
|
|
self.logo_frame = ttk.Frame(self.frame, style="B.TFrame")
|
|
self.logo_frame.grid(pady=5, sticky=tk.W + tk.E)
|
|
|
|
self.dummy_frame = ttk.Frame(self.logo_frame, style="C.TFrame") # keeps the logo in the middle
|
|
self.dummy_frame.grid(row=0, column=1, padx=5)
|
|
|
|
self.logo_label = ttk.Label(self.logo_frame, image=self.logo_picture)
|
|
self.logo_label.grid(row=0, column=1, padx=5)
|
|
|
|
self.wallet_balance_frame = ttk.Frame(self.logo_frame, style="C.TFrame")
|
|
self.wallet_balance_frame.grid(sticky=tk.E + tk.N, row=0, column=2)
|
|
|
|
self.logo_frame.grid_columnconfigure(0, weight=1, uniform="a")
|
|
self.logo_frame.grid_columnconfigure(1, weight=2, uniform="b")
|
|
self.logo_frame.grid_columnconfigure(2, weight=1, uniform="a")
|
|
|
|
self.wallet_balance = ttk.Label(
|
|
self.wallet_balance_frame,
|
|
text=" -- LBC"
|
|
)
|
|
self.wallet_balance.grid(row=0, column=0)
|
|
|
|
dropdown_file_name = "drop_down.gif"
|
|
try:
|
|
dropdown_file = os.path.join(os.path.dirname(__file__), dropdown_file_name)
|
|
except NameError:
|
|
dropdown_file = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), "lbrynet",
|
|
"lbrynet_downloader_gui", dropdown_file_name)
|
|
|
|
self.dropdown_picture = tk.PhotoImage(
|
|
file=dropdown_file
|
|
)
|
|
|
|
def get_new_address():
|
|
def show_address(address):
|
|
w = AddressWindow(self.master, address)
|
|
w.show()
|
|
d = defer.maybeDeferred(self.downloader.get_new_address)
|
|
d.addCallback(show_address)
|
|
|
|
def show_error(err):
|
|
tkMessageBox.showerror(title="Failed to get new address", message=err.getErrorMessage())
|
|
|
|
d.addErrback(show_error)
|
|
|
|
self.wallet_menu = tk.Menu(
|
|
self.master, tearoff=0
|
|
)
|
|
self.wallet_menu.add_command(label="Get new LBRYcrd address", command=get_new_address)
|
|
|
|
if os.name == "nt":
|
|
button_cursor = ""
|
|
else:
|
|
button_cursor = "hand1"
|
|
|
|
self.wallet_menu_button = ttk.Button(self.wallet_balance_frame, image=self.dropdown_picture,
|
|
style="Stop.TButton", cursor=button_cursor)
|
|
self.wallet_menu_button.grid(row=0, column=1, padx=(5, 0))
|
|
|
|
def popup(event):
|
|
self.wallet_menu.tk_popup(event.x_root, event.y_root)
|
|
|
|
self.wallet_menu_button.bind("<Button-1>", popup)
|
|
|
|
self.uri_frame = ttk.Frame(self.frame, style="B.TFrame")
|
|
self.uri_frame.grid()
|
|
|
|
self.uri_label = ttk.Label(
|
|
self.uri_frame, text="lbry://"
|
|
)
|
|
self.uri_label.grid(row=0, column=0, sticky=tk.E, pady=2)
|
|
|
|
self.entry_font = tkFont.Font(size=11)
|
|
|
|
self.uri_entry = ttk.Entry(self.uri_frame, width=50, foreground="#222222", font=self.entry_font)
|
|
self.uri_entry.grid(row=0, column=1, padx=2, pady=2)
|
|
|
|
def copy_command():
|
|
self.uri_entry.event_generate('<Control-c>')
|
|
|
|
def cut_command():
|
|
self.uri_entry.event_generate('<Control-x>')
|
|
|
|
def paste_command():
|
|
self.uri_entry.event_generate('<Control-v>')
|
|
|
|
def popup(event):
|
|
selection_menu = tk.Menu(
|
|
self.master, tearoff=0
|
|
)
|
|
if self.uri_entry.selection_present():
|
|
selection_menu.add_command(label=" Cut ", command=cut_command)
|
|
selection_menu.add_command(label=" Copy ", command=copy_command)
|
|
selection_menu.add_command(label=" Paste ", command=paste_command)
|
|
selection_menu.tk_popup(event.x_root, event.y_root)
|
|
|
|
self.uri_entry.bind("<Button-3>", popup)
|
|
|
|
self.uri_button = ttk.Button(
|
|
self.uri_frame, text="Go", command=self._open_stream,
|
|
style='Lookup.LBRY.TButton', cursor=button_cursor
|
|
)
|
|
self.uri_button.grid(row=0, column=2, pady=2, padx=0)
|
|
|
|
def _start_downloader(self):
|
|
self.downloader = LBRYDownloader()
|
|
d = self.downloader.start()
|
|
d.addCallback(lambda _: self.downloader.do_first_run())
|
|
d.addCallback(self._show_welcome_message)
|
|
return d
|
|
|
|
def _show_welcome_message(self, points_sent):
|
|
if points_sent != 0.0:
|
|
w = WelcomeWindow(self.master, points_sent)
|
|
w.show()
|
|
|
|
def stream_removed(self):
|
|
if self.streams_frame is not None:
|
|
if len(self.streams_frame.winfo_children()) == 0:
|
|
self.streams_frame.destroy()
|
|
self.streams_frame = None
|
|
|
|
def _start_checking_wallet_balance(self):
|
|
|
|
def set_balance(balance):
|
|
self.wallet_balance.configure(text=locale.format_string("%.2f LBC", (round(balance, 2),),
|
|
grouping=True))
|
|
|
|
def update_balance():
|
|
balance = self.downloader.session.wallet.get_available_balance()
|
|
set_balance(balance)
|
|
|
|
def start_looping_call():
|
|
self.wallet_balance_check = task.LoopingCall(update_balance)
|
|
self.wallet_balance_check.start(5)
|
|
|
|
d = self.downloader.session.wallet.get_balance()
|
|
d.addCallback(set_balance)
|
|
d.addCallback(lambda _: start_looping_call())
|
|
|
|
def _stop_checking_wallet_balance(self):
|
|
if self.wallet_balance_check is not None:
|
|
self.wallet_balance_check.stop()
|
|
|
|
def _enable_lookup(self):
|
|
self.uri_entry.bind('<Return>', self._open_stream)
|
|
|
|
def _open_stream(self, event=None):
|
|
if self.streams_frame is None:
|
|
self.streams_frame = ttk.Frame(self.frame, style="B2.TFrame")
|
|
self.streams_frame.grid(sticky=tk.E + tk.W)
|
|
uri = self.uri_entry.get()
|
|
self.uri_entry.delete(0, tk.END)
|
|
stream_frame = StreamFrame(self, "lbry://" + uri)
|
|
|
|
self.downloader.download_stream(stream_frame, uri)
|
|
|
|
|
|
def start_downloader():
|
|
|
|
log_format = "(%(asctime)s)[%(filename)s:%(lineno)s] %(funcName)s(): %(message)s"
|
|
logging.basicConfig(level=logging.DEBUG, format=log_format, filename="downloader.log")
|
|
sys.stdout = open("downloader.out.log", 'w')
|
|
sys.stderr = open("downloader.err.log", 'w')
|
|
|
|
locale.setlocale(locale.LC_ALL, '')
|
|
|
|
app = App()
|
|
|
|
d = task.deferLater(reactor, 0, app.start)
|
|
|
|
reactor.run()
|
|
|
|
if __name__ == "__main__":
|
|
start_downloader() |