yong-purple_ruby 0.4.1 → 0.5.3

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.
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