Java: Regarder un répertoire pour déplacer de gros fichiers

j'ai écrit un programme qui surveille un répertoire et lorsque les fichiers sont créés, il change le nom et les déplace vers un nouveau répertoire. Dans ma première implémentation, J'ai utilisé L'API de service de veille de Java qui fonctionnait très bien lorsque je testais des fichiers 1kb. Le problème qui est apparu est qu'en réalité les fichiers créés sont n'importe où de 50-300mb. Lorsque cela se produisait, L'API de l'observateur trouvait le fichier immédiatement, mais ne pouvait pas le déplacer parce qu'il était encore en cours d'écriture. J'ai essayé mettre l'observateur dans une boucle (qui génère des exceptions jusqu'à ce que le fichier puisse être déplacé) mais cela semble assez inefficace.

comme ça n'a pas marché, j'ai essayé d'utiliser un minuteur qui vérifie le dossier tous les 10s et déplace les fichiers quand il le peut. C'est la méthode que j'ai fini par aller pour.

Question: y a-t-il de toute façon à signaler quand un fichier est fait en étant écrit sans faire une vérification d'exception ou en comparant continuellement la taille? J'aime l'idée d'utiliser L'API de Watcher juste une fois pour chaque fichier au lieu de vérifier continuellement avec une minuterie (et de courir dans les exceptions).

toutes les réponses sont grandement appréciées!

nt

27
demandé sur nite 2010-07-30 11:17:40

13 réponses

Écrire un autre fichier pour indiquer que le fichier original est terminé. I. G ' fileorg.DAT 'est en croissance si fait créer un fichier' fileorg.fait " et vérifier seulement pour le fileorg.terminé".

avec des conventions de nommage astucieuses, vous ne devriez pas avoir de problèmes.

11
répondu stacker 2010-07-30 07:29:18

j'ai rencontré le même problème aujourd'hui. I my usecase un petit délai avant l'importation du fichier 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 dessus.

La partie importante de la mise en œuvre est comme suit. Le programme attend que le temps d'attente expire ou qu'un nouvel événement se produise. Le temps d'expiration est réinitialisé à chaque fois qu'un fichier est modifié. Si un fichier est supprimé avant l'expiration du délai, il est supprimé de la liste. J'utilise la méthode de sondage avec un délai d'attente de la expirationtime, qui est (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);
    }
}
19
répondu Jasper Krijgsman 2011-01-24 15:10:14

deux solutions:

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

utilisez un préfixe unique pour les fichiers incomplets. Quelque chose comme myhugefile.zip.inc au lieu de myhugefile.zip . Renommer les fichiers télécharger / la création est terminée. Exclure. inc des fichiers à partir de la montre.

La seconde est d'utiliser un autre dossier sur le même disque pour créer / upload / écrire les fichiers et les déplacer vers le dossier regardé une fois qu'ils sont prêts. Le déplacement devrait être une action atomique s'ils sont sur le même disque (dépendant du système de fichiers, je suppose).

de toute façon, les clients de créer les fichiers devront faire un travail supplémentaire.

9
répondu Sean Patrick Floyd 2017-05-23 12:10:04

je sais que c'est une vieille question, mais peut-être que ça peut aider quelqu'un.

j'ai eu le même problème, donc ce que j'ai fait était le suivant:

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!");

        }

quand le fichier est créé, il va devenir de plus en plus grand. Donc ce que j'ai fait était de comparer le poids séparé par une seconde. L'application sera dans la boucle jusqu'à ce que les poids sont les mêmes.

4
répondu user1322265 2013-03-08 16:09:19

bien qu'il ne soit pas possible d'être notifié par L'API de service de L'Observateur lorsque la copie ainsi finir, toutes les options semblent être 'travailler autour' (y compris celle-ci!).

Comme indiqué ci-dessus,

1) déplacer ou copier N'est pas une option sur UNIX;

2) Fichier.canWrite retourne toujours true si vous avez la permission d'écrire, même si le fichier est encore copié;

3) attend jusqu'à un temps d'arrêt ou un nouveau l'événement se serait une option, mais que faire si le système est surchargé, mais la copie n'est pas terminée? si le timeout est une grande valeur, le programme attendrait si longtemps.

4) Écrire un autre fichier pour 'signaler' que la copie terminée n'est pas une option si vous ne faites que lire le fichier, et non créer.

une alternative est d'utiliser le code ci-dessous:

boolean locked = true;

