#!/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