Подтвердить что ты не робот

Java: просмотр каталога для перемещения больших файлов

Я пишу программу, которая следит за каталогом и когда в нем создаются файлы, она меняет имя и перемещает их в новый каталог. В моей первой реализации я использовал Java Watch Service API, который отлично работал, когда я тестировал 1kb файлы. Проблема, которая возникла, заключается в том, что на самом деле создаваемые файлы находятся где угодно от 50-300 мб. Когда это произошло, API-интерфейс наблюдателя сразу найдет файл, но не сможет его переместить, поскольку он все еще записывается. Я попытался помещать наблюдателя в цикл (который генерировал исключения, пока файл не мог быть перемещен), но это показалось довольно неэффективным.

Так как это не сработало, я попытался использовать таймер, который проверяет папку каждые 10 секунд, а затем перемещает файлы, когда это возможно. Это метод, в который я в конечном итоге пошел.

Вопрос: Есть ли в любом случае сигнал, когда файл записывается без проверки исключения или постоянного сравнения размера? Мне нравится идея использовать API Watcher только один раз для каждого файла, а не постоянно проверять таймер (и работать с исключениями).

Все отзывы приветствуются!

нт

4b9b3361

Ответ 1

Напишите другой файл как указание на то, что исходный файл будет завершен. I.g 'fileorg.dat' растет, если сделано, создайте файл 'fileorg.done' и проверьте только для файла fileorg.done.

С умными соглашениями об именах у вас не должно быть проблем.

Ответ 2

Сегодня я столкнулся с той же проблемой. Я очень немного задерживаю, прежде чем файл импортируется, и это не является большой проблемой, и я все еще хотел использовать API NIO2. Решение, которое я выбрал, состояло в том, чтобы дождаться, пока файл не будет изменен на 10 секунд, прежде чем выполнять какие-либо операции над ним.

Важная часть реализации заключается в следующем. Программа ждет, пока не истечет время ожидания или не произойдет новое событие. Время истечения срока действия reset при каждом изменении файла. Если файл удаляется до истечения времени ожидания, он удаляется из списка. Я использую метод опроса с тайм-аутом ожидаемого срока действия, то есть (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);
    }
}

Ответ 3

Два решения:

Первое - это небольшое изменение ответа укладчика:

Используйте уникальный префикс для неполных файлов. Что-то вроде myhugefile.zip.inc вместо myhugefile.zip. Переименуйте файлы при завершении загрузки/создания. Исключить файлы .inc из часов.

Вторым является использование другой папки на том же диске для создания/загрузки/записи файлов и перемещения их в просматриваемую папку, как только они будут готовы. Перемещение должно быть атомарным действием, если они находятся на одном диске (зависит от файловой системы, я думаю).

В любом случае, клиенты, которые создают файлы, должны будут выполнить дополнительную работу.

Ответ 4

Я знаю, что это старый вопрос, но, возможно, он может помочь кому-то.

У меня была такая же проблема, поэтому я сделал следующее:

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

        }

Когда файл создается, он будет становиться все больше и больше. Так что я сделал, чтобы сравнить вес, разделенный секундой. Приложение будет находиться в цикле, пока оба веса не будут одинаковыми.

Ответ 5

Несмотря на то, что API-интерфейс Watcher не может быть подтвержден при копировании SO, все параметры, похоже, "работают" (включая этот!).

Как отмечалось выше,

1) Перемещение или копирование в UNIX не является параметром;

2) File.canWrite всегда возвращает true, если у вас есть разрешение на запись, даже если файл все еще копируется;

3) Ожидает, что время ожидания или возникновение нового события будет вариантом, но что, если система перегружена, но копия не была закончена? если таймаут является большим значением, программа будет ждать так долго.

4) Запись другого файла в "флаг", который закончила копия, не является вариантом, если вы просто потребляете файл, а не создаете.

Альтернативой является использование кода ниже:

boolean locked = true;

