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
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
|