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.

422 lines
13 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. #!/usr/bin/env python2
  2. ''' Contains code for main app frame & custom ListCtrl. '''
  3. import os.path
  4. import wx
  5. from wx.lib.pubsub import setuparg1
  6. from wx.lib.pubsub import pub as Publisher
  7. from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
  8. from .optionsframe import OptionsFrame
  9. from .updthread import UpdateThread
  10. from .dlthread import DownloadManager
  11. from .utils import (
  12. YOUTUBEDL_BIN,
  13. get_config_path,
  14. get_icon_file,
  15. shutdown_sys,
  16. get_time,
  17. open_dir
  18. )
  19. from .info import (
  20. __appname__
  21. )
  22. class MainFrame(wx.Frame):
  23. ''' Youtube-dlG main frame. '''
  24. FRAME_SIZE = (700, 490)
  25. TEXTCTRL_SIZE = (-1, -1)
  26. BUTTONS_SIZE = (90, 30)
  27. BUTTONS_SPACE = 80
  28. SIZE_20 = 20
  29. SIZE_10 = 10
  30. SIZE_5 = 5
  31. URLS_LABEL = "URLs"
  32. DOWNLOAD_LABEL = "Download"
  33. UPDATE_LABEL = "Update"
  34. OPTIONS_LABEL = "Options"
  35. ERROR_LABEL = "Error"
  36. STOP_LABEL = "Stop"
  37. INFO_LABEL = "Info"
  38. WELCOME_MSG = "Welcome"
  39. SUCC_REPORT_MSG = ("Successfully downloaded {0} url(s) in {1} "
  40. "day(s) {2} hour(s) {3} minute(s) {4} second(s)")
  41. DL_COMPLETED_MSG = "Download completed"
  42. URL_REPORT_MSG = "Downloading {0} url(s)"
  43. CLOSING_MSG = "Stopping downloads"
  44. CLOSED_MSG = "Downloads stopped"
  45. PROVIDE_URL_MSG = "You need to provide at least one url"
  46. DOWNLOAD_STARTED = "Download started"
  47. VIDEO_LABEL = "Video"
  48. SIZE_LABEL = "Size"
  49. PERCENT_LABEL = "Percent"
  50. ETA_LABEL = "ETA"
  51. SPEED_LABEL = "Speed"
  52. STATUS_LABEL = "Status"
  53. STATUSLIST_COLUMNS = (
  54. ('filename', 0, VIDEO_LABEL, 150, True),
  55. ('filesize', 1, SIZE_LABEL, 80, False),
  56. ('percent', 2, PERCENT_LABEL, 65, False),
  57. ('eta', 3, ETA_LABEL, 45, False),
  58. ('speed', 4, SPEED_LABEL, 90, False),
  59. ('status', 5, STATUS_LABEL, 160, False)
  60. )
  61. def __init__(self, opt_manager, log_manager, parent=None):
  62. wx.Frame.__init__(self, parent, title=__appname__, size=self.FRAME_SIZE)
  63. self.opt_manager = opt_manager
  64. self.log_manager = log_manager
  65. self.download_manager = None
  66. self.update_thread = None
  67. self.app_icon = get_icon_file()
  68. if self.app_icon is not None:
  69. self.app_icon = wx.Icon(self.app_icon, wx.BITMAP_TYPE_PNG)
  70. self.SetIcon(self.app_icon)
  71. # Create options frame
  72. self._options_frame = OptionsFrame(self)
  73. # Create components
  74. self._panel = wx.Panel(self)
  75. self._url_text = self._create_statictext(self.URLS_LABEL)
  76. self._url_list = self._create_textctrl(wx.TE_MULTILINE | wx.TE_DONTWRAP, self._on_urllist_edit)
  77. self._download_btn = self._create_button(self.DOWNLOAD_LABEL, self._on_download)
  78. self._update_btn = self._create_button(self.UPDATE_LABEL, self._on_update)
  79. self._options_btn = self._create_button(self.OPTIONS_LABEL, self._on_options)
  80. self._status_list = ListCtrl(self.STATUSLIST_COLUMNS,
  81. parent=self._panel,
  82. style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES)
  83. self._status_bar = self._create_statictext(self.WELCOME_MSG)
  84. # Bind extra events
  85. self.Bind(wx.EVT_CLOSE, self._on_close)
  86. self._set_sizers()
  87. # Set threads wx.CallAfter handlers using subscribe
  88. self._set_publisher(self._update_handler, 'update')
  89. self._set_publisher(self._status_list_handler, 'dlworker')
  90. self._set_publisher(self._download_manager_handler, 'dlmanager')
  91. def _set_publisher(self, handler, topic):
  92. Publisher.subscribe(handler, topic)
  93. def _create_statictext(self, label):
  94. statictext = wx.StaticText(self._panel, label=label)
  95. return statictext
  96. def _create_textctrl(self, style=None, event_handler=None):
  97. if style is None:
  98. textctrl = wx.TextCtrl(self._panel, size=self.TEXTCTRL_SIZE)
  99. else:
  100. textctrl = wx.TextCtrl(self._panel, size=self.TEXTCTRL_SIZE, style=style)
  101. if event_handler is not None:
  102. textctrl.Bind(wx.EVT_TEXT, event_handler)
  103. return textctrl
  104. def _create_button(self, label, event_handler=None):
  105. btn = wx.Button(self._panel, label=label, size=self.BUTTONS_SIZE)
  106. if event_handler is not None:
  107. btn.Bind(wx.EVT_BUTTON, event_handler)
  108. return btn
  109. def _create_popup(self, text, title, style):
  110. ''' Create popup message. '''
  111. wx.MessageBox(text, title, style)
  112. def _set_sizers(self):
  113. hor_sizer = wx.BoxSizer(wx.HORIZONTAL)
  114. vertical_sizer = wx.BoxSizer(wx.VERTICAL)
  115. vertical_sizer.AddSpacer(self.SIZE_10)
  116. vertical_sizer.Add(self._url_text)
  117. vertical_sizer.Add(self._url_list, 1, wx.EXPAND)
  118. vertical_sizer.AddSpacer(self.SIZE_10)
  119. buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
  120. buttons_sizer.Add(self._download_btn)
  121. buttons_sizer.Add(self._update_btn, flag=wx.LEFT | wx.RIGHT, border=self.BUTTONS_SPACE)
  122. buttons_sizer.Add(self._options_btn)
  123. vertical_sizer.Add(buttons_sizer, flag=wx.ALIGN_CENTER_HORIZONTAL)
  124. vertical_sizer.AddSpacer(self.SIZE_10)
  125. vertical_sizer.Add(self._status_list, 2, wx.EXPAND)
  126. vertical_sizer.AddSpacer(self.SIZE_5)
  127. vertical_sizer.Add(self._status_bar)
  128. vertical_sizer.AddSpacer(self.SIZE_5)
  129. hor_sizer.Add(vertical_sizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, border=self.SIZE_20)
  130. self._panel.SetSizer(hor_sizer)
  131. def _update_youtubedl(self):
  132. ''' Update youtube-dl executable. '''
  133. self._update_btn.Disable()
  134. self._download_btn.Disable()
  135. self.update_thread = UpdateThread(self.opt_manager.options['youtubedl_path'])
  136. def _status_bar_write(self, msg):
  137. ''' Write msg to self._status_bar. '''
  138. self._status_bar.SetLabel(msg)
  139. def _reset_buttons(self):
  140. ''' Reset GUI and variables after download process. '''
  141. self._download_btn.SetLabel(self.DOWNLOAD_LABEL)
  142. self._download_btn.Enable()
  143. self._update_btn.Enable()
  144. def _print_stats(self):
  145. ''' Print stats to self._status_bar after downloading. '''
  146. suc_downloads = self.download_manager.successful
  147. dtime = get_time(self.download_manager.time_it_took)
  148. days = int(dtime['days'])
  149. hours = int(dtime['hours'])
  150. minutes = int(dtime['minutes'])
  151. seconds = int(dtime['seconds'])
  152. msg = self.SUCC_REPORT_MSG.format(suc_downloads, days, hours, minutes, seconds)
  153. self._status_bar_write(msg)
  154. def _after_download(self):
  155. ''' Run tasks after download process has finished. '''
  156. if self.opt_manager.options['shutdown']:
  157. self.opt_manager.save_to_file()
  158. shutdown_sys(self.opt_manager.options['sudo_password'])
  159. else:
  160. self._create_popup(self.DL_COMPLETED_MSG, self.INFO_LABEL, wx.OK | wx.ICON_INFORMATION)
  161. if self.opt_manager.options['open_dl_dir']:
  162. open_dir(self.opt_manager.options['save_path'])
  163. def _status_list_handler(self, msg):
  164. data = msg.data
  165. self._status_list.write(data)
  166. # Report urls been downloaded
  167. msg = self.URL_REPORT_MSG.format(self.download_manager.active())
  168. self._status_bar_write(msg)
  169. def _download_manager_handler(self, msg):
  170. ''' Handle messages from DownloadManager. '''
  171. data = msg.data
  172. if data == 'finished':
  173. self._print_stats()
  174. self._reset_buttons()
  175. self.download_manager = None
  176. self._after_download()
  177. elif data == 'closed':
  178. self._status_bar_write(self.CLOSED_MSG)
  179. self._reset_buttons()
  180. self.download_manager = None
  181. else:
  182. self._status_bar_write(self.CLOSING_MSG)
  183. def _update_handler(self, msg):
  184. ''' Handle messages from UpdateThread. '''
  185. data = msg.data
  186. if data == 'finish':
  187. self._reset_buttons()
  188. self.update_thread = None
  189. else:
  190. self._status_bar_write(data)
  191. def _get_urls(self):
  192. return self._url_list.GetValue().split('\n')
  193. def _start_download(self):
  194. ''' Handle pre-download tasks & start download process. '''
  195. self._status_list.clear()
  196. self._status_list.load_urls(self._get_urls())
  197. if self._status_list.is_empty():
  198. self._create_popup(self.PROVIDE_URL_MSG,
  199. self.ERROR_LABEL,
  200. wx.OK | wx.ICON_EXCLAMATION)
  201. else:
  202. self.download_manager = DownloadManager(self._status_list.get_items(),
  203. self.opt_manager,
  204. self.log_manager)
  205. self._status_bar_write(self.DOWNLOAD_STARTED)
  206. self._download_btn.SetLabel(self.STOP_LABEL)
  207. self._update_btn.Disable()
  208. def _on_urllist_edit(self, event):
  209. ''' Dynamically add url for download.'''
  210. if self.download_manager is not None:
  211. self._status_list.load_urls(self._get_urls(), self.download_manager.add_url)
  212. def _on_download(self, event):
  213. ''' Event handler method for self._download_btn. '''
  214. if self.download_manager is None:
  215. self._start_download()
  216. else:
  217. self.download_manager.stop_downloads()
  218. def _on_update(self, event):
  219. ''' Event handler method for self._update_btn. '''
  220. self._update_youtubedl()
  221. def _on_options(self, event):
  222. ''' Event handler method for self._options_btn. '''
  223. self._options_frame.load_all_options()
  224. self._options_frame.Show()
  225. def _on_close(self, event):
  226. ''' Event handler method (wx.EVT_CLOSE). '''
  227. if self.download_manager is not None:
  228. self.download_manager.stop_downloads()
  229. self.download_manager.join()
  230. if self.update_thread is not None:
  231. self.update_thread.join()
  232. self.opt_manager.save_to_file()
  233. self.Destroy()
  234. class ListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin):
  235. '''
  236. Custom ListCtrl class.
  237. Accessible Methods
  238. write()
  239. Params: Python dictionary that contains data to write
  240. Return: None
  241. has_url()
  242. Params: Url to search
  243. Return: True if url in ListCtrl, else False
  244. add_url()
  245. Params: Url to add
  246. Return: None
  247. clear()
  248. Params: None
  249. Return: None
  250. is_empty()
  251. Params: None
  252. Return: True if ListCtrl is empty, else False
  253. get_items()
  254. Params: None
  255. Return: Python list that contains all ListCtrl items
  256. get_last_item()
  257. Params: None
  258. Return: Last item inserted in ListCtrl
  259. '''
  260. def __init__(self, columns, *args, **kwargs):
  261. wx.ListCtrl.__init__(self, *args, **kwargs)
  262. ListCtrlAutoWidthMixin.__init__(self)
  263. self.columns = columns
  264. self._list_index = 0
  265. self._url_list = set()
  266. self._set_columns()
  267. def write(self, data):
  268. ''' Write data on ListCtrl row-column. '''
  269. for column in self.columns:
  270. column_key = column[0]
  271. self._write_data(data[column_key], data['index'], column[1])
  272. def load_urls(self, url_list, func=None):
  273. for url in url_list:
  274. url = url.replace(' ', '')
  275. if url and not self.has_url(url):
  276. self.add_url(url)
  277. if func is not None:
  278. # Custom hack to add url into download_manager
  279. # i am gonna change this
  280. item = self._get_item(self._list_index - 1)
  281. func(item)
  282. def has_url(self, url):
  283. ''' Return True if url in ListCtrl, else return False. '''
  284. return url in self._url_list
  285. def add_url(self, url):
  286. ''' Add url on ListCtrl. '''
  287. self.InsertStringItem(self._list_index, url)
  288. self._url_list.add(url)
  289. self._list_index += 1
  290. def clear(self):
  291. ''' Clear ListCtrl & reset self._list_index. '''
  292. self.DeleteAllItems()
  293. self._list_index = 0
  294. self._url_list = set()
  295. def is_empty(self):
  296. ''' Return True if list is empty. '''
  297. return self._list_index == 0
  298. def get_items(self):
  299. ''' Return list of items in ListCtrl. '''
  300. items = []
  301. for row in xrange(self._list_index):
  302. item = self._get_item(row)
  303. items.append(item)
  304. return items
  305. def _write_data(self, data, row, column):
  306. ''' Write data on row, column. '''
  307. if isinstance(data, basestring):
  308. self.SetStringItem(row, column, data)
  309. def _get_item(self, index):
  310. ''' Return single item base on index. '''
  311. item = self.GetItem(itemId=index, col=0)
  312. data = dict(url=item.GetText(), index=index)
  313. return data
  314. def _set_columns(self):
  315. for column in self.columns:
  316. self.InsertColumn(column[1], column[2], width=column[3])
  317. if column[4]:
  318. self.setResizeColumn(column[1])