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
# START Controller methods
def on_quit(self) -> None:
def on_quit(self) -> bool:
"""
[event function for controller]
=> Call this event when a request to exit is thrown.
"""
self.__download_task.stop()
print("Quit... homecontroller END")
if self.__download_task and self.__download_task.is_alive():
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
# START Task methods
@ -96,7 +105,7 @@ class HomeController:
* :url: -> Url for webpic.
* :name: -> Working dir name for webpic.
"""
print("start callback called")
print("start callback called") # REMOVE
self.__view.clear_logs()
if self.__webpic.download(url, name):
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
launched in the controller and the execution never left it.
"""
print("stop callback called")
print("stop callback called") # REMOVE
self.__webpic.stop()
# END Task methods

View File

@ -1,3 +1,6 @@
import os
class MainController:
"""
Controller - MainController
@ -19,7 +22,7 @@ class MainController:
"""
Constructor
:config: -> The application configuration (a dictionary).
* :config: -> The application configuration (a dictionary).
"""
# Setup variables
self.__config = config
@ -31,17 +34,17 @@ class MainController:
[function for 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
view.set_window_title(self.get_config('app_name'))
def on_open_folder(self) -> None:
"""
[event function for view]
=> Event launch when you ask to open the current folder.
"""
# TODO on_open_folder
print("on_open_folder") # TODO remove
os.startfile(self.get_config('app_folder'))
def on_quite(self) -> None:
"""
@ -77,7 +80,7 @@ class MainController:
[function for controller]
=> 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)
@ -86,16 +89,34 @@ class MainController:
[function for controller]
=> 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):
return self.__config.get(name)
else:
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
# START Controller events
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)
# END Controller events

View File

@ -1,4 +1,5 @@
import os
import sys
from urllib import request
from urllib.error import HTTPError, URLError
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).
"""
self.thread_run = False
sys.exit(0)
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.")
for i, img in enumerate(images):
print("check")
print(id(self))
if not self.thread_run:
exit()
print("return")
try:
self.messenger(f"Start downloading image {i}.")
img_link = self.__find_img_link(img)

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import tkinter as tk
from tkinter import messagebox
from controller.MainController import MainController
@ -6,12 +7,12 @@ class MainWindow(tk.Tk):
"""
View - MainWindow
dec...
TODO dec...
@author Jérémi Nihart / EndMove
@link https://git.endmove.eu/EndMove/WebPicDownloader
@version 1.0.0
@since 2022-08-30
@version 1.0.1
@since 2022-09-04
"""
# Variables
__controller: MainController = None
@ -20,6 +21,11 @@ class MainWindow(tk.Tk):
# Constructor
def __init__(self, controller: MainController) -> None:
"""
Constructor
* :controller: -> The main application cpntroller.
"""
super().__init__()
# Init view repository
@ -40,10 +46,10 @@ class MainWindow(tk.Tk):
[internal function]
=> Initialize window parameters
"""
self.title('Tkinter app')
# self.title('My tkinter app')
# self.geometry('450x250')
self.resizable(False, False)
self.config(bg='#f7ef38')
# self.config(bg='#f7ef38')
def __init_top_menu(self) -> None:
"""
@ -53,7 +59,7 @@ class MainWindow(tk.Tk):
main_menu = tk.Menu(self)
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_command(label="Quit", command=self.__controller.on_quite)
main_menu.add_cascade(label="File", menu=col1_menu)
@ -74,23 +80,32 @@ class MainWindow(tk.Tk):
# END Internal methods
# START App methods
def add_view(self, frame, view):
def add_view(self, frame, view) -> None:
"""
[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
# END App 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]
=> Allows to display the selected frame provided that it
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.__frame_id:
@ -102,8 +117,28 @@ class MainWindow(tk.Tk):
def close_window(self) -> None:
"""
[function for app & controller]
TODO
[function for controller]
=> Closes the main window and stops the program from the controller.
"""
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