while (locked) {
    RandomAccessFile raf = null;
    try {
            raf = new RandomAccessFile(file, "r"); // it will throw FileNotFoundException. It's not needed to use 'rw' because if the file is delete while copying, 'w' option will create an empty file.
            raf.seek(file.length()); // just to make sure everything was copied, goes to the last byte
            locked = false;
        } catch (IOException e) {
            locked = file.exists();
            if (locked) {
                System.out.println("File locked: '" + file.getAbsolutePath() + "'");
                Thread.sleep(1000); // waits some time
            } else { 
                System.out.println("File was deleted while copying: '" + file.getAbsolutePath() + "'");
            }
    } finally {
            if (raf!=null) {
                raf.close();    
            }
        }
}
3
répondu Felipe Guimaraes 2012-10-03 18:13:18

ressemble à Apache Camel gère le problème file-not-done-uploading en essayant de renommer le fichier (java.io.File.renameTo). Si le renommage échoue, aucun verrou de lecture, mais continuez d'essayer. Lorsque le renommage réussit, ils le renomment à nouveau, puis procèdent au traitement prévu.

voir opérations.renameFile ci-dessous. Voici les liens vers la source Apache Camel: GenericFileRenameExclusiveReadLockstrategy.java et 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;
}
3
répondu Flint O'Brien 2013-07-23 16:18:47

il s'agit d'une discussion très intéressante , car il s'agit certainement d'un cas d'utilisation de pain et de beurre: attendre qu'un nouveau fichier soit créé et ensuite réagir au fichier d'une certaine manière. La condition de course ici est intéressante, car certainement l'exigence de haut niveau ici est d'obtenir un événement et puis obtenir effectivement (au moins) un verrou de lecture sur le dossier. Avec de grands fichiers ou tout simplement beaucoup de créations de fichiers, cela pourrait nécessiter un ensemble de threads de travail qui juste essayez périodiquement d'obtenir des serrures sur les dossiers nouvellement créés et, quand ils sont réussis, font réellement le travail. Mais comme NT le sait sûrement, il faudrait le faire avec soin pour qu'il soit à l'échelle, car il s'agit en fin de compte d'une approche de sondage, et l'évolutivité et le sondage ne sont pas deux mots qui vont bien ensemble.

0
répondu Stefan 2011-01-20 20:00:54

j'ai dû faire face à une situation similaire lorsque j'ai implémenté un observateur de système de fichiers pour transférer des fichiers téléchargés. La solution que j'ai mise en œuvre pour résoudre ce problème consiste en ce qui suit:

1-tout d'abord, maintenez une carte du fichier non traité (aussi longtemps que le fichier est encore copié, le système de fichiers génère Modify_Event, de sorte que vous pouvez les ignorer si le drapeau est faux).

2-dans votre fileprocesseur, vous récupérez un fichier de la liste et vérifiez si elle est verrouillée par le système de fichiers, si oui, vous obtiendrez une exception, attrapez juste cette exception et mettez votre thread en état d'attente (I. e 10 secondes) et puis réessayez jusqu'à ce que la serrure est libérée. Après avoir traité le fichier, vous pouvez soit changer le drapeau à true ou le supprimer de la carte.

Cette solution ne sera pas efficace si les nombreuses versions du même fichier sont transférées pendant le délai d'attente.

santé, Ramzi

0
répondu Ramcis 2011-12-12 15:00:01

selon l'urgence avec laquelle vous devez déplacer le fichier une fois qu'il est écrit, Vous pouvez également vérifier une date de dernière modification stable et ne déplacer le fichier qu'une fois qu'il est désactivé. La quantité de temps dont vous avez besoin pour être stable peut dépendre de la mise en œuvre, mais je suppose que quelque chose avec un horodatage modifié en dernier qui n'a pas changé depuis 15 secondes devrait être assez stable pour être déplacé.

0
répondu Eric B. 2012-10-03 18:32:56

pour les gros fichiers sous linux, les fichiers sont copiés avec une extension de .filepart. Vous n'avez qu'à vérifier l'extension en utilisant l'api commons et enregistrer L'événement ENTRY_CREATE. J'ai testé avec mon .fichiers csv (1 Go) et ajouter qu'il a fonctionné

public void run()
{
    try
    {
        WatchKey key = myWatcher.take();
        while (key != null)
        {
            for (WatchEvent event : key.pollEvents())
            {
                if (FilenameUtils.isExtension(event.context().toString(), "filepart"))
                {
                    System.out.println("Inside the PartFile " + event.context().toString());
                } else
                {
                    System.out.println("Full file Copied " + event.context().toString());
                    //Do what ever you want to do with this files.
                }
            }
            key.reset();
            key = myWatcher.take();
        }
    } catch (InterruptedException e)
    {
        e.printStackTrace();
    }
}
0
répondu Pawan Kumar 2015-04-20 07:52:14

si vous n'avez pas le contrôle sur le processus d'écriture, enregistrez tous les événements ENTRY_CREATED et observez s'il y a patterns .

