Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Ratage d'évènements lorsque recursive = true {WatchDog\Python34}

+2 votes

Je prépare un script observant un dossier et sous-dossiers au déplacement de dossiers finissant par un ".D" (fichiers de résultats d'une machine).

En mettant le paramètre recursive = False, j'arrive à voir la création de plusieurs dossiers.D simultanément (mais l'Observer ne regarde alors pas dans les sous-dossiers)
En mettant le paramètre recursive = True, je regarde bien dans les sous-dossiers, mais l'Observer rate les dossiers qui sont déplacés simultanéments

Code source :

import os
import sys
import time
import logging
import configparser
from watchdog.observers import Observer
import watchdog.observers.api as api
import watchdog.events as we
from pathtools.path import absolute_path, parent_dir_path

def LectureINI(path):
    data = path.split('\\')
    data = data[-1]
    print(data)
    file_runstart = path + '\\runstart.txt'
    tps1 = time.clock()
    while os.path.isfile(file_runstart) == False:
        time.sleep(1)
        tps2 = time.clock()
        if (tps2-tps1) > 15:
            break
    try:
        file_runstart = open(file_runstart, 'r')
        lines = file_runstart.readlines()
        ilines = iter(lines)
        for i in ilines:
            if "Sample Name" in i:
                sample = i.split(' = ')
                sample = sample[-1]
                break
    except:
        sample = '<Sample Name non récupérable>'
    file_ini = path + '\\PRE_POST.INI'
    while os.path.isfile(file_ini) == False:
        time.sleep(1)
    cfg.read(file_ini)
    try:
        date = cfg['POSTRUN']['Date']
    except:
        date = '<Date non récupérable>'
    try:
        instr = cfg['POSTRUN']['Instrument Name']
    except:
        instr = '<Nom instrument non récupérable>'

    infos = date + '\t' + instr + '\t' + data + '\t' + sample
    return infos

class MonHandler(we.FileSystemEventHandler):
    #méthode appelée lorsqu'un fichier est modifié
    def on_created(self, event):

        my_path = event.src_path
        if my_path[-2:]== ".D" and not "$" in my_path:
            print('Détection DATA')
            fileinfo = LectureINI(my_path)
            logging.info("%s\t%s", my_path, fileinfo)

cfg = configparser.ConfigParser()
cfg.read('C:\Windows\win.ini')
read_path = cfg['WatchData']['Path']
BASEDIR = read_path[:-1]
if read_path == None:
    BASEDIR == 'C:\\'

#if __name__ == "__main__":
logging.basicConfig(filename='WatchData.log', level=logging.INFO,
                    format='%(asctime)s\t%(message)s',
                    datefmt='%Y-%m-%d\t%H:%M:%S')
event_handler = MonHandler()
#Création du thread qui va observer
observer = Observer()
observer.schedule(event_handler, BASEDIR, recursive = True)
observer.start()
try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    #CRTL+C pour stopper la routine
    observer.stop()
    #Attendre que tous les threads se terminent proprement
observer.join()

Quelqu'un aurait une idée du pourquoi du comment ?
J'aurais besoin de regarder tous les dossiers et sous-dossiers à la création de 1 ou plusieurs dossiers.D simultanément.

demandé 16-Fev-2016 par farameris (134 points)

2 Réponses

0 votes
 
Meilleure réponse

Au final, je suis parti sur la bibliothèque PyWin32 pour manipuler en direct les fonctionnalités windows exploitées de toute façon par WatchDog. Le résultat est fonctionnel.

J'ai lancé mon Handler perso via un thread à part (pas sûr que ce soit forcément obligé).
EDIT : Pas besoin

