Ruby4Skype 0.2.3 → 0.3.1

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 (43) hide show
  1. data/LICENSE +3 -0
  2. data/README +14 -0
  3. data/Rakefile +67 -0
  4. data/lib/skypeapi.rb +570 -509
  5. data/lib/skypeapi/application.rb +79 -77
  6. data/lib/skypeapi/call.rb +243 -230
  7. data/lib/skypeapi/chat.rb +162 -172
  8. data/lib/skypeapi/chatmember.rb +26 -28
  9. data/lib/skypeapi/chatmessage.rb +81 -65
  10. data/lib/skypeapi/error.rb +8 -0
  11. data/lib/skypeapi/event.rb +25 -26
  12. data/lib/skypeapi/filetransfer.rb +47 -28
  13. data/lib/skypeapi/group.rb +72 -73
  14. data/lib/skypeapi/menuitem.rb +44 -44
  15. data/lib/skypeapi/message.rb +39 -41
  16. data/lib/skypeapi/object.rb +246 -82
  17. data/lib/skypeapi/os/etc.rb +48 -98
  18. data/lib/skypeapi/os/mac.rb +92 -4
  19. data/lib/skypeapi/os/notifier.rb +31 -0
  20. data/lib/skypeapi/os/window_event_queue.rb +198 -0
  21. data/lib/skypeapi/os/window_messagehandler.rb +120 -0
  22. data/lib/skypeapi/os/windows.rb +170 -306
  23. data/lib/skypeapi/profile.rb +190 -120
  24. data/lib/skypeapi/sharefunctions.rb +31 -23
  25. data/lib/skypeapi/sms.rb +87 -67
  26. data/lib/skypeapi/user.rb +159 -99
  27. data/lib/skypeapi/version.rb +3 -3
  28. data/lib/skypeapi/voicemail.rb +59 -52
  29. data/spec/matcher_be_boolean.rb +10 -0
  30. data/spec/skypeapi/application_spec.rb +76 -0
  31. data/spec/skypeapi/chat_spec.rb +356 -0
  32. data/spec/skypeapi/chatmember_spec.rb +42 -0
  33. data/spec/skypeapi/chatmessage_spec.rb +89 -0
  34. data/spec/skypeapi/event_spec.rb +31 -0
  35. data/spec/skypeapi/filetransfer_spec.rb +67 -0
  36. data/spec/skypeapi/group_spec.rb +16 -0
  37. data/spec/skypeapi/menuitem_spec.rb +37 -0
  38. data/spec/skypeapi/os/windows_spec.rb +305 -0
  39. data/spec/skypeapi/profile_spec.rb +22 -0
  40. data/spec/skypeapi/user_spec.rb +25 -0
  41. data/spec/skypeapi_spec.rb +528 -0
  42. metadata +32 -12
  43. data/lib/skypeapi/os/timer.rb +0 -108
@@ -1,170 +1,116 @@
1
1
  require 'swin'
2
2
  require 'Win32API'
3
+ require 'timeout'
4
+ require 'skypeapi/os/etc'
5
+ require 'skypeapi/os/window_event_queue'
6
+ require 'skypeapi/os/window_messagehandler'
3
7
 
4
8
  module SkypeAPI
5
9
  module OS
10
+ WAIT_CMD_LIMIT = 30.0 #sec
11
+ PING_CYCLE = 10.0 #sec
12
+ PING_LIMIT = 5.0 # < PING_CYCLE
13
+ SLEEP_INTERVAL = 0.001
14
+
15
+ HWND_BROADCAST = 0xFFFF
16
+ WM_COPYDATA = 0x004A
17
+ WM_CLOSE = 0x10
18
+ WM_USER = 0x0400
19
+ WM_USER_MSG = WM_USER + 1
20
+ SKYPECONTROLAPI_ATTACH_SUCCESS=0
21
+ SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION=1
22
+ SKYPECONTROLAPI_ATTACH_REFUSED=2
23
+ SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE=3
24
+ SKYPECONTROLAPI_ATTACH_API_AVAILABLE=0x8001
25
+
26
+ RegisterWindowMessage = Win32API.new('user32','RegisterWindowMessageA', 'P', 'L')
27
+ SendMessage = Win32API.new("user32", "SendMessageA", ['L']*4, 'L')
28
+ PostMessage = Win32API.new("user32", "PostMessageA", 'LLLP', 'L')
29
+
6
30
  class Windows < Abstruct
