adsp 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/AUTHORS +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +300 -0
  5. data/lib/adsp/error.rb +14 -0
  6. data/lib/adsp/file.rb +76 -0
  7. data/lib/adsp/option.rb +51 -0
  8. data/lib/adsp/stream/abstract.rb +206 -0
  9. data/lib/adsp/stream/delegates.rb +39 -0
  10. data/lib/adsp/stream/raw/abstract.rb +69 -0
  11. data/lib/adsp/stream/raw/compressor.rb +110 -0
  12. data/lib/adsp/stream/raw/decompressor.rb +80 -0
  13. data/lib/adsp/stream/raw/native_compressor.rb +58 -0
  14. data/lib/adsp/stream/raw/native_decompressor.rb +44 -0
  15. data/lib/adsp/stream/reader.rb +234 -0
  16. data/lib/adsp/stream/reader_helpers.rb +219 -0
  17. data/lib/adsp/stream/stat.rb +80 -0
  18. data/lib/adsp/stream/writer.rb +206 -0
  19. data/lib/adsp/stream/writer_helpers.rb +102 -0
  20. data/lib/adsp/string.rb +58 -0
  21. data/lib/adsp/validation.rb +46 -0
  22. data/lib/adsp/version.rb +7 -0
  23. data/lib/adsp.rb +8 -0
  24. data/test/common.rb +108 -0
  25. data/test/coverage_helper.rb +18 -0
  26. data/test/file.test.rb +120 -0
  27. data/test/minitest.rb +20 -0
  28. data/test/mock/common.rb +57 -0
  29. data/test/mock/file.rb +60 -0
  30. data/test/mock/stream/raw/compressor.rb +20 -0
  31. data/test/mock/stream/raw/decompressor.rb +20 -0
  32. data/test/mock/stream/raw/native_compressor.rb +82 -0
  33. data/test/mock/stream/raw/native_decompressor.rb +70 -0
  34. data/test/mock/stream/reader.rb +18 -0
  35. data/test/mock/stream/writer.rb +18 -0
  36. data/test/mock/string.rb +44 -0
  37. data/test/option.rb +66 -0
  38. data/test/stream/abstract.rb +125 -0
  39. data/test/stream/minitar.test.rb +50 -0
  40. data/test/stream/raw/abstract.rb +45 -0
  41. data/test/stream/raw/compressor.test.rb +166 -0
  42. data/test/stream/raw/decompressor.test.rb +166 -0
  43. data/test/stream/reader.test.rb +643 -0
  44. data/test/stream/reader_helpers.test.rb +421 -0
  45. data/test/stream/writer.test.rb +610 -0
  46. data/test/stream/writer_helpers.test.rb +267 -0
  47. data/test/string.test.rb +95 -0
  48. data/test/validation.rb +71 -0
  49. data/test/version.test.rb +18 -0
  50. metadata +274 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7bd22e8463be2343be9f61ce80d54261f32aefed31b3f6b6550f38e41a5c0401
