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.

203 lines
6.2 KiB

10 years ago
10 years ago
10 years ago
  1. #! /usr/bin/env python
  2. from time import sleep
  3. from threading import Thread
  4. from wx import CallAfter
  5. from wx.lib.pubsub import setuparg1
  6. from wx.lib.pubsub import pub as Publisher
  7. from .YDLOptionsParser import OptionsParser
  8. from .DownloadObject import DownloadObject
  9. from .Utils import (
  10. get_youtubedl_filename,
  11. remove_file,
  12. file_exist,
  13. fix_path
  14. )
  15. class DownloadManager(Thread):
  16. PUBLISHER_TOPIC = 'download_manager'
  17. MAX_DOWNLOAD_THREADS = 3
  18. def __init__(self, download_list, opt_manager, log_manager=None):
  19. super(DownloadManager, self).__init__()
  20. self.download_list = download_list
  21. self.opt_manager = opt_manager
  22. self.log_manager = log_manager
  23. self._threads_lst = []
  24. self._stopped = False
  25. self._running = True
  26. self._kill = False
  27. self.start()
  28. def run(self):
  29. while self._running:
  30. # If download list is not empty
  31. if self.download_list:
  32. dl_item = self.download_list[0]
  33. index = dl_item['index']
  34. url = dl_item['url']
  35. self._check_download_queue()
  36. if self._running:
  37. self._download(url, index)
  38. self.download_list.pop(0)
  39. else:
  40. if not self.downloading():
  41. self._running = False
  42. else:
  43. sleep(0.1)
  44. self._terminate_all()
  45. if not self._kill:
  46. if self._stopped:
  47. self._callafter('closed')
  48. else:
  49. self._callafter('finished')
  50. def downloading(self):
  51. ''' Return True if at least one download thread is alive '''
  52. for thread in self._threads_lst:
  53. if thread.is_alive():
  54. return True
  55. return False
  56. def add_download_item(self, item):
  57. ''' Add download item on download list '''
  58. self.download_list.append(item)
  59. def get_items_counter(self):
  60. ''' Return download videos counter '''
  61. counter = 0
  62. for thread in self._threads_lst:
  63. if thread.is_alive():
  64. counter += 1
  65. return len(self.download_list) + counter
  66. def close(self, kill=False):
  67. self._callafter('closing')
  68. self._running = False
  69. self._stopped = True
  70. self._kill = kill
  71. def _download(self, url, index):
  72. ''' Download given url '''
  73. dl_thread = DownloadThread(url, index, self.opt_manager, self.log_manager)
  74. self._threads_lst.append(dl_thread)
  75. def _terminate_all(self):
  76. ''' Close down all download threads '''
  77. for thread in self._threads_lst:
  78. if thread.is_alive():
  79. thread.close()
  80. thread.join()
  81. def _check_download_queue(self):
  82. while len(self._threads_lst) >= self.MAX_DOWNLOAD_THREADS:
  83. sleep(1)
  84. for thread in self._threads_lst:
  85. if not self._running:
  86. return
  87. if not thread.is_alive():
  88. self._threads_lst.remove(thread)
  89. def _callafter(self, data):
  90. CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data)
  91. class DownloadThread(Thread):
  92. '''
  93. Params
  94. url: URL to download.
  95. index: ListCtrl index for the current DownloadThread.
  96. opt_manager: OptionsHandler.OptionsHandler object.
  97. log_manager: Any logger which implements log().
  98. Accessible Methods
  99. close()
  100. Params: None
  101. '''
  102. PUBLISHER_TOPIC = 'download_thread'
  103. def __init__(self, url, index, opt_manager, log_manager=None):
  104. super(DownloadThread, self).__init__()
  105. self.log_manager = log_manager
  106. self.opt_manager = opt_manager
  107. self.index = index
  108. self.url = url
  109. self._dl_object = None
  110. self.start()
  111. def run(self):
  112. youtubedl_path = self._get_youtubedl_path()
  113. options = OptionsParser(self.opt_manager).parse()
  114. self._dl_object = DownloadObject(youtubedl_path, self._data_hook, self.log_manager)
  115. return_code = self._dl_object.download(self.url, options)
  116. if self.opt_manager.options['clear_dash_files']:
  117. self._clear_dash()
  118. if return_code == DownloadObject.OK:
  119. self._callafter({'status': 'Finished'})
  120. elif return_code == DownloadObject.ERROR:
  121. self._callafter({'status': 'Error', 'speed': '', 'eta': ''})
  122. elif return_code == DownloadObject.STOPPED:
  123. self._callafter({'status': 'Stopped', 'speed': '', 'eta': ''})
  124. elif return_code == DownloadObject.ALREADY:
  125. self._callafter({'status': 'Already-Downloaded'})
  126. def close(self):
  127. if self._dl_object is not None:
  128. self._callafter({'status': 'Stopping'})
  129. self._dl_object.stop()
  130. def _clear_dash(self):
  131. ''' Clear DASH files after ffmpeg mux '''
  132. for fl in self._dl_object.files_list:
  133. if file_exist(fl):
  134. remove_file(fl)
  135. def _data_hook(self, data):
  136. ''' Extract process status and call CallAfter '''
  137. data['status'] = self._get_status(data)
  138. self._callafter(data)
  139. def _callafter(self, data):
  140. ''' Add self.index on data and send data back to caller '''
  141. data['index'] = self.index
  142. CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data)
  143. def _get_status(self, data):
  144. ''' Return download process status from data['status'] '''
  145. if data['playlist_index'] is not None:
  146. playlist_info = '%s/%s' % (data['playlist_index'], data['playlist_size'])
  147. else:
  148. playlist_info = ''
  149. if data['status'] == 'pre_process':
  150. msg = 'Pre-Processing %s' % playlist_info
  151. elif data['status'] == 'download':
  152. msg = 'Downloading %s' % playlist_info
  153. elif data['status'] == 'post_process':
  154. msg = 'Post-Processing %s' % playlist_info
  155. else:
  156. msg = ''
  157. return msg
  158. def _get_youtubedl_path(self):
  159. ''' Retrieve youtube-dl path '''
  160. path = self.opt_manager.options['youtubedl_path']
  161. path = fix_path(path) + get_youtubedl_filename()
  162. return path