So I basically have written a program in python using tkinter and urllib.request which is supposed to work as a downloader, but each downloader has to have a pause or cancel button but I can’t seem to find anyway to do this! Recently I bumped into the same question in stackoverflow( the link: Is it possible to stop (cancel) urlretrieve process?) and it seems like that I have to use threads or multi-processing but I don’t have a single idea how to do this! By the way how is threading or multi-processing going to help with the canceling pausing the download? Can someone explain to me what should I do? Is there anyway to do this without threading or multi-processing? If there is not, can you explain how to use threading or multi-processing in this program because I don’t have a single clue what to do! Please help me out on this. My code:
from tkinter import * from tkinter import font as tkFont import random import urllib.request import requests def printsth(): print("Yay it works! ") def main_menu(): root = Tk() root.title('8-bit downloader ') root.iconbitmap(r"C:UsersrayanraveshPycharmProjectsGUI_Calculatoricon.ico") root.geometry("600x280") # the top menu num = IntVar() chum = IntVar() # var = IntVar() menu = Menu(root) root.config(menu=menu) submenu = Menu(menu) menu.add_cascade(label="Settings", menu=submenu) def custom_op(): custom = Toplevel() custom.iconbitmap(r"C:UsersrayanraveshPycharmProjectsGUI_Calculatoricon.ico") submenu.add_command(label="Customization ", command=custom_op) def settings_op(): global gps set_win = Toplevel() set_win.iconbitmap(r"C:UsersrayanraveshPycharmProjectsGUI_Calculatoricon.ico") path_label = Label(set_win, text="Current default download path: ") path_entry = Entry(set_win, width=30) file_read = open('Data.txt', 'r') data_base = file_read.read() path_entry.insert(0, data_base) file_read.close() def default_output(): global location file_read2 = open('Data.txt', 'r+') file_read2.truncate(0) file_read2.close() write_file2 = open('Data.txt', 'w') write_file2.write(path_entry.get()) write_file2.close() location = path_entry.get() + "\" default_location = location.replace("\", "\\") path_btn = Button(set_win, text="Submit ", command=default_output) path_label.pack(anchor=CENTER, expand=1) path_entry.pack(anchor=CENTER, expand=1) path_btn.pack(anchor=CENTER, expand=1) submenu.add_command(label="Settings ", command=settings_op) submenu.add_separator() submenu.add_command(label="Exit", command=root.destroy) # the section menu editmenu = Menu(menu) menu.add_cascade(label="Sections(soon)", menu=editmenu) editmenu.add_command(label="Downloader", command=printsth) editmenu.add_command(label="Converter", command=printsth) editmenu.add_command(label="Media Player", command=printsth) editmenu.add_command(label="Editor", command=printsth) # the tool bar toolbar = Frame(root, bg="light gray") insert_button = Button(toolbar, text="Insert an image", command=printsth) insert_button.pack(side=LEFT, padx=2, pady=2) print_button = Button(toolbar, text="Print", command=printsth) print_button.pack(side=LEFT, padx=2, pady=2) toolbar.pack(side=TOP, fill=X) # the download function def download_image(): global formatname if num.get() == 1: name = random.randrange(1, 1000000) else: name = str(name_entry.get()) formatname = str(format_entry.get()) '''if var.get() == 1: operator = str(url_entry.get()) formatname = '.' + operator[-3] + operator[-2] + operator[-1] else: pass''' fullname = str(name) + formatname url = str(url_entry.get()) fw = open('file-size.txt', 'w') file_size = int(requests.head(url, headers={'accept-encoding': ''}).headers['Content-Length']) fw.write(str(file_size)) fw.close() if chum.get() == 1: filee = open('Data.txt', 'r') destination = filee.read() path = destination output_entry.insert(0, destination) filee.close() else: output_entry.delete(0, END) path = str(output_entry.get()) + "\" urllib.request.urlretrieve(url, path.replace("\", "\\") + fullname) # the status bar status_bar = Label(root, text="Downloading...", bd=1, relief=SUNKEN, anchor=W) status_bar.pack(side=BOTTOM, fill=X) # the download frame body_frame = Frame(root, bg="light blue") download_button = Button(body_frame, text="Download! ", command=download_image, border=3, width=20, height=5) download_design = tkFont.Font(size=12, slant='italic') download_button['font'] = download_design download_button.pack(side=LEFT, pady=5, padx=5) body_frame.pack(side=LEFT, fill=Y) # the main interaction menu inter_frame = Frame(root) url_entry = Entry(inter_frame, width=30) label = Label(inter_frame, text="Enter the image URL: ") file_format = Label(inter_frame, text="Choose your file format: ") format_entry = Entry(inter_frame, width=30) file_name = Label(inter_frame, text="File's name: ") name_entry = Entry(inter_frame, width=30) check_name = Checkbutton(inter_frame, text="Give a random name", variable=num) # check_format = Checkbutton(inter_frame, text="Download with default format", variable=var) check_default = Checkbutton(inter_frame, text="Download to default path", variable=chum) output_path = Label(inter_frame, text="Choose output path: ") output_entry = Entry(inter_frame, width=30) file_name.pack(anchor=CENTER, expand=1) name_entry.pack(anchor=CENTER, expand=1) check_name.pack(anchor=CENTER, expand=1) label.pack(anchor=CENTER, expand=1) url_entry.pack(anchor=CENTER, expand=1) file_format.pack(anchor=CENTER, expand=1) format_entry.pack(anchor=CENTER, expand=1) format_entry.insert(0, '.') # check_format.pack(anchor=CENTER) output_path.pack(anchor=CENTER, expand=1) output_entry.pack(anchor=CENTER, expand=1) check_default.pack(anchor=CENTER, expand=1) inter_frame.pack(expand=1) root.mainloop() # the end! main_menu()
Advertisement
Answer
When running a function like urlretrieve
in a single thread or process, there is no way to stop it because there is only one thread or process.
In this case, you are calling urlretrieve
from a tkinter
callback. Those are called by tkinter from the mainloop
, effectively interrupting the mainloop
.
This might not be a problem for a small download from a host on a fast connection. But if the download takes second or minutes then you have a problem. Because while urlretrieve
is in progress, your GUI is unresponsive because the mainloop
is waiting for your callback to finish.
So even if you were to have a “Cancel” button, it would not respond als long as urlretrieve
is running.
Read the urlretrieve
function from the file urllib/request.py
in your Python directory. It’s not a big function and should be relatively easy to follow.
Internally, urlretrieve
contains a loop that read
s from a URL and writes to a file. By defaults, such reads wait until there is anything to read. The thing is; there is no way to interrupt this loop.
So one way or another, you will have to re-write urlretrieve. In this re-written version you should check in every iteration of the inner loop if you should continue.
You basically have two options;
- Work the functionality of
urlretrieve
into the event loop. - Work the functionality of
urlretrieve
into a different thread.
Each has their pros and cons. If you are using Python 3, starting a threading.Thread
is probably the easiest thing to do because tkinter
in Python 3 is thread-safe.
For an example of the first approach, see unlock-excel.pyw
from my scripts repo on github. In this application, a long operation is divided into several small steps that are called from the tkinter
eventloop via the after
method.
I don’t have an example handy for the method with using a thread. Basically you have to rewrite urlretrieve
to check a variable (e.g. a threading.Event
) that signals if it should stop in the inner while
loop.