diff --git a/lbrynet/lbrynet_downloader_gui/DownloaderApp.py b/lbrynet/lbrynet_downloader_gui/DownloaderApp.py new file mode 100644 index 000000000..b205275ba --- /dev/null +++ b/lbrynet/lbrynet_downloader_gui/DownloaderApp.py @@ -0,0 +1,348 @@ +import Tkinter as tk +import logging +import sys +import tkFont +import tkMessageBox +import ttk +from lbrynet.lbrynet_downloader_gui.LBRYDownloader import LBRYDownloader +from lbrynet.lbrynet_downloader_gui.StreamFrame import StreamFrame +import locale +import os +from twisted.internet import defer, reactor, tksupport, task + + +class DownloaderApp(object): + def __init__(self): + self.master = None + self.downloader = None + self.wallet_balance_check = None + self.streams_frame = None + + def start(self): + + d = defer.maybeDeferred(self._start_root) + d.addCallback(lambda _: self._draw_main()) + d.addCallback(lambda _: self._start_downloader()) + d.addCallback(lambda _: self._start_checking_wallet_balance()) + d.addCallback(lambda _: self._enable_lookup()) + + def show_error_and_stop(err): + 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("", 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('') + + def cut_command(): + self.uri_entry.event_generate('') + + def paste_command(): + self.uri_entry.event_generate('') + + 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("", 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('', 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) + + +class WelcomeWindow(object): + def __init__(self, root, points_sent): + self.root = root + self.points_sent = points_sent + + def show(self): + window = tk.Toplevel(self.root, background="#FFFFFF") + window.transient(self.root) + window.wm_title("Welcome to LBRY") + window.protocol("WM_DELETE_WINDOW", window.destroy) + window.resizable(0, 0) + + text_box = tk.Text(window, width=45, height=3, relief=tk.FLAT, borderwidth=0, + highlightthickness=0) + + points_string = locale.format_string("%.2f LBC", (round(self.points_sent, 2),), + grouping=True) + + text_box.insert(tk.END, "Thank you for using LBRY! You have been\n" + "given %s for free because we love\n" + "you. Please give them 60 seconds to show up." % points_string) + text_box.grid(row=0, padx=10, pady=5, columnspan=2) + text_box.config(state='normal') + + window.focus_set() + + +class AddressWindow(object): + def __init__(self, root, address): + self.root = root + self.address = address + + def show(self): + window = tk.Toplevel(self.root, background="#FFFFFF") + window.transient(self.root) + window.wm_title("New address") + window.protocol("WM_DELETE_WINDOW", window.destroy) + window.resizable(0, 0) + + text_box = tk.Text(window, width=35, height=1, relief=tk.FLAT, borderwidth=0, + highlightthickness=0) + text_box.insert(tk.END, self.address) + text_box.grid(row=0, padx=10, pady=5, columnspan=2) + text_box.config(state='normal') + + def copy_to_clipboard(): + self.root.clipboard_clear() + self.root.clipboard_append(text_box.get('1.0', 'end-1c')) + + def copy_command(): + text_box.event_generate("") + + copy_menu = tk.Menu( + self.root, tearoff=0 + ) + copy_menu.add_command(label=" Copy ", command=copy_command) + + def popup(event): + if text_box.tag_ranges("sel"): + copy_menu.tk_popup(event.x_root, event.y_root) + + text_box.bind("", popup) + + copy_button = ttk.Button( + window, text="Copy", command=copy_to_clipboard, style="LBRY.TButton" + ) + copy_button.grid(row=1, column=0, pady=(0, 5), padx=5, sticky=tk.E) + + done_button = ttk.Button( + window, text="OK", command=window.destroy, style="LBRY.TButton" + ) + done_button.grid(row=1, column=1, pady=(0, 5), padx=5, sticky=tk.W) + window.focus_set() \ No newline at end of file diff --git a/lbrynet/lbrynet_downloader_gui/LBRYDownloader.py b/lbrynet/lbrynet_downloader_gui/LBRYDownloader.py new file mode 100644 index 000000000..fb0b7b63e --- /dev/null +++ b/lbrynet/lbrynet_downloader_gui/LBRYDownloader.py @@ -0,0 +1,281 @@ +import binascii +import logging +import tkMessageBox +from Crypto import Random +from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE +from lbrynet.core import StreamDescriptor +from lbrynet.core.LBRYcrdWallet import LBRYcrdWallet +from lbrynet.core.PaymentRateManager import PaymentRateManager +from lbrynet.core.Session import LBRYSession +from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier +from lbrynet.lbryfile.LBRYFileMetadataManager import TempLBRYFileMetadataManager +from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType +from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory +from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier +import os +import requests +from twisted.internet import threads, defer, task + + +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=4447) + 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()) \ No newline at end of file diff --git a/lbrynet/lbrynet_downloader_gui/StreamFrame.py b/lbrynet/lbrynet_downloader_gui/StreamFrame.py new file mode 100644 index 000000000..93e436bfa --- /dev/null +++ b/lbrynet/lbrynet_downloader_gui/StreamFrame.py @@ -0,0 +1,402 @@ +import Tkinter as tk +import sys +import tkFont +import ttk +import locale +import os + + +class StreamFrame(object): + def __init__(self, app, uri): + self.app = app + self.uri = uri + self.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)) \ No newline at end of file diff --git a/lbrynet/lbrynet_downloader_gui/downloader.py b/lbrynet/lbrynet_downloader_gui/downloader.py index 29a48de04..be6736779 100644 --- a/lbrynet/lbrynet_downloader_gui/downloader.py +++ b/lbrynet/lbrynet_downloader_gui/downloader.py @@ -1,1022 +1,9 @@ -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 + +from lbrynet.lbrynet_downloader_gui.DownloaderApp import DownloaderApp +from twisted.internet import reactor, task 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("") - - copy_menu = tk.Menu( - self.root, tearoff=0 - ) - copy_menu.add_command(label=" Copy ", command=copy_command) - - def popup(event): - if text_box.tag_ranges("sel"): - copy_menu.tk_popup(event.x_root, event.y_root) - - text_box.bind("", popup) - - copy_button = ttk.Button( - window, text="Copy", command=copy_to_clipboard, style="LBRY.TButton" - ) - copy_button.grid(row=1, column=0, pady=(0, 5), padx=5, sticky=tk.E) - - done_button = ttk.Button( - window, text="OK", command=window.destroy, style="LBRY.TButton" - ) - done_button.grid(row=1, column=1, pady=(0, 5), padx=5, sticky=tk.W) - window.focus_set() - - -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("", 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('') - - def cut_command(): - self.uri_entry.event_generate('') - - def paste_command(): - self.uri_entry.event_generate('') - - 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("", 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('', 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(): @@ -1028,7 +15,7 @@ def start_downloader(): locale.setlocale(locale.LC_ALL, '') - app = App() + app = DownloaderApp() d = task.deferLater(reactor, 0, app.start)