Seite 1 von 1

Prüfen ob das Programm bereits gestartet wurde

Verfasst: Mittwoch 13. Dezember 2006, 12:29
von gerold
Hi!

Hier eine Möglichkeit, wie man mit einem Lockfile sicherstellen kann, dass die eigene Anwendung nur einmal gestartet werden kann.

Es wird ein Lockfile angelegt und beim Beenden des Programms wieder gelöscht. Damit ein nicht gelöschtes Lockfile den Start der Anwendung nicht blockiert, wird in das Lockfile die PID des Programmes geschrieben. Diese wird beim Starten des Programms ausgelesen und es wird geprüft, ob ein Programm mit der im Lockfile angegebenen PID noch läuft.
Läuft das Programm mit der angegebenen PID *nicht*, dann wird das Lockfile gelöscht und die Anwendung wird gestartet.

Alls Fallback wurde eine XMLRPC-Lösung mit eingebaut. Können die laufenden PIDs nicht ermittelt werden, dann startet sich ein XMLRPC-Server, der die Anwendung als "gesperrt" kennzeichnet.

first_start.py:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
"""
Die Klasse ``FirstStart`` ist dazu da, um zu prüfen ob ein Programm bereits
gestartet wurde. 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. Einen Beispielaufruf findet man in der ``main()``.

Created:       2006-12-13 by Gerold Penz - gerold.penz(at)tirol.utanet.at
Requirements:  Python: http://www.python.org/
"""

import os
import sys


def _get_current_pids():
    """
    Returns current process-id's.
   
    :return: List with process-id's.
    """
    
    if sys.platform.startswith("win"):
        # Windows
        try:
            import win32process
            # pywin32 is installed --> EnumProcesses
            return list(win32process.EnumProcesses())
        except ImportError:
            try:
                import ctypes as ct
                # ctypes is installed --> try 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 and ctypes are not installed --> tasklist.exe
                # runs with Windows XP and higher.
                import csv
                csvlines = []
                current_pids = []
                for line in os.popen("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
        current_pids = []
        for filename in os.listdir("/proc"):
            if os.path.isdir(os.path.join("/proc", filename)):
                try:
                    current_pids.append(int(filename))
                except ValueError:
                    pass
        return current_pids


class FirstStart(object):
    
    usexmlrpc = False
    
    def __init__(self, appname):
        """
        Initialisiert die Klasse und legt den Lockfilenamen fest.
        
        :param appname: Eindeutiger Name der Anwendung. Anhand dieses
          Parameters wird der Name des Lockfiles oder der Serverport für den
          XMLRPC-Server generiert.
        """
        
        self.appname = appname
        self.xmlrpcport = None
        
        # Lockfilename festlegen
        appdatadir = os.environ.get("APPDATA", None)
        if appdatadir:
            lockdir = os.path.join(appdatadir, "Lockfiles")
        else:
            lockdir = "/var/lock"
        self.lockfiledir = lockdir
        self.lockfilename = os.path.join(lockdir, appname + "_pylock.lock")


    def _get_lockfile_pid(self):
        """
        Gibt die PID zurück, die im Lockfile steht.
        """
        
        f = file(self.lockfilename, "r")
        try:
            pid = int(f.readline().strip())
        except ValueError:
            import warnings
            warnings.warn("Lockfile without valid PID: '%s'" % self.lockfilename)
            raise
        f.close()
        return pid
    
    
    def _exists_lockfile(self):
        """
        Prüft ob das Lockfile existiert.
        """
        
        return os.path.isfile(self.lockfilename)
    
    
    def __xmlrpc_server(self, port):
        """
        XMLRPC-Server. Dieser läuft, gestartet von ``self._start_xmlrpc_server``,
        innerhalb eines eigenen Threads.
        """
        
        from SimpleXMLRPCServer import SimpleXMLRPCServer         
        
        def is_up():
            return True
       
        server = SimpleXMLRPCServer(("localhost", port))
        server.register_function(is_up)
        server.serve_forever()


    def _start_xmlrpc_server(self, port):
        """
        Startet den XMLRPC-Server
        """
        
        from thread import start_new_thread
        
        start_new_thread(self.__xmlrpc_server, (port,))
    
    
    def create_lock(self):
        """
        Erstellt ein Lockfile mit der aktuellen PID oder startet den
        XMLRPC-Server.
        """
        
        if not self.usexmlrpc:
            lockfiledir = self.lockfiledir
            lockfilename = self.lockfilename
            
            if not(os.path.isdir(lockfiledir)):
                os.makedirs(lockfiledir)
            
            f = file(lockfilename, "w")
            f.write(str(os.getpid()))
            f.close()
        else:
            self._start_xmlrpc_server(self.xmlrpcport)
    
    create_lockfile = create_lock # --> um abwärtskompatibel zu bleiben
    
    
    def delete_lock(self):
        """
        Löscht das Lockfile der Anwendung. Der XMLRPC-Server wird NICHT beendet, da
        dieser sowiso nach Programmende automatisch beendet wird.
        
        (Sollte es notwendig sein, dass der XMLRPC-Server mit dem Aufruf dieser
        Methode beendet wird, dann müsste man den XMLRPC-Server so umschreiben, 
        dass nicht ``server.serve_forever()`` verwendet wird.)
        """
        
        if not self.usexmlrpc:
            if os.path.isfile(self.lockfilename):
                os.remove(self.lockfilename)
        else:
            pass
    
    delete_lockfile = delete_lock # --> um abwärtskompatibel zu bleiben


    def is_first_start(self):
        """
        Prüft ob die beim Initialisieren angegebene Anwendung bereits ausgeführt wird.
        
        :return: True, wenn die Anwendung bereits läuft.
          False, wenn die Anwendung noch nicht läuft.
        """
        
        if not self.usexmlrpc:
            # LOCKFILE-Lösung
            lockfilename = self.lockfilename
            
            # Existiert das Lockfile?
            if not self._exists_lockfile():
                return True
            
            # Ist das Programm noch gestartet?
            try:
                lockfilepid = self._get_lockfile_pid()
                currentpids = _get_current_pids()
            except (NotImplementedError, ValueError, IOError):
                # Fallback --> XMLRPC
                self.usexmlrpc = True
                return self.is_first_start()
            if lockfilepid in currentpids:
                # Das Programm läuft noch
                return False
            else:
                # Das Programm mit der ermittelten PID ist nicht gestartet.
                self.delete_lockfile()
                return True
            
            # OK
            return True
        else:
            # XMLRPC-Lösung
            import xmlrpclib
            import socket
            self.xmlrpcport = int(
                "5" + str(hash(self.appname)).replace("0", "").replace("-", "")[:4]
            )
            try:
                server = xmlrpclib.ServerProxy("http://localhost:" + str(self.xmlrpcport))
                return not bool(server.is_up())
            except socket.error:
                return True


def main():
    """Testen"""
    
    import time
    
    firststart = FirstStart("testapplikation")
    
    if firststart.is_first_start():
        firststart.create_lock()
        
        # Hier wird gearbeitet
        for i in range(4):
            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."


if __name__ == "__main__":
    main()
Wie immer -- hier der Aufruf: Wenn du etwas weißt, was das Codeschnipsel besser machen könnte -- einfach raus damit. Ich werde es ins Beispiel einbauen.

Und hier wird aufgezeigt, wie das Modul "first_start.py" in einem anderen Python-Modul verwendet werden kann:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-

from first_start import FirstStart

def main():
    fs = FirstStart("testapplikation")
    if fs.is_first_start():
        fs.create_lock()
        ###################################################
        ### Hier wird gearbeitet.                       ###
        ###                                             ###
        ### Es ist egal, ob das Programm unter          ###
        ### Linux oder Windows läuft.                   ###
        ###                                             ###
        ### http://www.python-forum.de/topic-8282.html  ###
        ###                                             ###
        ###################################################
        fs.delete_lock()
    else:
        print "Das Programm wurde bereits gestartet."

if __name__ == "__main__":
    main()
lg
Gerold
:-)

