purple_ruby 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,2 @@
1
+ == 0.1.0
2
+
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ ext/extconf.rb
2
+ ext/purple_ruby.c
3
+ ext/reconnect.c
4
+ ext/account.c
5
+ examples/purplegw_example.rb
6
+ Manifest.txt
7
+ History.txt
8
+ README.txt
9
+ Rakefile
data/README.txt ADDED
@@ -0,0 +1,53 @@
1
+ == OVERVIEW
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).
4
+
5
+ For MSN, we recommend msn-pecan ( http://code.google.com/p/msn-pecan/ ), which is more more stable than official MSN plugin.
6
+
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.
8
+
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.
10
+
11
+ == INSTALLATION
12
+
13
+ Ubuntu:
14
+ ---------------
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
+ wget -O /etc/yum.repos.d/pidgin.repo http://rpm.pidgin.im/centos/pidgin.repo
22
+ yum -y install glib2-devel libpurple-devel
23
+ gem sources -a http://gems.github.com (you only have to do this once)
24
+ sudo gem install yong-purple_ruby
25
+
26
+ OSX:
27
+ ----
28
+ sudo port -d selfupdate
29
+ sudo port install gnutls
30
+ (wait forever....)
31
+ sudo port install nss
32
+ (wait forever....)
33
+ wget http://downloads.sourceforge.net/pidgin/pidgin-2.5.7.tar.bz2
34
+ tar xvjf pidgin-2.5.7.tar.bz2
35
+ cd pidgin-2.5.7
36
+ ./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
37
+ make
38
+ (wait forever...)
39
+ sudo make install
40
+
41
+ edit your ~/.bash_profile and add this line
42
+ export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
43
+
44
+ gem sources -a http://gems.github.com (you only have to do this once)
45
+ sudo gem install yong-purple_ruby
46
+
47
+ == Copyright
48
+
49
+ purple_ruby is Copyright (c) 2009 Xue Yong Zhi and Intridea, Inc. ( http://intridea.com ), released under the GPL License.
50
+
51
+
52
+
53
+
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+
2
+ EXT = "ext/ruburple_ext.#{Config::CONFIG['DLEXT']}"
3
+
4
+ task :default => EXT
5
+
6
+ file EXT => ["ext/extconf.rb", "ext/purple_ruby.c"] do
7
+ Dir.chdir "ext" do
8
+ ruby "extconf.rb"
9
+ sh "make"
10
+ end
11
+ end
@@ -0,0 +1,94 @@
1
+ #
2
+ #Example Usage:
3
+ #
4
+ #Start the daemon and receive IM:
5
+ #$ruby examples/purplegw_example.rb prpl-msn user@hotmail.com password prpl-jabber user@gmail.com password
6
+ #
7
+ #Send im:
8
+ #$ irb
9
+ #irb(main):001:0> require 'examples/purplegw_example'
10
+ #irb(main):007:0> PurpleGWExample.deliver 'prpl-jabber', 'friend@gmail.com', 'hello worlds!'
11
+ #
12
+
13
+ require 'hpricot'
14
+ require 'socket'
15
+ require File.expand_path(File.join(File.dirname(__FILE__), '../ext/purple_ruby'))
16
+
17
+ class PurpleGWExample
18
+ SERVER_IP = "127.0.0.1"
19
+ SERVER_PORT = 9877
20
+
21
+ def start configs
22
+ PurpleRuby.init false #use 'true' if you want to see the debug messages
23
+
24
+ puts "Available protocols:", PurpleRuby.list_protocols
25
+
26
+ accounts = {}
27
+ configs.each {|config|
28
+ puts "logging in #{config[:username]} (#{config[:protocol]})..."
29
+ account = PurpleRuby.login(config[:protocol], config[:username], config[:password])
30
+ accounts[config[:protocol]] = account
31
+ }
32
+
33
+ #handle incoming im messages
34
+ PurpleRuby.watch_incoming_im do |acc, sender, message|
35
+ sender = sender[0...sender.index('/')] if sender.index('/') #discard anything after '/'
36
+ text = (Hpricot(message)).to_plain_text
37
+ puts "recv: #{acc.username}, #{sender}, #{text}"
38
+ end
39
+
40
+ PurpleRuby.watch_signed_on_event do |acc|
41
+ puts "signed on: #{acc.username}"
42
+ end
43
+
44
+ PurpleRuby.watch_connection_error do |acc, type, description|
45
+ puts "connection_error: #{acc.username} #{type} #{description}"
46
+ true #'true': auto-reconnect; 'false': do nothing
47
+ end
48
+
49
+ #request can be: 'SSL Certificate Verification' etc
50
+ PurpleRuby.watch_request do |title, primary, secondary, who|
51
+ puts "request: #{title}, #{primary}, #{secondary}, #{who}"
52
+ true #'true': accept a request; 'false': ignore a request
53
+ end
54
+
55
+ #request for authorization when someone adds this account to their buddy list
56
+ PurpleRuby.watch_new_buddy do |acc, remote_user, message|
57
+ puts "new buddy: #{acc.username} #{remote_user} #{message}"
58
+ true #'true': accept; 'false': deny
59
+ end
60
+
61
+ PurpleRuby.watch_notify_message do |type, title, primary, secondary|
62
+ puts "notification: #{type}, #{title}, #{primary}, #{secondary}"
63
+ end
64
+
65
+ #listen a tcp port, parse incoming data and send it out.
66
+ #We assume the incoming data is in the following format (separated by comma):
67
+ #<protocol>,<user>,<message>
68
+ PurpleRuby.watch_incoming_ipc(SERVER_IP, SERVER_PORT) do |data|
69
+ protocol, user, message = data.split(",").collect{|x| x.chomp.strip}
70
+ puts "send: #{protocol},#{user},#{message}"
71
+ puts accounts[protocol].send_im(user, message)
72
+ end
73
+
74
+ PurpleRuby.main_loop_run
75
+ end
76
+
77
+ def self.deliver(protocol, to_users, message)
78
+ to_users = [to_users] unless to_users.is_a?(Array)
79
+ to_users.each do |user|
80
+ t = TCPSocket.new(SERVER_IP, SERVER_PORT)
81
+ t.print "#{protocol},#{user},#{message}"
82
+ t.close
83
+ end
84
+ end
85
+ end
86
+
87
+ if ARGV.length >= 3
88
+ configs = []
89
+ configs << {:protocol => ARGV[0], :username => ARGV[1], :password => ARGV[2]}
90
+ configs << {:protocol => ARGV[3], :username => ARGV[4], :password => ARGV[5]} if ARGV.length >= 6
91
+ #add more accounts here if you like
92
+ PurpleGWExample.new.start configs
93
+ end
94
+
data/ext/account.c ADDED
@@ -0,0 +1,162 @@
1
+ /*
2
+ * adopted from finch's gntaccount.c
3
+ */
4
+
5
+ /* finch
6
+ *
7
+ * Finch is the legal property of its developers, whose names are too numerous
8
+ * to list here. Please refer to the COPYRIGHT file distributed with this
9
+ * source distribution.
10
+ *
11
+ * This program is free software; you can redistribute it and/or modify
12
+ * it under the terms of the GNU General Public License as published by
13
+ * the Free Software Foundation; either version 2 of the License, or
14
+ * (at your option) any later version.
15
+ *
16
+ * This program is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program; if not, write to the Free Software
23
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24
+ */
25
+
26
+ #include <libpurple/account.h>
27
+ #include <libpurple/conversation.h>
28
+ #include <libpurple/core.h>
29
+ #include <libpurple/debug.h>
30
+ #include <libpurple/cipher.h>
31
+ #include <libpurple/eventloop.h>
32
+ #include <libpurple/ft.h>
33
+ #include <libpurple/log.h>
34
+ #include <libpurple/notify.h>
35
+ #include <libpurple/prefs.h>
36
+ #include <libpurple/prpl.h>
37
+ #include <libpurple/pounce.h>
38
+ #include <libpurple/request.h>
39
+ #include <libpurple/savedstatuses.h>
40
+ #include <libpurple/sound.h>
41
+ #include <libpurple/status.h>
42
+ #include <libpurple/util.h>
43
+ #include <libpurple/whiteboard.h>
44
+ #include <libpurple/network.h>
45
+
46
+ #include <ruby.h>
47
+
48
+ extern ID CALL;
49
+ extern VALUE cAccount;
50
+ extern VALUE new_buddy_handler;
51
+
52
+ extern VALUE check_callback(VALUE, const char*);
53
+
54
+ static char *
55
+ make_info(PurpleAccount *account, PurpleConnection *gc, const char *remote_user,
56
+ const char *id, const char *alias, const char *msg)
57
+ {
58
+ if (msg != NULL && *msg == '\0')
59
+ msg = NULL;
60
+
61
+ return g_strdup_printf(_("%s%s%s%s has made %s his or her buddy%s%s"),
62
+ remote_user,
63
+ (alias != NULL ? " (" : ""),
64
+ (alias != NULL ? alias : ""),
65
+ (alias != NULL ? ")" : ""),
66
+ (id != NULL
67
+ ? id
68
+ : (purple_connection_get_display_name(gc) != NULL
69
+ ? purple_connection_get_display_name(gc)
70
+ : purple_account_get_username(account))),
71
+ (msg != NULL ? ": " : "."),
72
+ (msg != NULL ? msg : ""));
73
+ }
74
+
75
+ static void
76
+ notify_added(PurpleAccount *account, const char *remote_user,
77
+ const char *id, const char *alias,
78
+ const char *msg)
79
+ {
80
+ char *buffer;
81
+ PurpleConnection *gc;
82
+
83
+ gc = purple_account_get_connection(account);
84
+
85
+ buffer = make_info(account, gc, remote_user, id, alias, msg);
86
+
87
+ purple_notify_info(NULL, NULL, buffer, NULL);
88
+
89
+ g_free(buffer);
90
+ }
91
+
92
+ static void
93
+ request_add(PurpleAccount *account, const char *remote_user,
94
+ const char *id, const char *alias,
95
+ const char *message)
96
+ {
97
+ if (new_buddy_handler != Qnil) {
98
+ VALUE args[3];
99
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
100
+ args[1] = rb_str_new2(NULL == remote_user ? "" : remote_user);
101
+ args[2] = rb_str_new2(NULL == message ? "" : message);
102
+ check_callback(new_buddy_handler, "new_buddy_handler");
103
+ VALUE v = rb_funcall2(new_buddy_handler, CALL, 3, args);
104
+
105
+ if (v != Qnil && v != Qfalse) {
106
+ PurpleConnection *gc = purple_account_get_connection(account);
107
+ if (g_list_find(purple_connections_get_all(), gc))
108
+ {
109
+ purple_blist_request_add_buddy(account, remote_user,
110
+ NULL, alias);
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ static void *request_authorize(PurpleAccount *account,
117
+ const char *remote_user,
118
+ const char *id,
119
+ const char *alias,
120
+ const char *message,
121
+ gboolean on_list,
122
+ PurpleAccountRequestAuthorizationCb auth_cb,
123
+ PurpleAccountRequestAuthorizationCb deny_cb,
124
+ void *user_data)
125
+ {
126
+ if (new_buddy_handler != Qnil) {
127
+ VALUE args[3];
128
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
129
+ args[1] = rb_str_new2(NULL == remote_user ? "" : remote_user);
130
+ args[2] = rb_str_new2(NULL == message ? "" : message);
131
+ VALUE v = rb_funcall2((VALUE)new_buddy_handler, CALL, 3, args);
132
+
133
+ if (v != Qnil && v != Qfalse) {
134
+ auth_cb(user_data);
135
+ purple_blist_request_add_buddy(account, remote_user, NULL, alias);
136
+ } else {
137
+ deny_cb(user_data);
138
+ }
139
+ }
140
+
141
+ return NULL;
142
+ }
143
+
144
+ static void
145
+ request_close(void *uihandle)
146
+ {
147
+ purple_request_close(PURPLE_REQUEST_ACTION, uihandle);
148
+ }
149
+
150
+ PurpleAccountUiOps account_ops =
151
+ {
152
+ notify_added,
153
+ NULL,
154
+ request_add,
155
+ request_authorize,
156
+ request_close,
157
+ NULL,
158
+ NULL,
159
+ NULL,
160
+ NULL
161
+ };
162
+
data/ext/extconf.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+ $CFLAGS = "#{ENV['CFLAGS']} -Wall -O3 -g"
3
+ pkg_config 'purple'
4
+ pkg_config 'glib-2.0'
5
+ pkg_config 'gthread-2.0'
6
+ create_makefile('purple_ruby')
data/ext/purple_ruby.c ADDED
@@ -0,0 +1,799 @@
1
+ /*
2
+ * Author: yong@intridea.com
3
+ *
4
+ * This program is free software; you can redistribute it and/or modify
5
+ * it under the terms of the GNU General Public License as published by
6
+ * the Free Software Foundation; either version 2 of the License, or
7
+ * (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful,
10
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ * GNU General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program; if not, write to the Free Software
16
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
17
+ *
18
+ */
19
+
20
+ #include <libpurple/account.h>
21
+ #include <libpurple/conversation.h>
22
+ #include <libpurple/core.h>
23
+ #include <libpurple/debug.h>
24
+ #include <libpurple/cipher.h>
25
+ #include <libpurple/eventloop.h>
26
+ #include <libpurple/ft.h>
27
+ #include <libpurple/log.h>
28
+ #include <libpurple/notify.h>
29
+ #include <libpurple/prefs.h>
30
+ #include <libpurple/prpl.h>
31
+ #include <libpurple/pounce.h>
32
+ #include <libpurple/request.h>
33
+ #include <libpurple/savedstatuses.h>
34
+ #include <libpurple/sound.h>
35
+ #include <libpurple/status.h>
36
+ #include <libpurple/util.h>
37
+ #include <libpurple/whiteboard.h>
38
+ #include <libpurple/network.h>
39
+
40
+ #include <ruby.h>
41
+ #include <errno.h>
42
+ #include <signal.h>
43
+ #include <stdarg.h>
44
+ #include <unistd.h>
45
+ #include <netinet/in.h>
46
+ #include <sys/socket.h>
47
+ #include <arpa/inet.h>
48
+ #include <fcntl.h>
49
+
50
+ #define PURPLE_GLIB_READ_COND (G_IO_IN | G_IO_HUP | G_IO_ERR)
51
+ #define PURPLE_GLIB_WRITE_COND (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL)
52
+
53
+ typedef struct _PurpleGLibIOClosure {
54
+ PurpleInputFunction function;
55
+ guint result;
56
+ gpointer data;
57
+ } PurpleGLibIOClosure;
58
+
59
+ static void purple_glib_io_destroy(gpointer data)
60
+ {
61
+ g_free(data);
62
+ }
63
+
64
+ static gboolean purple_glib_io_invoke(GIOChannel *source, GIOCondition condition, gpointer data)
65
+ {
66
+ PurpleGLibIOClosure *closure = data;
67
+ PurpleInputCondition purple_cond = 0;
68
+
69
+ if (condition & PURPLE_GLIB_READ_COND)
70
+ purple_cond |= PURPLE_INPUT_READ;
71
+ if (condition & PURPLE_GLIB_WRITE_COND)
72
+ purple_cond |= PURPLE_INPUT_WRITE;
73
+
74
+ closure->function(closure->data, g_io_channel_unix_get_fd(source),
75
+ purple_cond);
76
+
77
+ return TRUE;
78
+ }
79
+
80
+ static guint glib_input_add(gint fd, PurpleInputCondition condition, PurpleInputFunction function,
81
+ gpointer data)
82
+ {
83
+ PurpleGLibIOClosure *closure = g_new0(PurpleGLibIOClosure, 1);
84
+ GIOChannel *channel;
85
+ GIOCondition cond = 0;
86
+
87
+ closure->function = function;
88
+ closure->data = data;
89
+
90
+ if (condition & PURPLE_INPUT_READ)
91
+ cond |= PURPLE_GLIB_READ_COND;
92
+ if (condition & PURPLE_INPUT_WRITE)
93
+ cond |= PURPLE_GLIB_WRITE_COND;
94
+
95
+ channel = g_io_channel_unix_new(fd);
96
+ closure->result = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, cond,
97
+ purple_glib_io_invoke, closure, purple_glib_io_destroy);
98
+
99
+ g_io_channel_unref(channel);
100
+ return closure->result;
101
+ }
102
+
103
+ static PurpleEventLoopUiOps glib_eventloops =
104
+ {
105
+ g_timeout_add,
106
+ g_source_remove,
107
+ glib_input_add,
108
+ g_source_remove,
109
+ NULL,
110
+ #if GLIB_CHECK_VERSION(2,14,0)
111
+ g_timeout_add_seconds,
112
+ #else
113
+ NULL,
114
+ #endif
115
+
116
+ /* padding */
117
+ NULL,
118
+ NULL,
119
+ NULL
120
+ };
121
+
122
+ static VALUE cPurpleRuby;
123
+ VALUE cAccount;
124
+ const char* UI_ID = "purplegw";
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
+
131
+ static VALUE im_handler = Qnil;
132
+ static VALUE signed_on_handler = Qnil;
133
+ static VALUE connection_error_handler = Qnil;
134
+ static VALUE notify_message_handler = Qnil;
135
+ static VALUE request_handler = Qnil;
136
+ static VALUE ipc_handler = Qnil;
137
+ static VALUE timer_handler = Qnil;
138
+ guint timer_timeout = 0;
139
+ VALUE new_buddy_handler = Qnil;
140
+
141
+ extern void
142
+ finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError reason,
143
+ const char *text);
144
+
145
+ extern void finch_connections_init();
146
+
147
+ VALUE inspect_rb_obj(VALUE obj)
148
+ {
149
+ return rb_funcall(obj, rb_intern("inspect"), 0, 0);
150
+ }
151
+
152
+ void set_callback(VALUE* handler, const char* handler_name)
153
+ {
154
+ if (!rb_block_given_p()) {
155
+ rb_raise(rb_eArgError, "%s: no block", handler_name);
156
+ }
157
+
158
+ if (Qnil != *handler) {
159
+ rb_raise(rb_eArgError, "%s should only be assigned once", handler_name);
160
+ }
161
+
162
+ *handler = rb_block_proc();
163
+ /*
164
+ * If you create a Ruby object from C and store it in a C global variable without
165
+ * exporting it to Ruby, you must at least tell the garbage collector about it,
166
+ * lest ye be reaped inadvertently:
167
+ */
168
+ rb_global_variable(handler);
169
+
170
+ if (rb_obj_class(*handler) != rb_cProc) {
171
+ rb_raise(rb_eTypeError, "%s got unexpected value: %s", handler_name,
172
+ RSTRING(inspect_rb_obj(*handler))->ptr);
173
+ }
174
+ }
175
+
176
+ void check_callback(VALUE handler, const char* handler_name){
177
+ if (rb_obj_class(handler) != rb_cProc) {
178
+ rb_raise(rb_eTypeError, "%s has unexpected value: %s",
179
+ handler_name,
180
+ RSTRING(inspect_rb_obj(handler))->ptr);
181
+ }
182
+ }
183
+
184
+ void report_disconnect(PurpleConnection *gc, PurpleConnectionError reason, const char *text)
185
+ {
186
+ if (Qnil != connection_error_handler) {
187
+ VALUE args[3];
188
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(gc));
189
+ args[1] = INT2FIX(reason);
190
+ args[2] = rb_str_new2(text);
191
+ check_callback(connection_error_handler, "connection_error_handler");
192
+ VALUE v = rb_funcall2(connection_error_handler, CALL, 3, args);
193
+
194
+ if (v != Qnil && v != Qfalse) {
195
+ finch_connection_report_disconnect(gc, reason, text);
196
+ }
197
+ }
198
+ }
199
+
200
+ static void* notify_message(PurpleNotifyMsgType type,
201
+ const char *title,
202
+ const char *primary,
203
+ const char *secondary)
204
+ {
205
+ if (notify_message_handler != Qnil) {
206
+ VALUE args[4];
207
+ args[0] = INT2FIX(type);
208
+ args[1] = rb_str_new2(NULL == title ? "" : title);
209
+ args[2] = rb_str_new2(NULL == primary ? "" : primary);
210
+ args[3] = rb_str_new2(NULL == secondary ? "" : secondary);
211
+ check_callback(notify_message_handler, "notify_message_handler");
212
+ rb_funcall2(notify_message_handler, CALL, 4, args);
213
+ }
214
+
215
+ return NULL;
216
+ }
217
+
218
+ static void write_conv(PurpleConversation *conv, const char *who, const char *alias,
219
+ const char *message, PurpleMessageFlags flags, time_t mtime)
220
+ {
221
+ if (im_handler != Qnil) {
222
+ PurpleAccount* account = purple_conversation_get_account(conv);
223
+ if (strcmp(purple_account_get_protocol_id(account), "prpl-msn") == 0 &&
224
+ (strstr(message, "Message could not be sent") != NULL ||
225
+ strstr(message, "Message was not sent") != NULL ||
226
+ strstr(message, "Message may have not been sent") != NULL
227
+ )
228
+ ) {
229
+ /* I have seen error like 'msn: Connection error from Switchboard server'.
230
+ * In that case, libpurple will notify user with two regular im message.
231
+ * The first message is an error message, the second one is the original message that failed to send.
232
+ */
233
+ notify_message(PURPLE_CONNECTION_ERROR_NETWORK_ERROR, message, purple_account_get_protocol_id(account), who);
234
+ } else {
235
+ VALUE args[3];
236
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, account);
237
+ args[1] = rb_str_new2(who);
238
+ args[2] = rb_str_new2(message);
239
+ check_callback(im_handler, "im_handler");
240
+ rb_funcall2(im_handler, CALL, 3, args);
241
+ }
242
+ }
243
+ }
244
+
245
+ static PurpleConversationUiOps conv_uiops =
246
+ {
247
+ NULL, /* create_conversation */
248
+ NULL, /* destroy_conversation */
249
+ NULL, /* write_chat */
250
+ NULL, /* write_im */
251
+ write_conv, /* write_conv */
252
+ NULL, /* chat_add_users */
253
+ NULL, /* chat_rename_user */
254
+ NULL, /* chat_remove_users */
255
+ NULL, /* chat_update_user */
256
+ NULL, /* present */
257
+ NULL, /* has_focus */
258
+ NULL, /* custom_smiley_add */
259
+ NULL, /* custom_smiley_write */
260
+ NULL, /* custom_smiley_close */
261
+ NULL, /* send_confirm */
262
+ NULL,
263
+ NULL,
264
+ NULL,
265
+ NULL
266
+ };
267
+
268
+ static PurpleConnectionUiOps connection_ops =
269
+ {
270
+ NULL, /* connect_progress */
271
+ NULL, /* connected */
272
+ NULL, /* disconnected */
273
+ NULL, /* notice */
274
+ NULL,
275
+ NULL, /* network_connected */
276
+ NULL, /* network_disconnected */
277
+ report_disconnect,
278
+ NULL,
279
+ NULL,
280
+ NULL
281
+ };
282
+
283
+ static void* request_action(const char *title, const char *primary, const char *secondary,
284
+ int default_action,
285
+ PurpleAccount *account,
286
+ const char *who,
287
+ PurpleConversation *conv,
288
+ void *user_data,
289
+ size_t action_count,
290
+ va_list actions)
291
+ {
292
+ if (request_handler != Qnil) {
293
+ VALUE args[4];
294
+ args[0] = rb_str_new2(NULL == title ? "" : title);
295
+ args[1] = rb_str_new2(NULL == primary ? "" : primary);
296
+ args[2] = rb_str_new2(NULL == secondary ? "" : secondary);
297
+ args[3] = rb_str_new2(NULL == who ? "" : who);
298
+ check_callback(request_handler, "request_handler");
299
+ VALUE v = rb_funcall2(request_handler, CALL, 4, args);
300
+
301
+ if (v != Qnil && v != Qfalse) {
302
+ /*const char *text =*/ va_arg(actions, const char *);
303
+ GCallback ok_cb = va_arg(actions, GCallback);
304
+ ((PurpleRequestActionCb)ok_cb)(user_data, default_action);
305
+ }
306
+ }
307
+
308
+ return NULL;
309
+ }
310
+
311
+ static PurpleRequestUiOps request_ops =
312
+ {
313
+ NULL, /*request_input*/
314
+ NULL, /*request_choice*/
315
+ request_action, /*request_action*/
316
+ NULL, /*request_fields*/
317
+ NULL, /*request_file*/
318
+ NULL, /*close_request*/
319
+ NULL, /*request_folder*/
320
+ NULL,
321
+ NULL,
322
+ NULL,
323
+ NULL
324
+ };
325
+
326
+ static PurpleNotifyUiOps notify_ops =
327
+ {
328
+ notify_message, /*notify_message*/
329
+ NULL, /*notify_email*/
330
+ NULL, /*notify_emails*/
331
+ NULL, /*notify_formatted*/
332
+ NULL, /*notify_searchresults*/
333
+ NULL, /*notify_searchresults_new_rows*/
334
+ NULL, /*notify_userinfo*/
335
+ NULL, /*notify_uri*/
336
+ NULL, /*close_notify*/
337
+ NULL,
338
+ NULL,
339
+ NULL,
340
+ NULL,
341
+ };
342
+
343
+ static PurpleCoreUiOps core_uiops =
344
+ {
345
+ NULL,
346
+ NULL,
347
+ NULL,
348
+ NULL,
349
+
350
+ /* padding */
351
+ NULL,
352
+ NULL,
353
+ NULL,
354
+ NULL
355
+ };
356
+
357
+ //I have tried to detect Ctrl-C using ruby's trap method,
358
+ //but it does not work as expected: it can not detect Ctrl-C
359
+ //until a network event occurs
360
+ static void sighandler(int sig)
361
+ {
362
+ switch (sig) {
363
+ case SIGINT:
364
+ g_main_loop_quit(main_loop);
365
+ break;
366
+ }
367
+ }
368
+
369
+ static VALUE init(VALUE self, VALUE debug, VALUE path)
370
+ {
371
+ signal(SIGCHLD, SIG_IGN);
372
+ signal(SIGPIPE, SIG_IGN);
373
+ signal(SIGINT, sighandler);
374
+
375
+ data_hash_table = g_hash_table_new(NULL, NULL);
376
+ fd_hash_table = g_hash_table_new(NULL, NULL);
377
+
378
+ purple_debug_set_enabled((debug == Qnil || debug == Qfalse) ? FALSE : TRUE);
379
+
380
+ if (path != Qnil) {
381
+ purple_util_set_user_dir(RSTRING(path)->ptr);
382
+ }
383
+
384
+ purple_core_set_ui_ops(&core_uiops);
385
+ purple_eventloop_set_ui_ops(&glib_eventloops);
386
+
387
+ if (!purple_core_init(UI_ID)) {
388
+ rb_raise(rb_eRuntimeError, "libpurple initialization failed");
389
+ }
390
+
391
+ /* Create and load the buddylist. */
392
+ purple_set_blist(purple_blist_new());
393
+ purple_blist_load();
394
+
395
+ /* Load the preferences. */
396
+ purple_prefs_load();
397
+
398
+ /* Load the pounces. */
399
+ purple_pounces_load();
400
+
401
+ return Qnil;
402
+ }
403
+
404
+ static VALUE watch_incoming_im(VALUE self)
405
+ {
406
+ purple_conversations_set_ui_ops(&conv_uiops);
407
+ set_callback(&im_handler, "im_handler");
408
+ return im_handler;
409
+ }
410
+
411
+ static VALUE watch_notify_message(VALUE self)
412
+ {
413
+ purple_notify_set_ui_ops(&notify_ops);
414
+ set_callback(&notify_message_handler, "notify_message_handler");
415
+ return notify_message_handler;
416
+ }
417
+
418
+ static VALUE watch_request(VALUE self)
419
+ {
420
+ purple_request_set_ui_ops(&request_ops);
421
+ set_callback(&request_handler, "request_handler");
422
+ return request_handler;
423
+ }
424
+
425
+ static VALUE watch_new_buddy(VALUE self)
426
+ {
427
+ purple_accounts_set_ui_ops(&account_ops);
428
+ set_callback(&new_buddy_handler, "new_buddy_handler");
429
+ return new_buddy_handler;
430
+ }
431
+
432
+ static void signed_on(PurpleConnection* connection)
433
+ {
434
+ VALUE args[1];
435
+ args[0] = Data_Wrap_Struct(cAccount, NULL, NULL, purple_connection_get_account(connection));
436
+ check_callback(signed_on_handler, "signed_on_handler");
437
+ rb_funcall2(signed_on_handler, CALL, 1, args);
438
+ }
439
+
440
+ static VALUE watch_signed_on_event(VALUE self)
441
+ {
442
+ set_callback(&signed_on_handler, "signed_on_handler");
443
+ int handle;
444
+ purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
445
+ PURPLE_CALLBACK(signed_on), NULL);
446
+ return signed_on_handler;
447
+ }
448
+
449
+ static VALUE watch_connection_error(VALUE self)
450
+ {
451
+ finch_connections_init();
452
+ purple_connections_set_ui_ops(&connection_ops);
453
+
454
+ set_callback(&connection_error_handler, "connection_error_handler");
455
+
456
+ /*int handle;
457
+ purple_signal_connect(purple_connections_get_handle(), "connection-error", &handle,
458
+ PURPLE_CALLBACK(connection_error), NULL);*/
459
+ return connection_error_handler;
460
+ }
461
+
462
+ static void _read_socket_handler(gpointer notused, int socket, PurpleInputCondition condition)
463
+ {
464
+ char message[4096] = {0};
465
+ int i = recv(socket, message, sizeof(message) - 1, 0);
466
+ if (i > 0) {
467
+ purple_debug_info("purple_ruby", "recv %d: %d\n", socket, i);
468
+
469
+ gpointer str = g_hash_table_lookup(data_hash_table, (gpointer)socket);
470
+ if (NULL == str) rb_raise(rb_eRuntimeError, "can not find socket: %d", socket);
471
+ rb_str_append((VALUE)str, rb_str_new2(message));
472
+ } else {
473
+ purple_debug_info("purple_ruby", "close connection %d: %d %d\n", socket, i, errno);
474
+
475
+ gpointer str = g_hash_table_lookup(data_hash_table, (gpointer)socket);
476
+ if (NULL == str) {
477
+ purple_debug_warning("purple_ruby", "can not find socket in data_hash_table %d\n", socket);
478
+ return;
479
+ }
480
+
481
+ gpointer purple_fd = g_hash_table_lookup(fd_hash_table, (gpointer)socket);
482
+ if (NULL == purple_fd) {
483
+ purple_debug_warning("purple_ruby", "can not find socket in fd_hash_table %d\n", socket);
484
+ return;
485
+ }
486
+
487
+ g_hash_table_remove(fd_hash_table, (gpointer)socket);
488
+ g_hash_table_remove(data_hash_table, (gpointer)socket);
489
+ purple_input_remove((guint)purple_fd);
490
+ close(socket);
491
+
492
+ VALUE args[1];
493
+ args[0] = (VALUE)str;
494
+ check_callback(ipc_handler, "ipc_handler");
495
+ rb_funcall2(ipc_handler, CALL, 1, args);
496
+ }
497
+ }
498
+
499
+ static void _accept_socket_handler(gpointer notused, int server_socket, PurpleInputCondition condition)
500
+ {
501
+ /* Check that it is a read condition */
502
+ if (condition != PURPLE_INPUT_READ)
503
+ return;
504
+
505
+ struct sockaddr_in their_addr; /* connector's address information */
506
+ socklen_t sin_size = sizeof(struct sockaddr);
507
+ int client_socket;
508
+ if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
509
+ purple_debug_warning("purple_ruby", "failed to accept %d: %d\n", client_socket, errno);
510
+ return;
511
+ }
512
+
513
+ int flags = fcntl(client_socket, F_GETFL);
514
+ fcntl(client_socket, F_SETFL, flags | O_NONBLOCK);
515
+ #ifndef _WIN32
516
+ fcntl(client_socket, F_SETFD, FD_CLOEXEC);
517
+ #endif
518
+
519
+ purple_debug_info("purple_ruby", "new connection: %d\n", client_socket);
520
+
521
+ guint purple_fd = purple_input_add(client_socket, PURPLE_INPUT_READ, _read_socket_handler, NULL);
522
+
523
+ g_hash_table_insert(data_hash_table, (gpointer)client_socket, (gpointer)rb_str_new2(""));
524
+ g_hash_table_insert(fd_hash_table, (gpointer)client_socket, (gpointer)purple_fd);
525
+ }
526
+
527
+ static VALUE watch_incoming_ipc(VALUE self, VALUE serverip, VALUE port)
528
+ {
529
+ struct sockaddr_in my_addr;
530
+ int soc;
531
+ int on = 1;
532
+
533
+ /* Open a listening socket for incoming conversations */
534
+ if ((soc = socket(PF_INET, SOCK_STREAM, 0)) < 0)
535
+ {
536
+ rb_raise(rb_eRuntimeError, "Cannot open socket: %s\n", g_strerror(errno));
537
+ return Qnil;
538
+ }
539
+
540
+ if (setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
541
+ {
542
+ rb_raise(rb_eRuntimeError, "SO_REUSEADDR failed: %s\n", g_strerror(errno));
543
+ return Qnil;
544
+ }
545
+
546
+ memset(&my_addr, 0, sizeof(struct sockaddr_in));
547
+ my_addr.sin_family = AF_INET;
548
+ my_addr.sin_addr.s_addr = inet_addr(RSTRING(serverip)->ptr);
549
+ my_addr.sin_port = htons(FIX2INT(port));
550
+ if (bind(soc, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) != 0)
551
+ {
552
+ rb_raise(rb_eRuntimeError, "Unable to bind to port %d: %s\n", (int)FIX2INT(port), g_strerror(errno));
553
+ return Qnil;
554
+ }
555
+
556
+ /* Attempt to listen on the bound socket */
557
+ if (listen(soc, 10) != 0)
558
+ {
559
+ rb_raise(rb_eRuntimeError, "Cannot listen on socket: %s\n", g_strerror(errno));
560
+ return Qnil;
561
+ }
562
+
563
+ set_callback(&ipc_handler, "ipc_handler");
564
+
565
+ /* Open a watcher in the socket we have just opened */
566
+ purple_input_add(soc, PURPLE_INPUT_READ, _accept_socket_handler, NULL);
567
+
568
+ return port;
569
+ }
570
+
571
+ static gboolean
572
+ do_timeout(gpointer data)
573
+ {
574
+ VALUE handler = data;
575
+ check_callback(handler, "timer_handler");
576
+ VALUE v = rb_funcall(handler, CALL, 0, 0);
577
+ return (v == Qtrue);
578
+ }
579
+
580
+ static VALUE watch_timer(VALUE self, VALUE delay)
581
+ {
582
+ set_callback(&timer_handler, "timer_handler");
583
+ if (timer_timeout != 0)
584
+ g_source_remove(timer_timeout);
585
+ timer_timeout = g_timeout_add(delay, do_timeout, timer_handler);
586
+ return delay;
587
+ }
588
+
589
+ static VALUE login(VALUE self, VALUE protocol, VALUE username, VALUE password)
590
+ {
591
+ PurpleAccount* account = purple_account_new(RSTRING(username)->ptr, RSTRING(protocol)->ptr);
592
+ if (NULL == account || NULL == account->presence) {
593
+ rb_raise(rb_eRuntimeError, "No able to create account: %s", RSTRING(protocol)->ptr);
594
+ }
595
+ purple_account_set_password(account, RSTRING(password)->ptr);
596
+ purple_account_set_remember_password(account, TRUE);
597
+ purple_account_set_enabled(account, UI_ID, TRUE);
598
+ PurpleSavedStatus *status = purple_savedstatus_new(NULL, PURPLE_STATUS_AVAILABLE);
599
+ purple_savedstatus_activate(status);
600
+
601
+ return Data_Wrap_Struct(cAccount, NULL, NULL, account);
602
+ }
603
+
604
+ static VALUE main_loop_run(VALUE self)
605
+ {
606
+ main_loop = g_main_loop_new(NULL, FALSE);
607
+ g_main_loop_run(main_loop);
608
+
609
+ #ifdef DEBUG_MEM_LEAK
610
+ printf("QUIT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
611
+ purple_core_quit();
612
+ if (im_handler == Qnil) rb_gc_unregister_address(&im_handler);
613
+ if (signed_on_handler == Qnil) rb_gc_unregister_address(&signed_on_handler);
614
+ if (connection_error_handler == Qnil) rb_gc_unregister_address(&connection_error_handler);
615
+ if (notify_message_handler == Qnil) rb_gc_unregister_address(&notify_message_handler);
616
+ if (request_handler == Qnil) rb_gc_unregister_address(&request_handler);
617
+ if (ipc_handler == Qnil) rb_gc_unregister_address(&ipc_handler);
618
+ if (timer_timeout != 0) g_source_remove(timer_timeout);
619
+ if (timer_handler == Qnil) rb_gc_unregister_address(&timer_handler);
620
+ if (new_buddy_handler == Qnil) rb_gc_unregister_address(&new_buddy_handler);
621
+ rb_gc_start();
622
+ #endif
623
+
624
+ return Qnil;
625
+ }
626
+
627
+ static VALUE main_loop_stop(VALUE self)
628
+ {
629
+ g_main_loop_quit(main_loop);
630
+ return Qnil;
631
+ }
632
+
633
+ static VALUE send_im(VALUE self, VALUE name, VALUE message)
634
+ {
635
+ PurpleAccount *account;
636
+ Data_Get_Struct(self, PurpleAccount, account);
637
+
638
+ if (purple_account_is_connected(account)) {
639
+ int i = serv_send_im(purple_account_get_connection(account), RSTRING(name)->ptr, RSTRING(message)->ptr, 0);
640
+ return INT2FIX(i);
641
+ } else {
642
+ return Qnil;
643
+ }
644
+ }
645
+
646
+ static VALUE username(VALUE self)
647
+ {
648
+ PurpleAccount *account;
649
+ Data_Get_Struct(self, PurpleAccount, account);
650
+ return rb_str_new2(purple_account_get_username(account));
651
+ }
652
+
653
+ static VALUE protocol_id(VALUE self)
654
+ {
655
+ PurpleAccount *account;
656
+ Data_Get_Struct(self, PurpleAccount, account);
657
+ return rb_str_new2(purple_account_get_protocol_id(account));
658
+ }
659
+
660
+ static VALUE protocol_name(VALUE self)
661
+ {
662
+ PurpleAccount *account;
663
+ Data_Get_Struct(self, PurpleAccount, account);
664
+ return rb_str_new2(purple_account_get_protocol_name(account));
665
+ }
666
+
667
+ static VALUE get_bool_setting(VALUE self, VALUE name, VALUE default_value)
668
+ {
669
+ PurpleAccount *account;
670
+ Data_Get_Struct(self, PurpleAccount, account);
671
+ gboolean value = purple_account_get_bool(account, RSTRING(name)->ptr,
672
+ (default_value == Qfalse || default_value == Qnil) ? FALSE : TRUE);
673
+ return (TRUE == value) ? Qtrue : Qfalse;
674
+ }
675
+
676
+ static VALUE get_string_setting(VALUE self, VALUE name, VALUE default_value)
677
+ {
678
+ PurpleAccount *account;
679
+ Data_Get_Struct(self, PurpleAccount, account);
680
+ const char* value = purple_account_get_string(account, RSTRING(name)->ptr, RSTRING(default_value)->ptr);
681
+ return (NULL == value) ? Qnil : rb_str_new2(value);
682
+ }
683
+
684
+ static VALUE list_protocols(VALUE self)
685
+ {
686
+ VALUE array = rb_ary_new();
687
+
688
+ GList *iter = purple_plugins_get_protocols();
689
+ int i;
690
+ for (i = 0; iter; iter = iter->next) {
691
+ PurplePlugin *plugin = iter->data;
692
+ PurplePluginInfo *info = plugin->info;
693
+ if (info && info->name) {
694
+ char s[256];
695
+ snprintf(s, sizeof(s) -1, "%s %s", info->id, info->name);
696
+ rb_ary_push(array, rb_str_new2(s));
697
+ }
698
+ }
699
+
700
+ return array;
701
+ }
702
+
703
+ static VALUE add_buddy(VALUE self, VALUE buddy)
704
+ {
705
+ PurpleAccount *account;
706
+ Data_Get_Struct(self, PurpleAccount, account);
707
+
708
+ PurpleBuddy* pb = purple_buddy_new(account, RSTRING(buddy)->ptr, NULL);
709
+
710
+ char* group = _("Buddies");
711
+ PurpleGroup* grp = purple_find_group(group);
712
+ if (!grp)
713
+ {
714
+ grp = purple_group_new(group);
715
+ purple_blist_add_group(grp, NULL);
716
+ }
717
+
718
+ purple_blist_add_buddy(pb, NULL, grp, NULL);
719
+ purple_account_add_buddy(account, pb);
720
+ return Qtrue;
721
+ }
722
+
723
+ static VALUE remove_buddy(VALUE self, VALUE buddy)
724
+ {
725
+ PurpleAccount *account;
726
+ Data_Get_Struct(self, PurpleAccount, account);
727
+
728
+ PurpleBuddy* pb = purple_find_buddy(account, RSTRING(buddy)->ptr);
729
+ if (NULL == pb) {
730
+ rb_raise(rb_eRuntimeError, "Failed to remove buddy for %s : %s does not exist", purple_account_get_username(account), RSTRING(buddy)->ptr);
731
+ }
732
+
733
+ char* group = _("Buddies");
734
+ PurpleGroup* grp = purple_find_group(group);
735
+ if (!grp)
736
+ {
737
+ grp = purple_group_new(group);
738
+ purple_blist_add_group(grp, NULL);
739
+ }
740
+
741
+ purple_blist_remove_buddy(pb);
742
+ purple_account_remove_buddy(account, pb, grp);
743
+ return Qtrue;
744
+ }
745
+
746
+ static VALUE has_buddy(VALUE self, VALUE buddy)
747
+ {
748
+ PurpleAccount *account;
749
+ Data_Get_Struct(self, PurpleAccount, account);
750
+ if (purple_find_buddy(account, RSTRING(buddy)->ptr) != NULL) {
751
+ return Qtrue;
752
+ } else {
753
+ return Qfalse;
754
+ }
755
+ }
756
+
757
+ static VALUE acc_delete(VALUE self)
758
+ {
759
+ PurpleAccount *account;
760
+ Data_Get_Struct(self, PurpleAccount, account);
761
+ purple_accounts_delete(account);
762
+ return Qnil;
763
+ }
764
+
765
+ void Init_purple_ruby()
766
+ {
767
+ CALL = rb_intern("call");
768
+
769
+ cPurpleRuby = rb_define_class("PurpleRuby", rb_cObject);
770
+ rb_define_singleton_method(cPurpleRuby, "init", init, 1);
771
+ rb_define_singleton_method(cPurpleRuby, "list_protocols", list_protocols, 0);
772
+ rb_define_singleton_method(cPurpleRuby, "watch_signed_on_event", watch_signed_on_event, 0);
773
+ rb_define_singleton_method(cPurpleRuby, "watch_connection_error", watch_connection_error, 0);
774
+ rb_define_singleton_method(cPurpleRuby, "watch_incoming_im", watch_incoming_im, 0);
775
+ rb_define_singleton_method(cPurpleRuby, "watch_notify_message", watch_notify_message, 0);
776
+ rb_define_singleton_method(cPurpleRuby, "watch_request", watch_request, 0);
777
+ rb_define_singleton_method(cPurpleRuby, "watch_new_buddy", watch_new_buddy, 0);
778
+ rb_define_singleton_method(cPurpleRuby, "watch_incoming_ipc", watch_incoming_ipc, 2);
779
+ rb_define_singleton_method(cPurpleRuby, "watch_timer", watch_timer, 1);
780
+ rb_define_singleton_method(cPurpleRuby, "login", login, 3);
781
+ rb_define_singleton_method(cPurpleRuby, "main_loop_run", main_loop_run, 0);
782
+ rb_define_singleton_method(cPurpleRuby, "main_loop_stop", main_loop_stop, 0);
783
+
784
+ rb_define_const(cPurpleRuby, "NOTIFY_MSG_ERROR", INT2NUM(PURPLE_NOTIFY_MSG_ERROR));
785
+ rb_define_const(cPurpleRuby, "NOTIFY_MSG_WARNING", INT2NUM(PURPLE_NOTIFY_MSG_WARNING));
786
+ rb_define_const(cPurpleRuby, "NOTIFY_MSG_INFO", INT2NUM(PURPLE_NOTIFY_MSG_INFO));
787
+
788
+ cAccount = rb_define_class_under(cPurpleRuby, "Account", rb_cObject);
789
+ rb_define_method(cAccount, "send_im", send_im, 2);
790
+ rb_define_method(cAccount, "username", username, 0);
791
+ rb_define_method(cAccount, "protocol_id", protocol_id, 0);
792
+ rb_define_method(cAccount, "protocol_name", protocol_name, 0);
793
+ rb_define_method(cAccount, "get_bool_setting", get_bool_setting, 2);
794
+ rb_define_method(cAccount, "get_string_setting", get_string_setting, 2);
795
+ rb_define_method(cAccount, "add_buddy", add_buddy, 1);
796
+ rb_define_method(cAccount, "remove_buddy", remove_buddy, 1);
797
+ rb_define_method(cAccount, "has_buddy?", has_buddy, 1);
798
+ rb_define_method(cAccount, "delete", acc_delete, 0);
799
+ }
data/ext/reconnect.c ADDED
@@ -0,0 +1,166 @@
1
+ /*
2
+ * adopted from finch's gntconn.c
3
+ */
4
+
5
+ /* finch
6
+ *
7
+ * Finch is the legal property of its developers, whose names are too numerous
8
+ * to list here. Please refer to the COPYRIGHT file distributed with this
9
+ * source distribution.
10
+ *
11
+ * This program is free software; you can redistribute it and/or modify
12
+ * it under the terms of the GNU General Public License as published by
13
+ * the Free Software Foundation; either version 2 of the License, or
14
+ * (at your option) any later version.
15
+ *
16
+ * This program is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program; if not, write to the Free Software
23
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24
+ */
25
+
26
+ #include <libpurple/account.h>
27
+ #include <libpurple/conversation.h>
28
+ #include <libpurple/core.h>
29
+ #include <libpurple/debug.h>
30
+ #include <libpurple/cipher.h>
31
+ #include <libpurple/eventloop.h>
32
+ #include <libpurple/ft.h>
33
+ #include <libpurple/log.h>
34
+ #include <libpurple/notify.h>
35
+ #include <libpurple/prefs.h>
36
+ #include <libpurple/prpl.h>
37
+ #include <libpurple/pounce.h>
38
+ #include <libpurple/request.h>
39
+ #include <libpurple/savedstatuses.h>
40
+ #include <libpurple/sound.h>
41
+ #include <libpurple/status.h>
42
+ #include <libpurple/util.h>
43
+ #include <libpurple/whiteboard.h>
44
+ #include <libpurple/network.h>
45
+
46
+ extern const char* UI_ID;
47
+
48
+ #define INITIAL_RECON_DELAY_MIN 8000
49
+ #define INITIAL_RECON_DELAY_MAX 60000
50
+
51
+ #define MAX_RECON_DELAY 600000
52
+
53
+ typedef struct {
54
+ int delay;
55
+ guint timeout;
56
+ } FinchAutoRecon;
57
+
58
+ /**
59
+ * Contains accounts that are auto-reconnecting.
60
+ * The key is a pointer to the PurpleAccount and the
61
+ * value is a pointer to a FinchAutoRecon.
62
+ */
63
+ static GHashTable *hash = NULL;
64
+
65
+ static void
66
+ free_auto_recon(gpointer data)
67
+ {
68
+ FinchAutoRecon *info = data;
69
+
70
+ if (info->timeout != 0)
71
+ g_source_remove(info->timeout);
72
+
73
+ g_free(info);
74
+ }
75
+
76
+ static gboolean
77
+ do_signon(gpointer data)
78
+ {
79
+ PurpleAccount *account = data;
80
+ FinchAutoRecon *info;
81
+ PurpleStatus *status;
82
+
83
+ purple_debug_info("autorecon", "do_signon called\n");
84
+ g_return_val_if_fail(account != NULL, FALSE);
85
+ info = g_hash_table_lookup(hash, account);
86
+
87
+ if (info)
88
+ info->timeout = 0;
89
+
90
+ status = purple_account_get_active_status(account);
91
+ if (purple_status_is_online(status))
92
+ {
93
+ purple_debug_info("autorecon", "calling purple_account_connect\n");
94
+ purple_account_connect(account);
95
+ purple_debug_info("autorecon", "done calling purple_account_connect\n");
96
+ }
97
+
98
+ return FALSE;
99
+ }
100
+
101
+ static gboolean
102
+ enable_account(gpointer data)
103
+ {
104
+ PurpleAccount *account = data;
105
+ FinchAutoRecon *info;
106
+
107
+ purple_debug_info("autorecon", "enable_account called\n");
108
+ g_return_val_if_fail(account != NULL, FALSE);
109
+ info = g_hash_table_lookup(hash, account);
110
+
111
+ if (info)
112
+ info->timeout = 0;
113
+
114
+ purple_account_set_enabled(account, UI_ID, TRUE);
115
+
116
+ return FALSE;
117
+ }
118
+
119
+ void
120
+ finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError reason,
121
+ const char *text)
122
+ {
123
+ PurpleAccount *account = purple_connection_get_account(gc);
124
+ FinchAutoRecon *info = g_hash_table_lookup(hash, account);
125
+
126
+ if (info == NULL) {
127
+ info = g_new0(FinchAutoRecon, 1);
128
+ g_hash_table_insert(hash, account, info);
129
+ info->delay = g_random_int_range(INITIAL_RECON_DELAY_MIN, INITIAL_RECON_DELAY_MAX);
130
+ } else {
131
+ info->delay = MIN(2 * info->delay, MAX_RECON_DELAY);
132
+ if (info->timeout != 0)
133
+ g_source_remove(info->timeout);
134
+ }
135
+
136
+ if (!purple_connection_error_is_fatal(reason)) {
137
+ info->timeout = g_timeout_add(info->delay, do_signon, account);
138
+ } else {
139
+ info->timeout = g_timeout_add(info->delay, enable_account, account);
140
+ }
141
+ }
142
+
143
+ static void
144
+ account_removed_cb(PurpleAccount *account, gpointer user_data)
145
+ {
146
+ g_hash_table_remove(hash, account);
147
+ }
148
+
149
+ static void *
150
+ finch_connection_get_handle(void)
151
+ {
152
+ static int handle;
153
+
154
+ return &handle;
155
+ }
156
+
157
+ void finch_connections_init()
158
+ {
159
+ hash = g_hash_table_new_full(
160
+ g_direct_hash, g_direct_equal,
161
+ NULL, free_auto_recon);
162
+
163
+ purple_signal_connect(purple_accounts_get_handle(), "account-removed",
164
+ finch_connection_get_handle(),
165
+ PURPLE_CALLBACK(account_removed_cb), NULL);
166
+ }
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: purple_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.2
5
+ platform: ruby
6
+ authors:
7
+ - yong
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-01 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hoe
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.8.3
24
+ version:
25
+ description: A ruby gem to write server that sends and recives IM messages
26
+ email: yong@intridea.com
27
+ executables: []
28
+
29
+ extensions:
30
+ - ext/extconf.rb
31
+ extra_rdoc_files:
32
+ - Manifest.txt
33
+ - History.txt
34
+ - README.txt
35
+ files:
36
+ - ext/extconf.rb
37
+ - ext/purple_ruby.c
38
+ - ext/reconnect.c
39
+ - ext/account.c
40
+ - examples/purplegw_example.rb
41
+ - Manifest.txt
42
+ - History.txt
43
+ - README.txt
44
+ - Rakefile
45
+ has_rdoc: true
46
+ homepage: http://www.intridea.com
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --main
52
+ - README.txt
53
+ require_paths:
54
+ - ext
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: purplegw_ruby
70
+ rubygems_version: 1.3.5
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: A ruby gem to write server that sends and recives IM messages
74
+ test_files: []
75
+