Trying ro fix multi-threading issue

This commit is contained in:
Jérémi N ‘EndMove’ 2022-09-04 17:38:45 +02:00
parent 03a6d9b54f
commit 27b05b6184
Signed by: EndMove
GPG Key ID: 65C4A02E1F5371A4
7 changed files with 111 additions and 28 deletions

View File

@ -76,13 +76,22 @@ class HomeController:
# END View events # END View events
# START Controller methods # START Controller methods
def on_quit(self) -> None: def on_quit(self) -> bool:
""" """
[event function for controller] [event function for controller]
=> Call this event when a request to exit is thrown. => Call this event when a request to exit is thrown.
""" """
self.__download_task.stop() if self.__download_task and self.__download_task.is_alive():
print("Quit... homecontroller END") if self.__main_controller.show_question_dialog(
"Are you sure?",
"Do you really want to quit while the download is running?\nThis will stop the download."
):
self.__download_task.stop()
self.__download_task.join()
return False
else:
return True
print("Quit... homecontroller END") # REMOVE
# END Controller methods # END Controller methods
# START Task methods # START Task methods
@ -96,7 +105,7 @@ class HomeController:
* :url: -> Url for webpic. * :url: -> Url for webpic.
* :name: -> Working dir name for webpic. * :name: -> Working dir name for webpic.
""" """
print("start callback called") print("start callback called") # REMOVE
self.__view.clear_logs() self.__view.clear_logs()
if self.__webpic.download(url, name): if self.__webpic.download(url, name):
self.__view.show_success_message("The download has been successfully completed.") self.__view.show_success_message("The download has been successfully completed.")
@ -110,6 +119,6 @@ class HomeController:
function will keep its controller context. In short it's as if the thread was function will keep its controller context. In short it's as if the thread was
launched in the controller and the execution never left it. launched in the controller and the execution never left it.
""" """
print("stop callback called") print("stop callback called") # REMOVE
self.__webpic.stop() self.__webpic.stop()
# END Task methods # END Task methods

View File

