yong-purple_ruby 0.4.1 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt CHANGED
@@ -1,17 +1,26 @@
1
1
  == OVERVIEW
2
2
 
3
- purple_ruby is a ruby gem to write servers that send and recive IM messages. It uses libpurple (http://developer.pidgin.im/wiki/WhatIsLibpurple) and therforce supports all protocols that Pidgin/Adium supports (MSN/Gtalk/Yahoo/AIM/ICQ etc).
3
+ purple_ruby is a ruby gem to write servers that send and recive IM messages. It uses libpurple ( http://developer.pidgin.im/wiki/WhatIsLibpurple ) and therforce supports all protocols that Pidgin/Adium supports (MSN/Gtalk/Yahoo/AIM/ICQ etc).
4
+
5
+ For MSN, we recommend msn-pecan ( http://code.google.com/p/msn-pecan/ ), which is more more stable than official MSN plugin.
4
6
 
5
7
  Please check examples/purplegw_example.rb for details. Bascially you just tell it what to do when an IM was received, and there is an embedded tcp 'proxy' which allows you send IM messages.
6
8
 
7
- Why not "ruburple"? I have used ruburple (http://rubyforge.org/projects/ruburple), but found it blocks a lot. libpurple needs to run its own event loop which interferes with ruby's green thread model. Ruburple's author has done lots of hard work to workaround the problem (http://rubyforge.org/pipermail/ruburple-development/2007-June/000005.html), but it does not work well.
9
+ Why not "ruburple"? I have used ruburple ( http://rubyforge.org/projects/ruburple ), but found it blocks a lot. libpurple needs to run its own event loop which interferes with ruby's green thread model. Ruburple's author has done lots of hard work to workaround the problem ( http://rubyforge.org/pipermail/ruburple-development/2007-June/000005.html ), but it does not work well.
8
10
 
9
11
  == INSTALLATION
10
12
 
11
- Linux (Ubuntu):
13
+ Ubuntu:
12
14
  ---------------
13
- apt-get install libpurple0 libpurple-dev
14
- gem install yong-purple_ruby
15
+ sudo apt-get install libpurple0 libpurple-dev
16
+ gem sources -a http://gems.github.com (you only have to do this once)
17
+ sudo gem install yong-purple_ruby
18
+
19
+ Redhat/Centos
20
+ ---------------
21
+ Follow instructions here: http://www.pidgin.im/download/centos_rhel/
22
+ gem sources -a http://gems.github.com (you only have to do this once)
23
+ sudo gem install yong-purple_ruby
15
24
 
16
25
  OSX:
17
26
  ----
@@ -20,9 +29,9 @@ sudo port install gnutls
20
29
  (wait forever....)
21
30
  sudo port install nss
22
31
  (wait forever....)
23
- wget http://downloads.sourceforge.net/pidgin/pidgin-2.5.5.tar.bz2
24
- tar xvjf pidgin-2.5.5.tar.bz2
25
- cd pidgin-2.5.5
32
+ wget http://downloads.sourceforge.net/pidgin/pidgin-2.5.7.tar.bz2
33
+ tar xvjf pidgin-2.5.7.tar.bz2
34
+ cd pidgin-2.5.7
26
35
  ./configure --disable-gtkui --disable-screensaver --disable-consoleui --disable-sm --disable-perl --disable-tk --disable-tcl --disable-gstreamer --disable-schemas-install --disable-gestures --disable-cap --disable-gevolution --disable-gtkspell --disable-startup-notification --disable-avahi --disable-nm --disable-dbus --disable-meanwhile
27
36
  make
28
37
  (wait forever...)
@@ -31,8 +40,13 @@ sudo make install
31
40
  edit your ~/.bash_profile and add this line
32
41
  export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
33
42
 
43
+ gem sources -a http://gems.github.com (you only have to do this once)
34
44
  sudo gem install yong-purple_ruby
35
45
 
46
+ == Copyright
47
+
48
+ purple_ruby is Copyright (c) 2009 Xue Yong Zhi and Intridea, Inc., released under the GPL License.
49
+
36
50
 
37
51
 
38
52
 
data/Rakefile CHANGED
@@ -3,7 +3,7 @@ require 'hoe'
3
3
 
4
4
  EXT = "ext/ruburple_ext.#{Hoe::DLEXT}"
5
5
 
6
- Hoe.new('purple_ruby', '0.4.1') do |p|
6
+ Hoe.new('purple_ruby', '0.5.3') do |p|
7
7
  p.author = 'yong'
8
8
  p.email = 'yong@intridea.com'
9
9
  p.url = 'http://www.intridea.com'
@@ -16,7 +16,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), '../ext/purple_ruby')
16
16
 
17
17
  class PurpleGWExample
18
18
  SERVER_IP = "127.0.0.1"
19
- SERVER_PORT = 9876
19
+ SERVER_PORT = 9877
20
20
 
21
21
  def start configs
22
22
  PurpleRuby.init false #use 'true' if you want to see the debug messages
@@ -25,15 +25,16 @@ class PurpleGWExample
25
25
 
26
26
  accounts = {}
27
27
  configs.each {|config|
28
+ puts "logging in #{config[:username]} (#{config[:protocol]})..."
28
29
  account = PurpleRuby.login(config[:protocol], config[:username], config[:password])
29
30
  accounts[config[:protocol]] = account
30
31
  }
31
32
 
32
33
  #handle incoming im messages
33
- PurpleRuby.watch_incoming_im do |receiver, sender, message|
34
+ PurpleRuby.watch_incoming_im do |acc, sender, message|
34
35
  sender = sender[0...sender.index('/')] if sender.index('/') #discard anything after '/'
35
36
  text = (Hpricot(message)).to_plain_text
36
- puts "recv: #{receiver}, #{sender}, #{text}"
37
+ puts "recv: #{acc.username}, #{sender}, #{text}"
37
38
  end
38
39
 
39
40
  PurpleRuby.watch_signed_on_event do |acc|
data/ext/account.c CHANGED
@@ -49,6 +49,8 @@ extern ID CALL;
49
49
  extern VALUE cAccount;
50
50
  extern VALUE new_buddy_handler;
51
51
 
52
+ extern VALUE check_callback(VALUE, const char*);
53
+
52
54
  static char *
53
55
  make_info(PurpleAccount *account, PurpleConnection *gc, const char *remote_user,
54
56
  const char *id, const char *alias, const char *msg)
@@ -93,11 +95,12 @@ request_add(PurpleAccount *account, const char *remote_user,
93
95
  const char *message)
94
96
  {
95
97
  if (new_buddy_handler != Qnil) {
96
- VALUE *args = g_new(VALUE, 3);
98
+ VALUE args[3];
97
99
  args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
98
100
  args[1] = rb_str_new2(NULL == remote_user ? "" : remote_user);
99
101
  args[2] = rb_str_new2(NULL == message ? "" : message);
100
- VALUE v = rb_funcall2((VALUE)new_buddy_handler, CALL, 3, args);
102
+ check_callback(new_buddy_handler, "new_buddy_handler");
103
+ VALUE v = rb_funcall2(new_buddy_handler, CALL, 3, args);
101
104
 
102
105
  if (v != Qnil && v != Qfalse) {
103
106
  PurpleConnection *gc = purple_account_get_connection(account);
@@ -107,8 +110,6 @@ request_add(PurpleAccount *account, const char *remote_user,
107
110
  NULL, alias);
108
111
  }
109
112
  }
110
-
111
- g_free(args);
112
113
  }
113
114
  }
114
115
 
@@ -123,7 +124,7 @@ static void *request_authorize(PurpleAccount *account,
123
124
  void *user_data)
124
125
  {
125
126
  if (new_buddy_handler != Qnil) {
126
- VALUE *args = g_new(VALUE, 3);
127
+ VALUE args[3];
127
128
  args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
128
129
  args[1] = rb_str_new2(NULL == remote_user ? "" : remote_user);
129
130
  args[2] = rb_str_new2(NULL == message ? "" : message);
@@ -135,8 +136,6 @@ static void *request_authorize(PurpleAccount *account,
135
136
  } else {
136
137
  deny_cb(user_data);
137
138
  }
138
-
139
- g_free(args);
140
139
  }
141
140
 
142
141
  return NULL;
data/ext/purple_ruby.c CHANGED
@@ -123,16 +123,18 @@ static VALUE cPurpleRuby;
123
123
  VALUE cAccount;
124
124
  const char* UI_ID = "purplegw";
125
125
  static GMainLoop *main_loop = NULL;
126
+ static GHashTable* data_hash_table = NULL;
127
+ static GHashTable* fd_hash_table = NULL;
128
+ ID CALL;
129
+ extern PurpleAccountUiOps account_ops;
130
+
126
131
  static VALUE im_handler = Qnil;
127
132
  static VALUE signed_on_handler = Qnil;
128
133
  static VALUE connection_error_handler = Qnil;
129
134
  static VALUE notify_message_handler = Qnil;
130
135
  static VALUE request_handler = Qnil;
136
+ static VALUE ipc_handler = Qnil;
131
137
  VALUE new_buddy_handler = Qnil;
132
- static GHashTable* data_hash_table = NULL;
133
- static GHashTable* fd_hash_table = NULL;
134
- ID CALL;
135
- extern PurpleAccountUiOps account_ops;
136
138
 
137
139
  extern void
138
140
  finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError reason,
@@ -140,42 +142,100 @@ finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError r
140
142
 
141
143
  extern void finch_connections_init();
142
144
 
145
+ VALUE inspect_rb_obj(VALUE obj)
146
+ {
147
+ return rb_funcall(obj, rb_intern("inspect"), 0, 0);
148
+ }
149
+
150
+ void set_callback(VALUE* handler, const char* handler_name)
151
+ {
152
+ if (!rb_block_given_p()) {
153
+ rb_raise(rb_eArgError, "%s: no block", handler_name);
154
+ }
155
+
156
+ if (Qnil != *handler) {
157
+ rb_raise(rb_eArgError, "%s should only be assigned once", handler_name);
158
+ }
159
+
160
+ *handler = rb_block_proc();
161
+ /*
162
+ * If you create a Ruby object from C and store it in a C global variable without
163
+ * exporting it to Ruby, you must at least tell the garbage collector about it,
164
+ * lest ye be reaped inadvertently:
165
+ */
166
+ rb_global_variable(handler);
167
+
168
+ if (rb_obj_class(*handler) != rb_cProc) {
169
+ rb_raise(rb_eTypeError, "%s got unexpected value: %s", handler_name,
170
+ RSTRING(inspect_rb_obj(*handler))->ptr);
171
+ }
172
+ }
173
+
174
+ void check_callback(VALUE handler, const char* handler_name){
175
+ if (rb_obj_class(handler) != rb_cProc) {
176
+ rb_raise(rb_eTypeError, "%s has unexpected value: %s",
177
+ handler_name,
178
+ RSTRING(inspect_rb_obj(handler))->ptr);
179
+ }
180
+ }
181
+
143
182
  void report_disconnect(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
144
183
  {
145
184
  if (Qnil != connection_error_handler) {
146
- VALUE *args = g_new(VALUE, 3);
185
+ VALUE args[3];
147
186
  args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(gc));
148
187
  args[1] = INT2FIX(reason);
149
188
  args[2] = rb_str_new2(text);
150
- VALUE v = rb_funcall2((VALUE)connection_error_handler, CALL, 3, args);
189
+ check_callback(connection_error_handler, "connection_error_handler");
190
+ VALUE v = rb_funcall2(connection_error_handler, CALL, 3, args);
151
191
 
152
192
  if (v != Qnil && v != Qfalse) {
153
193
  finch_connection_report_disconnect(gc, reason, text);
154
194
  }
155
-
156
- g_free(args);
157
195
  }
158
196
  }
159
197
 
198
+ static void* notify_message(PurpleNotifyMsgType type,
199
+ const char *title,
200
+ const char *primary,
201
+ const char *secondary)
202
+ {
203
+ if (notify_message_handler != Qnil) {
204
+ VALUE args[4];
205
+ args[0] = INT2FIX(type);
206
+ args[1] = rb_str_new2(NULL == title ? "" : title);
207
+ args[2] = rb_str_new2(NULL == primary ? "" : primary);
208
+ args[3] = rb_str_new2(NULL == secondary ? "" : secondary);
209
+ check_callback(notify_message_handler, "notify_message_handler");
210
+ rb_funcall2(notify_message_handler, CALL, 4, args);
211
+ }
212
+
213
+ return NULL;
214
+ }
215
+
160
216
  static void write_conv(PurpleConversation *conv, const char *who, const char *alias,
161
217
  const char *message, PurpleMessageFlags flags, time_t mtime)
162
218
  {
163
219
  if (im_handler != Qnil) {
164
220
  PurpleAccount* account = purple_conversation_get_account(conv);
165
221
  if (strcmp(purple_account_get_protocol_id(account), "prpl-msn") == 0 &&
166
- strstr(message, "Message could not be sent") != NULL) {
222
+ (strstr(message, "Message could not be sent") != NULL ||
223
+ strstr(message, "Message was not sent") != NULL ||
224
+ strstr(message, "Message may have not been sent") != NULL
225
+ )
226
+ ) {
167
227
  /* I have seen error like 'msn: Connection error from Switchboard server'.
168
228
  * In that case, libpurple will notify user with two regular im message.
169
229
  * The first message is an error message, the second one is the original message that failed to send.
170
230
  */
171
- report_disconnect(purple_account_get_connection(account), PURPLE_CONNECTION_ERROR_NETWORK_ERROR, message);
231
+ notify_message(PURPLE_CONNECTION_ERROR_NETWORK_ERROR, message, purple_account_get_protocol_id(account), who);
172
232
  } else {
173
- VALUE *args = g_new(VALUE, 3);
174
- args[0] = rb_str_new2(purple_account_get_username(account));
233
+ VALUE args[3];
234
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
175
235
  args[1] = rb_str_new2(who);
176
236
  args[2] = rb_str_new2(message);
237
+ check_callback(im_handler, "im_handler");
177
238
  rb_funcall2(im_handler, CALL, 3, args);
178
- g_free(args);
179
239
  }
180
240
  }
181
241
  }
@@ -218,24 +278,6 @@ static PurpleConnectionUiOps connection_ops =
218
278
  NULL
219
279
  };
220
280
 
221
- static void* notify_message(PurpleNotifyMsgType type,
222
- const char *title,
223
- const char *primary,
224
- const char *secondary)
225
- {
226
- if (notify_message_handler != Qnil) {
227
- VALUE *args = g_new(VALUE, 4);
228
- args[0] = INT2FIX(type);
229
- args[1] = rb_str_new2(NULL == title ? "" : title);
230
- args[2] = rb_str_new2(NULL == primary ? "" : primary);
231
- args[3] = rb_str_new2(NULL == secondary ? "" : secondary);
232
- rb_funcall2(notify_message_handler, CALL, 4, args);
233
- g_free(args);
234
- }
235
-
236
- return NULL;
237
- }
238
-
239
281
  static void* request_action(const char *title, const char *primary, const char *secondary,
240
282
  int default_action,
241
283
  PurpleAccount *account,
@@ -246,11 +288,12 @@ static void* request_action(const char *title, const char *primary, const char *
246
288
  va_list actions)
247
289
  {
248
290
  if (request_handler != Qnil) {
249
- VALUE *args = g_new(VALUE, 4);
291
+ VALUE args[4];
250
292
  args[0] = rb_str_new2(NULL == title ? "" : title);
251
293
  args[1] = rb_str_new2(NULL == primary ? "" : primary);
252
294
  args[2] = rb_str_new2(NULL == secondary ? "" : secondary);
253
295
  args[3] = rb_str_new2(NULL == who ? "" : who);
296
+ check_callback(request_handler, "request_handler");
254
297
  VALUE v = rb_funcall2(request_handler, CALL, 4, args);
255
298
 
256
299
  if (v != Qnil && v != Qfalse) {
@@ -258,8 +301,6 @@ static void* request_action(const char *title, const char *primary, const char *
258
301
  GCallback ok_cb = va_arg(actions, GCallback);
259
302
  ((PurpleRequestActionCb)ok_cb)(user_data, default_action);
260
303
  }
261
-
262
- g_free(args);
263
304
  }
264
305
 
265
306
  return NULL;
@@ -356,42 +397,42 @@ static VALUE init(VALUE self, VALUE debug)
356
397
  static VALUE watch_incoming_im(VALUE self)
357
398
  {
358
399
  purple_conversations_set_ui_ops(&conv_uiops);
359
- im_handler = rb_block_proc();
400
+ set_callback(&im_handler, "im_handler");
360
401
  return im_handler;
361
402
  }
362
403
 
363
404
  static VALUE watch_notify_message(VALUE self)
364
405
  {
365
406
  purple_notify_set_ui_ops(&notify_ops);
366
- notify_message_handler = rb_block_proc();
407
+ set_callback(&notify_message_handler, "notify_message_handler");
367
408
  return notify_message_handler;
368
409
  }
369
410
 
370
411
  static VALUE watch_request(VALUE self)
371
412
  {
372
413
  purple_request_set_ui_ops(&request_ops);
373
- request_handler = rb_block_proc();
414
+ set_callback(&request_handler, "request_handler");
374
415
  return request_handler;
375
416
  }
376
417
 
377
418
  static VALUE watch_new_buddy(VALUE self)
378
419
  {
379
420
  purple_accounts_set_ui_ops(&account_ops);
380
- new_buddy_handler = rb_block_proc();
421
+ set_callback(&new_buddy_handler, "new_buddy_handler");
381
422
  return new_buddy_handler;
382
423
  }
383
424
 
384
425
  static void signed_on(PurpleConnection* connection)
385
426
  {
386
- VALUE *args = g_new(VALUE, 1);
427
+ VALUE args[1];
387
428
  args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(connection));
388
- rb_funcall2((VALUE)signed_on_handler, CALL, 1, args);
389
- g_free(args);
429
+ check_callback(signed_on_handler, "signed_on_handler");
430
+ rb_funcall2(signed_on_handler, CALL, 1, args);
390
431
  }
391
432
 
392
433
  static VALUE watch_signed_on_event(VALUE self)
393
434
  {
394
- signed_on_handler = rb_block_proc();
435
+ set_callback(&signed_on_handler, "signed_on_handler");
395
436
  int handle;
396
437
  purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
397
438
  PURPLE_CALLBACK(signed_on), NULL);
@@ -403,14 +444,15 @@ static VALUE watch_connection_error(VALUE self)
403
444
  finch_connections_init();
404
445
  purple_connections_set_ui_ops(&connection_ops);
405
446
 
406
- connection_error_handler = rb_block_proc();
447
+ set_callback(&connection_error_handler, "connection_error_handler");
448
+
407
449
  /*int handle;
408
450
  purple_signal_connect(purple_connections_get_handle(), "connection-error", &handle,
409
451
  PURPLE_CALLBACK(connection_error), NULL);*/
410
452
  return connection_error_handler;
411
453
  }
412
454
 
413
- static void _read_socket_handler(gpointer data, int socket, PurpleInputCondition condition)
455
+ static void _read_socket_handler(gpointer notused, int socket, PurpleInputCondition condition)
414
456
  {
415
457
  char message[4096] = {0};
416
458
  int i = recv(socket, message, sizeof(message) - 1, 0);
@@ -440,14 +482,14 @@ static void _read_socket_handler(gpointer data, int socket, PurpleInputCondition
440
482
  purple_input_remove((guint)purple_fd);
441
483
  close(socket);
442
484
 
443
- VALUE *args = g_new(VALUE, 1);
485
+ VALUE args[1];
444
486
  args[0] = (VALUE)str;
445
- rb_funcall2((VALUE)data, CALL, 1, args);
446
- g_free(args);
487
+ check_callback(ipc_handler, "ipc_handler");
488
+ rb_funcall2(ipc_handler, CALL, 1, args);
447
489
  }
448
490
  }
449
491
 
450
- static void _accept_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition)
492
+ static void _accept_socket_handler(gpointer notused, int server_socket, PurpleInputCondition condition)
451
493
  {
452
494
  /* Check that it is a read condition */
453
495
  if (condition != PURPLE_INPUT_READ)
@@ -469,7 +511,7 @@ static void _accept_socket_handler(gpointer data, int server_socket, PurpleInput
469
511
 
470
512
  purple_debug_info("purple_ruby", "new connection: %d\n", client_socket);
471
513
 
472
- guint purple_fd = purple_input_add(client_socket, PURPLE_INPUT_READ, _read_socket_handler, data);
514
+ guint purple_fd = purple_input_add(client_socket, PURPLE_INPUT_READ, _read_socket_handler, NULL);
473
515
 
474
516
  g_hash_table_insert(data_hash_table, (gpointer)client_socket, (gpointer)rb_str_new2(""));
475
517
  g_hash_table_insert(fd_hash_table, (gpointer)client_socket, (gpointer)purple_fd);
@@ -504,10 +546,10 @@ static VALUE watch_incoming_ipc(VALUE self, VALUE serverip, VALUE port)
504
546
  return Qnil;
505
547
  }
506
548
 
507
- VALUE proc = rb_block_proc();
549
+ set_callback(&ipc_handler, "ipc_handler");
508
550
 
509
551
  /* Open a watcher in the socket we have just opened */
510
- purple_input_add(soc, PURPLE_INPUT_READ, _accept_socket_handler, (gpointer)proc);
552
+ purple_input_add(soc, PURPLE_INPUT_READ, _accept_socket_handler, NULL);
511
553
 
512
554
  return port;
513
555
  }
@@ -530,6 +572,20 @@ static VALUE main_loop_run(VALUE self)
530
572
  {
531
573
  main_loop = g_main_loop_new(NULL, FALSE);
532
574
  g_main_loop_run(main_loop);
575
+
576
+ #ifdef DEBUG_MEM_LEAK
577
+ printf("QUIT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
578
+ purple_core_quit();
579
+ if (im_handler == Qnil) rb_gc_unregister_address(&im_handler);
580
+ if (signed_on_handler == Qnil) rb_gc_unregister_address(&signed_on_handler);
581
+ if (connection_error_handler == Qnil) rb_gc_unregister_address(&connection_error_handler);
582
+ if (notify_message_handler == Qnil) rb_gc_unregister_address(&notify_message_handler);
583
+ if (request_handler == Qnil) rb_gc_unregister_address(&request_handler);
584
+ if (ipc_handler == Qnil) rb_gc_unregister_address(&ipc_handler);
585
+ if (new_buddy_handler == Qnil) rb_gc_unregister_address(&new_buddy_handler);
586
+ rb_gc_start();
587
+ #endif
588
+
533
589
  return Qnil;
534
590
  }
535
591
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yong-purple_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - yong