4
+ data.tar.gz: 690e5d1c232c781f2bd48b099b01f8ee2d60a7b79aadec7255775110acfedf1f
5
+ SHA512:
6
+ metadata.gz: 03e5c419cebc46df3e64aa5f71905358c0f7dc5d16a417b2239d22c9e93eca734258da71a819037d6c754b4c4a077a9d177e13f9ec8310ee87187d875606f120
7
+ data.tar.gz: 5759268ce78057265a84f667966e0869973da161035498438ef88e9c41fc06bdc4984ac8fd73f3b58a0010b2971eabc932ff874d152487c5f8ab6d60d1c92e3d
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Andrew Aladjev
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 AUTHORS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,300 @@
1
+ # Abstract data stream processor
2
+
3
+ | AppVeyor | Jenkins | Github actions | Codecov | Gem |
4
+ | :------: | :-----: | :------------: | :-----: | :--: |
5
+ | [![AppVeyor test status](https://ci.appveyor.com/api/projects/status/github/andrew-aladev/adsp?branch=master&svg=true)](https://ci.appveyor.com/project/andrew-aladev/adsp/branch/master) | [![Jenkins test status](http://37.187.122.190:58182/buildStatus/icon?job=adsp)](http://37.187.122.190:58182/job/adsp) | [![Github Actions test status](https://github.com/andrew-aladev/adsp/workflows/test/badge.svg?branch=master)](https://github.com/andrew-aladev/adsp/actions) | [![Codecov](https://codecov.io/gh/andrew-aladev/adsp/branch/master/graph/badge.svg)](https://codecov.io/gh/andrew-aladev/adsp) | [![Gem](https://img.shields.io/gem/v/adsp.svg)](https://rubygems.org/gems/adsp) |
6
+
7
+ ## Installation
8
+
9
+ Operating systems: GNU/Linux, FreeBSD, OSX.
10
+
11
+ ## Usage
12
+
13
+ There are simple APIs: `String` and `File`. Also you can use generic streaming API: `Stream::Writer` and `Stream::Reader`.
14
+
15
+ ```ruby
16
+ require "adsp"
17
+
18
+ data = ADSP::String.compress "sample string"
19
+ puts ADSP::String.decompress(data)
20
+
21
+ ADSP::File.compress "file.txt", "file.txt.archive"
22
+ ADSP::File.decompress "file.txt.archive", "file.txt"
23
+
24
+ ADSP::Stream::Writer.open("file.txt.archive") { |writer| writer << "sample string" }
25
+ puts ADSP::Stream::Reader.open("file.txt.archive") { |reader| reader.read }
26
+
27
+ writer = ADSP::Stream::Writer.new output_socket
28
+ begin
29
+ bytes_written = writer.write_nonblock "sample string"
30
+ # handle "bytes_written"
31
+ rescue IO::WaitWritable
32
+ # handle wait
33
+ ensure
34
+ writer.close
35
+ end
36
+
37
+ reader = ADSP::Stream::Reader.new input_socket
38
+ begin
39
+ puts reader.read_nonblock(512)
40
+ rescue IO::WaitReadable
41
+ # handle wait
42
+ rescue ::EOFError
43
+ # handle eof
44
+ ensure
45
+ reader.close
46
+ end
47
+ ```
48
+
49
+ You can create and read `tar.archive` archives with [minitar](https://github.com/halostatue/minitar).
50
+
51
+ ```ruby
52
+ require "adsp"
53
+ require "minitar"
54
+
55
+ ADSP::Stream::Writer.open "file.tar.archive" do |writer|
56
+ Minitar::Writer.open writer do |tar|
57
+ tar.add_file_simple "file", :data => "sample string"
58
+ end
59
+ end
60
+
61
+ ADSP::Stream::Reader.open "file.tar.archive" do |reader|
62
+ Minitar::Reader.open reader do |tar|
63
+ tar.each_entry do |entry|
64
+ puts entry.name
65
+ puts entry.read
66
+ end
67
+ end
68
+ end
69
+ ```
70
+
71
+ All functionality (including streaming) can be used inside multiple threads with [parallel](https://github.com/grosser/parallel).
72
+ This code will provide heavy load for your CPU.
73
+
74
+ ```ruby
75
+ require "adsp"
76
+ require "parallel"
77
+
78
+ Parallel.each large_datas do |large_data|
79
+ ADSP::String.compress large_data
80
+ end
81
+ ```
82
+
83
+ ## Options
84
+
85
+ | Option | Values | Default | Description |
86
+ |---------------------------------|----------------|------------|-------------|
87
+ | `source_buffer_length` | 0 - inf | 0 (auto) | internal buffer length for source data |
88
+ | `destination_buffer_length` | 0 - inf | 0 (auto) | internal buffer length for description data |
89
+
90
+ There are internal buffers for compressed and decompressed data.
91
+ For example you want to use 1 KB as `source_buffer_length` for compressor - please use 256 B as `destination_buffer_length`.
92
+ You want to use 256 B as `source_buffer_length` for decompressor - please use 1 KB as `destination_buffer_length`.
93
+
94
+ Possible compressor options:
95
+ ```
96
+ :source_buffer_length
97
+ :destination_buffer_length
98
+ ```
99
+
100
+ Possible decompressor options:
101
+ ```
102
+ :source_buffer_length
103
+ :destination_buffer_length
104
+ ```
105
+
106
+ Example:
107
+
108
+ ```ruby
109
+ require "adsp"
110
+
111
+ data = ADSP::String.compress "sample string", :source_buffer_length => 512
112
+ puts ADSP::String.decompress(data, :source_buffer_length => 512)
113
+ ```
114
+
115
+ ## String
116
+
117
+ String maintains destination buffer only, so it accepts `destination_buffer_length` option only.
118
+
119
+ ```
120
+ ::compress(source, options = {})
121
+ ::decompress(source, options = {})
122
+ ```
123
+
124
+ `source` is a source string.
125
+
126
+ ## File
127
+
128
+ File maintains both source and destination buffers, it accepts both `source_buffer_length` and `destination_buffer_length` options.
129
+
130
+ ```
131
+ ::compress(source, destination, options = {})
132
+ ::decompress(source, destination, options = {})
133
+ ```
134
+
135
+ `source` and `destination` are file pathes.
136
+
137
+ ## Stream::Writer
138
+
139
+ Its behaviour is similar to builtin [`Zlib::GzipWriter`](https://ruby-doc.org/stdlib/libdoc/zlib/rdoc/Zlib/GzipWriter.html).
140
+
141
+ Writer maintains destination buffer only, so it accepts `destination_buffer_length` option only.
142
+
143
+ ```
144
+ ::open(file_path, options = {}, :external_encoding => nil, :transcode_options => {}, &block)
145
+ ```
146
+
147
+ Open file path and create stream writer associated with opened file.
148
+ Data will be transcoded to `:external_encoding` using `:transcode_options` before compressing.
149
+
150
+ It may be tricky to use both `:pledged_size` and `:transcode_options`. You have to provide size of transcoded input.
151
+
152
+ ```
153
+ ::new(destination_io, options = {}, :external_encoding => nil, :transcode_options => {})
154
+ ```
155
+
156
+ Create stream writer associated with destination io.
157
+ Data will be transcoded to `:external_encoding` using `:transcode_options` before compressing.
158
+
159
+ It may be tricky to use both `:pledged_size` and `:transcode_options`. You have to provide size of transcoded input.
160
+
161
+ ```
162
+ #set_encoding(external_encoding, nil, transcode_options)
163
+ ```
164
+
165
+ Set another encodings, `nil` is just for compatibility with `IO`.
166
+
167
+ ```
168
+ #io
169
+ #to_io
170
+ #stat
171
+ #external_encoding
172
+ #transcode_options
173
+ #pos
174
+ #tell
175
+ ```
176
+
177
+ See [`IO`](https://ruby-doc.org/core/IO.html) docs.
178
+
179
+ ```
180
+ #write(*objects)
181
+ #flush
182
+ #rewind
183
+ #close
184
+ #closed?
185
+ ```
186
+
187
+ See [`Zlib::GzipWriter`](https://ruby-doc.org/stdlib/libdoc/zlib/rdoc/Zlib/GzipWriter.html) docs.
188
+
189
+ ```
190
+ #write_nonblock(object, *options)
191
+ #flush_nonblock(*options)
192
+ #rewind_nonblock(*options)
193
+ #close_nonblock(*options)
194
+ ```
195
+
196
+ Special asynchronous methods missing in `Zlib::GzipWriter`.
197
+ `rewind` wants to `close`, `close` wants to `write` something and `flush`, `flush` want to `write` something.
198
+ So it is possible to have asynchronous variants for these synchronous methods.
199
+ Behaviour is the same as `IO#write_nonblock` method.
200
+
201
+ All nonblock operations for file will raise `EBADF` error on Windows.
202
+ Setting file into nonblocking mode is [not available on Windows](https://github.com/ruby/ruby/blob/master/win32/win32.c#L4388).
203
+
204
+ ```
205
+ #<<(object)
206
+ #print(*objects)
207
+ #printf(*args)
208
+ #putc(object, encoding: ::Encoding::BINARY)
209
+ #puts(*objects)
210
+ ```
211
+
212
+ Typical helpers, see [`Zlib::GzipWriter`](https://ruby-doc.org/stdlib/libdoc/zlib/rdoc/Zlib/GzipWriter.html) docs.
213
+
214
+ ## Stream::Reader
215
+
216
+ Its behaviour is similar to builtin [`Zlib::GzipReader`](https://ruby-doc.org/stdlib/libdoc/zlib/rdoc/Zlib/GzipReader.html).
217
+
218
+ Reader maintains both source and destination buffers, it accepts both `source_buffer_length` and `destination_buffer_length` options.
219
+
220
+ ```
221
+ ::open(file_path, options = {}, :external_encoding => nil, :internal_encoding => nil, :transcode_options => {}, &block)
222
+ ```
223
+
224
+ Open file path and create stream reader associated with opened file.
225
+ Data will be force encoded to `:external_encoding` and transcoded to `:internal_encoding` using `:transcode_options` after decompressing.
226
+
227
+ ```
228
+ ::new(source_io, options = {}, :external_encoding => nil, :internal_encoding => nil, :transcode_options => {})
229
+ ```
230
+
231
+ Create stream reader associated with source io.
232
+ Data will be force encoded to `:external_encoding` and transcoded to `:internal_encoding` using `:transcode_options` after decompressing.
233
+
234
+ ```
235
+ #set_encoding(external_encoding, internal_encoding, transcode_options)
236
+ ```
237
+
238
+ Set another encodings.
239
+
240
+ ```
241
+ #io
242
+ #to_io
243
+ #stat
244
+ #external_encoding
245
+ #internal_encoding
246
+ #transcode_options
247
+ #pos
248
+ #tell
249
+ ```
250
+
251
+ See [`IO`](https://ruby-doc.org/core/IO.html) docs.
252
+
253
+ ```
254
+ #read(bytes_to_read = nil, out_buffer = nil)
255
+ #eof?
256
+ #rewind
257
+ #close
258
+ #closed?
259
+ ```
260
+
261
+ See [`Zlib::GzipReader`](https://ruby-doc.org/stdlib/libdoc/zlib/rdoc/Zlib/GzipReader.html) docs.
262
+
263
+ ```
264
+ #readpartial(bytes_to_read = nil, out_buffer = nil)
265
+ #read_nonblock(bytes_to_read, out_buffer = nil, *options)
266
+ ```
267
+
268
+ See [`IO`](https://ruby-doc.org/core/IO.html) docs.
269
+
270
+ ```
271
+ #getbyte
272
+ #each_byte(&block)
273
+ #readbyte
274
+ #ungetbyte(byte)
275
+
276
+ #getc
277
+ #readchar
278
+ #each_char(&block)
279
+ #ungetc(char)
280
+
281
+ #lineno
282
+ #lineno=
283
+ #gets(separator = $OUTPUT_RECORD_SEPARATOR, limit = nil)
284
+ #readline
285
+ #readlines
286
+ #each(&block)
287
+ #each_line(&block)
288
+ #ungetline(line)
289
+ ```
290
+
291
+ Typical helpers, see [`Zlib::GzipReader`](https://ruby-doc.org/stdlib/libdoc/zlib/rdoc/Zlib/GzipReader.html) docs.
292
+
293
+ ## CI
294
+
295
+ Please visit [scripts/test-images](scripts/test-images).
296
+ See universal test script [scripts/ci_test.sh](scripts/ci_test.sh) for CI.
297
+
298
+ ## License
299
+
300
+ MIT license, see [LICENSE](LICENSE) and [AUTHORS](AUTHORS).
data/lib/adsp/error.rb ADDED
@@ -0,0 +1,14 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ module ADSP
5
+ class BaseError < ::StandardError; end
6
+
7
+ class ValidateError < BaseError; end
8
+
9
+ class NotEnoughDestinationError < BaseError; end
10
+ class UsedAfterCloseError < BaseError; end
11
+
12
+ class NotImplementedError < BaseError; end
13
+ class UnexpectedError < BaseError; end
14
+ end
data/lib/adsp/file.rb ADDED
@@ -0,0 +1,76 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "error"
5
+ require_relative "option"
6
+ require_relative "validation"
7
+
8
+ module ADSP
9
+ # ADSP::File class.
10
+ class File
11
+ # Current option class.
12
+ Option = ADSP::Option
13
+
14
+ # Current buffer length names.
15
+ # It is a part of compressor and decompressor options.
16
+ BUFFER_LENGTH_NAMES = %i[source_buffer_length destination_buffer_length].freeze
17
+
18
+ # Compresses data from +source+ file path to +destination+ file path.
19
+ # Option: +:source_buffer_length+ source buffer length.
20
+ # Option: +:destination_buffer_length+ destination buffer length.
21
+ def self.compress(source, destination, options = {})
22
+ Validation.validate_string source
23
+ Validation.validate_string destination
24
+
25
+ options = self::Option.get_compressor_options options, BUFFER_LENGTH_NAMES
26
+
27
+ open_files source, destination do |source_io, destination_io|
28
+ native_compress_io source_io, destination_io, options
29
+ end
30
+
31
+ nil
32
+ end
33
+
34
+ # :nocov:
35
+
36
+ # Internal method for compressing data from +source_io+ file to +destination_io+ file.
37
+ def self.native_compress_io(source_io, destination_io, options)
38
+ raise NotImplementedError
39
+ end
40
+
41
+ # :nocov:
42
+
43
+ # Decompresses data from +source+ file path to +destination+ file path.
44
+ # Option: +:source_buffer_length+ source buffer length.
45
+ # Option: +:destination_buffer_length+ destination buffer length.
46
+ def self.decompress(source, destination, options = {})
47
+ Validation.validate_string source
48
+ Validation.validate_string destination
49
+
50
+ options = self::Option.get_decompressor_options options, BUFFER_LENGTH_NAMES
51
+
52
+ open_files source, destination do |source_io, destination_io|
53
+ native_decompress_io source_io, destination_io, options
54
+ end
55
+
56
+ nil
57
+ end
58
+
59
+ # :nocov:
60
+
61
+ # Internal method for decompressing data from +source_io+ file to +destination_io+ file.
62
+ def self.native_decompress_io(source_io, destination_io, options)
63
+ raise NotImplementedError
64
+ end
65
+
66
+ # :nocov:
67
+
68
+ private_class_method def self.open_files(source, destination, &_block)
69
+ ::File.open source, "rb" do |source_io|
70
+ ::File.open destination, "wb" do |destination_io|
71
+ yield source_io, destination_io
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,51 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "validation"
5
+
6
+ module ADSP
7
+ # ADSP::Option class.
8
+ class Option
9
+ # Current default buffer length.
10
+ # It will be used when buffer length option is not defined.
11
+ DEFAULT_BUFFER_LENGTH = 0
12
+
13
+ # Validates and processes default values for compressor +options+.
14
+ # +buffer_length_names+ is an array of buffer length names (option names).
15
+ def self.get_compressor_options(options, buffer_length_names = [])
16
+ Validation.validate_hash options
17
+ Validation.validate_array buffer_length_names
18
+
19
+ buffer_length_names.each { |name| Validation.validate_symbol name }
20
+
21
+ buffer_length_defaults = buffer_length_names.each_with_object({}) do |name, defaults|
22
+ defaults[name] = DEFAULT_BUFFER_LENGTH
23
+ end
24
+
25
+ options = buffer_length_defaults.merge options
26
+
27
+ buffer_length_names.each { |name| Validation.validate_not_negative_integer options[name] }
28
+
29
+ options
30
+ end
31
+
32
+ # Validates and processes default values for decompressor +options+.
33
+ # +buffer_length_names+ is an array of buffer length names (option names).
34
+ def self.get_decompressor_options(options, buffer_length_names = [])
35
+ Validation.validate_hash options
36
+ Validation.validate_array buffer_length_names
37
+
38
+ buffer_length_names.each { |name| Validation.validate_symbol name }
39
+
40
+ buffer_length_defaults = buffer_length_names.each_with_object({}) do |name, defaults|
41
+ defaults[name] = DEFAULT_BUFFER_LENGTH
42
+ end
43
+
44
+ options = buffer_length_defaults.merge options
45
+
46
+ buffer_length_names.each { |name| Validation.validate_not_negative_integer options[name] }
47
+
48
+ options
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,206 @@
1
+ # Abstract data stream processor.
2
+ # Copyright (c) 2021 AUTHORS, MIT License.
3
+
4
+ require_relative "delegates"
5
+ require_relative "stat"
6
+ require_relative "../error"
7
+ require_relative "../validation"
8
+
9
+ module ADSP
10
+ module Stream
11
+ # ADSP::Stream::Abstract class.
12
+ class Abstract
13
+ # Native stream is typically not seekable.
14
+ # We don't need to implement methods like "seek" and "pos=".
15
+
16
+ # Typically we may not maintain correspondance between bytes
17
+ # consumed from source and bytes written to destination.
18
+ # So we are going to consume all source bytes and
19
+ # maintain buffer with remaining destination data.
20
+
21
+ include Delegates
22
+
23
+ # Native stream.
24
+ attr_reader :io
25
+
26
+ # Native stream status info.
27
+ attr_reader :stat
28
+
29
+ # Encoding name for destination data.
30
+ attr_reader :external_encoding
31
+
32
+ # Encoding name for source data.
33
+ attr_reader :internal_encoding
34
+
35
+ # Transcode options for native stream.
36
+ attr_reader :transcode_options
37
+
38
+ # Current offset for source data.
39
+ attr_reader :pos
40
+ alias tell pos
41
+
42
+ # Initializes stream using +io+ native stream and +options+.
43
+ # Option: +:external_encoding+ encoding name for destination data.
44
+ # Option: +:internal_encoding+ encoding name for source data.
45
+ # Option: +:transcode_options+ transcode options for data.
46
+ def initialize(io, options = {})
47
+ @raw_stream = create_raw_stream
48
+ @io = io
49
+
50
+ @stat = Stat.new @io.stat if @io.respond_to? :stat
51
+
52
+ set_encoding options[:external_encoding], options[:internal_encoding], options[:transcode_options]
53
+ reset_buffer
54
+ reset_io_advise
55
+
56
+ @pos = 0
57
+ end
58
+
59
+ # :nocov:
60
+
61
+ # Creates raw stream.
62
+ protected def create_raw_stream
63
+ raise NotImplementedError
64
+ end
65
+
66
+ # :nocov:
67
+
68
+ # -- buffer --
69
+
70
+ # Resets internal source buffer.
71
+ protected def reset_buffer
72
+ @buffer = ::String.new :encoding => ::Encoding::BINARY
73
+ end
74
+
75
+ # -- advise --
76
+
77
+ # Resets native stream advise.
78
+ protected def reset_io_advise
79
+ # Both compressor and decompressor need sequential io access.
80
+ @io.advise :sequential if @io.respond_to? :advise
81
+ rescue ::Errno::ESPIPE
82
+ # ok
83
+ end
84
+
85
+ # Sets access mode for native stream, noop.
86
+ def advise
87
+ # Noop
88
+ nil
89
+ end
90
+
91
+ # -- encoding --
92
+
93
+ # Sets encoding for source and destination data.
94
+ # First argument: +:external_encoding+ encoding name for destination data.
95
+ # Second argument: +:internal_encoding+ encoding name for source data.
96
+ # Third argument: +:transcode_options+ transcode options for data.
97
+ def set_encoding(*args)
98
+ external_encoding, internal_encoding, transcode_options = process_set_encoding_arguments(*args)
99
+
100
+ set_target_encoding :@external_encoding, external_encoding
101
+ set_target_encoding :@internal_encoding, internal_encoding
102
+ @transcode_options = transcode_options
103
+
104
+ self
105
+ end
106
+
107
+ # Processes encoding for source and destination data.
108
+ # First argument: +:external_encoding+ encoding name for destination data.
109
+ # Second argument: +:internal_encoding+ encoding name for source data.
110
+ # Third argument: +:transcode_options+ transcode options for data.
111
+ protected def process_set_encoding_arguments(*args)
112
+ external_encoding = args[0]
113
+
114
+ unless external_encoding.nil? || external_encoding.is_a?(::Encoding)
115
+ Validation.validate_string external_encoding
116
+
117
+ # First argument can be "external_encoding:internal_encoding".
118
+ match = %r{(.+?):(.+)}.match external_encoding
119
+
120
+ unless match.nil?
121
+ external_encoding = match[0]
122
+ internal_encoding = match[1]
123
+
124
+ transcode_options = args[1]
125
+ Validation.validate_hash transcode_options unless transcode_options.nil?
126
+
127
+ return [external_encoding, internal_encoding, transcode_options]
128
+ end
129
+ end
130
+
131
+ internal_encoding = args[1]
132
+ unless internal_encoding.nil? || internal_encoding.is_a?(::Encoding)
133
+ Validation.validate_string internal_encoding
134
+ end
135
+
136
+ transcode_options = args[2]
137
+ Validation.validate_hash transcode_options unless transcode_options.nil?
138
+
139
+ [external_encoding, internal_encoding, transcode_options]
140
+ end
141
+
142
+ # Sets +value+ for encoding +name+.
143
+ protected def set_target_encoding(name, value)
144
+ unless value.nil? || value.is_a?(::Encoding)
145
+ begin
146
+ value = ::Encoding.find value
147
+ rescue ::ArgumentError
148
+ raise ValidateError, "invalid #{name} encoding"
149
+ end
150
+ end
151
+
152
+ instance_variable_set name, value
153
+ end
154
+
155
+ # Returns encoding for source data if defined.
156
+ # Returns encoding for destination data if encoding for source data is not defined.
157
+ # Returns binary encoding if encodings for source and destination dara are not defined.
158
+ protected def target_encoding
159
+ return @internal_encoding unless @internal_encoding.nil?
160
+ return @external_encoding unless @external_encoding.nil?
161
+
162
+ ::Encoding::BINARY
163
+ end
164
+
165
+ # -- etc --
166
+
167
+ # Resets stream and source position.
168
+ # Returns zero (offset for source data).
169
+ def rewind
170
+ @raw_stream = create_raw_stream
171
+
172
+ @io.rewind if @io.respond_to? :rewind
173
+
174
+ reset_buffer
175
+ reset_io_advise
176
+
177
+ @pos = 0
178
+
179
+ 0
180
+ end
181
+
182
+ # Closes stream.
183
+ def close
184
+ @io.close if @io.respond_to? :close
185
+
186
+ nil
187
+ end
188
+
189
+ # Returns whether stream is closed.
190
+ def closed?
191
+ return false unless @raw_stream.closed?
192
+
193
+ if @io.respond_to? :closed
194
+ @io.closed?
195
+ else
196
+ true
197
+ end
198
+ end
199
+
200
+ # Returns self object.
201
+ def to_io
202
+ self
203
+ end
204
+ end
205
+ end
206
+ end