* riece-async.el: User riece-ruby (partially).
[riece] / lisp / server.rb
1 # A simple IPC server executing Ruby programs.
2
3 require 'thread'
4
5 class Server
6   module B
7     def output(s)
8       puts("# output #{Thread.current[:rubyserv_name]} #{s}\r\n")
9     end
10     module_function :output
11   end
12
13   def initialize
14     @buf = ''
15     @que = Queue.new
16     @thr = Hash.new
17     @cnt = 0
18   end
19
20   def dispatch(line)
21     case line.chomp
22     when /\AD /
23       @buf << $'
24     when /\A(\S+)\s*/
25       c = $1
26       r = $'
27       d = "dispatch_#{c.downcase}"
28       if respond_to?(d, true)
29         Thread.start do
30           self.send(d, c, r)
31         end
32       else
33         puts("ERR 103 Unknown command\r\n")
34       end
35     end
36   end
37
38   def dispatch_cancel(c, r)
39     puts("ERR 100 Not implemented\r\n")
40   end
41
42   def dispatch_bye(c, r)
43     puts("ERR 100 Not implemented\r\n")
44   end
45
46   def dispatch_auth(c, r)
47     puts("ERR 100 Not implemented\r\n")
48   end
49
50   def dispatch_reset(c, r)
51     puts("ERR 100 Not implemented\r\n")
52   end
53
54   def dispatch_end(c, r)
55     enq_data
56   end
57
58   def dispatch_help(c, r)
59     puts("ERR 100 Not implemented\r\n")
60   end
61
62   def dispatch_quit(c, r)
63     puts("ERR 100 Not implemented\r\n")
64   end
65
66   def dispatch_eval(c, r)
67     r = deq_data if r.empty?
68     name = nil
69     Thread.exclusive do
70       while @thr.include?(name = @cnt.to_s)
71         @cnt += 1
72       end
73       @thr[name] = Thread.current
74     end
75     puts("S name #{name}\r\n")
76     puts("OK\r\n")
77     Thread.current[:rubyserv_name] = name
78     begin
79       Thread.current[:rubyserv_error] = false
80       Thread.current[:rubyserv_response] = eval(r, B.module_eval('binding()'))
81     rescue Exception => e
82       Thread.current[:rubyserv_error] = true
83       Thread.current[:rubyserv_response] = e.to_s.sub(/\A.*?\n/, '')
84     end
85     puts("# exit #{name}\r\n")
86   end
87
88   def dispatch_poll(c, r)
89     thr = @thr[r]
90     if !thr
91       puts("ERR 105 Parameter error: no such name \"#{r}\"\r\n")
92     elsif thr.alive?
93       puts("S running #{r}\r\n")
94       puts("OK\r\n")
95     else
96       if thr[:rubyserv_error]
97         puts("S exited #{r}\r\n")
98       else
99         puts("S finished #{r}\r\n")
100       end
101       if d = thr[:rubyserv_response]
102         send_data(d.to_s)
103       end
104       puts("OK\r\n")
105     end
106   end
107
108   def dispatch_exit(c, r)
109     thr = @thr[r]
110     if !thr
111       puts("ERR 105 Parameter error: no such name \"#{r}\"\r\n")
112       return
113     end
114     thr.kill if thr.alive?
115     @thr.delete(r)
116     puts("OK\r\n")
117   end
118
119   def escape(s)
120     s.gsub(/[%\r\n]/) {|m| '%%%02X' % m[0]}
121   end
122
123   def unescape(s)
124     s.gsub(/%([0-9A-Z][0-9A-Z])/) {[$1].pack('H*')}
125   end
126
127   def send_data(d)
128     d = escape(d)
129     begin
130       len = [d.length, 998].min   # 998 = 1000 - "D "
131       puts("D #{d[0 ... len]}\r\n")
132       d = d[len .. -1]
133     end until d.empty?
134   end
135
136   def enq_data
137     d = unescape(@buf)
138     @buf = ''
139     @que.enq(d)
140   end
141
142   def deq_data
143     @que.deq
144   end
145 end
146
147 if $0 == __FILE__
148   server = Server.new
149   while gets
150     server.dispatch($_)
151   end
152 end