@ -1,3 +1,6 @@
import os
class MainController: class MainController:
""" """
Controller - MainController Controller - MainController
@ -19,7 +22,7 @@ class MainController:
""" """
Constructor Constructor
:config: -> The application configuration (a dictionary). * :config: -> The application configuration (a dictionary).
""" """
# Setup variables # Setup variables
self.__config = config self.__config = config
@ -31,17 +34,17 @@ class MainController:
[function for view] [function for view]
=> Allow to define the controller view. => Allow to define the controller view.
:view: -> The view that this controller manage. * :view: -> The view that this controller manage and setup it.
""" """
self.__view = view self.__view = view
view.set_window_title(self.get_config('app_name'))
def on_open_folder(self) -> None: def on_open_folder(self) -> None:
""" """
[event function for view] [event function for view]
=> Event launch when you ask to open the current folder. => Event launch when you ask to open the current folder.
""" """
# TODO on_open_folder os.startfile(self.get_config('app_folder'))
print("on_open_folder") # TODO remove
def on_quite(self) -> None: def on_quite(self) -> None:
""" """
@ -77,7 +80,7 @@ class MainController:
[function for controller] [function for controller]
=> Allows you to request a frame change in the main window. => Allows you to request a frame change in the main window.
:frame: -> The frame we want to display on the window instead of the current frame. * :frame: -> The frame we want to display on the window instead of the current frame.
""" """
self.__view.show_frame(frame) self.__view.show_frame(frame)
@ -86,16 +89,34 @@ class MainController:
[function for controller] [function for controller]
=> Allows controllers to access the application's configuration. => Allows controllers to access the application's configuration.
:name: -> The name of the configuration parameter for which we want to access the configured value. * :name: -> The name of the configuration parameter for which we want to access the configured value.
""" """
if self.__config.get(name): if self.__config.get(name):
return self.__config.get(name) return self.__config.get(name)
else: else:
raise ValueError("Unable to find a configuration with this name") raise ValueError("Unable to find a configuration with this name")
def show_question_dialog(self, title: str='title', message: str='question?', icon: str='question') -> bool:
"""
[function for controller]
=> Ask a question to the user and block until he answers with yes or no.
* :title: ->
* :message: ->
* :icon: ->
"""
return self.__view.show_question_dialog(title, message, icon)
# END Controller methods # END Controller methods
# START Controller events # START Controller events
def subscribe_to_quite_event(self, callback) -> None: def subscribe_to_quite_event(self, callback) -> None:
# TODO """
[function for controller]
=> Subscription function allowing to be warned if a request to quit occurs.
In the case where the callback function returns False the process continues
but if the callback returns True the process is aborted.
* :callback: -> Callback function that will be called when a request to exit occurs.
"""
self.__quite_event_subscribers.append(callback) self.__quite_event_subscribers.append(callback)
# END Controller events # END Controller events

View File

@ -1,4 +1,5 @@
import os import os
import sys
from urllib import request from urllib import request
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from bs4 import BeautifulSoup, Tag, ResultSet from bs4 import BeautifulSoup, Tag, ResultSet
@ -138,6 +139,7 @@ class WebPicDownloader():
Stop the downloading and processing of images (method for use in a thread). Stop the downloading and processing of images (method for use in a thread).
""" """
self.thread_run = False self.thread_run = False
sys.exit(0)
def download(self, url: str, folder_name: str) -> bool: def download(self, url: str, folder_name: str) -> bool:
""" """
@ -158,8 +160,10 @@ class WebPicDownloader():
self.messenger(f"WebPicDownloader found {len(images)} images on the website.") self.messenger(f"WebPicDownloader found {len(images)} images on the website.")
for i, img in enumerate(images): for i, img in enumerate(images):
print("check")
print(id(self))
if not self.thread_run: if not self.thread_run:
exit() print("return")
try: try:
self.messenger(f"Start downloading image {i}.") self.messenger(f"Start downloading image {i}.")
img_link = self.__find_img_link(img) img_link = self.__find_img_link(img)

View File

@ -45,7 +45,9 @@ class AsyncTask(threading.Thread):
[Internal function of (threading.Thread)] [Internal function of (threading.Thread)]
[!] : This function must not be used! Start the task with {AsyncTask.start()} ! [!] : This function must not be used! Start the task with {AsyncTask.start()} !
""" """
print("runn")
self.__run_callback(*self.__run_args) self.__run_callback(*self.__run_args)
print("stopp")
def stop(self) -> None: def stop(self) -> None:
""" """

View File

@ -20,15 +20,21 @@ class HomeView(ttk.Frame):
# Constructor # Constructor
def __init__(self, parent, controller: HomeController): def __init__(self, parent, controller: HomeController):
super().__init__(parent) """
Constructor
# Init view * :parent: -> The main windows container.
self.__init_content() * :controller: -> The view controller
"""
super().__init__(parent)
# Save and setup main controller # Save and setup main controller
self.__controller = controller self.__controller = controller
controller.set_view(self) controller.set_view(self)
# Init view
self.__init_content()
# START Internal function # START Internal function
def __init_content(self) -> None: def __init_content(self) -> None:
""" """

View File

@ -19,6 +19,12 @@ class InfoView(ttk.Frame):
# Constructor # Constructor
def __init__(self, parent, controller: InfoController): def __init__(self, parent, controller: InfoController):
"""
Constructor
* :parent: -> The main windows container.
* :controller: -> The view controller
"""
super().__init__(parent) super().__init__(parent)
# create widgets # create widgets

View File

@ -1,4 +1,5 @@
import tkinter as tk import tkinter as tk
from tkinter import messagebox
from controller.MainController import MainController from controller.MainController import MainController
@ -6,12 +7,12 @@ class MainWindow(tk.Tk):
""" """
View - MainWindow View - MainWindow
dec... TODO dec...
@author Jérémi Nihart / EndMove @author Jérémi Nihart / EndMove
@link https://git.endmove.eu/EndMove/WebPicDownloader @link https://git.endmove.eu/EndMove/WebPicDownloader
@version 1.0.0 @version 1.0.1
@since 2022-08-30 @since 2022-09-04
""" """
# Variables # Variables
__controller: MainController = None __controller: MainController = None
@ -20,6 +21,11 @@ class MainWindow(tk.Tk):
# Constructor # Constructor
def __init__(self, controller: MainController) -> None: def __init__(self, controller: MainController) -> None:
"""
Constructor
* :controller: -> The main application cpntroller.
"""
super().__init__() super().__init__()
# Init view repository # Init view repository
@ -40,10 +46,10 @@ class MainWindow(tk.Tk):
[internal function] [internal function]
=> Initialize window parameters => Initialize window parameters
""" """
self.title('Tkinter app') # self.title('My tkinter app')
# self.geometry('450x250') # self.geometry('450x250')
self.resizable(False, False) self.resizable(False, False)
self.config(bg='#f7ef38') # self.config(bg='#f7ef38')
def __init_top_menu(self) -> None: def __init_top_menu(self) -> None:
""" """
@ -53,7 +59,7 @@ class MainWindow(tk.Tk):
main_menu = tk.Menu(self) main_menu = tk.Menu(self)
col1_menu = tk.Menu(main_menu, tearoff=0) col1_menu = tk.Menu(main_menu, tearoff=0)
col1_menu.add_command(label="Open folder", command=self.__controller.on_open_folder) col1_menu.add_command(label="Open app folder", command=self.__controller.on_open_folder)
col1_menu.add_separator() col1_menu.add_separator()
col1_menu.add_command(label="Quit", command=self.__controller.on_quite) col1_menu.add_command(label="Quit", command=self.__controller.on_quite)
main_menu.add_cascade(label="File", menu=col1_menu) main_menu.add_cascade(label="File", menu=col1_menu)
@ -74,23 +80,32 @@ class MainWindow(tk.Tk):
# END Internal methods # END Internal methods
# START App methods # START App methods
def add_view(self, frame, view): def add_view(self, frame, view) -> None:
""" """
[function for app] [function for app]
:frame: -> the frame id of the view to add. * :frame: -> the frame id of the view to add.
""" """
self.__views[frame] = view self.__views[frame] = view
# END App methods # END App methods
# START Controller methods # START Controller methods
def show_frame(self, frame): def set_window_title(self, title: str) -> None:
"""
[function for controller]
=> Sets the title of the main window.
* :title: -> Window title.
"""
self.title(title)
def show_frame(self, frame) -> None:
""" """
[function for app & controller] [function for app & controller]
=> Allows to display the selected frame provided that it => Allows to display the selected frame provided that it
has been previously added to the frame dictionary. has been previously added to the frame dictionary.
:frame: -> the frame if of the view to display. * :frame: -> the frame if of the view to display.
""" """
if self.__views.get(frame): if self.__views.get(frame):
if self.__frame_id: if self.__frame_id:
@ -102,8 +117,28 @@ class MainWindow(tk.Tk):
def close_window(self) -> None: def close_window(self) -> None:
""" """
[function for app & controller] [function for controller]
TODO => Closes the main window and stops the program from the controller.
""" """
self.destroy() self.destroy()
def show_question_dialog(self, title: str, message: str, icon: str='question') -> bool:
"""
[function for controller]
=> TODO DESC
* :message: ->
* RETURN ->
"""
return messagebox.askquestion(title, message, icon=icon)
def show_information_dialog(self, title: str, message: str, icon: str='information') -> None:
"""
[function for controller]
=> TODO DESC
* :message: ->
* RETURN ->
"""
messagebox.showinfo(self, title, message, icon=icon)
# END Controller methods # END Controller methods