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
212 lines
5.5 KiB
#! /usr/bin/env python
|
|
|
|
import subprocess
|
|
from time import sleep
|
|
from threading import Thread
|
|
|
|
from wx import CallAfter
|
|
from wx.lib.pubsub import setuparg1
|
|
from wx.lib.pubsub import pub as Publisher
|
|
|
|
from .Utils import (
|
|
remove_spaces,
|
|
string_to_array,
|
|
get_encoding,
|
|
encode_list,
|
|
remove_file,
|
|
get_os_type
|
|
)
|
|
|
|
MAX_DOWNLOAD_THREADS = 3
|
|
PUBLISHER_TOPIC = 'download'
|
|
|
|
class DownloadManager(Thread):
|
|
|
|
def __init__(self, options, downloadlist, clear_dash_files, logmanager=None):
|
|
super(DownloadManager, self).__init__()
|
|
self.clear_dash_files = clear_dash_files
|
|
self.downloadlist = downloadlist
|
|
self.logmanager = logmanager
|
|
self.options = options
|
|
self.running = True
|
|
self.procList = []
|
|
self.procNo = 0
|
|
self.start()
|
|
|
|
def run(self):
|
|
while self.running:
|
|
if self.downloadlist:
|
|
# Extract url, index from data
|
|
url, index = self.extract_data()
|
|
# Wait for your turn if there are not more positions in 'queue'
|
|
while self.procNo >= MAX_DOWNLOAD_THREADS:
|
|
proc = self.check_queue(0.5)
|
|
if proc != None:
|
|
self.procList.remove(proc)
|
|
self.procNo -= 1
|
|
# If we still running create new ProcessWrapper thread
|
|
if self.running:
|
|
self.procList.append(
|
|
ProcessWrapper(
|
|
self.options,
|
|
url,
|
|
index,
|
|
self.clear_dash_files,
|
|
self.logmanager
|
|
)
|
|
)
|
|
self.procNo += 1
|
|
else:
|
|
# Return True if at least one process is alive else return False
|
|
if not self.downloading():
|
|
self.running = False
|
|
else:
|
|
sleep(1)
|
|
|
|
# If we reach here close down all child threads
|
|
self.terminate_all()
|
|
CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['finish', -1])
|
|
|
|
def add_download_item(self, downloadItem):
|
|
self.downloadlist.append(downloadItem)
|
|
|
|
def extract_data(self):
|
|
data = self.downloadlist.pop(0)
|
|
url = data['url']
|
|
index = data['index']
|
|
return url, index
|
|
|
|
def terminate_all(self):
|
|
for proc in self.procList:
|
|
if proc.isAlive():
|
|
proc.close()
|
|
proc.join()
|
|
|
|
def downloading(self):
|
|
for proc in self.procList:
|
|
if proc.isAlive():
|
|
return True
|
|
return False
|
|
|
|
def check_queue(self, t=1):
|
|
for proc in self.procList:
|
|
if not self.running: break
|
|
if not proc.isAlive():
|
|
return proc
|
|
sleep(t)
|
|
return None
|
|
|
|
def close(self):
|
|
self.procNo = 0
|
|
self.running = False
|
|
CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['close', -1])
|
|
|
|
class ProcessWrapper(Thread):
|
|
|
|
def __init__(self, options, url, index, clear_dash_files, log=None):
|
|
super(ProcessWrapper, self).__init__()
|
|
self.clear_dash_files = clear_dash_files
|
|
self.options = options
|
|
self.index = index
|
|
self.log = log
|
|
self.url = url
|
|
self.filenames = []
|
|
self.proc = None
|
|
self.stopped = False
|
|
self.err = False
|
|
self.start()
|
|
|
|
def run(self):
|
|
self.proc = subprocess.Popen(self.get_cmd(),
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
startupinfo=self.set_process_info())
|
|
# while subprocess is alive and NOT the current thread
|
|
while self.proc_is_alive():
|
|
# read output
|
|
output = self.read()
|
|
if output != '':
|
|
if self.err:
|
|
self.write_to_log(output)
|
|
CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['error', self.index])
|
|
else:
|
|
# process output
|
|
data = self.proc_output(output)
|
|
CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, data)
|
|
if not self.err and not self.stopped:
|
|
if self.clear_dash_files:
|
|
self.cleardash()
|
|
CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['finish', self.index])
|
|
|
|
def write_to_log(self, data):
|
|
if self.log != None:
|
|
self.log.write(data)
|
|
|
|
def extract_filename(self, data):
|
|
data_list = data.split(':', 1)
|
|
if 'Destination' in data_list[0].split():
|
|
self.filenames.append(data_list[1].lstrip())
|
|
|
|
def cleardash(self):
|
|
if self.filenames:
|
|
CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['remove', self.index])
|
|
for f in self.filenames:
|
|
remove_file(f)
|
|
|
|
def close(self):
|
|
self.proc.kill()
|
|
self.stopped = True
|
|
CallAfter(Publisher.sendMessage, PUBLISHER_TOPIC, ['close', self.index])
|
|
|
|
def proc_is_alive(self):
|
|
return self.proc.poll() == None
|
|
|
|
def read(self):
|
|
output = self.proc.stdout.readline()
|
|
if output == '':
|
|
output = self.proc.stderr.readline()
|
|
if output != '':
|
|
self.err = True
|
|
return output.rstrip()
|
|
|
|
def proc_output(self, output):
|
|
if self.clear_dash_files: self.extract_filename(output)
|
|
data = remove_spaces(string_to_array(output))
|
|
data.append(self.index)
|
|
data = self.filter_data(data)
|
|
return data
|
|
|
|
def filter_data(self, data):
|
|
''' Filters data for output exceptions '''
|
|
filter_list = ['Destination:', '100%', 'Resuming']
|
|
if len(data) > 3:
|
|
if data[0] == '[download]':
|
|
if data[1] in filter_list or len(data[1]) > 11:
|
|
return ['ignore', self.index]
|
|
if data[1] == 'Downloading':
|
|
if data[2] == 'video':
|
|
return ['playlist', data[3], data[5], self.index]
|
|
else:
|
|
return ['ignore', self.index]
|
|
else:
|
|
if data[1] == 'UnicodeWarning:':
|
|
self.err = False
|
|
return ['ignore', self.index]
|
|
return data
|
|
|
|
def get_cmd(self):
|
|
enc = get_encoding()
|
|
if enc != None:
|
|
cmd = encode_list(self.options + [self.url], enc)
|
|
else:
|
|
cmd = self.options + [self.url]
|
|
return cmd
|
|
|
|
def set_process_info(self):
|
|
if get_os_type() == 'nt':
|
|
info = subprocess.STARTUPINFO()
|
|
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
return info
|
|
else:
|
|
return None
|
|
|