aws-sdk-resources 2.11.549
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/aws-sdk-resources.rb +91 -0
- data/lib/aws-sdk-resources/batch.rb +143 -0
- data/lib/aws-sdk-resources/builder.rb +85 -0
- data/lib/aws-sdk-resources/builder_sources.rb +105 -0
- data/lib/aws-sdk-resources/collection.rb +107 -0
- data/lib/aws-sdk-resources/definition.rb +331 -0
- data/lib/aws-sdk-resources/documenter.rb +70 -0
- data/lib/aws-sdk-resources/documenter/base_operation_documenter.rb +279 -0
- data/lib/aws-sdk-resources/documenter/data_operation_documenter.rb +25 -0
- data/lib/aws-sdk-resources/documenter/has_many_operation_documenter.rb +69 -0
- data/lib/aws-sdk-resources/documenter/has_operation_documenter.rb +66 -0
- data/lib/aws-sdk-resources/documenter/operation_documenter.rb +20 -0
- data/lib/aws-sdk-resources/documenter/resource_operation_documenter.rb +53 -0
- data/lib/aws-sdk-resources/documenter/waiter_operation_documenter.rb +77 -0
- data/lib/aws-sdk-resources/errors.rb +15 -0
- data/lib/aws-sdk-resources/operation_methods.rb +83 -0
- data/lib/aws-sdk-resources/operations.rb +280 -0
- data/lib/aws-sdk-resources/options.rb +17 -0
- data/lib/aws-sdk-resources/request.rb +39 -0
- data/lib/aws-sdk-resources/request_params.rb +140 -0
- data/lib/aws-sdk-resources/resource.rb +243 -0
- data/lib/aws-sdk-resources/services/ec2.rb +21 -0
- data/lib/aws-sdk-resources/services/ec2/instance.rb +29 -0
- data/lib/aws-sdk-resources/services/iam.rb +19 -0
- data/lib/aws-sdk-resources/services/s3.rb +20 -0
- data/lib/aws-sdk-resources/services/s3/bucket.rb +131 -0
- data/lib/aws-sdk-resources/services/s3/encryption.rb +21 -0
- data/lib/aws-sdk-resources/services/s3/encryption/client.rb +369 -0
- data/lib/aws-sdk-resources/services/s3/encryption/decrypt_handler.rb +174 -0
- data/lib/aws-sdk-resources/services/s3/encryption/default_cipher_provider.rb +63 -0
- data/lib/aws-sdk-resources/services/s3/encryption/default_key_provider.rb +38 -0
- data/lib/aws-sdk-resources/services/s3/encryption/encrypt_handler.rb +50 -0
- data/lib/aws-sdk-resources/services/s3/encryption/errors.rb +13 -0
- data/lib/aws-sdk-resources/services/s3/encryption/io_auth_decrypter.rb +56 -0
- data/lib/aws-sdk-resources/services/s3/encryption/io_decrypter.rb +29 -0
- data/lib/aws-sdk-resources/services/s3/encryption/io_encrypter.rb +69 -0
- data/lib/aws-sdk-resources/services/s3/encryption/key_provider.rb +29 -0
- data/lib/aws-sdk-resources/services/s3/encryption/kms_cipher_provider.rb +71 -0
- data/lib/aws-sdk-resources/services/s3/encryption/materials.rb +58 -0
- data/lib/aws-sdk-resources/services/s3/encryption/utils.rb +79 -0
- data/lib/aws-sdk-resources/services/s3/file_downloader.rb +169 -0
- data/lib/aws-sdk-resources/services/s3/file_part.rb +75 -0
- data/lib/aws-sdk-resources/services/s3/file_uploader.rb +58 -0
- data/lib/aws-sdk-resources/services/s3/multipart_file_uploader.rb +187 -0
- data/lib/aws-sdk-resources/services/s3/multipart_upload.rb +42 -0
- data/lib/aws-sdk-resources/services/s3/multipart_upload_error.rb +16 -0
- data/lib/aws-sdk-resources/services/s3/object.rb +290 -0
- data/lib/aws-sdk-resources/services/s3/object_copier.rb +99 -0
- data/lib/aws-sdk-resources/services/s3/object_multipart_copier.rb +180 -0
- data/lib/aws-sdk-resources/services/s3/object_summary.rb +73 -0
- data/lib/aws-sdk-resources/services/s3/presigned_post.rb +651 -0
- data/lib/aws-sdk-resources/services/sns.rb +7 -0
- data/lib/aws-sdk-resources/services/sns/message_verifier.rb +171 -0
- data/lib/aws-sdk-resources/services/sqs.rb +7 -0
- data/lib/aws-sdk-resources/services/sqs/queue_poller.rb +521 -0
- data/lib/aws-sdk-resources/source.rb +39 -0
- metadata +118 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Encryption
|
4
|
+
|
5
|
+
# This module defines the interface required for a {Client#key_provider}.
|
6
|
+
# A key provider is any object that:
|
7
|
+
#
|
8
|
+
# * Responds to {#encryption_materials} with an {Materials} object.
|
9
|
+
#
|
10
|
+
# * Responds to {#key_for}, receiving a JSON document String,
|
11
|
+
# returning an encryption key. The returned encryption key
|
12
|
+
# must be one of:
|
13
|
+
#
|
14
|
+
# * `OpenSSL::PKey::RSA` - for asymmetric encryption
|
15
|
+
# * `String` - 32, 24, or 16 bytes long, for symmetric encryption
|
16
|
+
#
|
17
|
+
module KeyProvider
|
18
|
+
|
19
|
+
# @return [Materials]
|
20
|
+
def encryption_materials; end
|
21
|
+
|
22
|
+
# @param [String<JSON>] materials_description
|
23
|
+
# @return [OpenSSL::PKey::RSA, String] encryption_key
|
24
|
+
def key_for(materials_description); end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
module Encryption
|
6
|
+
# @api private
|
7
|
+
class KmsCipherProvider
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@kms_key_id = options[:kms_key_id]
|
11
|
+
@kms_client = options[:kms_client]
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Array<Hash,Cipher>] Creates an returns a new encryption
|
15
|
+
# envelope and encryption cipher.
|
16
|
+
def encryption_cipher
|
17
|
+
encryption_context = { "kms_cmk_id" => @kms_key_id }
|
18
|
+
key_data = @kms_client.generate_data_key(
|
19
|
+
key_id: @kms_key_id,
|
20
|
+
encryption_context: encryption_context,
|
21
|
+
key_spec: 'AES_256',
|
22
|
+
)
|
23
|
+
cipher = Utils.aes_encryption_cipher(:CBC)
|
24
|
+
cipher.key = key_data.plaintext
|
25
|
+
envelope = {
|
26
|
+
'x-amz-key-v2' => encode64(key_data.ciphertext_blob),
|
27
|
+
'x-amz-iv' => encode64(cipher.iv = cipher.random_iv),
|
28
|
+
'x-amz-cek-alg' => 'AES/CBC/PKCS5Padding',
|
29
|
+
'x-amz-wrap-alg' => 'kms',
|
30
|
+
'x-amz-matdesc' => Json.dump(encryption_context)
|
31
|
+
}
|
32
|
+
[envelope, cipher]
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Cipher] Given an encryption envelope, returns a
|
36
|
+
# decryption cipher.
|
37
|
+
def decryption_cipher(envelope)
|
38
|
+
encryption_context = Json.load(envelope['x-amz-matdesc'])
|
39
|
+
key = @kms_client.decrypt(
|
40
|
+
ciphertext_blob: decode64(envelope['x-amz-key-v2']),
|
41
|
+
encryption_context: encryption_context,
|
42
|
+
).plaintext
|
43
|
+
iv = decode64(envelope['x-amz-iv'])
|
44
|
+
block_mode =
|
45
|
+
case envelope['x-amz-cek-alg']
|
46
|
+
when 'AES/CBC/PKCS5Padding'
|
47
|
+
:CBC
|
48
|
+
when 'AES/GCM/NoPadding'
|
49
|
+
:GCM
|
50
|
+
else
|
51
|
+
type = envelope['x-amz-cek-alg'].inspect
|
52
|
+
msg = "unsupported content encrypting key (cek) format: #{type}"
|
53
|
+
raise Errors::DecryptionError, msg
|
54
|
+
end
|
55
|
+
Utils.aes_decryption_cipher(block_mode, key, iv)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def encode64(str)
|
61
|
+
Base64.encode64(str).split("\n") * ""
|
62
|
+
end
|
63
|
+
|
64
|
+
def decode64(str)
|
65
|
+
Base64.decode64(str)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
module Encryption
|
6
|
+
class Materials
|
7
|
+
|
8
|
+
# @option options [required, OpenSSL::PKey::RSA, String] :key
|
9
|
+
# The master key to use for encrypting/decrypting all objects.
|
10
|
+
#
|
11
|
+
# @option options [String<JSON>] :description ('{}')
|
12
|
+
# The encryption materials description. This is must be
|
13
|
+
# a JSON document string.
|
14
|
+
#
|
15
|
+
def initialize(options = {})
|
16
|
+
@key = validate_key(options[:key])
|
17
|
+
@description = validate_desc(options[:description])
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [OpenSSL::PKey::RSA, String]
|
21
|
+
attr_reader :key
|
22
|
+
|
23
|
+
# @return [String<JSON>]
|
24
|
+
attr_reader :description
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def validate_key(key)
|
29
|
+
case key
|
30
|
+
when OpenSSL::PKey::RSA then key
|
31
|
+
when String
|
32
|
+
if [32, 24, 16].include?(key.bytesize)
|
33
|
+
key
|
34
|
+
else
|
35
|
+
msg = "invalid key, symmetric key required to be 16, 24, or "
|
36
|
+
msg << "32 bytes in length, saw length " + key.bytesize.to_s
|
37
|
+
raise ArgumentError, msg
|
38
|
+
end
|
39
|
+
else
|
40
|
+
msg = "invalid encryption key, expected an OpenSSL::PKey::RSA key "
|
41
|
+
msg << "(for asymmetric encryption) or a String (for symmetric "
|
42
|
+
msg << "encryption)."
|
43
|
+
raise ArgumentError, msg
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_desc(description)
|
48
|
+
Json.load(description)
|
49
|
+
description
|
50
|
+
rescue Json::ParseError, EncodingError
|
51
|
+
msg = "expected description to be a valid JSON document string"
|
52
|
+
raise ArgumentError, msg
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
module Encryption
|
6
|
+
# @api private
|
7
|
+
module Utils
|
8
|
+
|
9
|
+
UNSAFE_MSG = "unsafe encryption, data is longer than key length"
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def encrypt(key, data)
|
14
|
+
case key
|
15
|
+
when OpenSSL::PKey::RSA # asymmetric encryption
|
16
|
+
warn(UNSAFE_MSG) if key.public_key.n.num_bits < cipher_size(data)
|
17
|
+
key.public_encrypt(data)
|
18
|
+
when String # symmetric encryption
|
19
|
+
warn(UNSAFE_MSG) if cipher_size(key) < cipher_size(data)
|
20
|
+
cipher = aes_encryption_cipher(:ECB, key)
|
21
|
+
cipher.update(data) + cipher.final
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def decrypt(key, data)
|
26
|
+
begin
|
27
|
+
case key
|
28
|
+
when OpenSSL::PKey::RSA # asymmetric decryption
|
29
|
+
key.private_decrypt(data)
|
30
|
+
when String # symmetric Decryption
|
31
|
+
cipher = aes_cipher(:decrypt, :ECB, key, nil)
|
32
|
+
cipher.update(data) + cipher.final
|
33
|
+
end
|
34
|
+
rescue OpenSSL::Cipher::CipherError
|
35
|
+
msg = 'decryption failed, possible incorrect key'
|
36
|
+
raise Errors::DecryptionError, msg
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [String] block_mode "CBC" or "ECB"
|
41
|
+
# @param [OpenSSL::PKey::RSA, String, nil] key
|
42
|
+
# @param [String, nil] iv The initialization vector
|
43
|
+
def aes_encryption_cipher(block_mode, key = nil, iv = nil)
|
44
|
+
aes_cipher(:encrypt, block_mode, key, iv)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [String] block_mode "CBC" or "ECB"
|
48
|
+
# @param [OpenSSL::PKey::RSA, String, nil] key
|
49
|
+
# @param [String, nil] iv The initialization vector
|
50
|
+
def aes_decryption_cipher(block_mode, key = nil, iv = nil)
|
51
|
+
aes_cipher(:decrypt, block_mode, key, iv)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param [String] mode "encrypt" or "decrypt"
|
55
|
+
# @param [String] block_mode "CBC" or "ECB"
|
56
|
+
# @param [OpenSSL::PKey::RSA, String, nil] key
|
57
|
+
# @param [String, nil] iv The initialization vector
|
58
|
+
def aes_cipher(mode, block_mode, key, iv)
|
59
|
+
cipher = key ?
|
60
|
+
OpenSSL::Cipher.new("aes-#{cipher_size(key)}-#{block_mode.downcase}") :
|
61
|
+
OpenSSL::Cipher.new("aes-256-#{block_mode.downcase}")
|
62
|
+
cipher.send(mode) # encrypt or decrypt
|
63
|
+
cipher.key = key if key
|
64
|
+
cipher.iv = iv if iv
|
65
|
+
cipher
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param [String] key
|
69
|
+
# @return [Integer]
|
70
|
+
# @raise ArgumentError
|
71
|
+
def cipher_size(key)
|
72
|
+
key.bytesize * 8
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'thread'
|
3
|
+
require 'set'
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
module Aws
|
7
|
+
module S3
|
8
|
+
# @api private
|
9
|
+
class FileDownloader
|
10
|
+
|
11
|
+
MIN_CHUNK_SIZE = 5 * 1024 * 1024
|
12
|
+
MAX_PARTS = 10_000
|
13
|
+
THREAD_COUNT = 10
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@client = options[:client] || Client.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Client]
|
20
|
+
attr_reader :client
|
21
|
+
|
22
|
+
def download(destination, options = {})
|
23
|
+
@path = destination
|
24
|
+
@mode = options[:mode] || "auto"
|
25
|
+
@thread_count = options[:thread_count] || THREAD_COUNT
|
26
|
+
@chunk_size = options[:chunk_size]
|
27
|
+
@bucket = options[:bucket]
|
28
|
+
@key = options[:key]
|
29
|
+
|
30
|
+
case @mode
|
31
|
+
when "auto" then multipart_download
|
32
|
+
when "single_request" then single_request
|
33
|
+
when "get_range"
|
34
|
+
if @chunk_size
|
35
|
+
resp = @client.head_object(bucket: @bucket, key: @key)
|
36
|
+
multithreaded_get_by_ranges(construct_chunks(resp.content_length))
|
37
|
+
else
|
38
|
+
msg = "In :get_range mode, :chunk_size must be provided"
|
39
|
+
raise ArgumentError, msg
|
40
|
+
end
|
41
|
+
else
|
42
|
+
msg = "Invalid mode #{@mode} provided, "\
|
43
|
+
"mode should be :single_request, :get_range or :auto"
|
44
|
+
raise ArgumentError, msg
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def multipart_download
|
51
|
+
resp = @client.head_object(bucket: @bucket, key: @key, part_number: 1)
|
52
|
+
count = resp.parts_count
|
53
|
+
if count.nil? || count <= 1
|
54
|
+
resp.content_length < MIN_CHUNK_SIZE ?
|
55
|
+
single_request :
|
56
|
+
multithreaded_get_by_ranges(construct_chunks(resp.content_length))
|
57
|
+
else
|
58
|
+
# partNumber is an option
|
59
|
+
resp = @client.head_object(bucket: @bucket, key: @key)
|
60
|
+
resp.content_length < MIN_CHUNK_SIZE ?
|
61
|
+
single_request :
|
62
|
+
compute_mode(resp.content_length, count)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def compute_mode(file_size, count)
|
67
|
+
chunk_size = compute_chunk(file_size)
|
68
|
+
part_size = (file_size.to_f / count.to_f).ceil
|
69
|
+
if chunk_size < part_size
|
70
|
+
multithreaded_get_by_ranges(construct_chunks(file_size))
|
71
|
+
else
|
72
|
+
multithreaded_get_by_parts(count)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def construct_chunks(file_size)
|
77
|
+
offset = 0
|
78
|
+
default_chunk_size = compute_chunk(file_size)
|
79
|
+
chunks = []
|
80
|
+
while offset <= file_size
|
81
|
+
progress = offset + default_chunk_size
|
82
|
+
chunks << "bytes=#{offset}-#{progress < file_size ? progress : file_size}"
|
83
|
+
offset = progress + 1
|
84
|
+
end
|
85
|
+
chunks
|
86
|
+
end
|
87
|
+
|
88
|
+
def compute_chunk(file_size)
|
89
|
+
if @chunk_size && @chunk_size > file_size
|
90
|
+
raise ArgumentError, ":chunk_size shouldn't exceed total file size."
|
91
|
+
else
|
92
|
+
default_chunk_size = @chunk_size || [(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE].max.to_i
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def sort_files(files)
|
97
|
+
# sort file by start range count or part number
|
98
|
+
files.sort do |a, b|
|
99
|
+
a[/([^\=]+)$/].split('-')[0].to_i <=> b[/([^\=]+)$/].split('-')[0].to_i
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def concatenate_parts(fileparts)
|
104
|
+
File.open(@path, 'wb')do |output_path|
|
105
|
+
sort_files(fileparts).each {|part| IO.copy_stream(part, output_path)}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def file_batches(chunks, dir, mode)
|
110
|
+
batches = []
|
111
|
+
chunks = (1..chunks) if mode.eql? 'part_number'
|
112
|
+
chunks.each_slice(@thread_count) do |slice|
|
113
|
+
batches << map_files(slice, dir, mode)
|
114
|
+
end
|
115
|
+
batches
|
116
|
+
end
|
117
|
+
|
118
|
+
def map_files(slice, dir, mode)
|
119
|
+
case mode
|
120
|
+
when 'range'
|
121
|
+
slice.inject({}) {|h, chunk| h[chunk] = File.join(dir, chunk); h}
|
122
|
+
when 'part_number'
|
123
|
+
slice.inject({}) {|h, part| h[part] = File.join(dir, "part_number=#{part}"); h}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def multithreaded_get_by_ranges(chunks)
|
128
|
+
thread_batches(chunks, 'range')
|
129
|
+
end
|
130
|
+
|
131
|
+
def multithreaded_get_by_parts(parts)
|
132
|
+
thread_batches(parts, 'part_number')
|
133
|
+
end
|
134
|
+
|
135
|
+
def thread_batches(chunks, param)
|
136
|
+
# create a tmp dir under destination dir for batches
|
137
|
+
dir = Dir.mktmpdir(nil, File.dirname(@path))
|
138
|
+
batches = file_batches(chunks, dir, param)
|
139
|
+
parts = batches.flat_map(&:values)
|
140
|
+
begin
|
141
|
+
batches.each do |batch|
|
142
|
+
threads = []
|
143
|
+
batch.each do |chunk, file|
|
144
|
+
threads << Thread.new do
|
145
|
+
resp = @client.get_object(
|
146
|
+
:bucket => @bucket,
|
147
|
+
:key => @key,
|
148
|
+
param.to_sym => chunk,
|
149
|
+
:response_target => file
|
150
|
+
)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
threads.each(&:join)
|
154
|
+
end
|
155
|
+
concatenate_parts(parts)
|
156
|
+
ensure
|
157
|
+
# clean up tmp dir
|
158
|
+
FileUtils.remove_entry(dir)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def single_request
|
163
|
+
@client.get_object(
|
164
|
+
bucket: @bucket, key: @key, response_target: @path
|
165
|
+
)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
|
4
|
+
# A utility class that provides an IO-like interface to a portion of
|
5
|
+
# a file on disk.
|
6
|
+
# @api private
|
7
|
+
class FilePart
|
8
|
+
|
9
|
+
# @option options [required,String,Pathname,File,Tempfile] :source
|
10
|
+
# @option options [required,Integer] :offset The file part will read
|
11
|
+
# starting at this byte offset.
|
12
|
+
# @option options [required,Integer] :size The maximum number of bytes to
|
13
|
+
# read from the `:offset`.
|
14
|
+
def initialize(options = {})
|
15
|
+
@source = options[:source]
|
16
|
+
@first_byte = options[:offset]
|
17
|
+
@last_byte = @first_byte + options[:size]
|
18
|
+
@size = options[:size]
|
19
|
+
@file = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String,Pathname,File,Tempfile]
|
23
|
+
attr_reader :source
|
24
|
+
|
25
|
+
# @return [Integer]
|
26
|
+
attr_reader :first_byte
|
27
|
+
|
28
|
+
# @return [Integer]
|
29
|
+
attr_reader :last_byte
|
30
|
+
|
31
|
+
# @return [Integer]
|
32
|
+
attr_reader :size
|
33
|
+
|
34
|
+
def read(bytes = nil, output_buffer = nil)
|
35
|
+
open_file unless @file
|
36
|
+
read_from_file(bytes, output_buffer)
|
37
|
+
end
|
38
|
+
|
39
|
+
def rewind
|
40
|
+
if @file
|
41
|
+
@file.seek(@first_byte)
|
42
|
+
@position = @first_byte
|
43
|
+
end
|
44
|
+
0
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
@file.close if @file
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def open_file
|
54
|
+
@file = File.open(@source, 'rb')
|
55
|
+
rewind
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_from_file(bytes, output_buffer)
|
59
|
+
if bytes
|
60
|
+
data = @file.read([remaining_bytes, bytes].min)
|
61
|
+
data = nil if data == ''
|
62
|
+
else
|
63
|
+
data = @file.read(remaining_bytes)
|
64
|
+
end
|
65
|
+
@position += data ? data.bytesize : 0
|
66
|
+
output_buffer ? output_buffer.replace(data || '') : data
|
67
|
+
end
|
68
|
+
|
69
|
+
def remaining_bytes
|
70
|
+
@last_byte - @position
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|