J'ai été obligé de rallonger extrêmement la taille du buffer lors de la création de la routine afin que tous mes dossier.D copiés soient détectés avec l'exécution du handler (au début j'étais sur 10000, et à ce moment là même problème que auparavant : il ne répertoriait pas tous les dossiers copiés). C'est à priori un paramètre à grandir manuellement afin de pouvoir détecter les cas de multi-copies assez longues (voir ici et ici).

La raison dans mon cas est sûrement que lors de l'exécution de mon handler, d'autres fichiers sont créés sur C:\ ce qui remplit peu à peu le buffer et donc vire les anciennes instances (celles qui m'intéressent donc).

Voir ici pour une doc plutôt complète sur le fonctionnement de cette API Windows.
Basé sur le code de Tim Golden --> voir ici

#!/usr/bin/env python

import os
import sys
import win32file
import win32event
import win32con
import logging
import configparser
import time
import threading

# Création de la liste d'action.
# Dans ce cas, uniquement besoin des fichiers créés, mais autres cas possibles à ajouter
ACTIONS = {
    1 : "Created",
}

# Dictionnaire résultat final
dict_log = {
    'date' : '',
    'chemin' : '',
    'instrument' : '',
    'methode' : '',
    'sample' : ''
}

# Différentes fonctions pour le Handler perso

def TestData(path):
    if path[-2:] == '.D' and not '$' in path:
        result = True
    else:
        result = False

    return result

def LectureRUNSTART(path):
    file = path + '\\runstart.txt'
    n = 1
    while n < 15:
        try:
            file = open(file, 'r')
            lines = file.readlines()
            ilines = iter(lines)
            for i in ilines:
                if 'Sample Name' in i:
                    sample = i.split(' = ')
                    sample = sample[-1]
                    break
                else:
                    sample = '<Sample Name non récupérable>'
            n = 15

        except FileNotFoundError:
            n += 1
            time.sleep(1)
            if n == 15:
                sample = '<Sample Name non récupérable>'

    global dict_log        
    dict_log['sample'] = sample
    return

def LectureINI(path):
    file_ini = path + '\\PRE_POST.INI'
    m = 1
    while m < 15:
        try:
            cfg2 = configparser.ConfigParser()
            cfg2.read(file_ini)
            m = 15
        except:
            m += 1
            time.sleep(1)
            if m == 15:
                date = '<Date non récupérable>'
                instr = '<Nom Instrument non récupérable>'
                method = '<Méthode non récupérable>'

    sections = ['POSTRUN', 'POSTRUN', 'Sequence']
    keys = ['date', 'instrument name', '_methfile$']
    i = 0

    while i < 3:
        if (sections[i] not in cfg2.sections() or keys[i] not in cfg2.options(sections[i])):
            if i == 0:
                date = '<Date non récupérable>'
            elif i == 1:
                instr = '<Nom Instrument non récupérable>'
            else:
                method = '<Méthode non récupérable>'
        else:
            value = cfg2[sections[i]][keys[i]]
            if i == 0:
                date = value
            elif i == 1:
                instr = value
            else:
                method = value
        i += 1
    global dict_log
    dict_log['date'] = date
    dict_log['methode'] = method
    dict_log['instrument'] = instr

    return

def Extraction(full_filename):
    logging.basicConfig(filename='C:\\WatchData\\WatchData.log',
                        level=logging.INFO,
                        format='%(asctime)s\t%(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')
    LectureRUNSTART(full_filename)
    LectureINI(full_filename)

    dict_log['chemin'] = full_filename

    infos = (dict_log['date'] + '\t' +
             dict_log['chemin'] + '\t' +
             dict_log['instrument'] + '\t' +
             dict_log['methode'] + '\t' +
             dict_log['sample'])
    logging.info('%s', infos)

    return


# Thanks to Claudio Grondi for the correct set of numbers
FILE_LIST_DIRECTORY = 0x0001

# Definition of path_to_watch
cfg = configparser.ConfigParser()
try:
    cfg.read('C:\Windows\win.ini')
    path_to_watch = cfg['WatchData']['Path']
    path_to_watch = path_to_watch[:-1]
except:
    path_to_watch = 'C:\\'

# Creating directory to watch
hDir = win32file.CreateFile (
  path_to_watch,
  FILE_LIST_DIRECTORY,
  win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
  None,
  win32con.OPEN_EXISTING,
  win32con.FILE_FLAG_BACKUP_SEMANTICS,
  None
)

# Watching routine
while 1:
  #
  # ReadDirectoryChangesW takes a previously-created
  # handle to a directory, a buffer size for results,
  # a flag to indicate whether to watch subtrees and
  # a filter of what changes to notify.
  #
  # NB Tim Juchcinski reports that he needed to up
  # the buffer size to be sure of picking up all
  # events when a large number of files were
  # deleted at once.
  #

  results = win32file.ReadDirectoryChangesW (
    hDir,
    #Augmenter buffer si problème de détection simultanée
    1000000,
    True,
    win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
     win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
     win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
     win32con.FILE_NOTIFY_CHANGE_SIZE |
     win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
     win32con.FILE_NOTIFY_CHANGE_SECURITY,
    None,
    None
  )

  for action, file in results:
    full_filename = os.path.join (path_to_watch, file)
    if TestData(full_filename) == True and ACTIONS.get(action, "Unknown") == "Created":
        print(full_filename, ACTIONS.get(action, "Unknown"))
        #threading.Thread(target=Extraction(full_filename)).start()
        Extraction(full_filename)
répondu 17-Fev-2016 par farameris (134 points)
sélectionné 17-Fev-2016 par farameris

C'est très spécifique à Windows cependant. Mais tant que ça te va. ;)

Oui c'est clair ;-)

EDIT : ça fonctionne sur des disques locaux mais j'ai un soucis inexpliqué avec des disques réseaux...

EDIT 2 : buffer size limitée à 64KB sur les disques réseau

+2 votes

Si tu es sous Windows, sache que ce système d'exploitation gère plutôt mal les événements de création/modification/suppression de fichiers, et ne fait pas la distinction entre répertoires et fichiers de part l'instrumentation qui est exploitée par watchdog, ce qui peut expliquer le problème.

Sous GNU/Linux, les événements de la plupart des systèmes de fichiers sont bien gérés nativement (ExtFS, RaiserFS, etc...), par contre par défaut le système bride les événements levés simultanément pour des raisons de performances (le commun des mortels n'a pas besoin d'historiser plusieurs milliers de modifications à la minute), il suffit alors de repousser cette limite plus loin.

Tout est très bien expliqué dans la documentation du produit :
http://pythonhosted.org/watchdog/installation.html#supported-platforms-and-caveats

répondu 16-Fev-2016 par debnet (1,024 points)

Je suis en effet -malheureusement- obligé de bosser avec Windows ici, car c'est la finalité du projet...

Par contre j'avais effectivement omis ce passage dans la doc, malgré après avoir parcouru les fonctions en long/large/travers !

...