Search code examples
pythonuser-interfacetkinterurllib

python: tkinter/urllib/requests - GUI not responding when running download command


I tried making a GUI where there is a download button and it downloads a file from the internet. There is also a progressbar which shows progress of the download..

the whole code:

#minimal reproductive example..

import os
import time
import yaml
import urllib
import requests
import tempfile
import tkinter as tk
from tkinter import ttk
from tkinter import *

TEMP = tempfile.gettempdir()

def download(progressbar=None): 
    start = time.time()
    url = 'https://proget.whirlpool.repl.co/information.yml'
    local_filename = TEMP+"\\"+url.split('/')[-1]
    url_file = urllib.request.urlopen(url)
    size= url_file.headers["Content-Length"]    
    print("Starting to download file", end = "\r")
    if progressbar:
        progressbar['maximum'] = int(int(size)/1024)
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, 'wb') as f:
            sz = 0
            for chunk in r.iter_content(chunk_size=8192):
                f.write(chunk)
                sz = sz+8192
                if progressbar:
                    progressbar['value'] = progressbar['value'] + 8192
    os.system(local_filename)

def Download(*args):
    download(progressbar=pb95)

root = tk.Tk()
style = ttk.Style(root)

pb95 = ttk.Progressbar(root,orient='horizontal', mode='determinate',length=500, maximum=1)
pb95.pack(side='top',fill='y')

downloadbtn = tk.Button(root,text='Download',font='Consolas 30', bg='green', fg='white',relief='flat', command=Download)
downloadbtn.pack(side='bottom',fill='x')

root.mainloop()

but when i click on the download button, the whole window stops responding.. and after some time, when the download is finished, it responds.. and the progressbar becomes 100% done..


Solution

  • Try this:

    import urllib
    import requests
    from tkinter import ttk
    import tkinter as tk
    
    # Import threading
    from threading import Thread
    
    
    def download(progressbar=None):
        global progress_value, progress_maximum, progress_done
        progress_value = 0
    
        url = 'https://proget.whirlpool.repl.co/information.yml'
    
        # I hard coded a large file for testing
        url = "https://mirror.bytemark.co.uk/ubuntu-releases/21.04/ubuntu-21.04-desktop-amd64.iso"
        # I also hard coded in a temp folder for testing
        local_filename = "tmp/"+url.split('/')[-1]
        url_file = urllib.request.urlopen(url)
        size= url_file.headers["Content-Length"]
        print("Starting to download file")
    
        # If you want running the file to take 50% of the progressbar uncomment the `*2`
        progress_maximum = int(size) # *2
    
        file = open(local_filename, "wb")
        with requests.get(url, stream=True) as r:
            r.raise_for_status()
            for chunk in r.iter_content(chunk_size=8192):
                file.write(chunk)
                progress_value += len(chunk)
        file.close()
    
        print("Running the file")
        # os.system(local_filename)
        print("Done")
        progress_value = progress_maximum
        progress_done = True
    
    def Download(*args):
        # Add a default value for the value of the progress bar:
        global progress_value, progress_maximum, progress_done
        progress_value = 0
        progress_maximum = 1
        progress_done = False
        # Start the new thread
        new_thread = Thread(target=download, daemon=True)
        new_thread.start()
        # Start the tkinter loop:
        root.after(100, tkinter_download_loop)
    
    def tkinter_download_loop():
        pb95["maximum"] = progress_maximum
        pb95["value"] = progress_value
        # After 100 ms call `tkinter_download_loop` again
        if not progress_done:
            root.after(100, tkinter_download_loop)
    
    root = tk.Tk()
    
    pb95 = ttk.Progressbar(root, orient="horizontal", length=500)
    pb95.pack(side="top", fill="y")
    
    downloadbtn = tk.Button(root, text="Download", command=Download)
    downloadbtn.pack(side="bottom", fill="x")
    
    root.mainloop()
    

    First of all, your code had a few mistakes:

    • You keep opening the file in "wb" mode, which overrides the last chunk that you downloaded.
    • You divide the size by 1024 for no reason.
    • Also the file you are downloading is very small. There is no need to iterate over it's contents. The code above assumes that you have a large file

    Things I did:

    • I added a tkinter loop that uses global variables to communicate with a new thread.
    • That thread downloads the file.
    • I also changed the url to a large file (> 1GB) just to check that it's working properly.
    • I also changed it so it opens the file only once so we can save the full file