Search code examples
pythonpython-3.xuser-interfacewxpython

My python installer freezes while working. How do I fix it?


I made a very simple GUI Installer for a game with wxPython. Although it is meant for a game it can technically be used to download and extract any zip file if you have the link. The problem is when I run the program the GUI freezes. It still downloads and extracts like its supposed to, but the GUI is completely frozen while this happens. I can't update text boxes or make a download bar unless I can unfreeze it. I know why it freezes I just don't know how to fix it. Could someone help me out?

Here is my code:

import requests, os, sys, zipfile, shutil, subprocess, wx, urllib

url = "{Put any zip file URL here to test the program}"
r = requests.get(url, stream = True)

class Frame(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
        myPanel = wx.Panel(self,-1)
        myButton = wx.Button(myPanel, -1, 'Download', size=(300,50), pos=(40,350))
        myButton.Bind(wx.EVT_LEFT_DOWN, self.onClick)
        self.Show(True)
    def onClick(self, e):
        print ('Clicked')
        if os.path.exists("RFMB6_WINDOWS"):
            print('\n\nRemoving old RFMP files...')
            subprocess.check_call(('attrib -R ' + 'RFMB6_WINDOWS' + '\\* /S').split())
            shutil.rmtree('RFMB6_WINDOWS')
            print('\nRemoved old files.')
        else:
            pass

        print('\n\nDownloading:')
        urllib.request.urlretrieve(url, 'RFMP.zip')
        print('\nDownload Complete.')
        print('\n\nExtracting...')
        zip_ref = zipfile.ZipFile("RFMP.zip", 'r')
        zip_ref.extractall("RFMB6_WINDOWS")
        zip_ref.close()
        print('\nExtraction Complete')
        print('\n\nCleaning up...')
        os.remove("RFMP.zip")
        print('\nDone! You have succesfully installed the newest version of the Ravenfield Multiplayer Private Alpha.')

app = wx.App()
frame = Frame(None, wx.ID_ANY, 'Image')
app.MainLoop()

Solution

  • Okay, so I figured it out. As I said I needed to multi-thread the GUI, but at the time I couldn't figure it out. Now I have. What I needed to do was multi-thread the GUI and my 'processes' on separate threads. This means that the GUI (what you see) is running on a separate thread than the one that is processing the data (downloading, extracting, etc.).

    Before you use this code I want to let you know this is not a finished program. It is just the bare minimum to download and extract a .zip from a download link without freezing.

    Here is the code I am using now to solve my problem:

    # Import libraries
    import requests, os, sys, zipfile, shutil, subprocess, wx, urllib, time
    from threading import *
    
    # Define variables
    url = "Put any zip file URL here to test the program"
    r = requests.get(url, stream = True)
    
    # Button definitions
    ID_START = wx.NewId()
    
    # Define notification event for thread completion
    EVT_RESULT_ID = wx.NewId()
    
    # Check  for old files
    def Check():
        if os.path.exists("Folder"):
            print('\n\nRemoving old files...')
            subprocess.check_call(('attrib -R ' + 'Folder' + '\\* /S').split())
            shutil.rmtree('Folder')
            print('\nRemoved old files.')
        else:
            pass
    
    # Download new file
    def Download():
        print('\n\nDownloading:')
        urllib.request.urlretrieve(url, 'temp.zip')
        print('\nDownload Complete.')
    
    # Extract new file
    def Extract():
        print('\n\nExtracting...')
        zip_ref = zipfile.ZipFile("temp.zip", 'r')
        zip_ref.extractall("Folder")
        zip_ref.close()
        print('\nExtraction Complete')
    
    # Delete the .zip file but leave the folder
    def Clean():
        print('\n\nCleaning up...')
        os.remove("temp.zip")
        print('\nDone!')
    
    # Thread class that executes processing
    class WorkerThread(Thread):
        """Worker Thread Class."""
        def __init__(self, notify_window):
            """Init Worker Thread Class."""
            Thread.__init__(self)
            self._notify_window = notify_window
            # This starts the thread running on creation.
            self.start()
    
    # This is what runs on a separate thread when you click the download button
        def run(self):
            """Run Worker Thread."""
            # This is the code executing in the new thread. 
            Check()
            Download()
            Extract()
            Clean()
    
    # GUI Frame class that spins off the worker thread
    class MainFrame(wx.Frame):
        """Class MainFrame."""
        def __init__(self, parent, id):
            """Create the MainFrame."""
            wx.Frame.__init__(self, parent, id, 'GUInstaller', 
                              style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
            self.SetSize(400, 350)
            wx.Button(self, ID_START, 'Download', size=(300,50), pos=(42,250))
            self.status = wx.StaticText(self, -1, '', pos=(0,200))
            self.Bind(wx.EVT_BUTTON, self.OnStart, id=ID_START)
    
            # And indicate we don't have a worker thread yet
            self.worker = None
    
        def OnStart(self, event):
            """Start Computation."""
            # Trigger the worker thread unless it's already busy
            if not self.worker:
                self.status.SetLabel('Downloading...')
                self.worker = WorkerThread(self)
    
        def OnResult(self, event):
            """Show Result status."""
            self.status.SetLabel('Done!')
            # The worker is done
            self.worker = None
    
    class MainApp(wx.App):
        """Class Main App."""
        def OnInit(self):
            """Init Main App."""
            self.frame = MainFrame(None, -1)
            self.frame.Show(True)
            self.SetTopWindow(self.frame)
            return True
    
    # Main Loop
    if __name__ == '__main__':
        app = MainApp(0)
        app.MainLoop()
    

    P.S. Not all of this is my code. I took an example from another website and modified it to work with my needs.