7
- require 'skypeapi/os/timer'
8
-
9
- WAIT_CMD_LIMIT = 10.0 #sec
10
- PING_CYCLE = 5.0 #sec
11
- PING_LIMIT = 3.0 # < PING_CYCLE
12
- SLEEP_INTERVAL = 0.001
13
-
14
- HWND_BROADCAST = 0xFFFF
15
- WM_COPYDATA = 0x004A
16
- WM_USER = 0x0400
17
- WM_USER_MSG = WM_USER + 1
18
- SKYPECONTROLAPI_ATTACH_SUCCESS=0
19
- SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION=1
20
- SKYPECONTROLAPI_ATTACH_REFUSED=2
21
- SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE=3
22
- SKYPECONTROLAPI_ATTACH_API_AVAILABLE=0x8001
23
-
24
- RegisterWindowMessage = Win32API.new('user32','RegisterWindowMessageA', 'P', 'L')
25
- SendMessage = Win32API.new("user32", "SendMessageA", ['L']*4, 'L')
26
- PostMessage = Win32API.new("user32", "PostMessageA", 'LLLP', 'L')
27
31
 
28
32
  def initialize
29
33
  @send_count = 0
30
- @queue = Array.new
31
- @callback = Hash.new
32
- @notify = Hash.new
33
- @event = Hash.new do |h,k|
34
- h[k] = Array.new
35
- end
34
+ @queue = WindowsEventQueue.new(self)
35
+
36
36
  @attached = false
37
37
  @first_attached = true
38
38
  @raise_when_detached = false
39
39
 
40
40
  @invoke_mutex = Mutex.new
41
- @invoke_block_mutex = Mutex.new
42
-
43
- add_event :available do
41
+ @invoke_callback_mutex = Mutex.new
42
+ @cv = ConditionVariable.new
43
+ @detached_mutex = Mutex.new
44
+
45
+ add_hook :available do
44
46
  SkypeAPI.attach
45
47
  end
46
48
 
47
- Timer.interval PING_CYCLE do
48
- invoke_callback('PING'){} if @attached
49
- end
50
-
51
- @reattach_mutex = Mutex.new
52
-
53
- @wmBuffer = Hash.new
54
- @wmHandler = SWin::LWFactory.new(SWin::Application.hInstance).newwindow nil
55
- @wmHandler.create
56
- @wmHandler.addEvent(WM_COPYDATA)
57
- @wmHandler.addEvent(WM_USER_MSG)
58
- @wmHandler.instance_variable_set :@skypeAPI,self
59
- @wmHandler.instance_variable_set :@wmBuffer,@wmBuffer
60
- @wmHandler.instance_variable_set :@queue,@queue
49
+ @wmHandler = SWin::LWFactory.new(SWin::Application.hInstance).newwindow nil, MessageHandler
50
+ @wmHandler.init self, @queue
61
51
 
62
- class << @wmHandler
63
- attr_reader :hSkypeAPIWindowHandle
64
-
65
- def msghandler(sMsg)
66
- case sMsg.msg
67
- when @dwAttachMsg
68
- case sMsg.lParam
69
- when SKYPECONTROLAPI_ATTACH_SUCCESS
70
- @hSkypeAPIWindowHandle = sMsg.wParam
71
- @queue.push Proc.new{@skypeAPI.invoke_callback("PROTOCOL 9999"){}}
72
-
73
- @queue.push Proc.new{@skypeAPI.do_event(:attach,:success)}
74
- unless @skypeAPI.attached
75
- if @skypeAPI.first_attached
76
- @queue.push Proc.new{@skypeAPI.do_event(:attached)}
77
- else
78
- @queue.push Proc.new{@skypeAPI.do_event(:reattached)}
79
- end
80
- end
81
- @skypeAPI.attached = true
82
- @skypeAPI.first_attached = false
83
- when SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION
84
- @queue.push Proc.new{@skypeAPI.do_event(:attach,:authorize)}
85
- @queue.push Proc.new{@skypeAPI.do_event(:authorize)}
86
- when SKYPECONTROLAPI_ATTACH_REFUSED
87
- @queue.push Proc.new{@skypeAPI.do_event(:attach,:refused)}
88
- @queue.push Proc.new{@skypeAPI.do_event(:refused)}
89
- @skypeAPI.attached = false
90
- when SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE
91
- @queue.push Proc.new{@skypeAPI.do_event(:attach, :not_available)}
92
- @queue.push Proc.new{@skypeAPI.do_event(:not_available)}
93
- @skypeAPI.attached = false
94
- when SKYPECONTROLAPI_ATTACH_API_AVAILABLE
95
- @queue.push Proc.new{@skypeAPI.do_event(:attach, :available)}
96
- @queue.push Proc.new{@skypeAPI.do_event(:available)}
97
- else
98
- @queue.push Proc.new{@skypeAPI.do_event(:attach,:unkown)}
99
- @queue.push Proc.new{@skypeAPI.do_event(:unkown)}
100
- end
101
- sMsg.retval = 1
102
- #return true
103
- when WM_COPYDATA
104
- if sMsg.wParam == @hSkypeAPIWindowHandle
105
- retval = application.cstruct2array(sMsg.lParam,"LLL")
106
- cmd = application.pointer2string(retval[2],retval[1])
107
- @skypeAPI.push_queue cmd
108
- sMsg.retval = 1
109
- return true
110
- end
111
- when WM_USER_MSG
112
- unless SendMessage.call(sMsg.wParam, WM_COPYDATA, sMsg.hWnd, sMsg.lParam)
113
- raise SkypeAPIError::Connect,"Skype not ready"
114
- end
115
- sMsg.retval = true
116
- return true
117
- end
118
- end
119
- end
120
-
121
- @wmHandler.create unless @wmHandler.alive?
122
- @dwDiscoverMsg = RegisterWindowMessage.call("SkypeControlAPIDiscover");
123
- raise SkypeAPIError::Attach,"SkypeControlAPIDiscover nothing" unless @dwDiscoverMsg
124
- @dwAttachMsg = RegisterWindowMessage.call("SkypeControlAPIAttach")
125
- raise SkypeAPIError::Attach,"SkypeControlAPIAttach nothing" unless @dwAttachMsg
126
- @wmHandler.instance_variable_set :@dwAttachMsg, @dwAttachMsg
127
- @wmHandler.addEvent @dwAttachMsg
52
+ #@queue.start_process_thread
53
+ start_wmhandler_loop
54
+ start_ping_thread
128
55
  end
