class Redwood::MBox::SSHFile

the file-like interface to a remote file

Constants

MAX_BUF_SIZE
MAX_TRANSFER_SIZE
REASONABLE_TRANSFER_SIZE
RECOVERABLE_ERRORS

upon these errors we'll try to rereconnect a few times

SIZE_CHECK_INTERVAL

Public Class Methods

new(host, fn, ssh_opts={}) click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 95
def initialize host, fn, ssh_opts={}
  @buf = Buffer.new
  @host = host
  @fn = fn
  @ssh_opts = ssh_opts
  @file_size = nil
  @offset = 0
  @say_id = nil
  @shell = nil
  @shell_mutex = nil
  @buf_mutex = Mutex.new
end

Public Instance Methods

connect() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 110
def connect
  do_remote nil
end
eof() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 115
def eof; eof?; end
eof?() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 114
def eof?; @offset >= size; end
gets() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 129
def gets
  return nil if eof?
  @buf_mutex.synchronize do
    make_buf_include @offset
    expand_buf_forward while @buf.index("\n", @offset).nil? && @buf.endd < size
    returning(@buf[@offset .. (@buf.index("\n", @offset) || -1)]) { |line| @offset += line.length }
  end
end
path() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 119
def path; @fn end
read(n) click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 138
def read n
  return nil if eof?
  @buf_mutex.synchronize do
    make_buf_include @offset, n
    @buf[@offset ... (@offset += n)]
  end
end
seek(loc;) click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 116
def seek loc; @offset = loc; end
size() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 121
def size
  if @file_size.nil? || (Time.now - @last_size_check) > SIZE_CHECK_INTERVAL
    @last_size_check = Time.now
    @file_size = do_remote("wc -c #@fn").split.first.to_i
  end
  @file_size
end
tell() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 117
def tell; @offset; end
to_s() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 108
def to_s; "mbox+ssh://#@host/#@fn"; end
total() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 118
def total; size; end

Private Instance Methods

do_remote(cmd, expected_size=0) click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 181
def do_remote cmd, expected_size=0
  retries = 0
  result = nil

  begin
    unsafe_connect
    if cmd
      # MBox::debug "sending command: #{cmd.inspect}"
      result = @shell_mutex.synchronize { x = @shell.send_command cmd; sleep 0.25; x }
      raise SSHFileError, "Failure during remote command #{cmd.inspect}: #{(result.stderr || result.stdout || "")[0 .. 100]}" unless result.status == 0
    end
    ## Net::SSH::Exceptions seem to happen every once in a while for
    ## no good reason.
  rescue Net::SSH::Exception, *RECOVERABLE_ERRORS
    if (retries += 1) <= 3
      @@shells_mutex.synchronize do
        @shell = nil
        @@shells[@key] = nil
      end
      retry
    end
    raise
  end

  result.stdout if cmd
end
expand_buf_forward(n=REASONABLE_TRANSFER_SIZE) click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 212
def expand_buf_forward n=REASONABLE_TRANSFER_SIZE
  @buf.add get_bytes(@buf.endd, n)
end
get_bytes(offset, size) click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 208
def get_bytes offset, size
  do_remote "tail -c +#{offset + 1} #@fn | head -c #{size}", size
end
make_buf_include(offset, size=0) click to toggle source

try our best to transfer somewhere between REASONABLE_TRANSFER_SIZE and MAX_TRANSFER_SIZE bytes

# File lib/sup/mbox/ssh-file.rb, line 218
def make_buf_include offset, size=0
  good_size = [size, REASONABLE_TRANSFER_SIZE].max

  trans_start, trans_size = 
    if @buf.empty?
      [offset, good_size]
    elsif offset < @buf.start
      if @buf.start - offset <= good_size
        start = [@buf.start - good_size, 0].max
        [start, @buf.start - start]
      elsif @buf.start - offset < MAX_TRANSFER_SIZE
        [offset, @buf.start - offset]
      else
        MBox::debug "clearing SSH buffer because buf.start #{@buf.start} - offset #{offset} >= #{MAX_TRANSFER_SIZE}"
        @buf.clear!
        [offset, good_size]
      end
    else
      return if [offset + size, self.size].min <= @buf.endd # whoohoo!
      if offset - @buf.endd <= good_size
        [@buf.endd, good_size]
      elsif offset - @buf.endd < MAX_TRANSFER_SIZE
        [@buf.endd, offset - @buf.endd]
      else
        MBox::debug "clearing SSH buffer because offset #{offset} - buf.end #{@buf.endd} >= #{MAX_TRANSFER_SIZE}"
        @buf.clear!
        [offset, good_size]
      end
    end          

  @buf.clear! if @buf.size > MAX_BUF_SIZE
  @buf.add get_bytes(trans_start, trans_size), trans_start
end
say(s) click to toggle source

TODO: share this code with imap

# File lib/sup/mbox/ssh-file.rb, line 149
def say s
  @say_id = BufferManager.say s, @say_id if BufferManager.instantiated?
  info s
end
shutup() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 154
def shutup
  BufferManager.clear @say_id if BufferManager.instantiated? && @say_id
  @say_id = nil
end
unsafe_connect() click to toggle source
# File lib/sup/mbox/ssh-file.rb, line 159
def unsafe_connect
  return if @shell

  @key = [@host, @ssh_opts[:username]]
  begin
    @shell, @shell_mutex = @@shells_mutex.synchronize do
      unless @@shells.member? @key
        say "Opening SSH connection to #{@host} for #@fn..."
        session = Net::SSH.start @host, @ssh_opts
        say "Starting SSH shell..."
        @@shells[@key] = [session.shell.sync, Mutex.new]
      end
      @@shells[@key]
    end
    
    say "Checking for #@fn..."
    @shell_mutex.synchronize { raise Errno::ENOENT, @fn unless @shell.test("-e #@fn").status == 0 }
  ensure
    shutup
  end
end