Package nMOLDYN :: Package GUI :: Module CheckJobsStatusDialog |
[hide private]
[frames] | no frames]

# Source Code for Module nMOLDYN.GUI.CheckJobsStatusDialog

      1  """This modules implements I{Help-->Check job status} dialog.
      2
      3  Classes:
      4      * CheckJobsStatusDialog: creates I{Help-->Check job status} dialog used to
      5      check the status of the nMOLDYN running jobs.
      6  """
      7
      8  # The standard distribution modules
      9  from glob import glob
     10  import re
     11  import os
     12  import sys
     13  if sys.platform == 'win32':
     14      import win32api
     15      import win32process
     16  else:
     17      import pwd
     18  from tempfile import gettempdir
     19
     20  # The Tcl/Tk modules
     21  import Dialog
     22  from Tkinter import *
     23
     24  # The ScientificPython modules.
     25  from Scientific.IO.TextFile import TextFile
     26
     27  # Package path
     28  nmoldyn_package_path = os.path.dirname(os.path.split(__file__)[0])
     29
     30 -class CheckJobsStatusDialog(Toplevel):
     31      """Sets up a dialog used to check the status of the nMOLDYN running jobs.
     32      """
     33
     34 -    def __init__(self, parent, title = None):
     35          """The constructor.
     36
     37          @param parent: the parent widget.
     38
     39          @param title: a string specifying the title of the dialog.
     40          @type title: string
     41          """
     42
     43          Toplevel.__init__(self, parent)
     44          self.transient(parent)
     45
     46          if title:
     47              self.title(title)
     48
     49          self.parent = parent
     50
     51          self.jobs = {}
     52
     53          body = Frame(self)
     54          self.initial_focus = self.body(body)
     55          body.grid(row = 0, column = 0, sticky = EW)
     56
     57          self.buttonbox()
     58
     59          self.grab_set()
     60
     61          if not self.initial_focus:
     62              self.initial_focus = self
     63
     64          self.protocol("WM_DELETE_WINDOW", self.cancel)
     65
     66          self.resizable(width = NO, height = NO)
     67
     68          self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50))
     69
     70          self.initial_focus.focus_set()
     71
     72          self.wait_window(self)
     73
     74 -    def body(self, master):
     75          """
     76          Create dialog body. Return widget that should have initial focus.
     77          """
     78
     79          jobsFrame = LabelFrame(master, text = 'Jobs status', bd = 2, relief = GROOVE)
     80          jobsFrame.grid(row = 0, column = 0, sticky = EW, padx = 3, pady = 3)
     81          jobsFrame.grid_columnconfigure(0, weight = 1)
     82
     83          textScrollbar = Scrollbar(jobsFrame)
     84          textScrollbar.grid(row = 0, column = 1, sticky = NS)
     85          self.jobsWindow = Text(jobsFrame, height = 20, width = 120)
     86          self.jobsWindow.grid(row = 0, column = 0)
     87          self.jobsWindow.config(yscrollcommand = textScrollbar)
     88          textScrollbar.config(command = self.jobsWindow.yview)
     89          b1 = Button(self.jobsWindow,\
     90                      bd = 0,\
     91                      bg = 'lightblue',\
     92                      highlightbackground = 'lightblue',\
     93                      disabledforeground = 'black',\
     94                      width = 15,\
     95                      height = 2,\
     96                      text = 'Kill job ?',\
     97                      font = ('Courier','10','bold'),\
     98                      anchor = CENTER,\
     99                      relief = FLAT,\
    100                      state = DISABLED)
    101          b2 = Button(self.jobsWindow,\
    102                      bd = 0,\
    103                      bg = 'lightblue',\
    104                      highlightbackground = 'lightblue',\
    105                      disabledforeground = 'black',\
    106                      width = 40,\
    107                      height = 2,\
    108                      text = 'Name',\
    109                      font = ('Courier','10','bold'),\
    110                      anchor = CENTER,\
    111                      relief = FLAT,\
    112                      state = DISABLED)
    113          b3 = Button(self.jobsWindow,\
    114                      bd = 0,\
    115                      bg = 'lightblue',\
    116                      highlightbackground = 'lightblue',\
    117                      disabledforeground = 'black',\
    118                      width = 25,\
    119                      height = 2,\
    120                      text = 'Started on',\
    121                      font = ('Courier','10','bold'),\
    122                      anchor = CENTER,\
    123                      relief = FLAT,\
    124                      state = DISABLED)
    125          b4 = Button(self.jobsWindow,\
    126                      bd = 0,\
    127                      bg = 'lightblue',\
    128                      highlightbackground = 'lightblue',\
    129                      disabledforeground = 'black',\
    130                      width = 5,\
    131                      height = 2,\
    132                      text = 'PID',\
    133                      font = ('Courier','10','bold'),\
    134                      anchor = CENTER,\
    135                      relief = FLAT,\
    136                     state = DISABLED)
    137          b5 = Button(self.jobsWindow,\
    138                      bd = 0,\
    139                      bg = 'lightblue',\
    140                      highlightbackground = 'lightblue',\
    141                      disabledforeground = 'black',\
    142                      width = 10,\
    143                      height = 2,\
    144                      text = 'Owner',\
    145                      font = ('Courier','10','bold'),\
    146                      anchor = CENTER,\
    147                      relief = FLAT,\
    148                      state = DISABLED)
    149          b6 = Button(self.jobsWindow,\
    150                      bd = 0,\
    151                      bg = 'lightblue',\
    152                      highlightbackground = 'lightblue',\
    153                      disabledforeground = 'black',\
    154                      width = 15,\
    155                      height = 2,\
    156                      text = 'Progress in %',\
    157                      font = ('Courier','10','bold'),\
    158                      anchor = CENTER,\
    159                      relief = FLAT,\
    160                      state = DISABLED)
    161
    162          self.jobsWindow.configure(state = NORMAL)
    163          self.jobsWindow.window_create(END, window = b1)
    164          self.jobsWindow.window_create(END, window = b2)
    165          self.jobsWindow.window_create(END, window = b3)
    166          self.jobsWindow.window_create(END, window = b4)
    167          self.jobsWindow.window_create(END, window = b5)
    168          self.jobsWindow.window_create(END, window = b6)
    169          self.jobsWindow.insert(END, '\n')
    170          self.jobsWindow.configure(state = DISABLED)
    171
    172          self.refresh()
    173
    174          return None
    175
    176 -    def buttonbox(self):
    177          """
    178          Add standard button box.
    179          """
    180
    181          # The frame that contains the 'Cancel' and 'OK' buttons.
    182          box = LabelFrame(self, text = 'Actions', bd = 2, relief = GROOVE)
    183          box.grid(row = 1, column = 0, sticky = EW, padx = 3, pady = 3)
    184          box.grid_columnconfigure(0, weight = 1)
    185
    186          w = Button(box, text = "Cancel", width = 10, command = self.cancel)
    187          w.grid(row = 0, column = 0, sticky = E)
    188          w = Button(box, text = "OK", width=10, command = self.ok, default=ACTIVE)
    189          w.grid(row = 0, column = 1, sticky = E)
    190
    191          self.bind("<Return>", self.ok)
    192          self.bind("<Escape>", self.cancel)
    193
    194      # Standard button semantics.
    195 -    def ok(self, event = None):
    196
    197          if not self.validate():
    198              self.initial_focus.focus_set()
    199              return
    200
    201          self.withdraw()
    202          self.update_idletasks()
    203
    204          self.apply()
    205
    206          self.cancel()
    207
    208 -    def cancel(self, event=None):
    209
    210          # Put focus back to the parent window
    211          self.parent.focus_set()
    212          self.destroy()
    213
    214      # Command hooks
    215 -    def validate(self):
    216          return True
    217
    218      # Command hooks
    219 -    def apply(self):
    220          pass
    221
    222 -    def refresh(self):
    223          """Refreshes the nMOLDYN running jobs list and its associated frame.
    224          """
    225
    226          # The list of the active and inactive jobs.
    227          self.activeJobs, self.inactiveJobs = self.findJobs()
    228
    229          print self.activeJobs, self.inactiveJobs
    230
    231          # The temporary file corresponding to the inactive jobs are deleted.
    232          for jobs in self.inactiveJobs:
    233              os.unlink(jobs)
    234
    235          jobsPID = []
    236          jobsStart = []
    237          jobsOwner = []
    238          jobsName = []
    239          jobsProgress = []
    240
    241          # Loop over all the nMOLDYN active jobs.
    242          for actJob in self.activeJobs:
    243
    244              # The rest is inside.
    245              tempFile = TextFile(actJob,'r')
    246              data = tempFile.readlines()
    247              tempFile.close()
    248
    249              # The job Id
    250              jobsPID.append(data[0].strip())
    251              # The job starting time.
    252              jobsStart.append(data[1].strip())
    253              # The owner of the job.
    254              jobsOwner.append(data[2].strip())
    255              # The job name.
    256              jobsName.append(data[3].strip())
    257              # The last line of the temporary file contains the current step of the running job.
    258              jobsProgress.append(data[-1].strip())
    259
    260          # Look for jobs that have finished.
    261          for k in self.jobs.keys():
    262              if k not in jobsPID:
    263                  self.jobs[k]['kill'].config(state = DISABLED)
    264                  self.jobs[k]['progress'].config(text = '100 %')
    265              else:
    266                  self.jobs[k]['progress'].config(text = jobsProgress[jobsPID.index(k)] + ' %')
    267
    268          for jobPID in jobsPID:
    269
    270              if not self.jobs.has_key(jobPID):
    271                  tempFile = self.activeJobs[jobsPID.index(jobPID)]
    272                  jobStart = jobsStart[jobsPID.index(jobPID)]
    273                  jobOwner = jobsOwner[jobsPID.index(jobPID)]
    274                  jobName = jobsName[jobsPID.index(jobPID)]
    275                  self.jobs[jobPID] = {'name' : jobName, 'start' : jobStart, 'owner' : jobOwner, 'file' : tempFile}
    276
    277                  self.jobs[jobPID]['kill'] = Button(self.jobsWindow,\
    278                                                     bd = 0,\
    279                                                     bg = 'red',\
    280                                                     width = 110,\
    281                                                     height = 22,\
    282                                                     bitmap = "@"+os.path.join(nmoldyn_package_path, 'GUI', 'Images', 'xbones.xbm'),\
    283                                                     anchor = CENTER,\
    284                                                     relief = RAISED,\
    285                                                     activebackground = 'red',\
    286                                                     command = lambda : self.killJobs(int(jobPID)))
    287
    288                  b2 = Button(self.jobsWindow,\
    289                              bd = 0,\
    290                              bg = 'white',\
    291                              highlightbackground = 'white',\
    292                              disabledforeground = 'black',\
    293                              width = 40,\
    294                              height = 2,\
    295                              text = self.jobs[jobPID]['name'],\
    296                              font = ('Courier','10','bold'),\
    297                              anchor = CENTER,\
    298                              relief = FLAT,\
    299                              state = DISABLED)
    300
    301                  b3 = Button(self.jobsWindow,\
    302                              bd = 0,\
    303                              bg = 'white',\
    304                              highlightbackground = 'white',\
    305                              disabledforeground = 'black',\
    306                              width = 25,\
    307                              height = 2,\
    308                              text = self.jobs[jobPID]['start'],\
    309                              font = ('Courier','10','bold'),\
    310                              anchor = CENTER,\
    311                              relief = FLAT,\
    312                              state = DISABLED)
    313
    314                  b4 = Button(self.jobsWindow,\
    315                              bd = 0,\
    316                              bg = 'white',\
    317                              highlightbackground = 'white',\
    318                              disabledforeground = 'black',\
    319                              width = 5,\
    320                              height = 2,\
    321                              text = jobPID,\
    322                              font = ('Courier','10','bold'),\
    323                              anchor = CENTER,\
    324                              relief = FLAT,\
    325                              state = DISABLED)
    326
    327                  b5 = Button(self.jobsWindow,\
    328                              bd = 0,\
    329                              bg = 'white',\
    330                              highlightbackground = 'white',\
    331                              disabledforeground = 'black',\
    332                              width = 10,\
    333                              height = 2,\
    334                              text = self.jobs[jobPID]['owner'],\
    335                              font = ('Courier','10','bold'),\
    336                              anchor = CENTER,\
    337                              relief = FLAT,\
    338                              state = DISABLED)
    339
    340                  self.jobs[jobPID]['progress'] = Button(self.jobsWindow,\
    341                                                         bd = 0,\
    342                                                         bg = 'white',\
    343                                                         highlightbackground = 'white',\
    344                                                         disabledforeground = 'black',\
    345                                                         width = 15,\
    346                                                         height = 2,\
    347                                                         text = jobsProgress[jobsPID.index(jobPID)] + ' %',\
    348                                                         font = ('Courier','10','bold'),\
    349                                                         anchor = CENTER,\
    350                                                         relief = FLAT,\
    351                                                         state = DISABLED)
    352
    353                  self.jobsWindow.configure(state = NORMAL)
    354                  self.jobsWindow.window_create(END, window = self.jobs[jobPID]['kill'])
    355                  self.jobsWindow.window_create(END, window = b2)
    356                  self.jobsWindow.window_create(END, window = b3)
    357                  self.jobsWindow.window_create(END, window = b4)
    358                  self.jobsWindow.window_create(END, window = b5)
    359                  self.jobsWindow.window_create(END, window = self.jobs[jobPID]['progress'])
    360                  self.jobsWindow.insert(END, '\n')
    361                  self.jobsWindow.configure(state = DISABLED)
    362
    363          # Register an alarm callback called every 5s.
    364          # Returns the alarm Id.
    365          self.afterId = self.jobsWindow.after(5000, self.refresh)
    366
    367 -    def findJobs(self):
    368          """
    369          This method find the nMOLDYN active and inactive jobs.
    370          Output:
    371              -a list of the temporary nMOLDYN running job logfiles.
    372          """
    373
    374          # Some platforms specific import and setups.
    375          if sys.platform == 'win32':
    376              owner = win32api.GetUserName()
    377          else:
    378              try:
    379                  owner = os.environ['USER']
    380              except KeyError:
    381                  owner = pwd.getpwuid(os.getuid())[0]
    382
    383          # The list of all the nMOLDYN temporary logfile names.
    384          jobNames = glob(os.path.join(gettempdir(),'*'+owner+'*moldyn'))
    385
    386          # |jobsDict| = lookup dictionnary whose keys are the jobs PID and the variables the jobs names.
    387          jobsDict = {}
    388          for job in jobNames:
    389              pid = re.findall(owner+'\.(\d*)\.',job)[0]
    390              jobsDict[pid] = job
    391
    392          activeJobs, inactiveJobs = [], []
    393          # Some platforms specific import and setups.
    394          if sys.platform == 'win32':
    395              for jobPID in jobsDict:
    396                  try:
    397                      jobHandle = win32api.OpenProcess(1, 0, int(jobPID))
    398                  except:
    399                      inactiveJobs.append(jobsDict[jobPID])
    400                  else:
    401                      activeJobs.append(jobsDict[jobPID])
    402                      win32api.CloseHandle(jobHandle)
    403          else:
    404              # |activeJobs| = list of currently nMOLDYN active jobs.
    405              for jobPID in jobsDict:
    406                  try:
    407                      os.getpgid(int(jobPID))
    408                  except:
    409                      inactiveJobs.append(jobsDict[jobPID])
    410                  else:
    411                      activeJobs.append(jobsDict[jobPID])
    412
    413          return activeJobs, inactiveJobs
    414
    415 -    def killJobs(self, pid):
    416          """
    417          This method is called when the user press the button 'Kill' of the dialog.
    418          It loops over all the running jobs and for those which have been selected to be killed
    419          asks the user to confirm that (s)he really wants to kill them. If so, kills them and
    420          updates the dialog.
    421
    422          Arguments:
    423              - pid: the pid of the job to kill.
    424          """
    425
    426          # A Warning dialog for the confirmation of the killing.
    427          dialog = Dialog.Dialog(None,\
    428                                 title = 'Warning',\
    429                                 text = 'Do you really want to kill the job with pid %s ?.' % pid,\
    430                                 bitmap = 'warning',\
    431                                 default = 0,\
    432                                 strings = ('No', 'Yes'))
    433
    434          if dialog.num == 0:
    435              # The job should not ne killed.
    436              return
    437
    438          elif dialog.num == 1:
    439              # WIN32
    440              if sys.platform == 'win32':
    441                  handle = win32api.OpenProcess(1, 0, pid)
    442                  win32api.TerminateProcess(handle, 0)
    443              # POSIX.
    444              else:
    445                  os.kill(pid, 9)
    446
    447              self.jobs[str(pid)]['progress'].config(text = 'Killed')
    448              try:
    449                  os.unlink(self.jobs[str(pid)]['file'])
    450              except:
    451                  pass
    452              del self.jobs[str(pid)]
    453
    454              # Once the job killed, updates the dialog.
    455              self.refresh()
    456