From 32a65d239bacce416f2996e5d347f1b07e383f10 Mon Sep 17 00:00:00 2001
From: destinyk <destiny.kuehn@student.nmt.edu>
Date: Mon, 27 Nov 2023 15:13:20 -0700
Subject: [PATCH] Add QThread

---
 fixhdr/fixhdr.py | 277 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 211 insertions(+), 66 deletions(-)

diff --git a/fixhdr/fixhdr.py b/fixhdr/fixhdr.py
index 3364307..df5d68e 100644
--- a/fixhdr/fixhdr.py
+++ b/fixhdr/fixhdr.py
@@ -302,6 +302,7 @@
 #   ie, self.DataDirs_e for the tkinter Entry widget was changed to
 #   self.DataDirs_le for the PySide LineEdit widget.
 # Added a button in Global Modify to clear location codes.
+# Implemented heavy functions to run as threads.
 ################################################################
 """
 
@@ -318,8 +319,8 @@ from PySide2.QtWidgets import (QApplication, QWidget, QComboBox, QLineEdit,
                                QHBoxLayout, QGridLayout, QGroupBox,
                                QLabel, QFormLayout, QSizePolicy,
                                QSpacerItem, QTextEdit, QFileDialog,
-                               QMessageBox)
-from PySide2.QtCore import Qt
+                               QMessageBox, QProgressDialog)
+from PySide2.QtCore import Qt, QThread, QObject, Signal, QEventLoop, QTimer
 from PySide2.QtGui import QColor, QTextCursor
 
 from fixhdr.LibTrace import *
@@ -2283,15 +2284,22 @@ records have no blockettes, so the strings start at offset
                     statsel = statsel.strip()
                     self.StatSelList.append(statsel)
 
-        # find & ID traces in DataDirList, setup various list for nb's
-        # first I set self.RunBuilddb so we know that this is running and I
-        #    have a variable to watch status of process
-        # next I spawn a thread to FindTrace
         self.RunBuilddb = 1
-        # tRun=threading.Thread(target=self.LaunchFindTrace)
 
-        self.launchfindTrace()
+        # run findTrace as a thread
+        if not BATCHMODE:
+            self.initThread("Build Trace db",
+                            self.launchfindTrace,
+                            self.afterBuildTrace)
+        else:
+            self.launchfindTrace()
+
+########################################
 
+    def afterBuildTrace(self):
+        """
+        Update fields after findTrace finishes
+        """
         # fill/update notebooks if not running batch mode
         if not BATCHMODE:
             info = "Building lists, updating frames"
@@ -2334,19 +2342,12 @@ records have no blockettes, so the strings start at offset
         # since we just rebuilt db no files have been altered
         self.AlteredText = ""
 
-########################################
-
     def launchfindTrace(self):
         """
         Separated out so FindTrace could be run as a thread
         """
 
-        (self.TraceDict, self.TraceTimeDict, NumFiles, rwError) = \
-            self.findTrace(self.DataDirList)
-
-        self.ActiveText = ""
-        self.FoundFiles = NumFiles
-        self.RWErrors = rwError
+        self.findTrace(self.DataDirList)
 
 ########################################
 
@@ -2408,7 +2409,10 @@ records have no blockettes, so the strings start at offset
                 if mod(cnt, 10):
                     pass
                 else:
-                    self.wait("Examining File: ", cnt)
+                    if not BATCHMODE:
+                        self.funcWorker.update.emit(cnt)
+                    else:
+                        self.wait("Examining File: ", cnt)
                 # build up list of mseed files w/ full pathnames
                 fullname = losjoin(directory, file)
                 if losisfile(fullname):
@@ -2490,7 +2494,12 @@ records have no blockettes, so the strings start at offset
                                           not losisfile(fullname)):
                     if fullname not in stack:
                         stackappend(fullname)
-        return file_list, time_list, NumMseedFiles, rwError
+
+        self.TraceDict = file_list
+        self.TraceTimeDict = time_list
+        self.ActiveText = ""
+        self.FoundFiles = NumMseedFiles
+        self.RWErrors = rwError
 
 ########################################
 
@@ -2601,19 +2610,11 @@ records have no blockettes, so the strings start at offset
 
         # if the user ignores warning proceed
         self.RunModHdrs = 1
-
-        # mRun=threading.Thread(target=self.ModHdrs)
-        self.modHdrs()
-        # mRun.start()
-        # stay here until thread dies
-        # mRun.join()
-
-        '''
-        if self.RunModHdrs:
-            self.killshowCancelTL(self.RunModHdrs)
-        '''
         self.IgnoreWarn = 0
 
+        # run as thread
+        self.initThread("Modify Headers", self.modHdrs)
+
 ########################################
 
     def modHdrs(self):
@@ -2689,7 +2690,10 @@ records have no blockettes, so the strings start at offset
                     if mod(cnt, 10):
                         pass
                     else:
-                        self.wait("Modifying Trace: ", cnt)
+                        if not BATCHMODE:
+                            self.funcWorker.update.emit(cnt)
+                        else:
+                            self.wait("Modifying Trace: ", cnt)
 
                     # determine file size and get block size from
                     # Blockette 1000
@@ -2975,23 +2979,8 @@ records have no blockettes, so the strings start at offset
         # write log and user information
         self.writeLog("\n<Modify Time>", "Time", "blue")
 
-        # tRun=threading.Thread(target=self.ApplyTimeCor)
-        self.applyTimeCor()
-        # tRun.start()
-        # stay here until thread dies
-        # tRun.join()
-
-        # nuke top level CancelTL
-        """
-        if self.RunApplyTimeCor:
-            self.killshowCancelTL(self.RunApplyTimeCor)
-        """
-
-        # some cleanup
-        self.writeLog("<end>", "Time", "blue")
-        self.resetTimeShift()
-        self.IgnoreWarn = 0
-        self.UseNewOnlyTime = 0
+        # run as thread
+        self.initThread("Apply Time Corrections", self.applyTimeCor)
 
 ########################################
 
@@ -3180,7 +3169,10 @@ records have no blockettes, so the strings start at offset
                     if mod(num_cnt, 10):
                         pass
                     else:
-                        self.wait(textstr, num_cnt)
+                        if not BATCHMODE:
+                            self.funcWorker.update.emit(num_cnt)
+                        else:
+                            self.wait(textstr, num_cnt)
 
             # keep track of time corrections applied
             if key in self.UpdateTimeDict:
@@ -3222,6 +3214,13 @@ records have no blockettes, so the strings start at offset
         self.ActiveText = ""
         # self.writeLog("<end>\n", "Time", "blue")
 
+        # some cleanup
+        self.writeLog("<end>", "Time", "blue")
+        if not BATCHMODE:
+            self.resetTimeShift()
+        self.IgnoreWarn = 0
+        self.UseNewOnlyTime = 0
+
 ########################################
 
     def launchUndoTimeCor(self):
@@ -3704,8 +3703,8 @@ records have no blockettes, so the strings start at offset
         else:
             self.TSEndTime = end
         # first record has min/max time for keyname
-        cnt = len(self.TraceTimeDict[keyname]) - 1
-        textstr = str(cnt) + " traces selected for key: " + keyname
+        self.timeCnt = len(self.TraceTimeDict[keyname]) - 1
+        textstr = str(self.timeCnt) + " traces selected for key: " + keyname
         self.addTextInfoBar(textstr, 'lightblue')
 
 ########################################
@@ -3814,20 +3813,15 @@ records have no blockettes, so the strings start at offset
 
         if endian == "big":
             self.ToEndian = "big"
+            title = "Change Endian To Big"
         else:
             self.ToEndian = "little"
+            title = "Change Endian To Little"
         # launch thread
-        # eRun=threading.Thread(target=self.ChangeEndian)
-        self.changeEndian()
-        # eRun.start()
-        # stay here until thread dies
-        # eRun.join()
-
-        '''
-        if self.RunChangeEndian.get():
-            self.killshowCancelTL(self.RunChangeEndian)
-        '''
-        self.ActiveText = ""
+
+        # run as thread
+        # func = lambda: self.changeEndian()
+        self.initThread(title, self.changeEndian)
 
 ########################################
 
@@ -3856,8 +3850,11 @@ records have no blockettes, so the strings start at offset
                 return
 
         files_left = numfiles
-        textstr = "Files Remaining: "
-        self.wait(textstr, files_left)
+        if BATCHMODE:
+            textstr = "Files Remaining: "
+            self.wait(textstr, files_left)
+        else:
+            self.addTextInfoBar()
 
         # write log and user information
         self.writeLog("\n<Change Endianess>", "Endian", "blue")
@@ -3877,7 +3874,10 @@ records have no blockettes, so the strings start at offset
             if mod(files_left, 5):
                 pass
             else:
-                self.wait(textstr, files_left)
+                if BATCHMODE:
+                    self.wait(textstr, files_left)
+                else:
+                    self.funcWorker.update.emit(corr_files)
             files_left -= 1
 
             msfile = Mseed(ifile)
@@ -3929,8 +3929,25 @@ records have no blockettes, so the strings start at offset
                         n += numblocks
                         break
                     else:
-                        bytes_written = PutBlk(
-                            blktype, blklist, endian, seekval)
+                        try:
+                            bytes_written = PutBlk(
+                                blktype, blklist, endian, seekval)
+                        except Exception as e:
+                            # if function not found
+                            # undo endian changes
+                            err = ("\n\tError at file " + str(ifile) +
+                                   ":\n\tself.Writeblk")
+                            err += (str(blktype) + " not in LibTrace.py")
+                            self.writeLog(err, "Endian", "red")
+                            msfile.close()
+                            self.undoEndian(ifile, n, b, endian)
+                            # remove file from list
+                            outlist.pop()
+                            # set error flag
+                            ErrFlag = 1
+                            # ensure outer while block will end
+                            n += numblocks
+                            break
                     addseek = next
                     b += 1
                 n += 1
@@ -3962,6 +3979,8 @@ records have no blockettes, so the strings start at offset
         infotext = "\nDone. Converted: " + str(corr_files) + " files."
         self.addTextInfoBar(infotext, 'green')
 
+        self.ActiveText = ""
+
 ########################################
 
     def undoEndian(self, infile, recnum, blknum, endian):
@@ -4441,12 +4460,15 @@ records have no blockettes, so the strings start at offset
         """
         Sets Value of var
         """
+        print(var)
+        print(value)
 
         if var == "DataDirs":
             for le in self.DataDirLeList:
                 le.clear()
         else:
             var = value
+            print("hi?")
 
 ########################################
 
@@ -5165,6 +5187,111 @@ records have no blockettes, so the strings start at offset
             # set cursor to beginning
             CorText.moveCursor(QTextCursor.Start, QTextCursor.MoveAnchor)
 
+########################################
+
+    def initThread(self, title, func, afterFunc=''):
+        """
+        Create thread to run function
+        """
+
+        self.thread = QThread(self)
+        self.funcWorker = Worker(self, func)
+
+        # set signals/slots
+        self.funcWorker.update.connect(self.updateProg)
+        self.thread.started.connect(self.funcWorker.run)
+        # function to run after thread finishes
+        if afterFunc:
+            self.funcWorker.finished.connect(afterFunc)
+
+        # delete worker and thread when done
+        self.funcWorker.finished.connect(self.thread.quit)
+        self.funcWorker.finished.connect(self.funcWorker.deleteLater)
+        self.thread.finished.connect(self.thread.deleteLater)
+
+        # launch progress window
+        if not BATCHMODE:
+            # get file num for progress window
+            numFiles = 0
+            if title == "Build Trace db":
+                for dir in self.DataDirList:
+                    numFiles += (
+                        sum([len(files) for r, d, files in os.walk(dir)]))
+
+            elif title == "Modify Headers":
+                inlist = sorted(list(self.UpdateHdrDict.keys()))
+                for key in inlist:
+                    ifilelist = (ifile for ifile in self.TraceDict[key])
+                    numFiles += (sum(1 for _ in ifilelist))
+
+            elif title == "Apply Time Corrections":
+                numFiles = self.timeCnt
+
+            elif title == "Change Endian To Little":
+                numFiles = len(self.BigEndianList)
+
+            elif title == "Change Endian To Big":
+                numFiles = len(self.LittleEndianList)
+
+            self.initProgWindow(title, numFiles)
+            self.funcWorker.finished.connect(lambda: self.progWin.done(0))
+            loop = QEventLoop(self)
+            QTimer.singleShot(100, loop.quit)
+            loop.exec_()
+
+        self.thread.start()
+
+########################################
+
+    def initProgWindow(self, title, numFiles):
+        """
+        Create progress dialog to update user of
+        current process status
+        """
+
+        text = title + " is Active. Please wait."
+        self.progWin = QProgressDialog(
+            labelText=text, minimum=0,
+            maximum=numFiles, parent=self)
+
+        cancel_b = QPushButton("Cancel")
+        cancel_b.setStyleSheet(
+            "QPushButton::hover{background-color:red;}")
+        cancel_b.clicked.connect(lambda: self.cancelFunc(title))
+        self.progWin.setCancelButton(cancel_b)
+
+        self.progWin.open()
+
+########################################
+
+    def cancelFunc(self, title):
+        """
+        If user cancels current progress
+        set loop var to 0
+        """
+        if title == "Build Trace db":
+            self.RunBuilddb = 0
+        elif title == "Modify Headers":
+            self.RunModHdrs = 0
+        elif title == "Apply Time Corrections":
+            self.RunApplyTimeCor = 0
+        else:
+            self.RunChangeEndian = 0
+
+########################################
+
+    def updateProg(self, cnt):
+        """
+        Update progress for user
+        """
+        if not BATCHMODE:
+            self.progWin.setValue(cnt)
+        else:
+            self.wait(cnt)
+
+    def setProgMax(self, max):
+        self.progWin.setMaximum(max)
+
 ########################################
 
     def closeEvent(self, event):
@@ -5177,6 +5304,24 @@ records have no blockettes, so the strings start at offset
 # end of main class
 
 
+class Worker(QObject):
+    """
+    Thread worker
+    """
+    update = Signal(int)
+    new_max = Signal(int)
+    finished = Signal()
+
+    def __init__(self, parent, func):
+        super().__init__()
+        self.parent = parent
+        self.func = func
+
+    def run(self):
+        self.func()
+        self.finished.emit()
+
+
 def main():
     app = QApplication()
     if BatchFile:
-- 
GitLab