dans mon cas, les fichiers sont créés via WebDav (Apache) et beaucoup de fichiers temporaires sont créés mais aussi deux ENTRY_CREATED des événements sont déclenchés pour le même fichier. Le deuxième événement ENTRY_CREATED indique que le processus de copie est complet.

Voici mon exemple ENTRY_CREATED des événements. Le chemin absolu du fichier est imprimé (votre journal peut différer, selon l'application qui écrit le fichier):

[info] application - /var/www/webdav/.davfs.tmp39dee1 was created
[info] application - /var/www/webdav/document.docx was created
[info] application - /var/www/webdav/.davfs.tmp054fe9 was created
[info] application - /var/www/webdav/document.docx was created
[info] application - /var/www/webdav/.DAV/__db.document.docx was created 

comme vous le voyez, je reçois deux ENTRY_CREATED événements pour document.docx . Après le deuxième événement, je sais que le dossier est complet. Les fichiers temporaires sont évidemment ignorés dans mon cas.

0
répondu enigma969 2017-07-25 12:48:25

donc, j'ai eu le même problème et j'ai eu la solution suivante travailler pour moi. Tentative infructueuse plus tôt-essayer de surveiller l'état "lastModifiedTime" de chaque fichier, mais j'ai remarqué que la croissance de la taille d'un grand fichier peut s'arrêter pendant un certain temps.(la taille ne change pas continuellement)

Basic Idea - pour chaque événement, Créer un fichier de déclenchement (dans un répertoire temporaire) dont le nom est du format suivant -

OriginalFileName_lastModifiedTime_numberoftries

Ce fichier est vide et tout le jeu est seulement dans le nom. Le fichier original ne sera considéré qu'après avoir passé des intervalles d'une durée spécifique sans changement de son statut "last Modified time". (Remarque: étant donné que c'est un fichier de stat, il n'y a pas de frais généraux -> O(1))

NOTE - ce fichier de déclenchement est géré par un service différent(dire FileTrigger ").

avantage -

  1. pas de sommeil ou d'attente pour maintenir le système.
  2. décharge l'observateur de fichier pour surveiller d'autres événements

CODE pour FileWatcher -

val triggerFileName: String = triggerFileTempDir + orifinalFileName + "_" + Files.getLastModifiedTime(Paths.get(event.getFile.getName.getPath)).toMillis + "_0"

// creates trigger file in temporary directory
val triggerFile: File = new File(triggerFileName)
val isCreated: Boolean = triggerFile.createNewFile()

if (isCreated)
    println("Trigger created: " + triggerFileName)
else
    println("Error in creating trigger file: " + triggerFileName)

CODE pour FileTrigger (cron job d'intervalle de 5 minutes) -

 val actualPath : String = "Original file directory here"
 val tempPath : String = "Trigger file directory here"
 val folder : File = new File(tempPath)    
 val listOfFiles = folder.listFiles()

for (i <- listOfFiles)
{

    // ActualFileName_LastModifiedTime_NumberOfTries
    val triggerFileName: String = i.getName
    val triggerFilePath: String = i.toString

    // extracting file info from trigger file name
    val fileInfo: Array[String] = triggerFileName.split("_", 3)
    // 0 -> Original file name, 1 -> last modified time, 2 -> number of tries

    val actualFileName: String = fileInfo(0)
    val actualFilePath: String = actualPath + actualFileName
    val modifiedTime: Long = fileInfo(1).toLong
    val numberOfTries: Int = fileStats(2).toInt

    val currentModifiedTime: Long = Files.getLastModifiedTime(Paths.get(actualFilePath)).toMillis
    val differenceInModifiedTimes: Long = currentModifiedTime - modifiedTime
    // checks if file has been copied completely(4 intervals of 5 mins each with no modification)
    if (differenceInModifiedTimes == 0 && numberOfTries == 3)
    {
        FileUtils.deleteQuietly(new File(triggerFilePath))
        println("Trigger file deleted. Original file completed : " + actualFilePath)
    }
    else
    {
        var newTriggerFileName: String = null
        if (differenceInModifiedTimes == 0)
        {
            // updates numberOfTries by 1
            newTriggerFileName = actualFileName + "_" + modifiedTime + "_" + (numberOfTries + 1)
        }
        else
        {
            // updates modified timestamp and resets numberOfTries to 0
            newTriggerFileName = actualFileName + "_" + currentModifiedTime + "_" + 0
        }

        // renames trigger file
        new File(triggerFilePath).renameTo(new File(tempPath + newTriggerFileName))
        println("Trigger file renamed: " + triggerFileName + " -> " + newTriggerFileName)
    }    
}
0
répondu Varun Chaudhary 2018-05-03 14:59:59

je suppose que java.io.File.canWrite () vous indiquera quand un fichier a été écrit.

-1
répondu emory 2010-07-30 08:42:35