Created
January 14, 2010 19:29
-
-
Save jaheba/277418 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) | |
## 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