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.
 
 
 

426 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 .LogManager import LogManager
from .OptionsFrame import OptionsFrame
from .UpdateThread import UpdateThread
from .OptionsManager import OptionsManager
from .DownloadThread import DownloadManager, DownloadThread
from .utils import (
get_youtubedl_filename,
get_config_path,
get_icon_path,
shutdown_sys,
get_time,
open_dir
)
from .data import (
__author__,
__appname__
)
CONFIG_PATH = os.path.join(get_config_path(), __appname__.lower())
class MainFrame(wx.Frame):
''' Youtube-dlG main frame. '''
def __init__(self, parent=None):
wx.Frame.__init__(self, parent, -1, __appname__, size=(650, 440))
self.init_gui()
icon = get_icon_path()
if icon is not None:
self.SetIcon(wx.Icon(icon, wx.BITMAP_TYPE_PNG))
Publisher.subscribe(self.update_handler, "update")
Publisher.subscribe(self.download_handler, "dlmanager")
Publisher.subscribe(self.download_handler, "dlthread")
self.opt_manager = OptionsManager(CONFIG_PATH)
self.download_manager = None
self.update_thread = None
self.log_manager = None
if self.opt_manager.options['enable_log']:
self.log_manager = LogManager(
CONFIG_PATH,
self.opt_manager.options['log_time']
)
def init_gui(self):
''' Initialize youtube-dlG GUI components & sizers. '''
panel = wx.Panel(self)
# Create components
self.url_list = wx.TextCtrl(panel,
size=(-1, 120),
style=wx.TE_MULTILINE | wx.TE_DONTWRAP)
self.download_button = wx.Button(panel, label='Download', size=(90, 30))
self.update_button = wx.Button(panel, label='Update', size=(90, 30))
self.options_button = wx.Button(panel, label='Options', size=(90, 30))
self.status_list = ListCtrl(panel,
style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
self.status_bar = wx.StaticText(panel, label='Author: ' + __author__)
# Set sizers
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.AddSpacer(10)
# URLs label
main_sizer.Add(wx.StaticText(panel, label='URLs'), flag=wx.LEFT, border=20)
# URLs list
horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
horizontal_sizer.Add(self.url_list, 1)
main_sizer.Add(horizontal_sizer, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=20)
main_sizer.AddSpacer(10)
# Buttons
horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
horizontal_sizer.Add(self.download_button)
horizontal_sizer.Add(self.update_button, flag=wx.LEFT | wx.RIGHT, border=80)
horizontal_sizer.Add(self.options_button)
main_sizer.Add(horizontal_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL)
main_sizer.AddSpacer(10)
# Status list
horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
horizontal_sizer.Add(self.status_list, 1, flag=wx.EXPAND)
main_sizer.Add(horizontal_sizer, 1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=20)
main_sizer.AddSpacer(5)
# Status bar
main_sizer.Add(self.status_bar, flag=wx.LEFT, border=20)
main_sizer.AddSpacer(5)
panel.SetSizer(main_sizer)
# Bind events
self.Bind(wx.EVT_BUTTON, self.OnDownload, self.download_button)
self.Bind(wx.EVT_BUTTON, self.OnUpdate, self.update_button)
self.Bind(wx.EVT_BUTTON, self.OnOptions, self.options_button)
self.Bind(wx.EVT_TEXT, self.OnListCtrlEdit, self.url_list)
self.Bind(wx.EVT_CLOSE, self.OnClose)
def youtubedl_exist(self):
''' Return True if youtube-dl executable exists. '''
path = os.path.join(self.opt_manager.options['youtubedl_path'],
get_youtubedl_filename())
return os.path.exists(path)
def update_youtubedl(self, quiet=False):
''' Update youtube-dl executable. '''
if not quiet:
self.update_button.Disable()
self.download_button.Disable()
self.update_thread = UpdateThread(
self.opt_manager.options['youtubedl_path'],
quiet
)
def status_bar_write(self, msg):
''' Write msg to self.status_bar. '''
self.status_bar.SetLabel(msg)
def reset(self):
''' Reset GUI and variables after download process. '''
self.download_button.SetLabel('Download')
self.update_button.Enable()
self.download_manager.join()
self.download_manager = None
def print_stats(self):
''' Print stats to self.status_bar after downloading. '''
successful_downloads = self.download_manager.successful_downloads
dtime = get_time(self.download_manager.time)
msg = 'Successfully downloaded %s url(s) in ' % successful_downloads
days = int(dtime['days'])
hours = int(dtime['hours'])
minutes = int(dtime['minutes'])
seconds = int(dtime['seconds'])
if days != 0:
msg += '%s days, ' % days
if hours != 0:
msg += '%s hours, ' % hours
if minutes != 0:
msg += '%s minutes, ' % minutes
msg += '%s seconds ' % seconds
self.status_bar_write(msg)
def fin_tasks(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('Downloads completed', 'Info', wx.OK | wx.ICON_INFORMATION)
if self.opt_manager.options['open_dl_dir']:
open_dir(self.opt_manager.options['save_path'])
def download_handler(self, msg):
''' Handle messages from DownloadManager & DownloadThread. '''
topic = msg.topic[0]
data = msg.data
if topic == 'dlthread':
self.status_list.write(data)
msg = 'Downloading %s url(s)' % self.download_manager.not_finished()
self.status_bar_write(msg)
if topic == 'dlmanager':
if data == 'closing':
self.status_bar_write('Stopping downloads')
if data == 'closed':
self.status_bar_write('Downloads stopped')
self.reset()
if data == 'finished':
self.print_stats()
self.reset()
self.fin_tasks()
def update_handler(self, msg):
''' Handle messages from UpdateThread. '''
if msg.data == 'finish':
self.download_button.Enable()
self.update_button.Enable()
self.update_thread.join()
self.update_thread = None
else:
self.status_bar_write(msg.data)
def load_on_list(self, url):
''' Load url on ListCtrl. Return True if url
loaded successfully, else return False.
'''
url = url.replace(' ', '')
if url != '' and not self.status_list.has_url(url):
self.status_list.add_url(url)
return True
return False
def start_download(self):
''' Handle pre-download tasks & start download process. '''
self.status_list.clear()
if not self.youtubedl_exist():
self.update_youtubedl(True)
for url in self.url_list.GetValue().split('\n'):
self.load_on_list(url)
if self.status_list.is_empty():
self.create_popup(
'You need to provide at least one url',
'Error',
wx.OK | wx.ICON_EXCLAMATION
)
else:
threads_list = []
for item in self.status_list.get_items():
threads_list.append(self.create_thread(item))
self.download_manager = DownloadManager(threads_list, self.update_thread)
self.status_bar_write('Download started')
self.download_button.SetLabel('Stop')
self.update_button.Disable()
def create_thread(self, item):
''' Return DownloadThread created from item. '''
return DownloadThread(item['url'],
item['index'],
self.opt_manager,
self.log_manager)
def create_popup(self, text, title, style):
''' Create popup message. '''
wx.MessageBox(text, title, style)
def OnListCtrlEdit(self, event):
''' Dynamically add url for download.'''
if self.download_manager is not None:
for url in self.url_list.GetValue().split('\n'):
# If url successfully loaded on list
if self.load_on_list(url):
thread = self.create_thread(self.status_list.get_last_item())
self.download_manager.add_thread(thread)
def OnDownload(self, event):
''' Event handler method for self.download_button. '''
if self.download_manager is None:
self.start_download()
else:
self.download_manager.close()
def OnUpdate(self, event):
''' Event handler method for self.update_button. '''
self.update_youtubedl()
def OnOptions(self, event):
''' Event handler method for self.options_button. '''
options_frame = OptionsFrame(
self.opt_manager,
parent=self,
logger=self.log_manager
)
options_frame.Show()
def OnClose(self, event):
''' Event handler method (wx.EVT_CLOSE). '''
if self.download_manager is not None:
self.download_manager.close()
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
'''
# Hold column for each data
DATA_COLUMNS = {
'filename': 0,
'filesize': 1,
'percent': 2,
'status': 5,
'speed': 4,
'eta': 3
}
def __init__(self, parent=None, style=0):
wx.ListCtrl.__init__(self, parent, -1, wx.DefaultPosition, wx.DefaultSize, style)
ListCtrlAutoWidthMixin.__init__(self)
self.InsertColumn(0, 'Video', width=150)
self.InsertColumn(1, 'Size', width=80)
self.InsertColumn(2, 'Percent', width=65)
self.InsertColumn(3, 'ETA', width=45)
self.InsertColumn(4, 'Speed', width=90)
self.InsertColumn(5, 'Status', width=160)
self.setResizeColumn(0)
self._list_index = 0
self._url_list = []
def write(self, data):
''' Write data on ListCtrl row-column. '''
for key in data:
if key in self.DATA_COLUMNS:
self._write_data(data[key], data['index'], self.DATA_COLUMNS[key])
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.append(url)
self._list_index += 1
def clear(self):
''' Clear ListCtrl & reset self._list_index. '''
self.DeleteAllItems()
self._list_index = 0
self._url_list = []
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 range(self._list_index):
item = self._get_item(row)
items.append(item)
return items
def get_last_item(self):
''' Return last item of ListCtrl '''
return self._get_item(self._list_index - 1)
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. '''
data = {}
item = self.GetItem(itemId=index, col=0)
data['url'] = item.GetText()
data['index'] = index
return data