1

我一直在尝试使用 Net::SFTP 下载文件,但一直出错。

该文件是部分下载的,只有 2.1 MB,所以它不是一个巨大的文件。我删除了文件的循环,甚至尝试只下载一个文件并得到相同的错误:

yml = YAML.load_file Rails.root.join('config', 'ftp.yml')
Net::SFTP.start(yml["url"], yml["username"], password: yml["password"]) do |sftp|
  sftp.dir.glob(File.join('users', 'import'), '*.csv').each do |f|
    sftp.download!(File.join('users', 'import', f.name), Rails.root.join('processing_files', 'download_files', f.name), read_size: 1024)
  end
end
NoMethodError: undefined method `close' for #<Pathname:0x007fc8fdb50ea0>
from /[my_working_ap_dir]/gems/net-sftp-2.1.2/lib/net/sftp/operations/download.rb:331:in `on_read'

我已经尽我所能向谷歌祈祷,但没有得到任何结果。

4

3 回答 3

4

Rails.root返回一个Pathname对象,但看起来 sftp 代码没有检查它是否有 Pathname 或 File 句柄,它只是使用它运行。当它遇到entry.sink.close它时会崩溃,因为路径名没有实现关闭。

路径名非常适合操作文件和目录的路径,但它们不能替代文件句柄。您可能会添加to_s哪个会返回一个字符串。

download以下是来自文档的调用摘要,提示预期的参数应该是一个字符串:

要从远程服务器下载单个文件,只需指定
远程和本地路径:

  下载器 = sftp.download("/path/to/remote.txt", "/path/to/local.txt")

我怀疑如果我深入研究代码,它将检查参数是否为字符串,如果不是,则假定它们是 IO 句柄。

有关ri Net::SFTP::Operations::Download更多信息,请参阅。


这是当前代码的摘录download!,您可以看到问题是如何发生的:

def download!(remote, local=nil, options={}, &block)
  require 'stringio' unless defined?(StringIO)
  destination = local || StringIO.new
  result = download(remote, destination, options, &block).wait
  local ? result : destination.string
end

local作为路径名传入。代码检查是否有传入的东西,但不是那是什么。如果没有传入任何内容,则假定它具有类似 IO 的功能,这就是 StringIO 为内存缓存提供的功能。

于 2014-07-24T16:19:21.790 回答
2

显然你不能使用Rails.root.join,这是导致问题的原因。这真的很愚蠢,因为它会下载文件的一部分。

改变:

sftp.download!(File.join('users', 'import', f.name), Rails.root.join('processing_files', 'download_files', f.name))

至:

sftp.download!(File.join('users', 'import', f.name), File.join('processing_files', 'download_files', f.name))
于 2014-07-23T21:17:18.020 回答
0

参数remote可以是一个Pathname对象,而参数local设置时应该是一个String或响应#write方法的对象。下面是工作代码

local_stringified_path = Rails.root.join('processing_files', f.name).to_s
sftp.download!(Pathname.new('/users/import'), local_stringified_path)

对于所有好奇的人,请阅读以下内容以了解此行为。

问题 NoMethodError: undefined method close' for #<Pathname:0x007fc8fdb50ea0>恰好发生 在方法#on_read中,下面是相关语句的代码片段。

if response.eof? 
   update_progress(:close, entry)
   entry.sink.close # ERRORED OUT LINE.. ideally when eof, file IO handler is supposed to be closed

什么是entry.sink

我们已经知道#download!方法需要两个参数,如下所示

sftp.download!(remote, local)

给定的 argsremote在此处local转换为 Entry 对象

[Entry.new(remote, local, recursive?)]

Entry只是这里_Struct

Entry = Struct.new(:remote, :local, :directory, :size, :handle, :offset, :sink)

好吧那什么是sink属性?我们会马上跳到那个..

一旦打开相关的远程文件以供读取,该方法就会使用此处的文件处理程序#on_open更新此属性。sink

找到下面的片段,

entry.sink = entry.local.respond_to?(:write) ? entry.local : ::File.open(entry.local, "wb")

这实际上只发生在给定local的路径对象没有实现它自己的#write方法在我们的场景中,Pathname对象确实响应写入

下面是控制台输出的一些片段,我在调试时在多个下载块调用之间进行了检查。它显示entryentry.sink显示了上面讨论的对象。在这里,我选择我的遥控器作为Pathname对象,本地作为String路径,通过成功下载返回正确的值entry.sink和那里..

0> entry 
=> #<struct Net::SFTP::Operations::Download::Entry remote=#<Pathname:214010463.xml>, local="214010463.xml", directory=nil, size=nil, handle="1", offset=32000, sink=#<File:214010463.xml>>


0> entry.sink
=> #<File:214010463.xml>
于 2020-08-27T18:45:32.540 回答