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.

318 lines
9.7 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
  1. #!/usr/bin/env python2
  2. """Youtubedlg module for managing the download process.
  3. This module is responsible for managing the download process
  4. and update the GUI interface.
  5. Note:
  6. It's not the actual module that downloads the urls
  7. thats the job of the 'downloaders' module.
  8. """
  9. import time
  10. import os.path
  11. from threading import Thread
  12. from wx import CallAfter
  13. from wx.lib.pubsub import setuparg1
  14. from wx.lib.pubsub import pub as Publisher
  15. from .parsers import OptionsParser
  16. from .updthread import UpdateThread
  17. from .downloaders import YoutubeDLDownloader
  18. from .utils import YOUTUBEDL_BIN
  19. class DownloadManager(Thread):
  20. """Manages the download process.
  21. Attributes:
  22. PUBLISHER_TOPIC (string): Subscription topic for the wx Publisher.
  23. WORKERS_NUMBER (int): Size of custom thread pool.
  24. WAIT_TIME (float): Time in seconds to sleep.
  25. Args:
  26. urls_list (list): Python list that contains multiple dictionaries
  27. with the url to download and the corresponding column(index) in
  28. which the worker should send the download process information.
  29. opt_manager (optionsmanager.OptionsManager): Object responsible for
  30. managing the youtubedlg options.
  31. log_manager (logmanager.LogManager): Object responsible for writing
  32. erros to the log.
  33. """
  34. PUBLISHER_TOPIC = 'dlmanager'
  35. WORKERS_NUMBER = 3
  36. WAIT_TIME = 0.1
  37. def __init__(self, urls_list, opt_manager, log_manager=None):
  38. super(DownloadManager, self).__init__()
  39. self.opt_manager = opt_manager
  40. self.log_manager = log_manager
  41. self.urls_list = urls_list
  42. self._time_it_took = 0
  43. self._successful = 0
  44. self._running = True
  45. self._workers = self._init_workers()
  46. self.start()
  47. @property
  48. def successful(self):
  49. """Return number of successful downloads. """
  50. return self._successful
  51. @property
  52. def time_it_took(self):
  53. """Return time in seconds it took for the
  54. download process to finish.
  55. """
  56. return self._time_it_took
  57. def increase_succ(self):
  58. """Increase number of successful downloads. """
  59. self._successful += 1
  60. def run(self):
  61. self._check_youtubedl()
  62. self._time_it_took = time.time()
  63. while self._running:
  64. for worker in self._workers:
  65. if worker.available() and self.urls_list:
  66. worker.download(self.urls_list.pop(0))
  67. time.sleep(self.WAIT_TIME)
  68. if not self.urls_list and self._jobs_done():
  69. break
  70. # Clean up
  71. for worker in self._workers:
  72. worker.close()
  73. worker.join()
  74. self._time_it_took = time.time() - self._time_it_took
  75. if not self._running:
  76. self._talk_to_gui('closed')
  77. else:
  78. self._talk_to_gui('finished')
  79. def active(self):
  80. """Return number of active items.
  81. active_items = workers that work + items waiting in the url_list.
  82. """
  83. counter = 0
  84. for worker in self._workers:
  85. if not worker.available():
  86. counter += 1
  87. counter += len(self.urls_list)
  88. return counter
  89. def stop_downloads(self):
  90. """Stop the download process. Also send 'closing'
  91. signal back to the GUI.
  92. Note:
  93. It does NOT kill the workers thats the job of the
  94. clean up task in the run() method.
  95. """
  96. self._talk_to_gui('closing')
  97. self._running = False
  98. for worker in self._workers:
  99. worker.stop_download()
  100. def add_url(self, url):
  101. """Add given url to the urls_list.
  102. Args:
  103. url (dictionary): Python dictionary that contains two keys,
  104. the url and the index of the corresponding column
  105. to send the download information back.
  106. """
  107. self.urls_list.append(url)
  108. def _talk_to_gui(self, data):
  109. """Send data back to the GUI using wx CallAfter and wx Publisher.
  110. Args:
  111. data (string): Unique signal string that informs the GUI for the
  112. download process.
  113. Note:
  114. DownloadManager supports 3 signals for the moment.
  115. 1) closing: The download process is closing.
  116. 2) closed: The download process has closed.
  117. 3) finished: The download process terminated normally.
  118. """
  119. CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data)
  120. def _check_youtubedl(self):
  121. """Check if youtube-dl binary exists. If not try to download it. """
  122. if not os.path.exists(self._youtubedl_path()):
  123. UpdateThread(self.opt_manager.options['youtubedl_path'], True).join()
  124. def _jobs_done(self):
  125. """Return True if the workers have finished their jobs.
  126. Else return False.
  127. """
  128. for worker in self._workers:
  129. if not worker.available():
  130. return False
  131. return True
  132. def _youtubedl_path(self):
  133. """Return the path of the youtube-dl binary. """
  134. path = self.opt_manager.options['youtubedl_path']
  135. path = os.path.join(path, YOUTUBEDL_BIN)
  136. return path
  137. def _init_workers(self):
  138. """Initialise the custom thread pool.
  139. Returns:
  140. Python list that contains the workers.
  141. """
  142. youtubedl = self._youtubedl_path()
  143. return [Worker(self.opt_manager, youtubedl, self.increase_succ, self.log_manager) for i in xrange(self.WORKERS_NUMBER)]
  144. class Worker(Thread):
  145. """Simple worker that downloads the given url using a downloader.
  146. Attributes:
  147. PUBLISHER_TOPIC (string): Subscription topic for the wx Publisher.
  148. WAIT_TIME (float): Time in seconds to sleep.
  149. Args:
  150. opt_manager (optionsmanager.OptionsManager): Check DownloadManager
  151. description.
  152. youtubedl (string): Absolute path to youtube-dl binary.
  153. increase_succ (DownloadManager.increase_succ() method): Callback to
  154. increase the number of successful downloads.
  155. log_manager (logmanager.LogManager): Check DownloadManager
  156. description.
  157. """
  158. PUBLISHER_TOPIC = 'dlworker'
  159. WAIT_TIME = 0.1
  160. def __init__(self, opt_manager, youtubedl, increase_succ, log_manager=None):
  161. super(Worker, self).__init__()
  162. self.increase_succ = increase_succ
  163. self.opt_manager = opt_manager
  164. self._downloader = YoutubeDLDownloader(youtubedl, self._data_hook, log_manager)
  165. self._options_parser = OptionsParser()
  166. self._running = True
  167. self._url = None
  168. self._index = -1
  169. self.start()
  170. def run(self):
  171. while self._running:
  172. if self._url is not None:
  173. options = self._options_parser.parse(self.opt_manager.options)
  174. ret_code = self._downloader.download(self._url, options)
  175. if (ret_code == YoutubeDLDownloader.OK or
  176. ret_code == YoutubeDLDownloader.ALREADY):
  177. self.increase_succ()
  178. # Reset
  179. self._url = None
  180. time.sleep(self.WAIT_TIME)
  181. def download(self, item):
  182. """Download given item.
  183. Args:
  184. item (dictionary): Python dictionary that contains two keys,
  185. the url and the index of the corresponding column
  186. to send the download information back.
  187. """
  188. self._url = item['url']
  189. self._index = item['index']
  190. def stop_download(self):
  191. """Stop the download process of the worker. """
  192. self._downloader.stop()
  193. def close(self):
  194. """Kill the worker after stopping the download process. """
  195. self._running = False
  196. self._downloader.stop()
  197. def available(self):
  198. """Return True if the worker has no job. Else False. """
  199. return self._url is None
  200. def _data_hook(self, data):
  201. """Callback method.
  202. This method takes the data from the downloader, merges the
  203. playlist_info with the current status (if any) and sends the
  204. data back to the GUI.
  205. Args:
  206. data (dictionary): Python dictionary that contains information
  207. about the download process. (See YoutubeDLDownloader class).
  208. """
  209. if data['status'] is not None and data['playlist_index'] is not None:
  210. playlist_info = ' '
  211. playlist_info += data['playlist_index']
  212. playlist_info += '/'
  213. playlist_info += data['playlist_size']
  214. data['status'] += playlist_info
  215. self._talk_to_gui(data)
  216. def _talk_to_gui(self, data):
  217. """Send the data back to the GUI after inserting the index. """
  218. data['index'] = self._index
  219. CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data)
  220. if __name__ == '__main__':
  221. """Direct call of module for testing.
  222. Raises:
  223. ValueError: Attempted relative import in non-package
  224. Note:
  225. Before you run the tests change relative imports else an exceptions
  226. will be raised. You need to change relative imports on all the modules
  227. you are gonna use.
  228. """
  229. print "No tests available"