2016-03-05 3 views
1

Я пытаюсь извлечь файлы с FTP-сервера с помощью Clojure. Я хотел бы загрузить все файлы, используя одно соединение. Для этого я использую https://github.com/miner/clj-ftp/blob/master/src/miner/ftp.clj clj-ftp. К сожалению, я не могу добиться этого одним соединением. Получил две функции:Повторное использование ftp-соединения в Clojure с использованием clj-ftp

(defn one-session [files] 
    (ftp/with-ftp [client ftp-url] 
    (map #(ftp/client-get client %1) 
     files))) 

(defn get-all [files] 
    (map #(ftp/with-ftp [client ftp-url] 
      (ftp/client-get client %1)) 
     files)) 

При звонке get-all все работает нормально. При попытке позвонить one-session У меня есть исключение NullPointerException org.apache.commons.net.SocketClient.getRemoteAddress (SocketClient.java:658)

Я заметил, что в clj-ftp есть много типов подсказок, есть ли у него в наличии на нем?

Всего StackTrace

Exception in thread "main" java.lang.NullPointerException, compiling:(/private/var/folders/4d/77tz4xfj7b1dkqtd3h4j10v40000gn/T/form-init2973639134882885374.clj:1:125) 
    at clojure.lang.Compiler.load(Compiler.java:7391) 
    at clojure.lang.Compiler.loadFile(Compiler.java:7317) 
    at clojure.main$load_script.invokeStatic(main.clj:275) 
    at clojure.main$init_opt.invokeStatic(main.clj:277) 
    at clojure.main$init_opt.invoke(main.clj:277) 
    at clojure.main$initialize.invokeStatic(main.clj:308) 
    at clojure.main$null_opt.invokeStatic(main.clj:342) 
    at clojure.main$null_opt.invoke(main.clj:339) 
    at clojure.main$main.invokeStatic(main.clj:421) 
    at clojure.main$main.doInvoke(main.clj:384) 
    at clojure.lang.RestFn.invoke(RestFn.java:421) 
    at clojure.lang.Var.invoke(Var.java:383) 
    at clojure.lang.AFn.applyToHelper(AFn.java:156) 
    at clojure.lang.Var.applyTo(Var.java:700) 
    at clojure.main.main(main.java:37) 
    Caused by: java.lang.NullPointerException 
    at org.apache.commons.net.SocketClient.getRemoteAddress(SocketClient.java:658) 
    at org.apache.commons.net.ftp.FTPClient._openDataConnection_(FTPClient.java:789) 
    at org.apache.commons.net.ftp.FTPClient._retrieveFile(FTPClient.java:1854) 
    at org.apache.commons.net.ftp.FTPClient.retrieveFile(FTPClient.java:1845) 
    at miner.ftp$client_get.invokeStatic(ftp.clj:144) 
    at miner.ftp$client_get.invoke(ftp.clj:138) 
    at miner.ftp$client_get.invokeStatic(ftp.clj:140) 
    at miner.ftp$client_get.invoke(ftp.clj:138) 
    at zephyr.fetch$one_session$fn__1296.invoke(fetch.clj:30) 
    at clojure.core$map$fn__4785.invoke(core.clj:2644) 
    at clojure.lang.LazySeq.sval(LazySeq.java:40) 
    at clojure.lang.LazySeq.seq(LazySeq.java:49) 
    at clojure.lang.RT.seq(RT.java:521) 
    at clojure.core$seq__4357.invokeStatic(core.clj:137) 
    at clojure.core$print_sequential.invokeStatic(core_print.clj:46) 
    at clojure.core$fn__6072.invokeStatic(core_print.clj:153) 
    at clojure.core$fn__6072.invoke(core_print.clj:153) 
    at clojure.lang.MultiFn.invoke(MultiFn.java:233) 
    at clojure.core$pr_on.invokeStatic(core.clj:3572) 
    at clojure.core$pr.invokeStatic(core.clj:3575) 
    at clojure.core$pr.invoke(core.clj:3575) 
+0

Не могли бы вы показать весь стек? –

ответ

3

Я проверил источник библиотеки FTP и, кажется, что вы должны понимать, ленивую последовательность, созданную map. В противном случае вызов ftp/client-get выполняется после выхода из блока with-ftp, когда элементы извлекаются из последовательности результатов и в то время уже созданное соединение уже закрыто.

Чтобы устранить эту проблему, необходимо форсировать реализацию последовательности с помощью doall:

(defn one-session [files] 
    (ftp/with-ftp [client ftp-url] 
    (doall 
     (map #(ftp/client-get client %1) 
      files)))) 

Это заставит все ftp/client-get звонки произойти в вашей with-ftp области.

С другой стороны, может быть нежелательно реализовать всю последовательность сразу, поскольку это может иметь опасные последствия (например, использование памяти). Вы можете прочитать больше о Clojure lazy seqs, смешанном с побочными эффектами в Stuart Sierra's blog post.

В вашем конкретном случае ftp/client-get возвращает boolean значение, указывающее, успешно ли загружен файл в локальный файл или нет, поэтому это не является большой проблемой. В других случаях вы можете перепроектировать ваш API, чтобы ваша функция принимала не только несколько файлов, но и функцию, которая инкапсулирует то, что вы хотите делать с каждым файлом, и применяете эту функцию к каждому значению, поскольку оно потребляется один за другим, не сохраняя целого последовательности в памяти. @Frank Хенард допустил, что вы можете использовать для этого doseq.

+1

Мне нравится «доза» для побочных эффектов. –

+0

Можно ли использовать 'pmap' в таком комбо? Я стараюсь, чтобы он зависал через 2 секунды. Пробовал также прямой «будущий» вызов, но я ударил память. – Sebastian

+1

Это зависит от того, является ли API, который вы используете, потокобезопасным. В этом случае вам нужно проверить, можно ли одновременно использовать FTPClient из нескольких потоков. –

Смежные вопросы