我一直在尝试使用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)
给定的参数remote
和local
在此转换为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>