0

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:\Users\rayanravesh\PycharmProjects\GUI_Calculator\icon.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:\Users\rayanravesh\PycharmProjects\GUI_Calculator\icon.ico")
    submenu.add_command(label="Customization ", command=custom_op)

    def settings_op():
        global gps
        set_win = Toplevel()
        set_win.iconbitmap(r"C:\Users\rayanravesh\PycharmProjects\GUI_Calculator\icon.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()
Omid Ki
  • 111
  • 1
  • 10
  • Does this answer your question? [How to cancel or pause a urllib request in python](https://stackoverflow.com/questions/61193811/how-to-cancel-or-pause-a-urllib-request-in-python) – acw1668 Apr 19 '20 at 00:35

1 Answers1

1

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 reads 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;

  1. Work the functionality of urlretrieve into the event loop.
  2. 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.

Roland Smith
  • 35,972
  • 3
  • 52
  • 79
  • ***because tkinter in Python 3 is thread-safe***: Do you have a reference about this? – stovfl Apr 18 '20 at 19:52
  • @stovfl See https://docs.python.org/3/library/tk.html. Quote: " In addition, the internal module _tkinter provides a threadsafe mechanism which allows Python and Tcl to interact." – Roland Smith Apr 18 '20 at 20:09
  • @stovfl As it turns out, this is not *unconditionally* true, looking at the different relevant issues raised over the years. The reality seems more complicated than a binary yes/no. There are examples here on stackoverflow where tkinter functions called from different threads work fine. – Roland Smith Apr 18 '20 at 20:23
  • In [`_tkinter.c`](https://github.com/python/cpython/blob/020f2aaaea95aef6f54ab31488926ed76017e41a/Modules/_tkinter.c#L180) i find *"The threading situation is complicated. Tcl is not thread-safe, except when configured with --enable-threads. ... Invoking commands from other threads is possible; _tkinter will queue an event for the interpreter thread"* – stovfl Apr 18 '20 at 20:54
  • @stovfl I found that as well. On my platform, a threaded tkinter is built by default. – Roland Smith Apr 18 '20 at 23:01