129
-
56
+
57
+ attr_reader :invoke_callback_mutex, :cv, :wmHandler
130
58
  attr_accessor :attached, :first_attached#,:received,:sent
59
+
60
+ #引数にmethodかブロックを渡しておくとSkypeAPIのNotification(コマンドのレスポンは除く)があった場合呼ばれる。
61
+ #ブロックはNotificationの文字列を一つ与えられて呼ばれる。
62
+ def set_notify_selector block=Proc.new
63
+ @queue.set_notify_selector block
64
+ end
131
65
 
66
+ #主にAttachなど、Skypeとのコネクション関係のイベントの通知を受ける為のブロックを設定する。
67
+ #引数 sym は :attach, :attached, :authorize, :refused, :not_available, :available, :unknown, :detachedを取る。
68
+ #引数 sym が :attachの場合、ブロックは一つの引数(:success,:authorize,:refused,:not_available,:available,:unknownのどれか)を与えられて呼ばれる。
69
+ def add_hook sym, block=Proc.new
70
+ @queue.add_hook sym, block
71
+ end
72
+
73
+ def del_hook sym, block=nil
74
+ @queue.del_hook sym, block
75
+ end
76
+
77
+ def exist_hook? sym
78
+ @queue.exist_hook? sym
79
+ end
80
+
81
+ def get_hook sym
82
+ @queue.get_hook sym
83
+ end
84
+
85
+ alias add_event add_hook
86
+ alias del_event del_hook
87
+ alias exist_event? exist_hook?
88
+ alias get_event get_hook
89
+
90
+ #SkypeAPIにAttachする。ブロックはしないのでつながるまで待つならばadd_hookでイベントを追加するか,attach_waitを使う。
132
91
  def attach name = nil #,&block)
133
- #post?
134
- unless PostMessage.call(HWND_BROADCAST, @dwDiscoverMsg, @wmHandler.hWnd, 0)
135
- raise SkypeAPIError::Attach,"SkypeControlAPIDiscover broadcast fail"
136
- end
137
- return true
92
+ @wmHandler.attach
138
93
  end
139
-
94
+
140
95
  def attach_wait name = nil
141
- flag = true
142
- add_event :attached do
143
- flag = false
144
- end
145
96
  attach name
146
- while flag
147
- polling
148
- sleep SLEEP_INTERVAL
149
- end
97
+ sleep SLEEP_INTERVAL until @attached
150
98
  end
