Programm Lock Mit diesem Skript kann man verhindern das ein Programm mehrmals gestartet wird, siehe DocString.

Aus http://www.python-forum.de/topic-8282.html :

   1 #!/usr/bin/env python
   2 # -*- coding: iso-8859-1 -*-
   3 """
   4 Die Klasse ``FirstStart`` ist dazu da, um zu prüfen ob ein Programm bereits
   5 gestartet wurde.
   6 Dabei wird zwischen einem Pro-User-Modus und einem globalen Modus
   7 unterschieden. Im Pro-User-Modus wird nur für den aktuellen Benutzer
   8 geprüft, ob das Programm bereits gestartet wurde, im globalen Modus
   9 geschieht dies benutzerübergreifend. (Siehe dazu auch die Beschreibung
  10 der __init__ Methode.)
  11 Der Mechanismus arbeitet mit einem Lockfile. Können die
  12 PIDs der laufenden Programme nicht eruiert werden, dann wird auf eine Lösung
  13 mit XMLRPC ausgewichen, allerdings nur im globbalen Modus.
  14 Einen Beispielaufruf findet man in der ``main()``.
  15 
  16 Ursprüngliche Version:   13.12.2006 Gerold Penz - gerold.penz(at)tirol.utanet.at
  17 Verschiedene Änderungen: 05.04.2007 Oliver Kitzing
  18 MacOSX Tests:            05.04.2007 Henrik "Kato" Lowack
  19 Anforderungen:           Python: http://www.python.org/
  20                          Für Windows wird "pywin32" oder alternativ
  21                          "ctypes" empfohlen, ab Windows XP geht es auch
  22                          ohne diese Erweiterungen.
  23                          (Python ab Version 2.5 hat ctypes bereits integriert.)
  24                          http://sourceforge.net/projects/pywin32/
  25                          http://sourceforge.net/projects/ctypes/
  26 """
  27 
  28 import os
  29 import sys
  30 
  31 
  32 def do_command(command):
  33     """
  34     Führt ein Shell-Kommando aus, je nach Python-Version in
  35     der "richtigen" Art und Weise.
  36     :return: Liste mit den Zeilen der Ausgabe des Shell-Kommandos.
  37     """
  38     try:
  39         # Für Python ab 2.4
  40         from subprocess import Popen, PIPE
  41         p1 = Popen(command, stdout=PIPE, shell=True)
  42         output = p1.communicate()[0]
  43         lines = output.split("\n")
  44     except ImportError:
  45         # Ältere Python Versionen
  46         p1 = os.popen(command, 'r')
  47         lines = p1.readlines()
  48 
  49     return lines
  50 
  51 
  52 def set_access_rights(targetdir, dir, files):
  53     """
  54     Das ist eine Callback - Funktion, die von
  55     "os.path.walk" aufgerufen wird. Wir brauchen diese, um im
  56     Falle des globalen Verzeichnisses die Zugriffsrechte
  57     für alle Verzeichnisse im Pfad zu setzen.
  58     HINWEIS: Diese Funktion sollte nie von uns selbst aufgerufen
  59     werden, es handelt sich um eine reine Callback-Funktion.
  60     :param targetdir: Das Startverzeichnis (voller Pfad)
  61     :param dir:       Das aktuelle Verzeichnis
  62     :param files:     Die zu bearbeiteten Dateien
  63     """
  64     import os
  65 
  66     if targetdir in dir:
  67         os.chmod(dir, 0777)
  68 
  69 
  70 def get_current_pids():
  71     """
  72     Liefert die aktuellen Prozess-IDs.
  73     Funktioniert mit Windows 32-Bit Versionen (64-Bit nicht getestet,
  74     sollte aber funktionieren), Linux, MacOSX.
  75     Sollte auch mit diversen BSD-Varianten funktionieren, aber noch nicht
  76     getestet.
  77 
  78     :return: Liste mit den Prozess-IDs
  79     """
  80 
  81     if sys.platform.startswith("win"):
  82         # Windows
  83         try:
  84             import win32process
  85             # pywin32 ist installiert --> EnumProcesses
  86             return list(win32process.EnumProcesses())
  87         except ImportError:
  88             try:
  89                 import ctypes as ct
  90                 # ctypes ist installiert --> Probiere mit psapi.dll
  91                 psapi = ct.windll.psapi
  92                 arr = ct.c_long * 1024
  93                 process_ids = arr()
  94                 cb = ct.sizeof(process_ids)
  95                 bytes_returned = ct.c_ulong()
  96                 psapi.EnumProcesses(ct.byref(process_ids), cb,
  97                                     ct.byref(bytes_returned))
  98                 return sorted(list(set(process_ids)))
  99             except ImportError:
 100                 # pywin32 und ctypes sind nicht installiert --> tasklist.exe
 101                 # Läuft mit Windows XP und höher.
 102                 import csv
 103                 csvlines = []
 104                 current_pids = []
 105                 for line in do_command("tasklist.exe /fo csv /nh"):
 106                     line = line.strip()
 107                     if line:
 108                         csvlines.append(line)
 109                 for line in csv.reader(csvlines):
 110                     current_pids.append(int(line[1]))
 111                 if not current_pids:
 112                     raise NotImplementedError("tasklist.exe not found (>WinXP)")
 113                 return current_pids
 114     else:
 115         # Linux, Cygwin, BSD-Varianten, MacOSX
 116         # Wir fragen hier nicht etwa das "/proc" Verzeichnis
 117         # ab, was unter Linux gehen würde, aber nicht unter den
 118         # BSD - Systemen (wie MacOSX), sondern wir lesen einfach
 119         # die Ausgabe des Befehls "ps a" aus.
 120         current_pids = []
 121         command = 'ps a'
 122         lines = do_command(command)
 123 
 124         # Überspringe erste Zeile, die die Header-Zeile ist
 125         for line in lines[1:]:
 126             line = line.strip()
 127             if line != "":
 128                 pid = line.strip().split(" ")[0]
 129                 try:
 130                     current_pids.append(int(pid))
 131                 except ValueError:
 132                     pass
 133 
 134         return current_pids
 135 
 136 
 137 class FirstStart(object):
 138 
 139     usexmlrpc = False
 140 
 141     def __init__(self, appname, global_lock = False, debug_mode = False):
 142         """
 143         Initialisiert die Klasse und legt den Lockfilenamen fest.
 144 
 145         :param appname: Eindeutiger Name der Anwendung. Anhand dieses
 146           Parameters wird der Name des Lockfiles oder der Serverport für den
 147           XMLRPC-Server generiert.
 148         :param global_lock: Wenn True, dann wird diese Anwendung systemweit
 149           nur einmal ausgeführt, auch bei Mehrbenutzer-Systemen.
 150         :param debug_mode: Wenn True, dann Ausgabe von Debug-Informationen.
 151         """
 152         self.os = ""
 153         self.appname = appname
 154         self.xmlrpcport = None
 155         self.global_lock = global_lock
 156         self.debug_mode = debug_mode
 157         self.appdir = None
 158         self.lockfiledir = None
 159         self.lockfilename = None
 160         self.xmlrpcport = int("5" + \
 161         str(hash(self.appname)).replace("0","").replace("-", "")[:4])
 162         lockdir = None
 163 
 164         self._debugprint("XMLRPCPORT: " + str(self.xmlrpcport))
 165 
 166         # Lockfilename festlegen
 167 
 168         if os.name in ['nt', 'dos']:
 169             self._debugprint("Windows-System entdeckt")
 170             self.os = "win"
 171             appdatadir = os.environ.get("APPDATA", None)
 172             allusersdir = os.environ.get("ALLUSERSPROFILE", None)
 173             if appdatadir and allusersdir:
 174                 globaldir = allusersdir + appdatadir[appdatadir.rfind('\\'):]
 175             else:
 176                 globaldir = None
 177         else:
 178             # POSIX - Code: Könnte eventuell noch problematisch
 179             # sein.. "/tmp" wirklich bei allen Linux / BSD Systemen
 180             # für normale Nutzer schreibbar? (Bei bisherigen Tests, ja).
 181             # Der Filesystem Hierarchy Standard ist hier nicht so ganz klar,
 182             # eventuell ist auch "/var/tmp" geeignet.
 183             self.os = "posix"
 184             self._debugprint("POSIX-System entdeckt")
 185             globaldir = "/tmp"
 186 
 187 
 188         if self.global_lock:
 189             self._debugprint("Global-Modus aktiv")
 190             if globaldir:
 191                 self.appdir = appdir = os.path.join(globaldir, appname)
 192                 lockdir = os.path.join(appdir, "Lockfiles")
 193             else:
 194                 lockdir = None
 195         else:
 196             # Wir speichern bei POSIX-Systemen nicht mehr in
 197             # "/var/lock", weil die Rechte nicht unbedingt bei jedem
 198             # System dergestalt gesetzt sind, dass Prozesse mit einfachen
 199             # Nutzerrechten auch schreiben können (z.B. gibt es bei diversen
 200             # Linux-Varianten hier Unterschiede), stattdessen speichern
 201             # wir im HOME - Verzeichnis.
 202             self._debugprint("Pro-User Modus aktiv")
 203             env_home = self._get_home_dir()
 204 
 205             if env_home:
 206                 self.appdir = appdir = os.path.join(env_home, "." + appname)
 207                 lockdir = os.path.join(appdir, "Lockfiles")
 208 
 209         # Prüfen, ob dieses Verzeichnis existiert, evtl. erstellen.
 210         if lockdir:
 211             if not os.path.exists(lockdir):
 212                 self._debugprint("Lockdir-Verzeichnis existiert noch nicht,"
 213                                 " erstelle es")
 214                 os.makedirs(lockdir)
 215                 # Setze noch die Zugriffsrechte (nur bei globalen Modus)
 216                 if self.global_lock:
 217                     os.path.walk(globaldir, set_access_rights, self.appdir)
 218 
 219 
 220             self.lockfiledir = lockdir
 221             self.lockfilename = os.path.join(lockdir, appname + "_pylock.lock")
 222             self._debugprint("Lockfilename: "  + str(self.lockfilename))
 223 
 224 
 225     def _get_lockfile_pid(self):
 226         """
 227         Gibt die PID zurueck, die im Lockfile steht.
 228         :return: Die PID.
 229         """
 230 
 231         f = file(self.lockfilename, "r")
 232         try:
 233             pid = int(f.readline().strip())
 234         except ValueError:
 235             import warnings
 236             warnings.warn("Lockfile without valid PID: '%s'" % self.lockfilename)
 237             raise
 238         f.close()
 239         return pid
 240 
 241 
 242     def _exists_lockfile(self):
 243         """
 244         Prüft ob das Lockfile existiert.
 245         :return: True, wenn ja, ansonsten False.
 246         """
 247         if self.lockfilename and os.path.isfile(self.lockfilename):
 248             return True
 249 
 250 
 251     def __xmlrpc_server(self, port):
 252         """
 253         XMLRPC-Server. Dieser läuft, gestartet von ``self._start_xmlrpc_server``,
 254         innerhalb eines eigenen Threads.
 255         :param port: Der zu verwendende Port für den XMLRPC-Server.
 256         """
 257 
 258         from SimpleXMLRPCServer import SimpleXMLRPCServer
 259         import socket # Wegen socket.error
 260 
 261         def is_up():
 262             return True
 263 
 264         try:
 265             server = SimpleXMLRPCServer(("localhost", port))
 266             server.register_function(is_up)
 267             server.serve_forever()
 268         except socket.error:
 269             pass
 270 
 271 
 272     def _start_xmlrpc_server(self, port):
 273         """
 274         Startet den XMLRPC-Server
 275         :param port: Der zu verwendende Port für den XMLRPC-Server.
 276         """
 277 
 278         from thread import start_new_thread
 279 
 280         start_new_thread(self.__xmlrpc_server, (port,))
 281 
 282 
 283     def create_lock(self):
 284         """
 285         Erstellt ein Lockfile mit der aktuellen PID oder startet den
 286         XMLRPC-Server.
 287         """
 288         if self.lockfilename:
 289             f = file(self.lockfilename, "w")
 290             f.write(str(os.getpid()))
 291             f.close()
 292         else:
 293             self._debugprint("Keine Lockfile-Erstellung moeglich,"
 294                             " versuche stattdessen XMLRPC-Loesung")
 295             self._start_xmlrpc_server(self.xmlrpcport)
 296 
 297     create_lockfile = create_lock # --> um abwärtskompatibel zu bleiben
 298 
 299 
 300     def delete_lock(self):
 301         """
 302         Löscht das Lockfile der Anwendung. Der XMLRPC-Server wird NICHT beendet, da
 303         dieser sowiso nach Programmende automatisch beendet wird.
 304 
 305         HINWEIS:
 306         Es wird nur der Lockfile selber gelöscht, nicht die angelegten Verzeichnisse.
 307         Sollte das gewünscht sein, müsste diese Methode noch dementsprechend
 308         umgeschrieben werden.
 309 
 310         (Sollte es notwendig sein, dass der XMLRPC-Server mit dem Aufruf dieser
 311         Methode beendet wird, dann müsste man den XMLRPC-Server so umschreiben,
 312         dass nicht ``server.serve_forever()`` verwendet wird.)
 313         """
 314 
 315         if not self.usexmlrpc:
 316             if self._exists_lockfile():
 317                 os.remove(self.lockfilename)
 318         else:
 319             pass
 320 
 321     delete_lockfile = delete_lock # --> um abwärtskompatibel zu bleiben
 322 
 323 
 324     def is_first_start(self):
 325         """
 326         Prüft ob die beim Initialisieren angegebene Anwendung bereits ausgeführt wird.
 327 
 328         :return: True, wenn die Anwendung bereits läuft.
 329                  False, wenn die Anwendung noch nicht läuft.
 330                  None, wenn kein Lockfile erstellt werden konnte.
 331         """
 332 
 333         if self.usexmlrpc:
 334             return self._check_via_xmlrpc()
 335 
 336         return self._check_via_lockfile()
 337 
 338 
 339     def _check_via_lockfile(self):
 340         """
 341         Prüft, ob eine Sperrung mittels Lockfile vorliegt.
 342         :return: False, wenn ein Lockfile da ist (umgekehrte Logik, weil
 343                  False aussagt "Programm NICHT starten", True wenn KEIN Lockfile
 344                  da ist ("Programm starten ERLAUBT")
 345         """
 346         # LOCKFILE-Lösung und PID-Lösung (letzteres nur für globalen Modus)
 347         self._debugprint("Versuche LOCKFILE-Methode")
 348         lockfilename = self.lockfilename
 349 
 350         # Wenn bei Initialisierung der FirstStart-Instanz erfolgreich das
 351         # Lockfile-Verzeichnis gefunden bzw. angelegt werden konnte,
 352         # dann prüfe ob darin das Lockfile existiert.
 353         # Wenn Lockfile-Verzeichnis nicht korrekt, Rückfall auf die
 354         # XMLRPC-Methode
 355         if self.lockfiledir:
 356             if not self._exists_lockfile():
 357                 self._debugprint("Es existiert KEIN Lockfile!")
 358                 return True
 359 
 360             # Lockfile ist da, wir checken jetzt die PID, ob es sich wirklich
 361             # um den gesuchten Prozess handelt, ansonsten könnte nämlich der
 362             # Lockfile zwar von der gleichen Anwendung stammen, die aber vorher
 363             # mal inkorrekt beendet worden ist.
 364             try:
 365                 lockfilepid = self._get_lockfile_pid()
 366                 currentpids = get_current_pids()
 367                 self._debugprint("LOCKFILEPID: " + str(lockfilepid))
 368             except (NotImplementedError, ValueError, IOError):
 369                 # Wenn die PID nicht ermittelt werden kann,
 370                 # können wir nicht wirklich weitermachen,
 371                 self._debugprint("Konnte PID nicht ermitteln, Abbruch!")
 372                 return None
 373         else:
 374             if self.global_lock:
 375                 self.usexmlrpc = True
 376                 return self.is_first_start()
 377             else:
 378                 return None
 379 
 380         if lockfilepid in currentpids:
 381             # Das Programm läuft noch
 382             self._debugprint("PID gefunden, Programmprozess laeuft noch!")
 383             return False
 384         else:
 385             # Das Programm mit der ermittelten PID ist nicht gestartet.
 386             self._debugprint("PID NICHT gefunden, starten erlaubt, "
 387                             "lösche evtl. verwaisten Lockfile!")
 388             self.delete_lockfile()
 389             return True
 390 
 391 
 392     def _check_via_xmlrpc(self):
 393         """
 394         Prüft, ob eine Sperrung über dem XMLRPC-Server vorliegt
 395         (nur für den globalen Modus von Belang).
 396         :return: True, wenn Sperrung vorhanden.
 397                  False, wenn nicht.
 398         """
 399         # XMLRPC-Lösung
 400         self._debugprint("Versuche XMLRPC-Methode")
 401 
 402         import xmlrpclib
 403         import socket
 404 
 405         try:
 406             server = xmlrpclib.ServerProxy("http://localhost:" +
 407                                            str(self.xmlrpcport))
 408             return not bool(server.is_up())
 409         except socket.error:
 410             return True
 411 
 412 
 413     def _get_home_dir(self):
 414         """
 415         Eine einfache Methode um "möglichst" plattformübergreifend das
 416         Home-Verzeichnis zu erhalten.
 417         (Getestet auf Windows 2000/XP (32-Bit), Linux, MacOSX. Vista und
 418         64-Bit-Windows-Varianten stehen noch aus.)
 419 
 420         :return: Das Homeverzeichnis als String, wenn Fehler dann None.
 421         """
 422 
 423         appdir = self.appname
 424         dirname = ""
 425         home_dir = ""
 426 
 427         try:
 428             if os.name == "posix":
 429                home_dir = os.environ.get("HOME", None)
 430             elif os.name in ['nt', 'dos']:
 431                 # Windows 2000 scheint die HOMEPATH Umgebungsvariable
 432                 # nicht zu kennen, in dem Falle wird auf USERPROFILE
 433                 # zurückgegriffen.
 434                 if os.getenv("HOMEPATH") == "\\":
 435                     home_dir = os.environ.get("USERPROFILE", None)
 436                 else:
 437                     # In HOMEPATH ist das Laufwerk nicht angegeben,
 438                     # das muss deswegen extra abgefragt werden mit
 439                     # HOMEDRIVE.
 440                     home_dir = os.path.join(os.environ.get("HOMEDRIVE", None) ,\
 441                                         os.environ.get("HOMEPATH", None))
 442             else:
 443                 return None
 444 
 445         except OSError:
 446             raise Exception, "Exception: " + str(sys.exc_info()[0])
 447             return None
 448 
 449         if not (os.path.exists(home_dir) and os.path.isdir(home_dir)):
 450             os.mkdir(home_dir)
 451 
 452         return home_dir
 453 
 454 
 455     def _debugprint(self, text):
 456         """
 457         Nur eine einfache Print-Methode, die nur aktiv
 458         ist wenn Instanzvariable self.debug_mode auf True steht.
 459         ACHTUNG: Variable self.debug_mode muss in dieser Klasse definiert sein.
 460         :param text: Der zu druckende Text.
 461         """
 462         if self.debug_mode:
 463             print text
 464 
 465 
 466 def main():
 467     """Testen"""
 468     import time
 469 
 470     globalmode = False
 471     debugmode = False
 472     valid_option_found = False
 473 
 474     if len(sys.argv) > 1:
 475         if "--global" in sys.argv:
 476             globalmode = True
 477             valid_option_found = True
 478 
 479         if "--debug" in sys.argv:
 480             debugmode = True
 481             valid_option_found = True
 482 
 483         # Diese Ausgabe eventuell mal den Gepflogenheiten bei
 484         # den Manpages (Linux/BSD und Konsorten) anpassen.
 485         # Ist aber ohnehin nur für diesen Beispielcode.
 486         if not valid_option_found:
 487             print ( "WARNUNG: Es war zumindest ein Parameter"
 488                     "vorhanden, der nicht gueltig war.")
 489             print ("Gueltige Optionen:")
 490             print ("--global: Pruefe systemweit, ob Prozess bereits laeuft.")
 491             print ("--debug: Erweiterte Informationen bei der Ausgabe.")
 492             sys.exit(1)
 493 
 494 
 495     firststart = FirstStart("testapplikation", globalmode, debugmode)
 496 
 497     # Wenn man noch den Fall unterscheiden wollte, ob "is_first_start()" wegen
 498     # eines Fehlers abgebrochen hat, müsste man explizit den Rückgabewert auf
 499     # "None" testen. Eventuell könnte man natürlich mal "is_first_start()" so
 500     # umschreiben, dass eine entsprechende Exception geworfen wird.
 501     if firststart.is_first_start():
 502         firststart.create_lock()
 503 
 504         # Hier wird gearbeitet
 505         for i in range(15):
 506             print i
 507             time.sleep(1)
 508         print
 509 
 510         ## wxPython-App
 511         #import wx
 512         #app = wx.PySimpleApp(True)
 513         #print "Ich bin ein Fenster"
 514         #app.MainLoop()
 515 
 516         firststart.delete_lock()
 517     else:
 518         print ("Das Programm wurde bereits gestartet oder keine "
 519               "Lockfile-Erstellung moeglich..")
 520 
 521 
 522 if __name__ == "__main__":
 523     main()

Tags: Codesnippets

Programm Lock (zuletzt geändert am 2010-01-14 15:25:15 durch anonym)