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.
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: []