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.

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