tkar 0.63

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,284 @@
1
+ require 'socket'
2
+ require 'thread'
3
+
4
+ module Tkar
5
+ module MessageStream
6
+ # Get a bidirectional stream for sending and receiving Tkar
7
+ # protocol messages in either binary or ascii format.
8
+ def self.for(argv, opts = {})
9
+ binary, client = opts["b"], opts["c"]
10
+ (binary ? Binary : Ascii).new(opts, *get_fds(argv, client))
11
+ end
12
+
13
+ def self.get_fds argv, client
14
+ case argv.size
15
+ when 0
16
+ [$stdin, $stdout]
17
+ when 1
18
+ case argv[0]
19
+ when /^\d+$/
20
+ if client
21
+ [TCPSocket.new("127.0.0.1", Integer(argv[0]))]
22
+ else
23
+ server = TCPServer.new("127.0.0.1", Integer(argv[0]))
24
+ flag = Socket.do_not_reverse_lookup
25
+ Socket.do_not_reverse_lookup = false
26
+ port = server.addr[1]
27
+ Socket.do_not_reverse_lookup = flag
28
+ puts "listening on port #{port}"
29
+ [server.accept]
30
+ end
31
+ else
32
+ if client
33
+ [UNIXSocket.new(argv[0])]
34
+ else
35
+ [UNIXServer.new(argv[0]).accept]
36
+ end
37
+ end
38
+ when 2
39
+ case argv[1]
40
+ when /^\d+$/
41
+ if client
42
+ [TCPSocket.new(argv[0], Integer(argv[1]))]
43
+ else
44
+ [TCPServer.new(argv[0], Integer(argv[1])).accept]
45
+ end
46
+ else
47
+ raise "Bad arguments--second arg must be port: #{argv.inspect}"
48
+ end
49
+ else
50
+ raise "Too many arguments: #{argv.inspect}"
51
+ end
52
+ end
53
+
54
+ class StreamClosed < IOError; end
55
+
56
+ class Base
57
+ def initialize(opts, fd_in, fd_out = fd_in)
58
+ @flip = opts["flip"]
59
+ @radians = opts["radians"]
60
+ @verbose = opts["v"]
61
+
62
+ @fd_in, @fd_out = fd_in, fd_out
63
+ @fd_in_stack = []
64
+ @fd_out_mutex = Mutex.new
65
+ end
66
+ end
67
+
68
+ # Translate ascii command text to ruby method calls.
69
+ class Ascii < Base
70
+ NORMALIZE = {}
71
+
72
+ %w{ a>dd d>el|delete m>ove>to r>ot|rotate p>ar>am s>h>ape u>p>date title
73
+ background|bg height width zoom_to|zoom view_at|view view_id wait
74
+ follow done bound>s load exit delete_all scale>_obj echo window_xy
75
+ }.each do |s|
76
+ alts = s.split("|")
77
+ cmd = alts.first.delete(">")
78
+ alts.each do |alt|
79
+ parts = alt.split(">")
80
+ parts.inject("") do |prefix, part|
81
+ prefix << part
82
+ NORMALIZE[prefix] = cmd
83
+ prefix
84
+ end
85
+ end
86
+ end
87
+
88
+ def conv_param(s)
89
+ s.slice!(/\.0+$/) # so that floats can be used for colors
90
+ (Integer(s) rescue Float(s)) rescue s
91
+ end
92
+
93
+ def flip_Float(s)
94
+ @flip ? -Float(s) : Float(s)
95
+ end
96
+
97
+ def conv_angle(s)
98
+ r = flip_Float(s)
99
+ @radians ? r : r * DEGREES_TO_RADIANS
100
+ end
101
+
102
+ ARG_CONVERSION = {
103
+ "add" => [nil, :Integer, nil, :Integer,
104
+ :Float, :flip_Float, :conv_angle, :conv_param],
105
+ "del" => [:Integer],
106
+ "moveto" => [:Integer, :Float, :flip_Float],
107
+ "rot" => [:Integer, :conv_angle],
108
+ "param" => [:Integer, :Integer, :conv_param], ## see below
109
+ "shape" => [nil, nil],
110
+ "update" => [],
111
+ "title" => [], ## see below
112
+ "background" => [:conv_param],
113
+ "height" => [:Float],
114
+ "width" => [:Float],
115
+ "zoom_to" => [:Float],
116
+ "view_at" => [:Float, :flip_Float],
117
+ "view_id" => [:Integer],
118
+ "wait" => [:Float],
119
+ "follow" => [:Integer],
120
+ "done" => [],
121
+ "bounds" => [:Float, :flip_Float, :Float, :flip_Float],
122
+ "load" => [], ## see below
123
+ "exit" => [],
124
+ "delete_all" => [],
125
+ "scale_obj" => [:Integer, :Float, :Float],
126
+ "echo" => [], ## see below
127
+ "window_xy" => [:Integer, :Integer],
128
+ }
129
+
130
+ def get_line
131
+ line = @fd_in.gets
132
+ while line == nil and not @fd_in_stack.empty?
133
+ @fd_in = @fd_in_stack.pop
134
+ line = @fd_in.gets
135
+ end
136
+ if line
137
+ while line =~ /(.*)\\\r?$/ # to allow continuation of long lines
138
+ next_line = @fd_in.gets
139
+ break unless next_line
140
+ line = $1 + next_line
141
+ end
142
+ end
143
+ line
144
+ end
145
+
146
+ class TryAgain < StandardError; end
147
+
148
+ # Returns next command in pipe, in form <tt>[:meth, arg, arg, ...]</tt>.
149
+ def get_cmd
150
+ begin
151
+ cmdline = get_line
152
+ raise StreamClosed, "Session ended" unless cmdline ## ?
153
+ end while cmdline =~ /^\s*(#|$)/
154
+ parse_cmd(cmdline)
155
+ rescue TryAgain
156
+ retry
157
+ rescue SystemCallError => ex
158
+ $stderr.puts ex.class, ex.message
159
+ raise StreamClosed, "Session ended"
160
+ end
161
+
162
+ def parse_cmd cmdline
163
+ $stderr.puts cmdline if @verbose
164
+ cmd, *args = cmdline.split
165
+ cmd = NORMALIZE[cmd] || (raise ArgumentError, "Bad command: #{cmd}")
166
+
167
+ case cmd
168
+ when "param"
169
+ return [cmd, Integer(args.shift), Integer(args.shift),
170
+ conv_param(args.join(" "))]
171
+ ## hacky, and loses multiple spaces
172
+ when "title"
173
+ return [cmd, args.join(" ")]
174
+ when "echo"
175
+ str = args.join(" ")
176
+ ## hacky, and loses multiple spaces
177
+ put_msg(str)
178
+ raise TryAgain # hm...
179
+ when "load"
180
+ filename = args.join(" ")
181
+ ## hacky, and loses multiple spaces
182
+ begin
183
+ new_fd_in = File.open(filename)
184
+ rescue Errno::ENOENT # if not absolute, try local
185
+ raise unless @fd_in_dir
186
+ new_fd_in = File.open(File.join(@fd_in_dir, filename))
187
+ end
188
+ @fd_in_dir ||= File.dirname(new_fd_in.path)
189
+ @fd_in_stack.push(@fd_in)
190
+ @fd_in = new_fd_in
191
+ raise TryAgain # hm...
192
+ end
193
+
194
+ conv = ARG_CONVERSION[cmd]
195
+ unless conv
196
+ raise "No argument conversion for command #{cmd.inspect}"
197
+ end
198
+ i = -1; last_i = conv.length - 1
199
+ args.map! do |arg|
200
+ i += 1 unless i == last_i # keep using the last conversion thereafter
201
+ (c = conv[i]) ? send(c, arg) : arg
202
+ end
203
+ [cmd, *args]
204
+ end
205
+
206
+ def put_msg(msg)
207
+ @fd_out_mutex.synchronize do ## why necessary?
208
+ @fd_out.puts msg
209
+ end
210
+ @fd_out.flush
211
+ rescue Errno::ECONNABORTED
212
+ end
213
+ end
214
+
215
+ # Translate binary command data to ruby method calls.
216
+ class Binary < Base
217
+ def get_cmd
218
+ lendata = @fd_in.recv(4)
219
+ raise "Session ended" if lendata.empty?
220
+
221
+ len = lendata.unpack("N")
222
+ if len < 4
223
+ raise ArgumentError, "Input too short: #{len}"
224
+ end
225
+ if len > 10000
226
+ raise ArgumentError, "Input too long: #{len}"
227
+ end
228
+
229
+ msg = ""
230
+ part = nil
231
+ while (delta = len - msg.length) > 0 and (part = @fd_in.recv(delta))
232
+ if part.length == 0
233
+ raise \
234
+ "Peer closed socket before finishing message --" +
235
+ " received #{msg.length} of #{len} bytes:\n" +
236
+ msg[0..99].unpack("H*")[0] + "..."
237
+ end
238
+ msg << part
239
+ end
240
+
241
+ raise StreamClosed, "Session ended" if msg.empty?
242
+ parse_cmd(msg)
243
+ end
244
+
245
+ CMD_DATA = {
246
+ 1 => ["add", "Z* N Z* N g3 N*"],
247
+ 2 => ["del", "N"],
248
+ 3 => ["moveto", "N g2"],
249
+ 4 => ["rot", "N g"],
250
+ 5 => ["param", "N n N"], ## fix to allow strings
251
+ 6 => ["shape", "Z* Z*"],
252
+ 7 => ["update", ""],
253
+ 8 => ["title", "Z*"],
254
+ 9 => ["background", "N"],
255
+ 10 => ["height", "g"],
256
+ 11 => ["width", "g"],
257
+ 12 => ["zoom_to", "g"],
258
+ 13 => ["view_at", "g2"],
259
+ 14 => ["view_id", "N"],
260
+ 15 => ["wait", "g"],
261
+ 16 => ["follow", "N"],
262
+ 17 => ["done", ""],
263
+ 18 => ["bounds", "NNNN"],
264
+ 19 => ["load", "Z*"],
265
+ 20 => ["exit", ""],
266
+ 21 => ["delete_all", ""],
267
+ 22 => ["scale_obj", "N g2"],
268
+ 23 => ["echo", "Z*"],
269
+ 24 => ["window_xy", "NN"],
270
+ }
271
+
272
+ def parse_cmd msg
273
+ cmd = msg[0..1].unpack("n")
274
+ cmd, fmt = CMD_DATA[cmd]
275
+ [cmd, *msg[2..-1].unpack(fmt)]
276
+ end
277
+
278
+ def put_msg(msg)
279
+ @fd_out.puts msg ## assume output ascii for now
280
+ @fd_out.flush
281
+ end
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,174 @@
1
+ # Ruby license. Copyright (C)2004-2009 Joel VanderWerf.
2
+ # Contact mailto:vjoel@users.sourceforge.net.
3
+ #
4
+ # A lightweight, non-drifting, self-correcting timer. Average error is bounded
5
+ # as long as, on average, there is enough time to complete the work done, and
6
+ # the timer is checked often enough. It is lightweight in the sense that no
7
+ # threads are created. Can be used either as an internal iterator (Timer.every)
8
+ # or as an external iterator (Timer.new). Obviously, the GC can cause a
9
+ # temporary slippage.
10
+ #
11
+ # Simple usage:
12
+ #
13
+ # require 'timer'
14
+ #
15
+ # Timer.every(0.1, 0.5) { |elapsed| puts elapsed }
16
+ #
17
+ # timer = Timer.new(0.1)
18
+ # 5.times do
19
+ # puts timer.elapsed
20
+ # timer.wait
21
+ # end
22
+ #
23
+ class Timer
24
+ # Yields to the supplied block every +period+ seconds. The value yielded is
25
+ # the total elapsed time (an instance of +Time+). If +expire+ is given, then
26
+ # #every returns after that amount of elapsed time.
27
+ def Timer.every(period, expire = nil)
28
+ target = time_start = Time.now
29
+ loop do
30
+ elapsed = Time.now - time_start
31
+ break if expire and elapsed > expire
32
+ yield elapsed
33
+ target += period
34
+ error = target - Time.now
35
+ sleep error if error > 0
36
+ end
37
+ end
38
+
39
+ # Make a Timer that can be checked when needed, using #wait or #if_ready. The
40
+ # advantage over Timer.every is that the timer can be checked on separate
41
+ # passes through a loop.
42
+ def initialize(period = 1)
43
+ @period = period
44
+ restart
45
+ end
46
+
47
+ attr_accessor :period
48
+
49
+ # Call this to restart the timer after a period of inactivity (e.g., the user
50
+ # hits the pause button, and then hits the go button).
51
+ def restart
52
+ @target = @time_start = Time.now
53
+ end
54
+
55
+ # Time on timer since instantiation or last #restart.
56
+ def elapsed
57
+ Time.now - @time_start
58
+ end
59
+
60
+ # Wait for the next cycle, if time remains in the current cycle. Otherwise,
61
+ # return immediately to caller.
62
+ def wait(per = nil)
63
+ @target += per || @period
64
+ error = @target - Time.now
65
+ sleep error if error > 0
66
+ true
67
+ end
68
+
69
+ # Yield to the block if no time remains in cycle. Otherwise, return
70
+ # immediately to caller
71
+ def if_ready
72
+ error = @target + @period - Time.now
73
+ if error <= 0
74
+ @target += @period
75
+ elapsed = Time.now - @time_start
76
+ yield elapsed
77
+ end
78
+ end
79
+ end
80
+
81
+ if __FILE__ == $0
82
+
83
+ require 'test/unit'
84
+
85
+ # These tests may not work on a slow machine or heavily loaded system; try
86
+ # adjusting FUDGE.
87
+ class Test_Timer < Test::Unit::TestCase # :nodoc:
88
+
89
+ STEPS = 100
90
+ PERIOD = 0.01
91
+ FUDGE = 0.01 # a constant independent of period.
92
+
93
+ def generic_test(steps = STEPS, period = PERIOD, fudge = FUDGE)
94
+ max_sleep = period/2
95
+
96
+ start_time = Time.now
97
+ yield steps, period, max_sleep
98
+ finish_time = Time.now
99
+
100
+ assert_in_delta(
101
+ start_time.to_f + steps*period,
102
+ finish_time.to_f,
103
+ period + fudge,
104
+ "delta = #{finish_time.to_f - (start_time.to_f + steps*period)}"
105
+ )
106
+ end
107
+
108
+ def test_every
109
+ generic_test do |steps, period, max_sleep|
110
+ Timer.every period do |elapsed|
111
+ s = rand()*max_sleep
112
+ #puts "#{elapsed} elapsed; sleeping #{s}"
113
+ sleep(s)
114
+ steps -= 1
115
+ break if steps == 0
116
+ end
117
+ end
118
+ end
119
+
120
+ def test_every_with_expire
121
+ generic_test do |steps, period, max_sleep|
122
+ Timer.every period, period*steps do
123
+ s = rand()*max_sleep
124
+ #puts "#{elapsed} elapsed; sleeping #{s}"
125
+ sleep(s)
126
+ end
127
+ end
128
+ end
129
+
130
+ def test_wait
131
+ generic_test do |steps, period, max_sleep|
132
+ timer = Timer.new(period)
133
+ steps.times do
134
+ s = rand()*max_sleep
135
+ #puts "#{timer.elapsed} elapsed; sleeping #{s}"
136
+ sleep(s)
137
+ timer.wait
138
+ end
139
+ end
140
+ end
141
+
142
+ def test_wait_per
143
+ offset = PERIOD / 2
144
+ generic_test(STEPS, PERIOD, FUDGE + offset) do |steps, period, max_sleep|
145
+ timer = Timer.new(period)
146
+ steps.times do |i|
147
+ s = rand()*max_sleep
148
+ #puts "#{timer.elapsed} elapsed; sleeping #{s}"
149
+ sleep(s)
150
+ timer.wait(i%2==0 ? period + offset : period - offset)
151
+ end
152
+ end
153
+ end
154
+
155
+ def test_if_ready
156
+ generic_test do |steps, period, max_sleep|
157
+ timer = Timer.new(period)
158
+ catch :done do
159
+ loop do
160
+ timer.if_ready do |elapsed|
161
+ s = rand()*max_sleep
162
+ #puts "#{elapsed} elapsed; sleeping #{s}"
163
+ sleep(s)
164
+ steps -= 1
165
+ throw :done if steps == 0
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ end
173
+
174
+ end