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.

259 lines
7.1 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
  1. #!/usr/bin/env python2
  2. ''' Youtube-dlG module to download videos & handle each download. '''
  3. import time
  4. import os.path
  5. from threading import Thread
  6. from wx import CallAfter
  7. from wx.lib.pubsub import setuparg1
  8. from wx.lib.pubsub import pub as Publisher
  9. from .parsers import OptionsParser
  10. from .downloaders import YoutubeDLDownloader
  11. from .utils import YOUTUBEDL_BIN
  12. class DownloadManager(Thread):
  13. '''
  14. Manage youtube-dlG download list.
  15. Params
  16. threads_list: Python list that contains DownloadThread objects.
  17. update_thread: UpdateThread.py thread.
  18. Accessible Methods
  19. close()
  20. Params: None
  21. Return: None
  22. add_thread()
  23. Params: DownloadThread object
  24. Return: None
  25. alive_threads()
  26. Params: None
  27. Return: Number of alive threads.
  28. not_finished()
  29. Params: None
  30. Return: Number of threads not finished yet.
  31. Properties
  32. successful_downloads: Number of successful downloads.
  33. time: Time (seconds) it took for all downloads to complete.
  34. '''
  35. PUBLISHER_TOPIC = 'dlmanager'
  36. MAX_DOWNLOAD_THREADS = 3
  37. def __init__(self, threads_list, update_thread=None):
  38. super(DownloadManager, self).__init__()
  39. self.threads_list = threads_list
  40. self.update_thread = update_thread
  41. self._successful_downloads = 0
  42. self._running = True
  43. self._time = 0
  44. self.start()
  45. def run(self):
  46. if self.update_thread is not None:
  47. self.update_thread.join()
  48. self._time = time.time()
  49. # Main loop
  50. while self._running and not self._threads_finished():
  51. for thread in self.threads_list:
  52. if not self._running:
  53. break
  54. self._start_thread(thread)
  55. time.sleep(0.1)
  56. # Make sure no child thread is alive
  57. for thread in self.threads_list:
  58. if thread.is_alive():
  59. thread.join()
  60. # Collect thread status
  61. if thread.status == 0:
  62. self._successful_downloads += 1
  63. self._time = time.time() - self._time
  64. if not self._running:
  65. self._callafter('closed')
  66. else:
  67. self._callafter('finished')
  68. @property
  69. def time(self):
  70. ''' Return time it took for every download to finish. '''
  71. return self._time
  72. @property
  73. def successful_downloads(self):
  74. ''' Return number of successful downloads. '''
  75. return self._successful_downloads
  76. def close(self):
  77. ''' Close DownloadManager. '''
  78. self._callafter('closing')
  79. self._running = False
  80. for thread in self.threads_list:
  81. thread.close()
  82. def add_thread(self, thread):
  83. ''' Add new DownloadThread on self.threads_list. '''
  84. self.threads_list.append(thread)
  85. def alive_threads(self):
  86. ''' Return number of alive threads in self.threads_list. '''
  87. counter = 0
  88. for thread in self.threads_list:
  89. if thread.is_alive():
  90. counter += 1
  91. return counter
  92. def not_finished(self):
  93. ''' Return number of threads not finished. '''
  94. counter = 0
  95. for thread in self.threads_list:
  96. if thread.ident is None or thread.is_alive():
  97. counter += 1
  98. return counter
  99. def _start_thread(self, thread):
  100. ''' Start given thread if not download queue full. '''
  101. while self.alive_threads() >= self.MAX_DOWNLOAD_THREADS:
  102. time.sleep(1)
  103. if not self._running:
  104. break
  105. # If thread has not started
  106. if thread.ident is None and self._running:
  107. thread.start()
  108. def _threads_finished(self):
  109. ''' Return True if all threads in self.threads_list have finished. '''
  110. for thread in self.threads_list:
  111. # If thread has not started or thread is alive
  112. if thread.ident is None or thread.is_alive():
  113. return False
  114. return True
  115. def _callafter(self, data):
  116. ''' CallAfter wrapper. '''
  117. CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data)
  118. class DownloadThread(Thread):
  119. '''
  120. YoutubeDLDownloader Thread wrapper for youtube-dlg.
  121. Params
  122. url: Video url to download.
  123. index: ListCtrl corresponding row for current thread.
  124. opt_manager: OptionsManager.OptionsManager object.
  125. log_manager: Any logger which implements log().
  126. Accessible Methods
  127. close()
  128. Params: None
  129. Return: None
  130. Properties
  131. status: Thread status.
  132. '''
  133. PUBLISHER_TOPIC = 'dlthread'
  134. def __init__(self, url, index, opt_manager, log_manager=None):
  135. super(DownloadThread, self).__init__()
  136. self.url = url
  137. self.index = index
  138. self.opt_manager = opt_manager
  139. self.log_manager = log_manager
  140. self._downloader = None
  141. self._status = 0
  142. self._options_parser = OptionsParser()
  143. def run(self):
  144. self._downloader = YoutubeDLDownloader(
  145. self._get_youtubedl_path(),
  146. self._data_hook,
  147. self.log_manager
  148. )
  149. options = self._options_parser.parse(self.opt_manager.options)
  150. return_code = self._downloader.download(self.url, options)
  151. if return_code == YoutubeDLDownloader.OK:
  152. self._callafter({'status': 'Finished'})
  153. elif return_code == YoutubeDLDownloader.ERROR:
  154. self._callafter({'status': 'Error', 'speed': '', 'eta': ''})
  155. self._status = 1
  156. elif return_code == YoutubeDLDownloader.STOPPED:
  157. self._callafter({'status': 'Stopped', 'speed': '', 'eta': ''})
  158. self._status = 1
  159. elif return_code == YoutubeDLDownloader.ALREADY:
  160. self._callafter({'status': 'Already-Downloaded'})
  161. @property
  162. def status(self):
  163. ''' Return thread status. Use this property after
  164. thread has joined. (self._status != 0) indicates there was
  165. an error.
  166. '''
  167. return self._status
  168. def close(self):
  169. ''' Close download thread. '''
  170. if self._downloader is not None:
  171. self._downloader.stop()
  172. def _data_hook(self, data):
  173. ''' Merge playlist_info with data['status'] and
  174. pass data to self._callafter.
  175. '''
  176. playlist_info = ''
  177. if data['playlist_index'] is not None:
  178. playlist_info = data['playlist_index']
  179. playlist_info += '/'
  180. playlist_info += data['playlist_size']
  181. if data['status'] is not None:
  182. data['status'] = data['status'] + ' ' + playlist_info
  183. self._callafter(data)
  184. def _callafter(self, data):
  185. ''' Add self.index on data and send data back to caller. '''
  186. data['index'] = self.index
  187. CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data)
  188. def _get_youtubedl_path(self):
  189. ''' Retrieve youtube-dl path. '''
  190. path = self.opt_manager.options['youtubedl_path']
  191. path = os.path.join(path, YOUTUBEDL_BIN)
  192. return path