/ src / singleinstance.py
singleinstance.py
  1  """
  2  This is based upon the singleton class from
  3  `tendo <https://github.com/pycontribs/tendo>`_
  4  which is under the Python Software Foundation License version 2
  5  """
  6  
  7  import atexit
  8  import os
  9  import sys
 10  
 11  from . import state
 12  
 13  try:
 14      import fcntl  # @UnresolvedImport
 15  except ImportError:
 16      pass
 17  
 18  
 19  class singleinstance(object):
 20      """
 21      Implements a single instance application by creating a lock file
 22      at appdata.
 23      """
 24      def __init__(self, flavor_id="", daemon=False):
 25          self.initialized = False
 26          self.counter = 0
 27          self.daemon = daemon
 28          self.lockPid = None
 29          self.lockfile = os.path.normpath(
 30              os.path.join(state.appdata, 'singleton%s.lock' % flavor_id))
 31  
 32          if state.enableGUI and not self.daemon and not state.curses:
 33              # Tells the already running (if any) application to get focus.
 34              from . import bitmessageqt
 35              bitmessageqt.init()
 36  
 37          self.lock()
 38  
 39          self.initialized = True
 40          atexit.register(self.cleanup)
 41  
 42      def lock(self):
 43          """Obtain single instance lock"""
 44          if self.lockPid is None:
 45              self.lockPid = os.getpid()
 46          if sys.platform == 'win32':
 47              try:
 48                  # file already exists, we try to remove
 49                  # (in case previous execution was interrupted)
 50                  if os.path.exists(self.lockfile):
 51                      os.unlink(self.lockfile)
 52                  self.fd = os.open(
 53                      self.lockfile,
 54                      os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_TRUNC
 55                  )
 56              except OSError as e:
 57                  if e.errno == 13:
 58                      sys.exit(
 59                          'Another instance of this application is'
 60                          ' already running')
 61                  raise
 62              else:
 63                  pidLine = "%i\n" % self.lockPid
 64                  os.write(self.fd, pidLine)
 65          else:  # non Windows
 66              self.fp = open(self.lockfile, 'a+')
 67              try:
 68                  if self.daemon and self.lockPid != os.getpid():
 69                      # wait for parent to finish
 70                      fcntl.lockf(self.fp, fcntl.LOCK_EX)
 71                  else:
 72                      fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
 73                  self.lockPid = os.getpid()
 74              except IOError:
 75                  sys.exit(
 76                      'Another instance of this application is'
 77                      ' already running')
 78              else:
 79                  pidLine = "%i\n" % self.lockPid
 80                  self.fp.truncate(0)
 81                  self.fp.write(pidLine)
 82                  self.fp.flush()
 83  
 84      def cleanup(self):
 85          """Release single instance lock"""
 86          if not self.initialized:
 87              return
 88          if self.daemon and self.lockPid == os.getpid():
 89              # these are the two initial forks while daemonizing
 90              try:
 91                  if sys.platform == 'win32':
 92                      if hasattr(self, 'fd'):
 93                          os.close(self.fd)
 94                  else:
 95                      fcntl.lockf(self.fp, fcntl.LOCK_UN)
 96              except (IOError, OSError):
 97                  pass
 98  
 99              return
100  
101          try:
102              if sys.platform == 'win32':
103                  if hasattr(self, 'fd'):
104                      os.close(self.fd)
105                      os.unlink(self.lockfile)
106              else:
107                  fcntl.lockf(self.fp, fcntl.LOCK_UN)
108                  if os.path.isfile(self.lockfile):
109                      os.unlink(self.lockfile)
110          except (IOError, OSError):
111              pass