Class: Service::SFTPService

Inherits:
Service
  • Object
show all
Defined in:
lib/active_storage/service/sftp_service.rb

Overview

Wraps a remote path as an Active Storage service. See ActiveStorage::Service for the generic API documentation that applies to all services.

Constant Summary collapse

MAX_CHUNK_SIZE =
64.kilobytes.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host:, user:, public_host: nil, root: './', public_root: './', password: nil, simple_public_urls: false, verify_via_http_get: false) ⇒ SFTPService

Returns a new instance of SFTPService.



14
15
16
17
18
19
20
21
22
23
# File 'lib/active_storage/service/sftp_service.rb', line 14

def initialize(host:, user:, public_host: nil, root: './', public_root: './', password: nil, simple_public_urls: false, verify_via_http_get: false)
  @host = host
  @user = user
  @root = root
  @public_host = public_host
  @public_root = public_root
  @password = password
  @simple_public_urls = simple_public_urls
  @verify_via_http_get = verify_via_http_get
end

Instance Attribute Details

#hostObject (readonly)

Returns the value of attribute host.



12
13
14
# File 'lib/active_storage/service/sftp_service.rb', line 12

def host
  @host
end

#public_hostObject (readonly)

Returns the value of attribute public_host.



12
13
14
# File 'lib/active_storage/service/sftp_service.rb', line 12

def public_host
  @public_host
end

#public_rootObject (readonly)

Returns the value of attribute public_root.



12
13
14
# File 'lib/active_storage/service/sftp_service.rb', line 12

def public_root
  @public_root
end

#rootObject (readonly)

Returns the value of attribute root.



12
13
14
# File 'lib/active_storage/service/sftp_service.rb', line 12

def root
  @root
end

#userObject (readonly)

Returns the value of attribute user.



12
13
14
# File 'lib/active_storage/service/sftp_service.rb', line 12

def user
  @user
end

Instance Method Details

#classic_url(key, expires_in:, filename:, disposition:, content_type:) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/active_storage/service/sftp_service.rb', line 159

def classic_url(key, expires_in:, filename:, disposition:, content_type:)
  instrument :url, key: key do |payload|
    raise NotConfigured, "public_host not defined." unless public_host
    content_disposition = content_disposition_with(type: disposition, filename: filename)
    verified_key_with_expiration = ActiveStorage.verifier.generate(
      {
        key: key,
        disposition: content_disposition,
        content_type: content_type
      },
      {
        expires_in: expires_in,
        purpose: :blob_key
      }
    )

    generated_url = url_helpers.rails_disk_service_url(verified_key_with_expiration,
                                                       host: public_host,
                                                       disposition: content_disposition,
                                                       content_type: content_type,
                                                       filename: filename
    )
    payload[:url] = generated_url
    generated_url
  end
end

#delete(key) ⇒ Object



95
96
97
98
99
100
101
102
103
# File 'lib/active_storage/service/sftp_service.rb', line 95

def delete(key)
  instrument :delete, key: key do
    through_sftp do |sftp|
      sftp.remove!(path_for(key))
    end
  rescue Net::SFTP::StatusException
    # Ignore files already deleted
  end
end

#delete_prefixed(prefix) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/active_storage/service/sftp_service.rb', line 105

def delete_prefixed(prefix)
  instrument :delete_prefixed, prefix: prefix do
    through_sftp do |sftp|
      sftp.dir.glob(root, "#{prefix}*") do |entry|
        begin
          sftp.remove!(entry.path)
        rescue Net::SFTP::StatusException
          # Ignore files already deleted
        end
      end
    end
  end
end

#download(key, chunk_size: MAX_CHUNK_SIZE, &block) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/active_storage/service/sftp_service.rb', line 35