151
-
152
- def invoke_prototype num, cmd
153
- unless @wmHandler.hSkypeAPIWindowHandle
154
- raise SkypeAPIError::Attach,"NullPointerException SendSkype!"
155
- return false
156
- end
157
-
158
- cmd = '#' + num.to_s + ' ' + cmd + "\0"
159
- pCopyData = @wmHandler.application.arg2cstructStr("LLS",0,cmd.length+1,cmd)
160
- unless PostMessage.call(@wmHandler.hWnd, WM_USER_MSG, @wmHandler.hSkypeAPIWindowHandle, pCopyData)
161
- @wmHandler.instance_variable_set :@hSkypeAPIWindowHandle,nil
162
- raise SkypeAPIError::Attach,"Skype not ready"
163
- end
164
- @queue.push(proc{do_event(:sent, cmd)}) if exist_event? :sent
99
+
100
+ def detached?
101
+ return true unless @attached
102
+ old_received_count = @queue.received_count
103
+ return false if _ping
104
+ return false if @queue.received_count - old_received_count > 0
165
105
  return true
166
106
  end
167
-
107
+ private :detached?
108
+
109
+ #SkypeAPIにコマンドを発行する。Skypeから返り値が戻ってくるまでブロックする。
110
+ def invoke cmd
111
+ super cmd
112
+ end
113
+
168
114
  def invoke_callback cmd,cb=Proc.new
169
115
  send_count = nil
170
116
  @invoke_mutex.synchronize do
@@ -172,198 +118,116 @@ module SkypeAPI
172
118
  @send_count += 1
173
119
  end
174
120
 
175
- @callback[send_count] = cb
121
+ @queue.callback[send_count] = cb
176
122
  begin
177
- invoke_prototype send_count, cmd
123
+ @wmHandler.invoke send_count, cmd
178
124
  rescue => e
179
- @callback.delete(send_count)
125
+ @queue.callback.delete(send_count)
180
126
  raise e
181
127
  end
182
- return true
128
+ return send_count
183
129
  end
184
- =begin
185
- def invoke_block cmd, waitLimit=nil
186
- @invoke_block_mutex.synchronize do
187
- res_val = nil
188
- current_thread = Thread.current
189
- invoke_callback cmd do |res|
190
- res_val = res
191
- current_thread.run
192
- end
193
- begin
194
- timeout 10 do
195
- sleep
196
- end
197
- rescue TimeoutError
198
- if ping
199
- retry
130
+ private :invoke_callback
131
+
132
+ def invoke_block cmd, wait_limit = WAIT_CMD_LIMIT
133
+ result = nil
134
+ retry_flag = false
135
+ send_count = invoke_callback(cmd){ |res| result = res }
136
+ begin
137
+ timeout(wait_limit){ @invoke_callback_mutex.synchronize{ @cv.wait @invoke_callback_mutex until result } }
138
+ rescue Timeout::Error
139
+ if detached?
140
+ if @raise_when_detached
141
+ raise SkypeAPI::Error::Timeout.new("timeout #{send_count} #{cmd}")
200
142
  else
201
- reattach
202
- return invoke_block(cmd)
143
+ status2detache
144
+ retry_flag = true
203
145
  end
204
146
  else
205
- return res_val
147
+ retry
206
148
  end
207
149
  end
150
+ return retry_flag ? invoke_block(cmd) : result
208
151
  end
209
-
210
- def reattach
211
- @attached = false
212
- @queue.push(proc{do_event(:detached)})
213
- if @raise_when_detached
214
- raise SkypeAPI::Error::Attach
215
- else
216
- SkypeAPI.attach
217
- loop do
218
- if @attached
219
- break
220
- end
221
- sleep SLEEP_INTERVAL
222
- end
223
- p 'reattached'
224
- end
225
- end
226
- =end
227
- #=begin
228
- def invoke_block cmd, waitLimit = WAIT_CMD_LIMIT
229
- resVal = nil
230
- invoke_callback cmd do |res|
231
- resVal = res
232
- end
233
- startTime = Time.now
234
- loop do
235
- polling
236
- if resVal
237
- return resVal
238
- end
239
-
240
- if Time.now - startTime > waitLimit
241
- if ping
242
- startTime = Time.now
243
- else
244
- if @attached
245
- @attached = false
246
- @queue.push(proc{do_event(:detached)})
247
- SkypeAPI.attach
248
- end
249
-
250
- if @raise_when_detached
251
- raise SkypeAPI::Error::Attach
252
- else
253
- loop do
254
- polling
255
- if @attached
256
- break
257
- end
258
- sleep SLEEP_INTERVAL
259
- end
260
- ret = invoke_block(cmd, waitLimit)
261
- return ret
262
- end
263
- end
152
+ private :invoke_block
153
+
154
+ def status2detache
155
+ @detached_mutex.synchronize do
156
+ if detached?
157
+ @attached = false
158
+ @queue.push_detached_hook
159
+ attach_wait
160
+ else
161
+ #wait?
162
+ #runtimeerro?
264
163
  end