while (locked) {
    RandomAccessFile raf = null;
    try {
            raf = new RandomAccessFile(file, "r"); // it will throw FileNotFoundException. It 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();    
            }
        }
}

Ответ 6

Похоже, что Apache Camel обрабатывает проблему с загрузкой файла, не загружая его, пытаясь переименовать файл (java.io.File.renameTo). Если переименование завершилось неудачей, не заблокируйте чтение, но продолжайте попытки. Когда переименование удастся, они переименуют его обратно, затем перейдут к намеченной обработке.

См. файл operations.renameFile ниже. Вот ссылки на источник Apache Camel: GenericFileRenameExclusiveReadLockStrategy.java и 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;
}

Ответ 7

Это очень интересная дискуссия, так как это, конечно же, случай использования хлеба и масла: дождитесь создания нового файла и затем каким-то образом отреагируйте на файл. Условие гонки здесь интересно, так как, конечно, требование высокого уровня здесь состоит в том, чтобы получить событие, а затем фактически получить (по крайней мере) блокировку чтения в файле. С большими файлами или просто с большим количеством созданий файлов для этого может потребоваться целый пул рабочих потоков, которые просто периодически пытаются получить блокировки на вновь созданных файлах и, когда они будут успешными, на самом деле выполняют эту работу. Но, как я уверен, что NT понимает, нужно было бы сделать это осторожно, чтобы сделать его масштабируемым, поскольку это, в конечном счете, подход к опросу, а масштабируемость и опрос - это не два слова, которые хорошо сочетаются.

Ответ 8

Мне приходилось иметь дело с подобной ситуацией, когда я реализовал наблюдатель файловой системы для передачи загруженных файлов. Решение, которое я реализовал для решения этой проблемы, состоит в следующем:

1- Прежде всего, сохраните карту необработанного файла (до тех пор, пока файл все еще копируется, файловая система генерирует Modify_Event, поэтому вы можете игнорировать их, если флаг имеет значение false).

2- В файле fileProcessor вы выбираете файл из списка и проверяете, заблокирован ли он файловой системой, если да, вы получите исключение, просто поймайте это исключение и поставьте свой поток в состояние ожидания (т.е. 10 секунд) а затем повторите попытку до тех пор, пока замок не будет отпущен. После обработки файла вы можете либо изменить флаг на true, либо удалить его с карты.

Это решение будет неэффективным, если многие версии одного и того же файла будут переданы во время временного интервала ожидания.

Cheers, Рамзи

Ответ 9

В зависимости от того, насколько срочно вам нужно переместить файл после его написания, вы также можете проверить стабильную временную метку с последними изменениями и только переместить файл, в котором он находится в покое. Количество времени, в течение которого вы должны быть стабильным, может быть зависимым от реализации, но я бы предположил, что что-то с последней измененной меткой времени, которая не изменилась за 15 секунд, должна быть достаточно стабильной, чтобы ее можно было перемещать.

Ответ 10

Для большого файла в Linux файлы копируются с расширением .filepart. Вам просто нужно проверить расширение с помощью commons api и зарегистрировать событие ENTRY_CREATE. Я проверил это с моими файлами .csv(1 ГБ) и добавил, что он работал

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();
    }
}

Ответ 11

Если у вас нет контроля над процессом записи, запишите все события ENTRY_CREATED и наблюдайте, есть ли шаблоны.

В моем случае файлы создаются через WebDav (Apache), и создается много временных файлов, но также срабатывают события два ENTRY_CREATED для одного и того же файла. Второе событие ENTRY_CREATED указывает, что процесс копирования завершен.

Вот мой пример ENTRY_CREATED событий. Абсолютный путь к файлу печатается (ваш журнал может отличаться в зависимости от приложения, которое записывает файл):

[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 

Как вы видите, я получаю два события ENTRY_CREATED для document.docx. После второго события я знаю, что файл завершен. Временные файлы, очевидно, игнорируются в моем случае.

Ответ 12

Я предполагаю, что java.io.File.canWrite() сообщит вам, когда файл был записан.