def download(key, chunk_size: MAX_CHUNK_SIZE, &block)
  if chunk_size > MAX_CHUNK_SIZE
    raise ChunkSizeError, "Maximum chunk size: #{MAX_CHUNK_SIZE}"
  end
  if block_given?
    instrument :streaming_download, key: key do
      through_sftp do |sftp|
        file = sftp.open!(path_for(key))
        buf = StringIO.new
        pos = 0
        eof = false
        until eof do
          request = sftp.read(file, pos, chunk_size) do |response|
            if response.eof?
              eof = true
            elsif !response.ok?
              raise SFTPResponseError, response.code
            else
              chunk = response[:data]
              block.call(chunk)
              buf << chunk
              pos += chunk.size
            end
          end
          request.wait
        end
        sftp.close(file)
        buf.string
      end
    end
  else
    instrument :download, key: key do
      io = StringIO.new
      through_sftp do |sftp|
        sftp.download!(path_for(key), io)
      end
      io.string
    rescue Errno::ENOENT
      raise ActiveStorage::FileNotFoundError
    end
  end
end

#download_chunk(key, range) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/active_storage/service/sftp_service.rb', line 78

def download_chunk(key, range)
  instrument :download_chunk, key: key, range: range do
    if range.size > MAX_CHUNK_SIZE
      raise ChunkSizeError, "Maximum chunk size: #{MAX_CHUNK_SIZE}"
    end
    chunk = StringIo.new
    through_sftp do |sftp|
      sftp.open(path_for(key)) do |file|
        chunk << sftp.read(file, range.begin, ranage.size).response[:data]
      end
    end
    chunk.string
  rescue Errno::ENOENT
    raise ActiveStorage::FileNotFoundError
  end
end

#exist?(key) ⇒ Boolean

Returns:

  • (Boolean)


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/active_storage/service/sftp_service.rb', line 119

def exist?(key)
  instrument :exist, key: key do |payload|
    answer = false

    if @verify_via_http_get
      uri = URI([public_host, relative_folder_for(key), key].join('/'))
      request = Net::HTTP.new uri.host
      response = request.request_head uri.path

      answer = (response.code.to_i == 200)
    else
      through_sftp do |sftp|
        # TODO Probably adviseable to let some more exceptions go through
        begin
          sftp.stat!(path_for(key)) do |response|
            answer = response.ok?
          end
        rescue Net::SFTP::StatusException => e
          answer = false
        end
      end
    end

    payload[:exist] = answer
    answer
  end
end

#headers_for_direct_upload(key, content_type:) ⇒ Object



218
219
220
# File 'lib/active_storage/service/sftp_service.rb', line 218

def headers_for_direct_upload(key, content_type:, **)
  { "Content-Type" => content_type }
end

#path_for(key) ⇒ Object



222
223
224
# File 'lib/active_storage/service/sftp_service.rb', line 222

def path_for(key)
  File.join folder_for(key), key
end

#public_url(key) ⇒ Object



186
187
188
189
190
191
192
193
# File 'lib/active_storage/service/sftp_service.rb', line 186

def public_url(key)
  instrument :url, key: key do |payload|
    raise NotConfigured, "public_host not defined." unless public_host
    generated_url = File.join(public_host, public_root, path_for(key), key)
    payload[:url] = generated_url
    generated_url
  end
end

#upload(key, io, checksum: nil) ⇒ Object



25
26
27
28
29
30
31
32
33
# File 'lib/active_storage/service/sftp_service.rb', line 25

def upload(key, io, checksum: nil, **)
  instrument :upload, key: key, checksum: checksum do
    ensure_integrity_of(io, checksum) if checksum
    mkdir_for(key)
    through_sftp do |sftp|
      sftp.upload!(io.path, path_for(key))
    end
  end
end

#url(key, expires_in:, filename:, disposition:, content_type:) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
# File 'lib/active_storage/service/sftp_service.rb', line 147

def url(key, expires_in:, filename:, disposition:, content_type:)
  if @simple_public_urls
    public_url(key)
  else
    classic_url(key,
                expires_in: expires_in,
                filename: filename,
                disposition: disposition,
                content_type: content_type)
  end
end

#url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/active_storage/service/sftp_service.rb', line 195

def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
  instrument :url, key: key do |payload|
    verified_token_with_expiration = ActiveStorage.verifier.generate(
        {
            key: key,
            content_type: content_type,
            content_length: content_length,
            checksum: checksum
        },
        { expires_in: expires_in,
          purpose: :blob_token }
    )

    generated_url = url_helpers.update_rails_disk_service_url(verified_token_with_expiration,
                                                              host: public_host
    )

    payload[:url] = generated_url

    generated_url
  end
end