265
-
266
- #Thread.pass
267
- sleep SLEEP_INTERVAL
268
164
  end
269
165
  end
270
- #=end
271
- def wait someAction=nil
272
- if someAction.class == Proc
273
- @wmHandler.application.messageloop do
274
- queue_process
275
- Timer.polling
276
- someAction.call
277
- end
278
- elsif block_given?
279
- @wmHandler.application.messageloop do
280
- queue_process
281
- Timer.polling
282
- yield
283
- end
284
- else
285
- @wmHandler.application.messageloop do
286
- queue_process
287
- Timer.polling
288
- end
289
- end
166
+
167
+ #notificationのメッセージループを新しいスレッドで回し始めます。
168
+ def start_messageloop
169
+ @queue.start_messageloop
290
170
  end
291
-
292
- def polling
293
- @wmHandler.application.doevents
294
- Thread.pass
295
- queue_process
296
- Timer.polling
171
+
172
+ #notificationのメッセージループをカレントスレッドで回しはじめます。すなわち帰ってきません。
173
+ def messageloop
174
+ @queue.messageloop
297
175
  end
298
-
299
- def close
300
- @wmHandler.close
176
+
177
+ #notificationのメッセージループを一度だけ回します。
178
+ def messagepolling
179
+ @queue.messagepolling
301
180
  end
302
181
 
303
- def push_queue res
304
- @queue.push(proc{do_event(:received, res.chop)})
305
- if res =~ /^(#(\d+?) )?(.+?)\000$/m
306
- if $2
307
- if @callback[$2.to_i]
308
- cb = @callback[$2.to_i]
309
- val = $3
310
- @callback.delete($2.to_i)
311
- #@queue.push(proc{cb.call val})
312
- cb.call val
313
- end
314
- else
315
- cmd = $3
316
-
317
- if cmd == 'CONNSTATUS LOGGEDOUT'
318
- @attached = false
319
- @queue.push(proc{do_event(:detached)})
320
- SkypeAPI.attach
321
- end
322
-
323
- flag = false
324
- @notify.each do |reg,action|
325
- if cmd =~ reg
326
- tmp = $1
327
- @queue.push(proc{action.call(tmp)})
328
- flag = true
329
- end
330
- end
331
-
332
- unless flag
333
- action = @notify[nil]
334
- @queue.push(proc{action.call(cmd)})
335
- end
182
+ alias polling messagepolling
183
+
184
+ def start_wmhandler_loop
185
+ @doevents_thread = Thread.new do
186
+ @message_loop_in = true
187
+ @wmHandler.application.messageloop do
188
+ sleep 0.01
336
189
  end
190
+ @message_loop_in = false
337
191
  end
338
192
  end
339
-
340
- private
341
- def queue_process
342
- while callback = @queue.shift
343
- #Thread.new do
344
- callback.call
345
- #end
193
+ private :start_wmhandler_loop
194
+
195
+
196
+ def start_ping_thread
197
+ Thread.new do
198
+ loop do
199
+ ping if @attached
200
+ sleep PING_CYCLE
201
+ end
346
202
  end
347
203
  end
348
- #=begin
204
+ private :start_ping_thread
205
+
206
+ #とりあえず、新しくインスタンスを作るとき、古いものはcloseしないといけない。
207
+ def close
208
+ break_messageloop
209
+ #@wmHandler.close
210
+ @queue.close
211
+ end
212
+
213
+ def break_messageloop
214
+ PostMessage.call(@wmHandler.hWnd, WM_CLOSE, 0,0)
215
+ sleep 0.123 while @message_loop_in
216
+ end
217
+ private :break_messageloop
218
+
349
219
  def ping
350
- resVal = nil
351
- invoke_callback 'PING' do |res|
352
- resVal = res
353
- end
354
- startTime = Time.now
355
- loop do
356
- polling##
357
- if resVal
358
- return resVal
359
- end
360
- if Time.now - startTime > PING_LIMIT
361
- return false
362
- end
363
- sleep SLEEP_INTERVAL
364
- end
220
+ invoke_block('PING',PING_LIMIT) == 'PONG'
365
221
  end
366
- #=end
222
+
223
+ def _ping
224
+ result = nil
225
+ invoke_callback('PING'){|res| result = res}
226
+ sleep PING_LIMIT
227
+ result == 'PONG'
228
+ end
229
+ private :ping
230
+
367
231
  end
368
232
  end
369
233
  end