32 votes

Java : Surveiller un répertoire pour déplacer des fichiers volumineux

J'ai écrit un programme qui surveille un répertoire et lorsque des fichiers y sont créés, il en change le nom et les déplace dans un nouveau répertoire. Dans ma première implémentation, j'ai utilisé l'API Watch Service de Java, ce qui a bien fonctionné lorsque je testais des fichiers de 1 kb. Le problème qui s'est présenté est qu'en réalité, les fichiers créés sont de 50 à 300 Mo. Dans ce cas, l'API de surveillance trouvait immédiatement le fichier mais ne pouvait pas le déplacer car il était toujours en cours d'écriture. J'ai essayé de placer l'observateur dans une boucle (qui générait des exceptions jusqu'à ce que le fichier puisse être déplacé) mais cela semblait plutôt inefficace.

Comme cela n'a pas fonctionné, j'ai essayé d'utiliser un timer qui vérifie le dossier toutes les 10s et déplace les fichiers quand il le peut. C'est la méthode que j'ai fini par adopter.

Question : Existe-t-il un moyen de signaler la fin de l'écriture d'un fichier sans vérifier les exceptions ou comparer continuellement la taille ? J'aime l'idée d'utiliser l'API Watcher une seule fois pour chaque fichier au lieu de vérifier continuellement avec un timer (et de rencontrer des exceptions).

Toutes les réponses sont très appréciées !

nt

22voto

Jasper Krijgsman Points 542

J'ai rencontré le même problème aujourd'hui. Dans mon cas, un petit délai avant que le fichier ne soit effectivement importé n'était pas un gros problème et je voulais quand même utiliser l'API NIO2. La solution que j'ai choisie était d'attendre qu'un fichier n'ait pas été modifié pendant 10 secondes avant d'effectuer toute opération sur celui-ci.

La partie importante de la mise en œuvre est la suivante. Le programme attend que le temps d'attente expire ou qu'un nouvel événement se produise. Le temps d'expiration est remis à zéro chaque fois qu'un fichier est modifié. Si un fichier est supprimé avant l'expiration du temps d'attente, il est retiré de la liste. J'utilise la méthode poll avec un délai d'attente correspondant au temps d'expiration prévu, c'est-à-dire (lastmodified+waitTime)-currentTime.

private final Map<Path, Long> expirationTimes = newHashMap();
private Long newFileWait = 10000L;

public void run() {
    for(;;) {
        //Retrieves and removes next watch key, waiting if none are present.
        WatchKey k = watchService.take();

        for(;;) {
            long currentTime = new DateTime().getMillis();

            if(k!=null)
                handleWatchEvents(k);

            handleExpiredWaitTimes(currentTime);

            // If there are no files left stop polling and block on .take()
            if(expirationTimes.isEmpty())
                break;

            long minExpiration = min(expirationTimes.values());
            long timeout = minExpiration-currentTime;
            logger.debug("timeout: "+timeout);
            k = watchService.poll(timeout, TimeUnit.MILLISECONDS);
        }
    }
}

private void handleExpiredWaitTimes(Long currentTime) {
    // Start import for files for which the expirationtime has passed
    for(Entry<Path, Long> entry : expirationTimes.entrySet()) {
        if(entry.getValue()<=currentTime) {
            logger.debug("expired "+entry);
            // do something with the file
            expirationTimes.remove(entry.getKey());
        }
    }
}

private void handleWatchEvents(WatchKey k) {
    List<WatchEvent<?>> events = k.pollEvents();
    for (WatchEvent<?> event : events) {
        handleWatchEvent(event, keys.get(k));
    }
    // reset watch key to allow the key to be reported again by the watch service
    k.reset();
}

private void handleWatchEvent(WatchEvent<?> event, Path dir) throws IOException {
    Kind<?> kind = event.kind();

    WatchEvent<Path> ev = cast(event);
        Path name = ev.context();
        Path child = dir.resolve(name);

    if (kind == ENTRY_MODIFY || kind == ENTRY_CREATE) {
        // Update modified time
        FileTime lastModified = Attributes.readBasicFileAttributes(child, NOFOLLOW_LINKS).lastModifiedTime();
        expirationTimes.put(name, lastModified.toMillis()+newFileWait);
    }

    if (kind == ENTRY_DELETE) {
        expirationTimes.remove(child);
    }
}

