You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

424 lines
13 KiB

#!/usr/bin/env python2
''' Contains code for main app frame & custom ListCtrl. '''
import os.path
import wx
from wx.lib.pubsub import setuparg1
from wx.lib.pubsub import pub as Publisher
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
from .optionsframe import OptionsFrame
from .updthread import UpdateThread
from .dlthread import DownloadManager
from .utils import (
YOUTUBEDL_BIN,
get_config_path,
get_icon_file,
shutdown_sys,
get_time,
open_dir
)
from .data import (
__appname__
)
class MainFrame(wx.Frame):
''' Youtube-dlG main frame. '''
FRAME_SIZE = (700, 490)
TEXTCTRL_SIZE = (-1, -1)
BUTTONS_SIZE = (90, 30)
BUTTONS_SPACE = 80
SIZE_20 = 20
SIZE_10 = 10
SIZE_5 = 5
URLS_LABEL = "URLs"
DOWNLOAD_LABEL = "Download"
UPDATE_LABEL = "Update"
OPTIONS_LABEL = "Options"
ERROR_LABEL = "Error"
STOP_LABEL = "Stop"
INFO_LABEL = "Info"
WELCOME_MSG = "Welcome"
SUCC_REPORT_MSG = ("Successfully downloaded {0} url(s) in {1} "
"day(s) {2} hour(s) {3} minute(s) {4} second(s)")
DL_COMPLETED_MSG = "Download completed"
URL_REPORT_MSG = "Downloading {0} url(s)"
CLOSING_MSG = "Stopping downloads"
CLOSED_MSG = "Downloads stopped"
PROVIDE_URL_MSG = "You need to provide at least one url"
DOWNLOAD_STARTED = "Download started"
VIDEO_LABEL = "Video"
SIZE_LABEL = "Size"
PERCENT_LABEL = "Percent"
ETA_LABEL = "ETA"
SPEED_LABEL = "Speed"
STATUS_LABEL = "Status"
STATUSLIST_COLUMNS = (
('filename', 0, VIDEO_LABEL, 150, True),
('filesize', 1, SIZE_LABEL, 80, False),
('percent', 2, PERCENT_LABEL, 65, False),
('eta', 3, ETA_LABEL, 45, False),
('speed', 4, SPEED_LABEL, 90, False),
('status', 5, STATUS_LABEL, 160, False)
)
def __init__(self, opt_manager, log_manager, parent=None):
wx.Frame.__init__(self, parent, title=__appname__, size=self.FRAME_SIZE)
self.opt_manager = opt_manager
self.log_manager = log_manager
self.download_manager = None
self.update_thread = None
self.app_icon = get_icon_file()
if self.app_icon is not None:
self.app_icon = wx.Icon(self.app_icon, wx.BITMAP_TYPE_PNG)
self.SetIcon(self.app_icon)
# Create options frame
self._options_frame = OptionsFrame(self)
# Create components
self._panel = wx.Panel(self)
self._url_text = self._create_statictext(self.URLS_LABEL)
self._url_list = self._create_textctrl(wx.TE_MULTILINE | wx.TE_DONTWRAP, self._on_urllist_edit)
self._download_btn = self._create_button(self.DOWNLOAD_LABEL, self._on_download)
self._update_btn = self._create_button(self.UPDATE_LABEL, self._on_update)
self._options_btn = self._create_button(self.OPTIONS_LABEL, self._on_options)
self._status_list = ListCtrl(self.STATUSLIST_COLUMNS,
parent=self._panel,
style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
self._status_bar = self._create_statictext(self.WELCOME_MSG)
# Bind extra events
self.Bind(wx.EVT_CLOSE, self._on_close)
self._set_sizers()
# Set threads wx.CallAfter handlers using subscribe
self._set_publisher(self._update_handler, 'update')
self._set_publisher(self._status_list_handler, 'dlworker')
self._set_publisher(self._download_manager_handler, 'dlmanager')
def _set_publisher(self, handler, topic):
Publisher.subscribe(handler, topic)
def _create_statictext(self, label):
statictext = wx.StaticText(self._panel, label=label)
return statictext
def _create_textctrl(self, style=None, event_handler=None):
if style is None:
textctrl = wx.TextCtrl(self._panel, size=self.TEXTCTRL_SIZE)
else:
textctrl = wx.TextCtrl(self._panel, size=self.TEXTCTRL_SIZE, style=style)
if event_handler is not None:
textctrl.Bind(wx.EVT_TEXT, event_handler)
return textctrl
def _create_button(self, label, event_handler=None):
btn = wx.Button(self._panel, label=label, size=self.BUTTONS_SIZE)
if event_handler is not None:
btn.Bind(wx.EVT_BUTTON, event_handler)
return btn
def _create_popup(self, text, title, style):
''' Create popup message. '''
wx.MessageBox(text, title, style)
def _set_sizers(self):
hor_sizer = wx.BoxSizer(wx.HORIZONTAL)
vertical_sizer = wx.BoxSizer(wx.VERTICAL)
vertical_sizer.AddSpacer(self.SIZE_10)
vertical_sizer.Add(self._url_text)
vertical_sizer.Add(self._url_list, 1, wx.EXPAND)
vertical_sizer.AddSpacer(self.SIZE_10)
buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
buttons_sizer.Add(self._download_btn)
buttons_sizer.Add(self._update_btn, flag=wx.LEFT | wx.RIGHT, border=self.BUTTONS_SPACE)
buttons_sizer.Add(self._options_btn)
vertical_sizer.Add(buttons_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL)
vertical_sizer.AddSpacer(self.SIZE_10)
vertical_sizer.Add(self._status_list, 2, wx.EXPAND)
vertical_sizer.AddSpacer(self.SIZE_5)
vertical_sizer.Add(self._status_bar)
vertical_sizer.AddSpacer(self.SIZE_5)
hor_sizer.Add(vertical_sizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, border=self.SIZE_20)
self._panel.SetSizer(hor_sizer)
def _update_youtubedl(self):
''' Update youtube-dl executable. '''
self._update_btn.Disable()
self._download_btn.Disable()
self.update_thread = UpdateThread(self.opt_manager.options['youtubedl_path'])
def _status_bar_write(self, msg):
''' Write msg to self._status_bar. '''
self._status_bar.SetLabel(msg)
def _reset_buttons(self):
''' Reset GUI and variables after download process. '''
self._download_btn.SetLabel(self.DOWNLOAD_LABEL)
self._download_btn.Enable()
self._update_btn.Enable()
def _print_stats(self):
''' Print stats to self._status_bar after downloading. '''
suc_downloads = self.download_manager.successful
dtime = get_time(self.download_manager.time_it_took)
days = int(dtime['days'])
hours = int(dtime['hours'])
minutes = int(dtime['minutes'])
seconds = int(dtime['seconds'])
msg = self.SUCC_REPORT_MSG.format(suc_downloads, days, hours, minutes, seconds)
self._status_bar_write(msg)
def _after_download(self):
''' Run tasks after download process has finished. '''
if self.opt_manager.options['shutdown']:
self.opt_manager.save_to_file()
shutdown_sys(self.opt_manager.options['sudo_password'])
else:
self._create_popup(self.DL_COMPLETED_MSG, self.INFO_LABEL, wx.OK | wx.ICON_INFORMATION)
if self.opt_manager.options['open_dl_dir']:
open_dir(self.opt_manager.options['save_path'])
def _status_list_handler(self, msg):
data = msg.data
self._status_list.write(data)
# Report urls been downloaded
msg = self.URL_REPORT_MSG.format(self.download_manager.active())
self._status_bar_write(msg)
def _download_manager_handler(self, msg):
''' Handle messages from DownloadManager. '''
data = msg.data
if data == 'finished':
self._print_stats()
self._reset_buttons()
self.download_manager = None
self._after_download()
elif data == 'closed':
self._status_bar_write(self.CLOSED_MSG)
self._reset_buttons()
self.download_manager = None
else:
self._status_bar_write(self.CLOSING_MSG)
def _update_handler(self, msg):
''' Handle messages from UpdateThread. '''
data = msg.data
if data == 'finish':
self._reset_buttons()
self.update_thread = None
else:
self._status_bar_write(data)
def _get_urls(self):
return self._url_list.GetValue().split('\n')
def _start_download(self):
''' Handle pre-download tasks & start download process. '''
self._status_list.clear()
self._status_list.load_urls(self._get_urls())
if self._status_list.is_empty():
self._create_popup(self.PROVIDE_URL_MSG,
self.ERROR_LABEL,
wx.OK | wx.ICON_EXCLAMATION)
else:
self.download_manager = DownloadManager(self._status_list.get_items(),
self.opt_manager,
self.log_manager)
self._status_bar_write(self.DOWNLOAD_STARTED)
self._download_btn.SetLabel(self.STOP_LABEL)
self._update_btn.Disable()
def _on_urllist_edit(self, event):
''' Dynamically add url for download.'''
if self.download_manager is not None:
self._status_list.load_urls(self._get_urls(), self.download_manager.add_url)
def _on_download(self, event):
''' Event handler method for self._download_btn. '''
if self.download_manager is None:
self._start_download()
else:
self.download_manager.stop_downloads()
def _on_update(self, event):
''' Event handler method for self._update_btn. '''
self._update_youtubedl()
def _on_options(self, event):
''' Event handler method for self._options_btn. '''
self._options_frame.load_all_options()
self._options_frame.Show()
def _on_close(self, event):
''' Event handler method (wx.EVT_CLOSE). '''
if self.download_manager is not None:
self.download_manager.stop_downloads()
self.download_manager.join()
if self.update_thread is not None:
self.update_thread.join()
self.opt_manager.save_to_file()
self.Destroy()
class ListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin):
'''
Custom ListCtrl class.
Accessible Methods
write()
Params: Python dictionary that contains data to write
Return: None
has_url()
Params: Url to search
Return: True if url in ListCtrl, else False
add_url()
Params: Url to add
Return: None
clear()
Params: None
Return: None
is_empty()
Params: None
Return: True if ListCtrl is empty, else False
get_items()
Params: None
Return: Python list that contains all ListCtrl items
get_last_item()
Params: None
Return: Last item inserted in ListCtrl
'''
def __init__(self, columns, *args, **kwargs):
wx.ListCtrl.__init__(self, *args, **kwargs)
ListCtrlAutoWidthMixin.__init__(self)
self.columns = columns
self._list_index = 0
self._url_list = set()
self._set_columns()
def write(self, data):
''' Write data on ListCtrl row-column. '''
for key in data:
for column in self.columns:
if key == column[0]:
self._write_data(data[key], data['index'], column[1])
break
def load_urls(self, url_list, func=None):
for url in url_list:
url = url.replace(' ', '')
if url and not self.has_url(url):
self.add_url(url)
if func is not None:
# Custom hack to add url into download_manager
# i am gonna change this
item = self._get_item(self._list_index - 1)
func(item)
def has_url(self, url):
''' Return True if url in ListCtrl, else return False. '''
return url in self._url_list
def add_url(self, url):
''' Add url on ListCtrl. '''
self.InsertStringItem(self._list_index, url)
self._url_list.add(url)
self._list_index += 1
def clear(self):
''' Clear ListCtrl & reset self._list_index. '''
self.DeleteAllItems()
self._list_index = 0
self._url_list = set()
def is_empty(self):
''' Return True if list is empty. '''
return self._list_index == 0
def get_items(self):
''' Return list of items in ListCtrl. '''
items = []
for row in xrange(self._list_index):
item = self._get_item(row)
items.append(item)
return items
def _write_data(self, data, row, column):
''' Write data on row, column. '''
if isinstance(data, basestring):
self.SetStringItem(row, column, data)
def _get_item(self, index):
''' Return single item base on index. '''
item = self.GetItem(itemId=index, col=0)
data = dict(url=item.GetText(), index=index)
return data
def _set_columns(self):
for column in self.columns:
self.InsertColumn(column[1], column[2], width=column[3])
if column[4]:
self.setResizeColumn(column[1])