serial_file 0.1.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.
- data/LICENSE +21 -0
- data/lib/serial_file.rb +193 -0
- metadata +50 -0
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2009, Michael H. Buselli
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
|
12
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ''AS IS'' AND ANY
|
13
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
14
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
15
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
|
16
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
17
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
18
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
19
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
20
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
21
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/lib/serial_file.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
class SerialFile
|
2
|
+
|
3
|
+
module ClassMethods
|
4
|
+
def sender (filename, options = {})
|
5
|
+
Sender.new(filename, options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def receiver (filename, options = {})
|
9
|
+
Receiver.new(filename, options)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
extend ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
module SerialFile::IO
|
17
|
+
BLOCK_SIZE = 4096
|
18
|
+
POLL_INTERVAL = 0.2
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Common code from #initialize
|
23
|
+
def setup_file (filename)
|
24
|
+
@filename = filename
|
25
|
+
@f = File.new(filename, "r+:BINARY")
|
26
|
+
@size = 16777216 # default to 16M filesize
|
27
|
+
@num_blocks = @size / BLOCK_SIZE
|
28
|
+
@block = 0
|
29
|
+
@block_pos = 4
|
30
|
+
@block_serial = 1
|
31
|
+
end
|
32
|
+
|
33
|
+
# Common code from #next_block
|
34
|
+
def next_block_common
|
35
|
+
@block += 1
|
36
|
+
@block = 0 if @block >= @num_blocks
|
37
|
+
@block_pos = 4
|
38
|
+
@block_serial += 1
|
39
|
+
end
|
40
|
+
|
41
|
+
# Go to the start of the current block. Used to write the block header.
|
42
|
+
def block_rewind
|
43
|
+
@f.pos = @block * BLOCK_SIZE
|
44
|
+
end
|
45
|
+
|
46
|
+
# Seek to the current block position. Used to write data.
|
47
|
+
def block_seek
|
48
|
+
@f.pos = @block * BLOCK_SIZE + @block_pos
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class SerialFile::Sender
|
53
|
+
include SerialFile::IO
|
54
|
+
|
55
|
+
def initialize (filename, options = {})
|
56
|
+
setup_file(filename)
|
57
|
+
zero
|
58
|
+
end
|
59
|
+
|
60
|
+
def puts (*args)
|
61
|
+
args.each do |string|
|
62
|
+
string << "\n" if string[-1] != "\n"
|
63
|
+
syswrite(string)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def syswrite (data)
|
68
|
+
remaining = data.dup.force_encoding("BINARY")
|
69
|
+
while remaining && !remaining.empty?
|
70
|
+
fit_in_block = BLOCK_SIZE - @block_pos
|
71
|
+
block_write(remaining[0...fit_in_block])
|
72
|
+
remaining = remaining[fit_in_block..-1]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
alias print syswrite
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# Wipe out the file to avoid confusion over what data has been used.
|
81
|
+
def zero
|
82
|
+
@f.rewind
|
83
|
+
@num_blocks.times do
|
84
|
+
@f.syswrite("\0" * BLOCK_SIZE)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Write data to block, but assume data fits in block. Update block header.
|
89
|
+
def block_write (data)
|
90
|
+
block_seek
|
91
|
+
@f.syswrite(data)
|
92
|
+
@block_pos += data.length
|
93
|
+
block_write_header
|
94
|
+
raise "block overwrite error" if @block_pos > BLOCK_SIZE
|
95
|
+
next_block if @block_pos == BLOCK_SIZE
|
96
|
+
end
|
97
|
+
|
98
|
+
def block_write_header
|
99
|
+
block_rewind
|
100
|
+
@f.syswrite([@block_serial, @block_pos].pack("nn"))
|
101
|
+
end
|
102
|
+
|
103
|
+
# Call next_block when the previous block is full and we need to prepare the
|
104
|
+
# next write to be in the next block.
|
105
|
+
def next_block
|
106
|
+
next_block_common
|
107
|
+
wait_for_block_to_write
|
108
|
+
end
|
109
|
+
|
110
|
+
# The next block is ready to be written to if the header is zeros. As
|
111
|
+
# a quick and dirty implementation, just sleep and poll for it.
|
112
|
+
def wait_for_block_to_write
|
113
|
+
loop do
|
114
|
+
block_rewind
|
115
|
+
data = @f.sysread(4)
|
116
|
+
return if data == "\0\0\0\0"
|
117
|
+
sleep POLL_INTERVAL
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class SerialFile::Receiver
|
123
|
+
include SerialFile::IO
|
124
|
+
|
125
|
+
def initialize (filename, options = {})
|
126
|
+
setup_file(filename)
|
127
|
+
@last_block_header = "\0\0\0\0"
|
128
|
+
end
|
129
|
+
|
130
|
+
def readpartial (num_bytes)
|
131
|
+
buffer = ""
|
132
|
+
loop do
|
133
|
+
bytes_ready = block_bytes_waiting_for_read
|
134
|
+
bytes_to_read = num_bytes < bytes_ready ? num_bytes : bytes_ready
|
135
|
+
return buffer if bytes_to_read.zero?
|
136
|
+
buffer << block_read(bytes_to_read)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def sysread (num_bytes)
|
141
|
+
buffer = ""
|
142
|
+
while buffer.length < num_bytes do
|
143
|
+
wait_for_block_to_read(num_bytes - buffer.length)
|
144
|
+
buffer << block_read(@block_bytes_waiting_for_read)
|
145
|
+
end
|
146
|
+
buffer
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
# Kind of like sysread, but num_bytes must read only from the current block
|
152
|
+
# and the data must be available (caller checks with
|
153
|
+
# block_bytes_waiting_for_read to ensure availability).
|
154
|
+
def block_read (num_bytes)
|
155
|
+
block_seek
|
156
|
+
data = @f.sysread(num_bytes)
|
157
|
+
@block_pos += data.length
|
158
|
+
raise "block overread error" if @block_pos > BLOCK_SIZE
|
159
|
+
next_block if @block_pos == BLOCK_SIZE
|
160
|
+
data
|
161
|
+
end
|
162
|
+
|
163
|
+
# Call next block when we've read everything from the previous block. We
|
164
|
+
# smash the block header for the block we just finished with to signal to
|
165
|
+
# the writer that it can reuse it.
|
166
|
+
def next_block
|
167
|
+
block_rewind
|
168
|
+
@f.syswrite("\0\0\0\0")
|
169
|
+
next_block_common
|
170
|
+
end
|
171
|
+
|
172
|
+
# The block contains data to read when the serial number matches and the
|
173
|
+
# @last_block_header does not match the previous read.
|
174
|
+
def wait_for_block_to_read (minimum_available = 1)
|
175
|
+
# Adjust minimum_available to be capped at the most bytes the block can hold.
|
176
|
+
block_space = BLOCK_SIZE - @block_pos
|
177
|
+
minimum_available = block_space if block_space < minimum_available
|
178
|
+
sleep POLL_INTERVAL while block_bytes_waiting_for_read < minimum_available
|
179
|
+
end
|
180
|
+
|
181
|
+
# Return the number of bytes in the current block that are available for
|
182
|
+
# reading.
|
183
|
+
def block_bytes_waiting_for_read
|
184
|
+
block_rewind
|
185
|
+
data = @f.sysread(4)
|
186
|
+
data_serial, new_pos = data.unpack("nn")
|
187
|
+
if @block_serial == data_serial && data != @last_block_header
|
188
|
+
@last_block_header = data
|
189
|
+
return @block_bytes_waiting_for_read = new_pos - @block_pos
|
190
|
+
end
|
191
|
+
0
|
192
|
+
end
|
193
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: serial_file
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael H. Buselli
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-07-01 00:00:00.000000000 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
description: ! 'Tool to pipe information through a filesystem
|
16
|
+
|
17
|
+
'
|
18
|
+
email: cosine@cosine.org
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- LICENSE
|
24
|
+
- lib/serial_file.rb
|
25
|
+
has_rdoc: true
|
26
|
+
homepage: http://cosine.org/
|
27
|
+
licenses: []
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project: serial_file
|
46
|
+
rubygems_version: 1.6.2
|
47
|
+
signing_key:
|
48
|
+
specification_version: 3
|
49
|
+
summary: Tool to pipe information through a filesystem
|
50
|
+
test_files: []
|