11voto

stacker Points 34209

Écrire un autre fichier pour indiquer que le fichier original est terminé. Par exemple, 'fileorg.dat' s'agrandit si c'est terminé, créez un fichier 'fileorg.done' et vérifiez seulement le 'fileorg.done'.

Avec des conventions de dénomination intelligentes, vous ne devriez pas avoir de problèmes.

9voto

Sean Patrick Floyd Points 109428

Deux solutions :

La première est une légère variation de la réponse de stacker :

Utilisez un préfixe unique pour les fichiers incomplets. Quelque chose comme myhugefile.zip.inc au lieu de myhugefile.zip . Renommez les fichiers lorsque le téléchargement / la création est terminé. Exclure les fichiers .inc de la surveillance.

La seconde consiste à utiliser un dossier différent sur le même disque pour créer/télécharger/écrire les fichiers et les déplacer vers le dossier surveillé une fois qu'ils sont prêts. Le déplacement devrait être une action atomique s'ils sont sur le même lecteur (dépendant du système de fichiers, je suppose).

Dans tous les cas, les clients qui créent les fichiers devront faire un travail supplémentaire.

5voto

Flint O'Brien Points 56

Il semble qu'Apache Camel gère le problème du fichier qui ne peut être téléchargé en essayant de renommer le fichier (java.io.File.renameTo). Si le renommage échoue, il n'y a pas de verrou de lecture, mais il continue à essayer. Lorsque le renommage réussit, il le renomme à nouveau, puis poursuit le traitement prévu.

Ver opérations.renameFile en dessous. Voici les liens vers les sources d'Apache Camel : GénériqueFileRenameExclusiveReadLockStrategy.java y FileUtil.java

public boolean acquireExclusiveReadLock( ... ) throws Exception {
   LOG.trace("Waiting for exclusive read lock to file: {}", file);

   // the trick is to try to rename the file, if we can rename then we have exclusive read
   // since its a Generic file we cannot use java.nio to get a RW lock
   String newName = file.getFileName() + ".camelExclusiveReadLock";

   // make a copy as result and change its file name
   GenericFile<T> newFile = file.copyFrom(file);
   newFile.changeFileName(newName);
   StopWatch watch = new StopWatch();

   boolean exclusive = false;
   while (!exclusive) {
        // timeout check
        if (timeout > 0) {
            long delta = watch.taken();
            if (delta > timeout) {
                CamelLogger.log(LOG, readLockLoggingLevel,
                        "Cannot acquire read lock within " + timeout + " millis. Will skip the file: " + file);
                // we could not get the lock within the timeout period, so return false
                return false;
            }
        }

        exclusive = operations.renameFile(file.getAbsoluteFilePath(), newFile.getAbsoluteFilePath());
        if (exclusive) {
            LOG.trace("Acquired exclusive read lock to file: {}", file);
            // rename it back so we can read it
            operations.renameFile(newFile.getAbsoluteFilePath(), file.getAbsoluteFilePath());
        } else {
            boolean interrupted = sleep();
            if (interrupted) {
                // we were interrupted while sleeping, we are likely being shutdown so return false
                return false;
            }
        }
   }

   return true;
}

4voto

user1322265 Points 11

Je sais que c'est une vieille question mais peut-être que cela peut aider quelqu'un.

J'avais le même problème, alors j'ai fait ce qui suit :

if (kind == ENTRY_CREATE) {
            System.out.println("Creating file: " + child);

            boolean isGrowing = false;
            Long initialWeight = new Long(0);
            Long finalWeight = new Long(0);

            do {
                initialWeight = child.toFile().length();
                Thread.sleep(1000);
                finalWeight = child.toFile().length();
                isGrowing = initialWeight < finalWeight;

            } while(isGrowing);

            System.out.println("Finished creating file!");

        }

Lorsque le fichier est créé, il devient de plus en plus gros. Donc ce que j'ai fait, c'est de comparer les poids séparés par une seconde. L'application restera en boucle jusqu'à ce que les deux poids soient identiques.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X