serial_file 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/LICENSE +21 -0
  2. data/lib/serial_file.rb +193 -0
  3. 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.
@@ -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: []