adsp 1.0.0
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/AUTHORS +1 -0
- data/LICENSE +21 -0
- data/README.md +300 -0
- data/lib/adsp/error.rb +14 -0
- data/lib/adsp/file.rb +76 -0
- data/lib/adsp/option.rb +51 -0
- data/lib/adsp/stream/abstract.rb +206 -0
- data/lib/adsp/stream/delegates.rb +39 -0
- data/lib/adsp/stream/raw/abstract.rb +69 -0
- data/lib/adsp/stream/raw/compressor.rb +110 -0
- data/lib/adsp/stream/raw/decompressor.rb +80 -0
- data/lib/adsp/stream/raw/native_compressor.rb +58 -0
- data/lib/adsp/stream/raw/native_decompressor.rb +44 -0
- data/lib/adsp/stream/reader.rb +234 -0
- data/lib/adsp/stream/reader_helpers.rb +219 -0
- data/lib/adsp/stream/stat.rb +80 -0
- data/lib/adsp/stream/writer.rb +206 -0
- data/lib/adsp/stream/writer_helpers.rb +102 -0
- data/lib/adsp/string.rb +58 -0
- data/lib/adsp/validation.rb +46 -0
- data/lib/adsp/version.rb +7 -0
- data/lib/adsp.rb +8 -0
- data/test/common.rb +108 -0
- data/test/coverage_helper.rb +18 -0
- data/test/file.test.rb +120 -0
- data/test/minitest.rb +20 -0
- data/test/mock/common.rb +57 -0
- data/test/mock/file.rb +60 -0
- data/test/mock/stream/raw/compressor.rb +20 -0
- data/test/mock/stream/raw/decompressor.rb +20 -0
- data/test/mock/stream/raw/native_compressor.rb +82 -0
- data/test/mock/stream/raw/native_decompressor.rb +70 -0
- data/test/mock/stream/reader.rb +18 -0
- data/test/mock/stream/writer.rb +18 -0
- data/test/mock/string.rb +44 -0
- data/test/option.rb +66 -0
- data/test/stream/abstract.rb +125 -0
- data/test/stream/minitar.test.rb +50 -0
- data/test/stream/raw/abstract.rb +45 -0
- data/test/stream/raw/compressor.test.rb +166 -0
- data/test/stream/raw/decompressor.test.rb +166 -0
- data/test/stream/reader.test.rb +643 -0
- data/test/stream/reader_helpers.test.rb +421 -0
- data/test/stream/writer.test.rb +610 -0
- data/test/stream/writer_helpers.test.rb +267 -0
- data/test/string.test.rb +95 -0
- data/test/validation.rb +71 -0
- data/test/version.test.rb +18 -0
- 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
|
+
| [](https://ci.appveyor.com/project/andrew-aladev/adsp/branch/master) | [](http://37.187.122.190:58182/job/adsp) | [](https://github.com/andrew-aladev/adsp/actions) | [](https://codecov.io/gh/andrew-aladev/adsp) | [](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
|
data/lib/adsp/option.rb
ADDED
@@ -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
|