Skip to content

Instantly share code, notes, and snippets.

@jaheba
Created January 14, 2010 19:29
Show Gist options
  • Save jaheba/277418 to your computer and use it in GitHub Desktop.
Save jaheba/277418 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#TODO: english docstrings
"""
Die Klasse ``FirstStart`` ist dazu da, um zu prüfen ob ein Programm bereits
gestartet wurde.
Dabei wird zwischen einem Pro-User-Modus und einem globalen Modus
unterschieden. Im Pro-User-Modus wird nur für den aktuellen Benutzer
geprüft, ob das Programm bereits gestartet wurde, im globalen Modus
geschieht dies benutzerübergreifend. (Siehe dazu auch die Beschreibung
der __init__ Methode.)
Der Mechanismus arbeitet mit einem Lockfile. Können die
PIDs der laufenden Programme nicht eruiert werden, dann wird auf eine Lösung
mit XMLRPC ausgewichen, allerdings nur im globbalen Modus.
Einen Beispielaufruf findet man in der ``main()``.
Ursprüngliche Version: 13.12.2006 Gerold Penz - gerold.penz(at)tirol.utanet.at
Verschiedene Änderungen: 05.04.2007 Oliver Kitzing
MacOSX Tests: 05.04.2007 Henrik "Kato" Lowack
Anforderungen: Python: http://www.python.org/
Für Windows wird "pywin32" oder alternativ
"ctypes" empfohlen, ab Windows XP geht es auch
ohne diese Erweiterungen.
(Python ab Version 2.5 hat ctypes bereits integriert.)
http://sourceforge.net/projects/pywin32/
http://sourceforge.net/projects/ctypes/
"""
import os
import sys
PLATFORM = "win" if os.name in ['nt', 'dos'] else "posix"
if PLATFORM is "posix" :
GLOBAL_LOCK_DIR = "/tmp"
USER_LOCK_DIR = os.environ["HOME"]
else:
win_ver = sys.getwindowsversion()
if win_ver >= (6,0): # >= Vista
#C:\ProgramData
GLOBAL_LOCK_DIR = os.environ["ALLUSERSPROFILE"]
else:#TODO does it have to be in APPDATA?
#C:\Documents and Settings\All Users\Application Data
GLOBAL_LOCK_DIR = os.path.join(os.environ["ALLUSERSPROFILE"], os.environ["APPDATA"].rsplit("\\",1[-1]))
if win_ver >= (5,0): # >= 2000
USER_LOCK_DIR = os.path.join(os.environ["HOMEDRIVE"], os.environ["HOMEPATH"])
else:
USER_LOCK_DIR = os.environ["USERPROFILE"]
def do_command(command):
p = Popen(command, stdout=PIPE)
output = p.communicate()[0]
return output.splitlines()
def set_access_rights(targetdir, dir, files):
"""
Das ist eine Callback - Funktion, die von
"os.path.walk" aufgerufen wird. Wir brauchen diese, um im
Falle des globalen Verzeichnisses die Zugriffsrechte
für alle Verzeichnisse im Pfad zu setzen.
HINWEIS: Diese Funktion sollte nie von uns selbst aufgerufen
werden, es handelt sich um eine reine Callback-Funktion.
:param targetdir: Das Startverzeichnis (voller Pfad)
:param dir: Das aktuelle Verzeichnis
:param files: Die zu bearbeiteten Dateien
"""
import os
if targetdir in dir:
os.chmod(dir, 0777)
def get_current_pids():
"""
Liefert die aktuellen Prozess-IDs.
Funktioniert mit Windows 32-Bit Versionen (64-Bit nicht getestet,
sollte aber funktionieren), Linux, MacOSX.
Sollte auch mit diversen BSD-Varianten funktionieren, aber noch nicht
getestet.
:return: Liste mit den Prozess-IDs
"""
if sys.platform.startswith("win"):
# Windows
try:
import win32process
# pywin32 ist installiert --> EnumProcesses
return list(win32process.EnumProcesses())
except ImportError:
try:
import ctypes as ct
# ctypes ist installiert --> Probiere mit psapi.dll
psapi = ct.windll.psapi
arr = ct.c_long * 1024
process_ids = arr()
cb = ct.sizeof(process_ids)
bytes_returned = ct.c_ulong()
psapi.EnumProcesses(ct.byref(process_ids), cb,
ct.byref(bytes_returned))
return sorted(list(set(process_ids)))
except ImportError:
# pywin32 und ctypes sind nicht installiert --> tasklist.exe
# Läuft mit Windows XP und höher.
import csv
csvlines = []
current_pids = []
for line in do_command("tasklist.exe /fo csv /nh"):
line = line.strip()
if line:
csvlines.append(line)
for line in csv.reader(csvlines):
current_pids.append(int(line[1]))
if not current_pids:
raise NotImplementedError("tasklist.exe not found (>WinXP)")
return current_pids
else:
# Linux, Cygwin, BSD-Varianten, MacOSX
# Wir fragen hier nicht etwa das "/proc" Verzeichnis
# ab, was unter Linux gehen würde, aber nicht unter den
# BSD - Systemen (wie MacOSX), sondern wir lesen einfach
# die Ausgabe des Befehls "ps a" aus.
current_pids = []
command = 'ps a'
lines = do_command(command)
# Überspringe erste Zeile, die die Header-Zeile ist
for line in lines[1:]:
line = line.strip()
if line != "":
pid = line.strip().split(" ")[0]
try:
current_pids.append(int(pid))
except ValueError:
pass
return current_pids
class RunUnique(object):
def __init__(self, name=None, dir=USER_LOCK_DIR, prefix='.', extension='.lock', debug=False):
self.name = name if name is not None else os.path.basename(sys.argv[0])
self.path = os.path.join(dir, "%s%s%s" %(prefix, self.name, extension))
self.debug = debug
self.locked = False
if not os.path.exists(dir):
os.makedirs(dir)
#if self.global_lock:
# os.path.walk(globaldir, set_access_rights, self.appdir)
def _get_pid(self):
"""
Gibt die PID zurueck, die im Lockfile steht.
:return: Die PID.
"""
with open (self.path) as f:
return int(f.readline().strip())
def _lock_exists(self):
"""
Prüft ob das Lockfile existiert.
:return: True, wenn ja, ansonsten False.
"""
return os.path.isfile(self.path)
def _delete_lock(self):
if self._lock_exists():
os.remove(self.path)
def acquire(self):
"""Erstellt ein Lockfile mit der aktuellen PID"""
if self.locked:
raise Exception("can't lock more than one time")
if not self.is_blocked():
with open(self.path, 'w') as f:
f.write(str(os.getpid()))
self.locked = True
return self
__enter__ = acquire
def release(self, *args, **kwargs):
"""Löscht das Lockfile der Anwendung"""
if self.locked:
self._delete_lock()
self.locked = False
return True
return False
__exit__ = release
def is_blocked(self):
"return True if any process is running (also not the current)"
try:
if self._get_pid() in get_current_pids():
return True
if self._lock_exists():
self._delete_lock()
return False
except (NotImplementedError, ValueError, IOError):
return None
def __nonzero__(self):
'we have the lock'
return self.locked
def main():
"""Testen"""
import time
globalmode = False
debugmode = False
valid_option_found = False
if len(sys.argv) > 1:
if "--global" in sys.argv:
globalmode = True
valid_option_found = True
if "--debug" in sys.argv:
debugmode = True
valid_option_found = True
# Diese Ausgabe eventuell mal den Gepflogenheiten bei
# den Manpages (Linux/BSD und Konsorten) anpassen.
# Ist aber ohnehin nur für diesen Beispielcode.
if not valid_option_found:
print ( "WARNUNG: Es war zumindest ein Parameter"
"vorhanden, der nicht gueltig war.")
print ("Gueltige Optionen:")
print ("--global: Pruefe systemweit, ob Prozess bereits laeuft.")
print ("--debug: Erweiterte Informationen bei der Ausgabe.")
sys.exit(1)
firststart = FirstStart("testapplikation", globalmode, debugmode)
# Wenn man noch den Fall unterscheiden wollte, ob "is_first_start()" wegen
# eines Fehlers abgebrochen hat, müsste man explizit den Rückgabewert auf
# "None" testen. Eventuell könnte man natürlich mal "is_first_start()" so
# umschreiben, dass eine entsprechende Exception geworfen wird.
if firststart.is_first_start():
firststart.create_lock()
# Hier wird gearbeitet
for i in range(15):
print i
time.sleep(1)
print
## wxPython-App
#import wx
#app = wx.PySimpleApp(True)
#print "Ich bin ein Fenster"
#app.MainLoop()
firststart.delete_lock()
else:
print ("Das Programm wurde bereits gestartet oder keine "
"Lockfile-Erstellung moeglich..")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment