Net::SFTP Errors



我一直在尝试使用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'

我已经尽我所能向谷歌祈祷,但没有取得任何进展。

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

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

以下是文档中download调用的摘要,提示期望的参数应该是String:

要从远程服务器下载单个文件,只需指定远程和本地路径:downloader=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作为Pathname传入。代码检查是否有传入的东西,但不是什么。如果没有传入任何东西,它会假设它具有类似IO的功能,这就是StringIO为内存缓存提供的功能。

显然您不能使用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))

参数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>问题就发生在这里在CCD_ 15方法中,下面是相关语句的代码片段。

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)

给定的参数remotelocal在此转换为Entry对象

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

而CCD_ 20在这里只是一个CCD_

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

那么sink属性是什么呢?我们马上跳到那个。。

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

找到下面的片段,

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

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

以下是控制台输出的一些片段,我在调试时在多个下载区块调用之间检查了这些片段。。其示出了显示上述对象的CCD_ 28和CCD_。在这里,我选择了我的远程为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>

最新更新