|
|
@ -1,6 +1,15 @@ |
|
|
|
#!/usr/bin/env python2 |
|
|
|
|
|
|
|
''' Youtube-dlG module to download videos & handle each download. ''' |
|
|
|
"""Youtubedlg module for managing the download process. |
|
|
|
|
|
|
|
This module is responsible for managing the download process |
|
|
|
and update the GUI interface. |
|
|
|
|
|
|
|
Note: |
|
|
|
It's not the actual module that downloads the urls |
|
|
|
thats the job of the 'downloaders' module. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
import time |
|
|
|
import os.path |
|
|
@ -19,7 +28,25 @@ from .utils import YOUTUBEDL_BIN |
|
|
|
|
|
|
|
class DownloadManager(Thread): |
|
|
|
|
|
|
|
""" DownloadManager """ |
|
|
|
"""Manages the download process. |
|
|
|
|
|
|
|
Attributes: |
|
|
|
PUBLISHER_TOPIC (string): Subscription topic for the wx Publisher. |
|
|
|
WORKERS_NUMBER (int): Size of custom thread pool. |
|
|
|
WAIT_TIME (float): Time in seconds to sleep. |
|
|
|
|
|
|
|
Args: |
|
|
|
urls_list (list): Python list that contains multiple dictionaries |
|
|
|
with the url to download and the corresponding column(index) in |
|
|
|
which the worker should send the download process information. |
|
|
|
|
|
|
|
opt_manager (optionsmanager.OptionsManager): Object responsible for |
|
|
|
managing the youtubedlg options. |
|
|
|
|
|
|
|
log_manager (logmanager.LogManager): Object responsible for writing |
|
|
|
erros to the log. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
PUBLISHER_TOPIC = 'dlmanager' |
|
|
|
WORKERS_NUMBER = 3 |
|
|
@ -40,13 +67,19 @@ class DownloadManager(Thread): |
|
|
|
|
|
|
|
@property |
|
|
|
def successful(self): |
|
|
|
"""Return number of successful downloads. """ |
|
|
|
return self._successful |
|
|
|
|
|
|
|
@property |
|
|
|
def time_it_took(self): |
|
|
|
"""Return time in seconds it took for the |
|
|
|
download process to finish. |
|
|
|
|
|
|
|
""" |
|
|
|
return self._time_it_took |
|
|
|
|
|
|
|
def increase_succ(self): |
|
|
|
"""Increase number of successful downloads. """ |
|
|
|
self._successful += 1 |
|
|
|
|
|
|
|
def run(self): |
|
|
@ -76,6 +109,10 @@ class DownloadManager(Thread): |
|
|
|
self._talk_to_gui('finished') |
|
|
|
|
|
|
|
def active(self): |
|
|
|
"""Return number of active items. |
|
|
|
active_items = workers that work + items waiting in the url_list. |
|
|
|
|
|
|
|
""" |
|
|
|
counter = 0 |
|
|
|
for worker in self._workers: |
|
|
|
if not worker.available(): |
|
|
@ -86,22 +123,56 @@ class DownloadManager(Thread): |
|
|
|
return counter |
|
|
|
|
|
|
|
def stop_downloads(self): |
|
|
|
"""Stop the download process. Also send 'closing' |
|
|
|
signal back to the GUI. |
|
|
|
|
|
|
|
Note: |
|
|
|
It does NOT kill the workers thats the job of the |
|
|
|
clean up task in the run() method. |
|
|
|
|
|
|
|
""" |
|
|
|
self._talk_to_gui('closing') |
|
|
|
self._running = False |
|
|
|
for worker in self._workers: |
|
|
|
worker.stop_download() |
|
|
|
|
|
|
|
def add_url(self, url): |
|
|
|
"""Add given url to the urls_list. |
|
|
|
|
|
|
|
Args: |
|
|
|
url (dictionary): Python dictionary that contains two keys, |
|
|
|
the url and the index of the corresponding column |
|
|
|
to send the download information back. |
|
|
|
|
|
|
|
""" |
|
|
|
self.urls_list.append(url) |
|
|
|
|
|
|
|
def _talk_to_gui(self, data): |
|
|
|
"""Send data back to the GUI using wx CallAfter and wx Publisher. |
|
|
|
|
|
|
|
Args: |
|
|
|
data (string): Unique signal string that informs the GUI for the |
|
|
|
download process. |
|
|
|
|
|
|
|
Note: |
|
|
|
DownloadManager supports 3 signals for the moment. |
|
|
|
1) closing: The download process is closing. |
|
|
|
2) closed: The download process has closed. |
|
|
|
3) finished: The download process terminated normally. |
|
|
|
|
|
|
|
""" |
|
|
|
CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data) |
|
|
|
|
|
|
|
def _check_youtubedl(self): |
|
|
|
"""Check if youtube-dl binary exists. If not try to download it. """ |
|
|
|
if not os.path.exists(self._youtubedl_path()): |
|
|
|
UpdateThread(self.opt_manager.options['youtubedl_path'], True).join() |
|
|
|
|
|
|
|
def _jobs_done(self): |
|
|
|
"""Return True if the workers have finished their jobs. |
|
|
|
Else return False. |
|
|
|
|
|
|
|
""" |
|
|
|
for worker in self._workers: |
|
|
|
if not worker.available(): |
|
|
|
return False |
|
|
@ -109,17 +180,44 @@ class DownloadManager(Thread): |
|
|
|
return True |
|
|
|
|
|
|
|
def _youtubedl_path(self): |
|
|
|
"""Return the path of the youtube-dl binary. """ |
|
|
|
path = self.opt_manager.options['youtubedl_path'] |
|
|
|
path = os.path.join(path, YOUTUBEDL_BIN) |
|
|
|
return path |
|
|
|
|
|
|
|
def _init_workers(self): |
|
|
|
"""Initialise the custom thread pool. |
|
|
|
|
|
|
|
Returns: |
|
|
|
Python list that contains the workers. |
|
|
|
|
|
|
|
""" |
|
|
|
youtubedl = self._youtubedl_path() |
|
|
|
return [Worker(self.opt_manager, youtubedl, self.increase_succ, self.log_manager) for i in xrange(self.WORKERS_NUMBER)] |
|
|
|
|
|
|
|
|
|
|
|
class Worker(Thread): |
|
|
|
|
|
|
|
"""Simple worker that downloads the given url using a downloader. |
|
|
|
|
|
|
|
Attributes: |
|
|
|
PUBLISHER_TOPIC (string): Subscription topic for the wx Publisher. |
|
|
|
WAIT_TIME (float): Time in seconds to sleep. |
|
|
|
|
|
|
|
Args: |
|
|
|
opt_manager (optionsmanager.OptionsManager): Check DownloadManager |
|
|
|
description. |
|
|
|
|
|
|
|
youtubedl (string): Absolute path to youtube-dl binary. |
|
|
|
|
|
|
|
increase_succ (DownloadManager.increase_succ() method): Callback to |
|
|
|
increase the number of successful downloads. |
|
|
|
|
|
|
|
log_manager (logmanager.LogManager): Check DownloadManager |
|
|
|
description. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
PUBLISHER_TOPIC = 'dlworker' |
|
|
|
WAIT_TIME = 0.1 |
|
|
|
|
|
|
@ -139,7 +237,7 @@ class Worker(Thread): |
|
|
|
def run(self): |
|
|
|
while self._running: |
|
|
|
if self._url is not None: |
|
|
|
options = self._options_parser.parse(self.opt_manager.options) |
|
|
|
options = self._options_parser.parse(self.opt_manager.options) |
|
|
|
ret_code = self._downloader.download(self._url, options) |
|
|
|
|
|
|
|
if (ret_code == YoutubeDLDownloader.OK or |
|
|
@ -152,20 +250,42 @@ class Worker(Thread): |
|
|
|
time.sleep(self.WAIT_TIME) |
|
|
|
|
|
|
|
def download(self, item): |
|
|
|
"""Download given item. |
|
|
|
|
|
|
|
Args: |
|
|
|
item (dictionary): Python dictionary that contains two keys, |
|
|
|
the url and the index of the corresponding column |
|
|
|
to send the download information back. |
|
|
|
|
|
|
|
""" |
|
|
|
self._url = item['url'] |
|
|
|
self._index = item['index'] |
|
|
|
|
|
|
|
def stop_download(self): |
|
|
|
"""Stop the download process of the worker. """ |
|
|
|
self._downloader.stop() |
|
|
|
|
|
|
|
def close(self): |
|
|
|
"""Kill the worker after stopping the download process. """ |
|
|
|
self._running = False |
|
|
|
self._downloader.stop() |
|
|
|
|
|
|
|
def available(self): |
|
|
|
"""Return True if the worker has no job. Else False. """ |
|
|
|
return self._url is None |
|
|
|
|
|
|
|
def _data_hook(self, data): |
|
|
|
"""Callback method. |
|
|
|
|
|
|
|
This method takes the data from the downloader, merges the |
|
|
|
playlist_info with the current status (if any) and sends the |
|
|
|
data back to the GUI. |
|
|
|
|
|
|
|
Args: |
|
|
|
data (dictionary): Python dictionary that contains information |
|
|
|
about the download process. (See YoutubeDLDownloader class). |
|
|
|
|
|
|
|
""" |
|
|
|
if data['status'] is not None and data['playlist_index'] is not None: |
|
|
|
playlist_info = ' ' |
|
|
|
playlist_info += data['playlist_index'] |
|
|
@ -177,15 +297,22 @@ class Worker(Thread): |
|
|
|
self._talk_to_gui(data) |
|
|
|
|
|
|
|
def _talk_to_gui(self, data): |
|
|
|
"""Send the data back to the GUI after inserting the index. """ |
|
|
|
data['index'] = self._index |
|
|
|
CallAfter(Publisher.sendMessage, self.PUBLISHER_TOPIC, data) |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
''' Direct call of module for testing. Before |
|
|
|
you run the tests change relative imports or you will |
|
|
|
get [ValueError: Attempted relative import in non-package]. |
|
|
|
You need to change relative imports on all the modules |
|
|
|
you are gonna use.''' |
|
|
|
"""Direct call of module for testing. |
|
|
|
|
|
|
|
Raises: |
|
|
|
ValueError: Attempted relative import in non-package |
|
|
|
|
|
|
|
Note: |
|
|
|
Before you run the tests change relative imports else an exceptions |
|
|
|
will be raised. You need to change relative imports on all the modules |
|
|
|
you are gonna use. |
|
|
|
|
|
|
|
""" |
|
|
|
print "No tests available" |
|
|
|
|