Edit: XMLRPC-Server als Fallback mit eingebaut.

Verfasst: Freitag 15. Dezember 2006, 15:10
von gerold
XMLRPC-Server wurde als Fallbacklösung eingebaut...

lg
Gerold
:-)

Verfasst: Freitag 6. April 2007, 21:52
von oliver1974
Anbei die überarbeitete Variante
(Wurde hier besprochen..)

EDIT: Bug im globalen Modus (Zugriffsrechte werden nicht korrekt gesetzt) beseitigt

Änderungen:
-------------------

- Unterscheidung zwischen "globalen Modus" (d.h. prüfe,
ob die Anwendung auf dem Gesamtsystem bereits einmal läuft)
und "pro-User" Modus, also prüfe nur, ob diese Anwendung
unter dem aktivem Benutzer einmal läuft. Letzteres dürfte
eigentlich der Standard sein.
- XMLRPC-Server nur noch als Fallback für den "globalen Modus",
macht für den "pro-User" Modus ja auch keinen Sinn, denke ich..
- Läuft jetzt auch unter MacOSX und sollte auch unter anderen BSD-Varianten
laufen
- Diverse Kleinigkeiten.

Wer vorher die alte Version eingesetzt hat, muss eigentlich nur
beachten, das standardmäßig jetzt der "pro-User" Modus
aktiv ist. Einfach mal die Docstrings lesen.

Dank auch an Henrik "Kato" Lowack für sein Testen auf MacOSX,
trotz Stress mit geplatzten Mailservern und anderen Kleinigkeiten...

Edit (jens): Code ist hier: http://wiki.python-forum.de/Programm%20Lock

Verfasst: Donnerstag 14. Januar 2010, 16:17
von jens
Da phpBB nicht viel code verträgt, hab ich aus aktuellem Anlass die letzte Version ins Wiki gepackt: http://wiki.python-forum.de/Programm%20Lock