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.

204 lines
5.2 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
  1. #! /usr/bin/env python
  2. import subprocess
  3. from time import sleep
  4. from threading import Thread
  5. from wx import CallAfter
  6. from wx.lib.pubsub import setuparg1
  7. from wx.lib.pubsub import pub as Publisher
  8. from .Utils import (
  9. remove_spaces,
  10. string_to_array,
  11. get_encoding,
  12. encode_list,
  13. remove_file,
  14. get_os_type
  15. )
  16. MAX_DOWNLOAD_THREADS = 3
  17. PUBLISHER_TOPIC = 'download'
  18. class DownloadManager(Thread):
  19. def __init__(self, options, downloadlist, clear_dash_files):
  20. super(DownloadManager, self).__init__()
  21. self.clear_dash_files = clear_dash_files
  22. self.downloadlist = downloadlist
  23. self.options = options
  24. self.running = True
  25. self.procList = []
  26. self.procNo = 0
  27. self.start()
  28. def run(self):
  29. while self.running:
  30. if self.downloadlist:
  31. # Extract url, index from data
  32. url, index = self.extract_data()
  33. # Wait for your turn if there are not more positions in 'queue'
  34. while self.procNo >= MAX_DOWNLOAD_THREADS:
  35. proc = self.check_queue(0.5)
  36. if proc != None:
  37. self.procList.remove(proc)
  38. self.procNo -= 1
  39. # If we still running create new ProcessWrapper thread
  40. if self.running:
  41. self.procList.append(
  42. ProcessWrapper(
  43. self.options,
  44. url,
  45. index,
  46. self.clear_dash_files
  47. )
  48. )
  49. self.procNo += 1
  50. else:
  51. # Return True if at least one process is alive else return False
  52. if not self.downloading():
  53. self.running = False
  54. else:
  55. sleep(1)
  56. # If we reach here close down all child threads
  57. self.terminate_all()
  58. CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['finish', -1])
  59. def add_download_item(self, downloadItem):
  60. self.downloadlist.append(downloadItem)
  61. def extract_data(self):
  62. data = self.downloadlist.pop(0)
  63. url = data['url']
  64. index = data['index']
  65. return url, index
  66. def terminate_all(self):
  67. for proc in self.procList:
  68. if proc.isAlive():
  69. proc.close()
  70. proc.join()
  71. def downloading(self):
  72. for proc in self.procList:
  73. if proc.isAlive():
  74. return True
  75. return False
  76. def check_queue(self, t=1):
  77. for proc in self.procList:
  78. if not self.running: break
  79. if not proc.isAlive():
  80. return proc
  81. sleep(t)
  82. return None
  83. def close(self):
  84. self.procNo = 0
  85. self.running = False
  86. CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['close', -1])
  87. class ProcessWrapper(Thread):
  88. def __init__(self, options, url, index, clear_dash_files):
  89. super(ProcessWrapper, self).__init__()
  90. self.clear_dash_files = clear_dash_files
  91. self.options = options
  92. self.index = index
  93. self.url = url
  94. self.filenames = []
  95. self.proc = None
  96. self.stopped = False
  97. self.err = False
  98. self.start()
  99. def run(self):
  100. self.proc = subprocess.Popen(self.get_cmd(),
  101. stdout=subprocess.PIPE,
  102. stderr=subprocess.PIPE,
  103. startupinfo=self.set_process_info())
  104. # while subprocess is alive and NOT the current thread
  105. while self.proc_is_alive():
  106. # read output
  107. output = self.read()
  108. if output != '':
  109. # process output
  110. data = self.proc_output(output)
  111. if self.err:
  112. CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['error', self.index])
  113. else:
  114. CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, data)
  115. if not self.err and not self.stopped:
  116. if self.clear_dash_files:
  117. self.cleardash()
  118. CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['finish', self.index])
  119. def extract_filename(self, data):
  120. data_list = data.split(':')
  121. if 'Destination' in data_list[0].split():
  122. self.filenames.append(data_list[1].lstrip())
  123. def cleardash(self):
  124. if self.filenames:
  125. CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['remove', self.index])
  126. for f in self.filenames:
  127. remove_file(f)
  128. def close(self):
  129. self.proc.kill()
  130. self.stopped = True
  131. CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['close', self.index])
  132. def proc_is_alive(self):
  133. return self.proc.poll() == None
  134. def read(self):
  135. output = self.proc.stdout.readline()
  136. if output == '':
  137. output = self.proc.stderr.readline()
  138. if output != '':
  139. self.err = True
  140. return output.rstrip()
  141. def proc_output(self, output):
  142. if self.clear_dash_files: self.extract_filename(output)
  143. data = remove_spaces(string_to_array(output))
  144. data.append(self.index)
  145. data = self.filter_data(data)
  146. return data
  147. def filter_data(self, data):
  148. ''' Filters data for output exceptions '''
  149. filter_list = ['Destination:', '100%', 'Resuming']
  150. if len(data) > 3:
  151. if data[0] == '[download]':
  152. if data[1] in filter_list or len(data[1]) > 11:
  153. return ['ignore', self.index]
  154. if data[1] == 'Downloading':
  155. if data[2] == 'video':
  156. return ['playlist', data[3], data[5], self.index]
  157. else:
  158. return ['ignore', self.index]
  159. else:
  160. if data[1] == 'UnicodeWarning:':
  161. self.err = False
  162. return ['ignore', self.index]
  163. return data
  164. def get_cmd(self):
  165. enc = get_encoding()
  166. if enc != None:
  167. cmd = encode_list(self.options + [self.url], enc)
  168. else:
  169. cmd = self.options + [self.url]
  170. return cmd
  171. def set_process_info(self):
  172. if get_os_type() == 'nt':
  173. info = subprocess.STARTUPINFO()
  174. info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
  175. return info
  176. else:
  177. return None