From 637792811948d71e8640e619991ac2d775ee2d03 Mon Sep 17 00:00:00 2001 From: MrS0m30n3 Date: Wed, 14 Oct 2015 16:34:23 +0300 Subject: [PATCH] Add back & forward communication between Worker and Main thread --- youtube_dl_gui/downloadmanager.py | 84 ++++++++++++++++++++++++++++--- youtube_dl_gui/mainframe.py | 44 ++++++++++++++-- 2 files changed, 119 insertions(+), 9 deletions(-) diff --git a/youtube_dl_gui/downloadmanager.py b/youtube_dl_gui/downloadmanager.py index 008d9d8..17edc46 100644 --- a/youtube_dl_gui/downloadmanager.py +++ b/youtube_dl_gui/downloadmanager.py @@ -166,6 +166,21 @@ class DownloadManager(Thread): """ self.urls_list.append(url) + def send_to_worker(self, data): + """Send data to the Workers. + + Args: + data (dict): Python dictionary that holds the 'index' + which is used to identify the Worker thread and the data which + can be any of the Worker's class valid data. For a list of valid + data keys see __init__() under the Worker class. + + """ + if 'index' in data: + for worker in self._workers: + if worker.has_index(data['index']): + worker.update_data(data) + def _talk_to_gui(self, data): """Send data back to the GUI using wxCallAfter and wxPublisher. @@ -174,7 +189,7 @@ class DownloadManager(Thread): download process. Note: - DownloadManager supports 3 signals. + DownloadManager supports 4 signals. 1) closing: The download process is closing. 2) closed: The download process has closed. 3) finished: The download process was completed normally. @@ -243,9 +258,12 @@ class Worker(Thread): self._successful = 0 self._running = True + self._wait_for_reply = False + self._data = { 'playlist_index': None, 'playlist_size': None, + 'new_filename': None, 'extension': None, 'filesize': None, 'filename': None, @@ -270,6 +288,13 @@ class Worker(Thread): ret_code == YoutubeDLDownloader.ALREADY): self._successful += 1 + # Ask GUI for name updates + self._talk_to_gui('receive', {'source': 'filename', 'dest': 'new_filename'}) + + # Wait until you get a reply + while self._wait_for_reply: + time.sleep(self.WAIT_TIME) + self._reset() time.sleep(self.WAIT_TIME) @@ -303,6 +328,19 @@ class Worker(Thread): """Return True if the worker has no job else False. """ return self._data['url'] is None + def has_index(self, index): + """Return True if index is equal to self._data['index'] else False. """ + return self._data['index'] == index + + def update_data(self, data): + """Update self._data from the given data. """ + if self._wait_for_reply: + # Update data only if a receive request has been issued + for key in data: + self._data[key] = data[key] + + self._wait_for_reply = False + @property def successful(self): """Return the number of successful downloads for current worker. """ @@ -359,10 +397,44 @@ class Worker(Thread): ) if len(temp_dict): - temp_dict['index'] = self._data['index'] - self._talk_to_gui(temp_dict) + self._talk_to_gui('send', temp_dict) - def _talk_to_gui(self, data): - """Send data back to the GUI. """ - CallAfter(Publisher.sendMessage, WORKER_PUB_TOPIC, data) + def _talk_to_gui(self, signal, data): + """Communicate with the GUI using wxCallAfter and wxPublisher. + + Send/Ask data to/from the GUI. Note that if the signal is 'receive' + then the Worker will wait until it receives a reply from the GUI. + + Args: + signal (string): Unique string that informs the GUI about the + communication procedure. + + data (dict): Python dictionary which holds the data to be sent + back to the GUI. If the signal is 'send' then the dictionary + contains the updates for the GUI (e.g. percentage, eta). If + the signal is 'receive' then the dictionary contains exactly + three keys. The 'index' (row) from which we want to retrieve + the data, the 'source' which identifies a column in the + wxListCtrl widget and the 'dest' which tells the wxListCtrl + under which key to store the retrieved data. + + Note: + Worker class supports 2 signals. + 1) send: The Worker sends data back to the GUI + (e.g. Send status updates). + 2) receive: The Worker asks data from the GUI + (e.g. Receive the name of a file). + + Structure: + ('send', {'index': , data_to_send*}) + + ('receive', {'index': , 'source': 'source_key', 'dest': 'destination_key'}) + + """ + data['index'] = self._data['index'] + + if signal == 'receive': + self._wait_for_reply = True + + CallAfter(Publisher.sendMessage, WORKER_PUB_TOPIC, (signal, data)) diff --git a/youtube_dl_gui/mainframe.py b/youtube_dl_gui/mainframe.py index 11f8181..46324dc 100644 --- a/youtube_dl_gui/mainframe.py +++ b/youtube_dl_gui/mainframe.py @@ -173,7 +173,7 @@ class MainFrame(wx.Frame): # Set threads wxCallAfter handlers using subscribe self._set_publisher(self._update_handler, UPDATE_PUB_TOPIC) - self._set_publisher(self._status_list_handler, WORKER_PUB_TOPIC) + self._set_publisher(self._download_worker_handler, WORKER_PUB_TOPIC) self._set_publisher(self._download_manager_handler, MANAGER_PUB_TOPIC) def _set_publisher(self, handler, topic): @@ -332,7 +332,7 @@ class MainFrame(wx.Frame): if not success: self._status_bar_write(self.OPEN_DIR_ERR.format(dir=self.opt_manager.options['save_path'])) - def _status_list_handler(self, msg): + def _download_worker_handler(self, msg): """downloadmanager.Worker thread handler. Handles messages from the Worker thread. @@ -341,7 +341,13 @@ class MainFrame(wx.Frame): See downloadmanager.Worker _talk_to_gui() method. """ - self._status_list.write(msg.data) + signal, data = msg.data + + if signal == 'send': + self._status_list.write(data) + + if signal == 'receive': + self.download_manager.send_to_worker(self._status_list.get(data)) def _download_manager_handler(self, msg): """downloadmanager.DownloadManager thread handler. @@ -529,6 +535,38 @@ class ListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin): self._url_list = set() self._set_columns() + def get(self, data): + """Return data from ListCtrl. + + Args: + data (dict): Dictionary which contains three keys. The 'index' + that identifies the current row, the 'source' which identifies + a column in the wxListCtrl and the 'dest' which tells + wxListCtrl under which key to store the retrieved value. For + more informations see the _talk_to_gui() method under + downloadmanager.py Worker class. + + Returns: + A dictionary which holds the 'index' (row) and the data from the + given row-column combination. + + Example: + args: data = {'index': 0, 'source': 'filename', 'dest': 'new_filename'} + + The wxListCtrl will store the value from the 'filename' column + into a new dictionary with a key value 'new_filename'. + + return: {'index': 0, 'new_filename': 'The filename retrieved'} + + """ + value = None + + # If the source column exists + if data['source'] in self.columns: + value = self.GetItemText(data['index'], self.columns[data['source']][0]) + + return {'index': data['index'], data['dest']: value} + def write(self, data): """Write data on ListCtrl row-column.