summaryrefslogtreecommitdiffabout
authorCesar Negrete (NuttyBunny) <cesar.negrete@gmail.com>2011-03-27 15:21:35 (GMT)
committer Cesar Negrete (NuttyBunny) <cesar.negrete@gmail.com>2011-03-27 15:21:35 (GMT)
commite92f63e6fbd315c6a26aab354631ee10a1eb3411 (patch) (side-by-side diff)
tree067ba8098bae804fbcd8e2c6149368a68eacf96a
parent95568461bac2e372f2be9590fa74f5c34b824704 (diff)
3.0.7 - Compiler files ready. Use them to crosscompile the latest version :)HEADmaster
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--3.0/Compiler Source Files/Patches/imaccountvalidator-patches33
-rw-r--r--3.0/Compiler Source Files/Patches/imlibpurpleservice-1.0-patches5813
-rw-r--r--3.0/Compiler Source Files/Patches/msn-pecan-patches495
-rw-r--r--3.0/Compiler Source Files/Patches/pidgin-facebookchat-patches105
-rw-r--r--3.0/Compiler Source Files/Patches/pidgin-patches29796
-rw-r--r--3.0/Compiler Source Files/imaccountvalidator-1.0/Makefile4
-rw-r--r--3.0/Compiler Source Files/imaccountvalidator-1.0/inc/IMAccountValidatorHandler.h2
-rw-r--r--3.0/Compiler Source Files/imaccountvalidator-1.0/src/IMAccountValidatorHandler.cpp2
-rw-r--r--3.0/Compiler Source Files/imlibpurpleservice-1.0/Makefile4
-rw-r--r--3.0/Compiler Source Files/imlibpurpleservice-1.0/Makefile.inc5
-rw-r--r--3.0/Compiler Source Files/imlibpurpleservice-1.0/inc/PalmImCommon.h2
-rw-r--r--3.0/Compiler Source Files/imlibpurpleservice-1.0/src/IMLoginState.cpp3
-rw-r--r--3.0/Compiler Source Files/imlibpurpleservice-1.0/src/IMServiceApp.cpp1
-rw-r--r--3.0/Compiler Source Files/imlibpurpleservice-1.0/src/LibpurpleAdapter.cpp16
-rw-r--r--3.0/Compiler Source Files/imlibpurpleservice-1.0/src/LibpurpleAdapterPrefs.cpp11
-rw-r--r--3.0/Compiler Source Files/imlibpurpleservice-1.0/src/OnEnabledHandler.cpp4
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/AUTHORS17
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/COPYING339
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ChangeLog1657
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/Makefile278
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/Makefile~279
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/README34
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/TODO102
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ab/pn_contact.c741
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ab/pn_contact.h315
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ab/pn_contact_priv.h94
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ab/pn_contactlist.c840
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ab/pn_contactlist.h114
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ab/pn_contactlist_priv.h45
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ab/pn_group.c86
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ab/pn_group.h39
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/clients/adium/Makefile.am83
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/cmdproc.c433
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/cmdproc.h52
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/cmdproc_private.h53
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/command.c89
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/command.h30
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/command_private.h43
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/msg.c811
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/msg.h272
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/msg_private.h88
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/table.c129
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/table.h36
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/table_private.h37
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/transaction.c152
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/transaction.h44
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cmd/transaction_private.h50
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_direct_conn.c319
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_direct_conn.h77
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_msnobj.c291
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_msnobj.h130
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_peer_call.c141
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_peer_call.h38
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_peer_call_priv.h62
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_peer_link.c820
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_peer_link.h77
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_peer_msg.c928
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_peer_msg.h59
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/cvr/pn_peer_msg_priv.h66
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/doc/.gitignore1
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/doc/Doxyfile257
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/doc/p2p-design.txt24
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/cab.h127
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/cabd.c1435
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/lzx.h185
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/lzxd.c907
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/mspack.h1878
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/mszip.h114
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/mszipd.c647
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/qtm.h120
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/qtmd.c528
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/system.c225
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libmspack/system.h82
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/common.c505
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/common.h146
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/dct4.c184
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/dct4.h30
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/decoder.c234
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/decoder.h52
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/encoder.c234
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/encoder.h47
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/huffman.c382
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/huffman.h35
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/huffman_consts.h528
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/rmlt.c133
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/rmlt.h30
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/libsiren/siren7.h30
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/ext/swfobject.h4
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/fix_purple.c70
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/fix_purple.h29
-rwxr-xr-x3.0/Compiler Source Files/msn-pecan-0.1.1/get-version11
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/cmd.c125
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/cmd.h44
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_cmd_server.c336
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_cmd_server.h41
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_dc_conn.c256
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_dc_conn.h39
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_http_server.c1108
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_http_server.h39
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_node.c720
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_node.h69
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_node_private.h102
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_parser.c226
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_parser.h39
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_ssl_conn.c442
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_ssl_conn.h43
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_stream.c270
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/io/pn_stream.h43
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/libpurple/xfer.c306
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/libpurple/xfer.h31
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/msn.c1959
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/nexus.c461
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/nexus.h48
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/notification.c1819
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/notification.h66
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/page.c85
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/page.h77
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pidgin-copyright505
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_buffer.c90
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_buffer.h39
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_dp_manager.c300
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_dp_manager.h33
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_error.c212
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_error.h34
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_global.h62
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_locale.h33
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_log.c180
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_log.c~179
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_log.h91
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_oim.c939
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_oim.h45
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_printf.c506
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_printf.h34
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_siren7.c156
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_siren7.h24
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_status.c298
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_status.h60
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_timer.h88
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_util.c974
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/pn_util.h74
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/ar.po827
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/da.po884
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/de.po922
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/eo.po809
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/es.po918
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/fi.po887
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/fr.po920
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/hu.po881
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/it.po919
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/nb.po860
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/nl.po868
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/pt.po882
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/pt_BR.po879
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/ru.po850
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/sr.po869
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/sv.po878
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/tr.po862
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/zh_CN.po847
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/po/zh_TW.po843
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/session.c569
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/session.h215
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/session_private.h110
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/switchboard.c1904
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/switchboard.h248
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/sync.c358
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/sync.h50
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/tests/.gitignore4
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/tests/Makefile87
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/tests/ab.c93
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/tests/buffer.c142
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/tests/cmd_parser.c61
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/tests/parser.c208
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/tests/printf.c90
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/tests/util.c141
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/win32/README37
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/win32/installer.nsi136
-rw-r--r--3.0/Compiler Source Files/msn-pecan-0.1.1/win32/resource.rc26
-rw-r--r--3.0/Compiler Source Files/pidgin-2.7.7/libpurple/blist.c6
-rw-r--r--3.0/Compiler Source Files/pidgin-2.7.7/libpurple/plugins/ssl/ssl-gnutls.c3
-rw-r--r--3.0/Compiler Source Files/pidgin-2.7.7/libpurple/protocols/jabber/roster.c4
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/COPYRIGHT5
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/Makefile30
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/arm/libjson-glib-1.0.sobin204759 -> 0 bytes
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/facebook.svg108
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/fb_blist.c27
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/fb_connection.c94
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/fb_messages.c7
-rwxr-xr-x[-rw-r--r--]3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/fb_util.c602
-rwxr-xr-x[-rw-r--r--]3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/fb_util.h78
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/i686/libjson-glib-1.0.sobin206236 -> 0 bytes
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/Makefile.am122
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-array.c714
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-builder.c683
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-builder.h106
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-debug.c38
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-debug.h46
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-enum-types.c.in39
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-enum-types.h32
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-enum-types.h.in30
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-gboxed.c354
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-generator.c870
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-generator.h107
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-glib.h45
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-glib.symbols165
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-gobject-private.h39
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-gobject.c918
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-gobject.h154
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-gvariant.c1301
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-gvariant.h46
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-marshal.list5
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-node.c800
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-object.c868
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-parser.c1425
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-parser.h173
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-reader.c932
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-reader.h142
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-scanner.c1861
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-scanner.h171
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-serializable.c231
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-types-private.h66
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-types.h334
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-version.h100
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/json-version.h.in100
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/Makefile.am54
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/array-test.c122
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/builder-test.c121
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/generator-test.c330
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/gvariant-test.c233
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/node-test.c112
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/object-test.c165
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/parser-test.c785
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/reader-test.c134
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/json-glib/tests/stream-load.json1
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/libfacebook.c6
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/libfacebook.sobin270376 -> 0 bytes
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/libfbxmpp.c584
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/libjson-glib-1.0.dllbin278906 -> 0 bytes
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/login.facebook.com.pem20
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/pidgin-facebookchat.rc28
-rw-r--r--3.0/Compiler Source Files/pidgin-facebookchat-1.6.9/rss.xml280
-rw-r--r--3.0/MessagingPlugins-Dist/com.palm.webosinternals.messaging_3.0.7_armv7.ipkbin0 -> 9625986 bytes
241 files changed, 91144 insertions, 16546 deletions
diff --git a/3.0/Compiler Source Files/Patches/imaccountvalidator-patches b/3.0/Compiler Source Files/Patches/imaccountvalidator-patches
new file mode 100644
index 0000000..1e492ac
--- a/dev/null
+++ b/3.0/Compiler Source Files/Patches/imaccountvalidator-patches
@@ -0,0 +1,33 @@
+diff -rupN imaccountvalidator-1.0//inc/IMAccountValidatorHandler.h imaccountvalidator-1.0-new//inc/IMAccountValidatorHandler.h
+--- imaccountvalidator-1.0//inc/IMAccountValidatorHandler.h 2011-03-25 10:35:57.680553000 -0600
++++ imaccountvalidator-1.0-new//inc/IMAccountValidatorHandler.h 2011-03-27 09:08:42.556552999 -0600
+@@ -40,6 +40,7 @@ class IMAccountValidatorApp;
+ #define PURPLE_ICQ "prpl-icq"
+ #define PURPLE_JABBER "prpl-jabber"
+ #define PURPLE_LIVE "prpl-msn"
++#define PURPLE_WLM "prpl-msn-pecan"
+ #define PURPLE_MYSPACE "prpl-myspace"
+ #define PURPLE_QQ "prpl-qq"
+ #define PURPLE_SAMETIME "prpl-meanwhile"
+@@ -55,6 +56,7 @@ class IMAccountValidatorApp;
+ #define TEMPLATE_ICQ "org.webosinternals.messaging.icq"
+ #define TEMPLATE_JABBER "org.webosinternals.messaging.jabber"
+ #define TEMPLATE_LIVE "org.webosinternals.messaging.live"
++#define TEMPLATE_WLM "org.webosinternals.messaging.wlm"
+ #define TEMPLATE_MYSPACE "org.webosinternals.messaging.myspace"
+ #define TEMPLATE_QQ "org.webosinternals.messaging.qq"
+ #define TEMPLATE_SAMETIME "org.webosinternals.messaging.sametime"
+diff -rupN imaccountvalidator-1.0//Makefile imaccountvalidator-1.0-new//Makefile
+--- imaccountvalidator-1.0//Makefile 2011-03-25 10:35:57.808553000 -0600
++++ imaccountvalidator-1.0-new//Makefile 2011-03-27 09:08:42.560553000 -0600
+@@ -7,7 +7,7 @@ LOCAL_INCLUDES := -I$(QPEDIR)/include/mo
+ -I./inc \
+ -I$(STAGING_INCDIR) \
+ -I.
+-LOCAL_CFLAGS := $(CFLAGS) -Wall -Werror -DMOJ_LINUX -DBOOST_NO_TYPEID $(LOCAL_INCLUDES) $(shell pkg-config --cflags glib-2.0 purple)
++LOCAL_CFLAGS := $(CFLAGS) -Wall -Werror -DMOJ_LINUX -DBOOST_NO_TYPEID $(LOCAL_INCLUDES) $(shell pkg-config --cflags glib-2.0 purple) -march=armv7-a
+ LOCAL_CPPFLAGS := $(CPPFLAGS) -fno-rtti
+
+-include Makefile.inc
+\ No newline at end of file
++include Makefile.inc
diff --git a/3.0/Compiler Source Files/Patches/imlibpurpleservice-1.0-patches b/3.0/Compiler Source Files/Patches/imlibpurpleservice-1.0-patches
new file mode 100644
index 0000000..1911bbe
--- a/dev/null
+++ b/3.0/Compiler Source Files/Patches/imlibpurpleservice-1.0-patches
@@ -0,0 +1,5813 @@
+diff -rupN imlibpurpleservice-1.0/inc/PalmImCommon.h imlibpurpleservice-1.0-new//inc/PalmImCommon.h
+--- imlibpurpleservice-1.0/inc/PalmImCommon.h 2011-03-25 10:45:40.648552999 -0600
++++ imlibpurpleservice-1.0-new//inc/PalmImCommon.h 2011-03-27 09:10:06.064552999 -0600
+@@ -40,6 +40,7 @@
+ #define SERVICENAME_ICQ "type_icq"
+ #define SERVICENAME_JABBER "type_jabber"
+ #define SERVICENAME_LIVE "type_live"
++#define SERVICENAME_WLM "type_wlm"
+ #define SERVICENAME_MYSPACE "type_myspace"
+ #define SERVICENAME_QQ "type_qq"
+ #define SERVICENAME_SAMETIME "type_sametime"
+@@ -57,6 +58,7 @@
+ #define CAPABILITY_ICQ "org.webosinternals.messaging.icq"
+ #define CAPABILITY_JABBER "org.webosinternals.messaging.jabber"
+ #define CAPABILITY_LIVE "org.webosinternals.messaging.live"
++#define CAPABILITY_WLM "org.webosinternals.messaging.wlm"
+ #define CAPABILITY_MYSPACE "org.webosinternals.messaging.myspace"
+ #define CAPABILITY_QQ "org.webosinternals.messaging.qq"
+ #define CAPABILITY_SAMETIME "org.webosinternals.messaging.sametime"
+diff -rupN imlibpurpleservice-1.0/Makefile imlibpurpleservice-1.0-new//Makefile
+--- imlibpurpleservice-1.0/Makefile 2011-03-25 10:45:40.672552999 -0600
++++ imlibpurpleservice-1.0-new//Makefile 2011-03-27 09:10:06.108553000 -0600
+@@ -7,7 +7,7 @@ LOCAL_INCLUDES := -I$(QPEDIR)/include/mo
+ -I./inc \
+ -I$(STAGING_INCDIR) \
+ -I.
+-LOCAL_CFLAGS := $(CFLAGS) -Wall -Werror -DMOJ_LINUX -DBOOST_NO_TYPEID $(LOCAL_INCLUDES) $(shell pkg-config --cflags glib-2.0 purple)
++LOCAL_CFLAGS := $(CFLAGS) -Wall -Werror -DMOJ_LINUX -DBOOST_NO_TYPEID $(LOCAL_INCLUDES) $(shell pkg-config --cflags glib-2.0 purple) -march=armv7-a
+ LOCAL_CPPFLAGS := $(CPPFLAGS) -fno-rtti
+
+-include Makefile.inc
+\ No newline at end of file
++include Makefile.inc
+diff -rupN imlibpurpleservice-1.0/Makefile.inc imlibpurpleservice-1.0-new//Makefile.inc
+--- imlibpurpleservice-1.0/Makefile.inc 2011-03-25 10:45:39.596552999 -0600
++++ imlibpurpleservice-1.0-new//Makefile.inc 2011-03-27 09:10:04.848552999 -0600
+@@ -1,5 +1,6 @@
++CXX = g++
+ LIBS := -llunaservice -lmojoluna -lmojodb -lmojocore -lpurple -lsanitize
+-LOCAL_LDFLAGS := $(LDFLAGS) $(LIBS) -Llib/armv6 -Llib/armv7 -Llib/x86 -Wl,-R/media/cryptofs/apps/usr/palm/applications/org.webosinternals.messaging/sysfiles/usr/lib -Wl,-Rlib/x86 -Wl,-Rlib/armv6 -Wl,-Rlib/armv7
++LOCAL_LDFLAGS := -Wl,-Rlib/armv7 -Llib/armv7 $(LDFLAGS) $(LIBS) -Wl,-R/media/cryptofs/apps/usr/palm/applications/org.webosinternals.messaging/sysfiles/usr/lib
+
+ IM_SOURCES := \
+ BuddyListConsolidator.cpp \
+@@ -27,7 +28,7 @@ IM_OBJECTS := $(IM_SOURCES:%.cpp=$(OBJDI
+ all: setup $(IM_TARGET)
+
+ $(IM_TARGET): $(IM_OBJECTS)
+- $(CXX) -o $(IM_TARGET) $(IM_OBJECTS) $(LOCAL_LDFLAGS)
++ $(CXX) -o $(IM_TARGET) $(IM_OBJECTS) $(LOCAL_LDFLAGS) lib/armv7/libstdc++.so.6
+
+ $(OBJDIR)/%.o: %.cpp
+ $(CXX) -MMD $(INCLUDES) $(LOCAL_CFLAGS) $(LOCAL_CPPFLAGS) -c $< -o $@
+diff -rupN imlibpurpleservice-1.0/src/IMLoginState.cpp imlibpurpleservice-1.0-new//src/IMLoginState.cpp
+--- imlibpurpleservice-1.0/src/IMLoginState.cpp 2011-03-25 10:45:40.740553000 -0600
++++ imlibpurpleservice-1.0-new//src/IMLoginState.cpp 2011-03-27 09:10:06.156553000 -0600
+@@ -1413,6 +1413,9 @@ void IMLoginSyncStateHandler::updateSync
+ else if (strcmp(serviceName, SERVICENAME_LIVE) == 0){
+ m_capabilityId.assign(CAPABILITY_LIVE);
+ }
++ else if (strcmp(serviceName, SERVICENAME_WLM) == 0){
++ m_capabilityId.assign(CAPABILITY_WLM);
++ }
+ else if (strcmp(serviceName, SERVICENAME_MYSPACE) == 0){
+ m_capabilityId.assign(CAPABILITY_MYSPACE);
+ }
+diff -rupN imlibpurpleservice-1.0/src/IMLoginState.cpp~ imlibpurpleservice-1.0-new//src/IMLoginState.cpp~
+--- imlibpurpleservice-1.0/src/IMLoginState.cpp~ 1969-12-31 18:00:00.000000000 -0600
++++ imlibpurpleservice-1.0-new//src/IMLoginState.cpp~ 2011-03-27 09:10:06.124553000 -0600
+@@ -0,0 +1,1538 @@
++/*
++ * IMLoginState.h
++ *
++ * Copyright 2010 Palm, Inc. All rights reserved.
++ *
++ * This program is free software and licensed under the terms of the GNU
++ * General Public License Version 2 as published by the Free
++ * Software Foundation;
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License,
++ * Version 2 along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-
++ * 1301, USA
++ *
++ * IMLibpurpleservice uses libpurple.so to implement a fully functional IM
++ * Transport service for use on a mobile device.
++ *
++ * IMLoginState class handles changes in user login status
++ */
++
++#include "IMLoginState.h"
++#include "db/MojDbQuery.h"
++#include "PalmImCommon.h"
++#include "IMDefines.h"
++#include "IMServiceApp.h"
++#include "ConnectionStateHandler.h"
++#include "DisplayController.h"
++#include "LibpurpleAdapter.h"
++
++/*
++ * IMLoginState
++ */
++IMLoginState::IMLoginState(MojService* service)
++: m_loginStateRevision(0),
++ m_signalHandler(NULL),
++ m_service(service)
++{
++ // Register for login callbacks
++ LibpurpleAdapter::assignIMLoginState(this);
++ // Register for connection changed callbacks.
++ ConnectionState::setLoginStateCallback(this);
++}
++
++IMLoginState::~IMLoginState()
++{
++ LibpurpleAdapter::assignIMLoginState(NULL);
++ ConnectionState::setLoginStateCallback(NULL);
++}
++
++void IMLoginState::handlerDone(IMLoginStateHandlerInterface* handler)
++{
++ // this gets called from "handler's" destructor
++ if (m_signalHandler != handler)
++ {
++ MojLogError(IMServiceApp::s_log, _T("handlerDone doesn't match. ours=%p, theirs=%p being destroyed"), m_signalHandler, handler);
++ }
++ else
++ {
++ m_signalHandler = NULL;
++ }
++}
++
++void IMLoginState::loginForTesting(MojServiceMessage* serviceMsg, const MojObject payload)
++{
++ m_signalHandler = new IMLoginStateHandler(m_service, m_loginStateRevision, this);
++ m_signalHandler->loginForTesting(serviceMsg, payload);
++}
++
++/*
++ * LoginState DB watch was triggered
++ */
++MojErr IMLoginState::handleLoginStateChange(MojServiceMessage* serviceMsg, const MojObject payload)
++{
++ // we should not be orphaning an existing handler...
++ if (NULL != m_signalHandler) {
++ MojLogError(IMServiceApp::s_log, _T("handleLoginStateChange: called when signal handler already existed."));
++ }
++
++ m_signalHandler = new IMLoginStateHandler(m_service, m_loginStateRevision, this);
++ return m_signalHandler->handleLoginStateChange(serviceMsg, payload);
++}
++
++/*
++ * Connection Manager subscription watch was triggered
++ */
++MojErr IMLoginState::handleConnectionChanged(const MojObject& payload)
++{
++ //TODO: this should be its own ConnectionChangedHandler
++ if (m_signalHandler == NULL)
++ {
++ MojLogWarning(IMServiceApp::s_log, _T("handleConnectionChanged needed to create a signal handler"));
++ m_signalHandler = new IMLoginStateHandler(m_service, m_loginStateRevision, this);
++ }
++ return m_signalHandler->handleConnectionChanged(payload);
++}
++
++/*
++ * Callback from libpurple when the account is signed on or off
++ */
++void IMLoginState::loginResult(const char* serviceName, const char* username, LoginResult type, bool loggedOut, const char* errCode, bool noRetry)
++{
++ if (m_signalHandler == NULL)
++ {
++ MojLogWarning(IMServiceApp::s_log, _T("loginResult needed to create a signal handler"));
++ m_signalHandler = new IMLoginStateHandler(m_service, m_loginStateRevision, this);
++ }
++ m_signalHandler->loginResult(serviceName, username, type, loggedOut, errCode, noRetry);
++}
++
++/*
++ * Callback from libpurple when buddy list is synced
++ */
++void IMLoginState::buddyListResult(const char* serviceName, const char* username, MojObject& buddyList, bool fullList)
++{
++ // fullList == false means it is just changes to an individual buddy
++ if (fullList == false)
++ {
++ MojLogError(IMServiceApp::s_log, _T("buddyListResult: fullList is false. Doing nothing."));
++ //TODO: Query com.palm.contact for this buddy to see if it needs to be updated (picture or name changed)
++ //TODO: Merge (with query) com.palm.imbuddystatus to update this buddy's availability and custom message
++ }
++ else
++ {
++ if (m_signalHandler == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("buddyListResult called but m_signalHandler == NULL. Doing nothing."));
++ }
++ else
++ {
++ m_signalHandler->fullBuddyListResult(serviceName, username, buddyList);
++ }
++ }
++}
++
++
++bool IMLoginState::getLoginStateData(const MojString& key, LoginStateData& state)
++{
++ bool found = (m_loginState.find(key) != m_loginState.end());
++ if (found)
++ {
++ state = m_loginState[key];
++ }
++ return found;
++}
++
++bool IMLoginState::getLoginStateData(const MojString& serviceName, const MojString& username, LoginStateData& state)
++{
++ std::map<MojString, LoginStateData>::iterator itr = m_loginState.begin();
++ bool found = false;
++ while (!found && itr != m_loginState.end())
++ {
++ state = itr->second;
++ found = (state.getServiceName() == serviceName && state.getUsername() == username);
++ if (!found)
++ {
++ itr++;
++ }
++ }
++
++ return found;
++}
++
++void IMLoginState::putLoginStateData(const MojString& key, LoginStateData& newState)
++{
++ m_loginState[key] = newState;
++}
++
++
++/*
++ * IMLoginStateHandler -- signal handler
++ */
++IMLoginStateHandler::IMLoginStateHandler(MojService* service, MojInt64 loginStateRevision, IMLoginState* loginStateController)
++: m_activityAdoptSlot(this, &IMLoginStateHandler::activityAdoptResult),
++ m_activityCompleteSlot(this, &IMLoginStateHandler::activityCompleteResult),
++ m_setWatchSlot(this, &IMLoginStateHandler::setWatchResult),
++ m_loginStateQuerySlot(this, &IMLoginStateHandler::loginStateQueryResult),
++ m_getCredentialsSlot(this, &IMLoginStateHandler::getCredentialsResult),
++ m_updateLoginStateSlot(this, &IMLoginStateHandler::updateLoginStateResult),
++ m_ignoreUpdateLoginStateSlot(this, &IMLoginStateHandler::ignoreUpdateLoginStateResult),
++ m_queryForContactsSlot(this, &IMLoginStateHandler::queryForContactsResult),
++ m_queryForBuddyStatusSlot(this, &IMLoginStateHandler::queryForBuddyStatusResult),
++ m_markBuddiesAsOfflineSlot(this, &IMLoginStateHandler::markBuddiesAsOfflineResult),
++ m_moveMessagesToPendingSlot(this, &IMLoginStateHandler::moveMessagesToPendingResult),
++ m_moveCommandsToPendingSlot(this, &IMLoginStateHandler::moveCommandsToPendingResult),
++ m_service(service),
++ m_dbClient(service, MojDbServiceDefs::ServiceName),
++ m_tempdbClient(service, MojDbServiceDefs::TempServiceName),
++ m_loginStateController(loginStateController),
++ m_activityId(-1),
++ m_workingLoginStateRev(loginStateRevision),
++ m_buddyListConsolidator(NULL)
++{
++}
++
++
++IMLoginStateHandler::~IMLoginStateHandler()
++{
++ // Tell IMLoginState that this handler is destroyed
++ m_loginStateController->handlerDone(this);
++}
++
++void IMLoginStateHandler::loginForTesting(MojServiceMessage* serviceMsg, const MojObject payload)
++{
++ LoginParams loginParams;
++
++ // your username and password go here!
++ loginParams.username = "xxx@aol.com";
++ loginParams.password = "xxx";
++ loginParams.serviceName = "type_aim";
++
++ loginParams.availability = PalmAvailability::ONLINE;
++ loginParams.customMessage = "";
++ loginParams.connectionType = "wan";
++ loginParams.localIpAddress = NULL;
++ loginParams.accountId = "";
++ LibpurpleAdapter::login(&loginParams, m_loginStateController);
++ serviceMsg->replySuccess();
++}
++/*
++ * This is an entry point into the IMLoginStateHandler machine. This is triggered when the db changes.
++ */
++MojErr IMLoginStateHandler::handleLoginStateChange(MojServiceMessage* serviceMsg, const MojObject payload)
++{
++ MojLogTrace(IMServiceApp::s_log);
++
++ m_activityId = -1;
++ // get the $activity object
++ MojObject activityObj;
++ bool found = payload.get("$activity", activityObj);
++ if (found)
++ {
++ found = activityObj.get("activityId", m_activityId);
++ }
++
++ if (!found)
++ {
++ MojLogError(IMServiceApp::s_log, _T("handleLoginStateChange parameter has no activityId"));
++ //TODO No activity, so query the activity manager to see if we've got one. If not, create/start one.
++ // For now, just do the query
++ queryLoginState();
++ }
++ else
++ {
++ adoptActivity();
++ }
++
++ serviceMsg->replySuccess();
++ return MojErrNone;
++}
++
++/*
++ * This is an entry point into the IMLoginStateHandler machine. This is triggered by connection changes
++ */
++MojErr IMLoginStateHandler::handleConnectionChanged(const MojObject payload)
++{
++ MojLogTrace(IMServiceApp::s_log);
++ MojErr err;
++ // Things to check:
++ // + no connection - ensure all logged out
++ // + specific interface dropped - ensure all accounts on that interface are logged off
++ // + wifi is available - log out any accounts on wan so they switch to wifi
++
++ // If there's not internet connection, set all records to offline
++ bool wanConnected = ConnectionState::wanConnected();
++ bool wifiConnected = ConnectionState::wifiConnected();
++ if (!ConnectionState::hasInternetConnection())
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("handleConnectionChanged no connection. setting all accounts offline"));
++ MojDbQuery query; // intentionally empty query since we want all records changed
++ query.from(IM_LOGINSTATE_KIND);
++ MojObject mergeProps;
++ mergeProps.putString("state", LOGIN_STATE_OFFLINE);
++ mergeProps.putString("ipAddress", "");
++ err = m_dbClient.merge(m_ignoreUpdateLoginStateSlot, query, mergeProps);
++
++ // Mark all buddies so they look offline to us
++ MojString empty;
++ empty.assign("");
++ markBuddiesAsOffline(empty);
++
++ // Also tell libpurple to disconnect
++ LibpurpleAdapter::deviceConnectionClosed(true, NULL);
++ }
++ // Two reasons for disconnecting from WAN: it went down or WiFi came up.
++ // WiFi connection is preferred over WAN
++ else if (wanConnected == false || (wanConnected == true && wifiConnected == true))
++ {
++ //NOTE don't need to mark the buddies as offline since we're just switching to WiFi
++
++ MojLogInfo(IMServiceApp::s_log, _T("handleConnectionChanged wan off, but wifi on. setting all wan accounts offline"));
++ MojDbQuery query;
++ query.where("ipAddress", MojDbQuery::OpEq, ConnectionState::wanIpAddress());
++ query.from(IM_LOGINSTATE_KIND);
++ MojObject mergeProps;
++ mergeProps.putString("state", LOGIN_STATE_OFFLINE);
++ mergeProps.putString("ipAddress", "");
++ err = m_dbClient.merge(m_ignoreUpdateLoginStateSlot, query, mergeProps);
++
++ // Also tell libpurple to disconnect
++ LibpurpleAdapter::deviceConnectionClosed(false, ConnectionState::wanIpAddress());
++ }
++ else if (wifiConnected == false)
++ {
++ //NOTE don't need to mark the buddies as offline since we're just switching to WAN
++
++ MojLogInfo(IMServiceApp::s_log, _T("handleConnectionChanged wifi off, setting them offline"));
++ MojDbQuery query;
++ query.where("ipAddress", MojDbQuery::OpEq, ConnectionState::wifiIpAddress());
++ query.from(IM_LOGINSTATE_KIND);
++ MojObject mergeProps;
++ mergeProps.putString("state", LOGIN_STATE_OFFLINE);
++ mergeProps.putString("ipAddress", "");
++ err = m_dbClient.merge(m_ignoreUpdateLoginStateSlot, query, mergeProps);
++
++ // Also tell libpurple to disconnect
++ LibpurpleAdapter::deviceConnectionClosed(false, ConnectionState::wifiIpAddress());
++ }
++
++ return MojErrNone;
++}
++
++MojErr IMLoginStateHandler::activityAdoptResult(MojObject& payload, MojErr resultErr)
++{
++ MojLogTrace(IMServiceApp::s_log);
++
++ if (resultErr != MojErrNone)
++ {
++ // adopt failed
++ MojString error;
++ MojErrToString(resultErr, error);
++ MojLogError(IMServiceApp::s_log, _T("activity manager adopt FAILED. error %d - %s"), resultErr, error.data());
++ }
++ else
++ {
++ //IMServiceHandler::logMojObjectJsonString(_T("activityAdoptResult payload: %s"), payload);
++ bool adopted = false;
++ payload.get("adopted", adopted); //TODO: check for "orphan"?
++
++ // we currently get 2 notifications from the adopt call. ignore the first one
++ if (adopted)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("Activity adopted. Querying Login State"));
++ queryLoginState();
++ }
++ }
++
++ return MojErrNone;
++}
++
++MojErr IMLoginStateHandler::activityCompleteResult(MojObject& payload, MojErr resultErr)
++{
++ //TODO: log if there are errors
++ m_activityAdoptSlot.cancel();
++ //setWatch();
++ return MojErrNone;
++}
++
++MojErr IMLoginStateHandler::setWatchResult(MojObject& payload, MojErr resultErr)
++{
++ //TODO: log if there are errors
++ return MojErrNone;
++}
++
++MojErr IMLoginStateHandler::loginStateQueryResult(MojObject& payload, MojErr resultErr)
++{
++ MojLogTrace(IMServiceApp::s_log);
++
++ if (resultErr != MojErrNone)
++ {
++ MojString error;
++ MojErrToString(resultErr, error);
++ MojLogError(IMServiceApp::s_log, _T("loginStateQuery failed. error %d - %s"), resultErr, error.data());
++ }
++ else
++ {
++ IMServiceHandler::logMojObjectJsonString(_T("loginStateQuery success: %s"), payload);
++ // result is in the form {results:[{_id, _rev, username, serviceName, state, availability, customMessage}]}
++ MojObject results;
++ payload.get("results", results);
++
++ processLoginStates(results);
++ }
++
++ return MojErrNone;
++}
++
++
++MojErr IMLoginStateHandler::updateLoginStateResult(MojObject& payload, MojErr resultErr)
++{
++ // DB state is updated so complete the activity and reset the watch so it
++ // can fire again and start the next state transition
++ completeAndResetWatch();
++ return MojErrNone;
++}
++
++MojErr IMLoginStateHandler::ignoreUpdateLoginStateResult(MojObject& payload, MojErr resultErr)
++{
++ // This is for intermediate changes to login state (like "logging in") so the result
++ // is not actionable.
++ if (resultErr != MojErrNone)
++ {
++ MojString error;
++ MojErrToString(resultErr, error);
++ MojLogError(IMServiceApp::s_log, _T("ERROR Intermediate UpdateLoginState. error %d - %s"), resultErr, error.data());
++ }
++ return MojErrNone;
++}
++
++// Set the state & availability to offline and set errorCode
++// dbmerge will then complete and reset the activity
++MojErr IMLoginStateHandler::handleBadCredentials(const MojString& serviceName, const MojString& username, const char* err)
++{
++ MojDbQuery query;
++ query.where("serviceName", MojDbQuery::OpEq, serviceName);
++ query.where("username", MojDbQuery::OpEq, username);
++ query.from(IM_LOGINSTATE_KIND);
++ MojObject mergeProps;
++ mergeProps.putInt("availability", PalmAvailability::OFFLINE);
++ mergeProps.putString("state", LOGIN_STATE_OFFLINE);
++ mergeProps.putString("ipAddress", "");
++ if (err != NULL)
++ {
++ mergeProps.putString("errorCode", err);
++ }
++ m_dbClient.merge(m_updateLoginStateSlot, query, mergeProps);
++
++ // update the syncState record for this account so account dashboard can display errors
++ // first we need to find our account id
++ LoginStateData state;
++ MojString accountId, service, user;
++ service.assign(serviceName);
++ user.assign(username);
++ bool found = m_loginStateController->getLoginStateData(service, user, state);
++ if (found) {
++ accountId = state.getAccountId();
++ MojRefCountedPtr<IMLoginSyncStateHandler> syncStateHandler(new IMLoginSyncStateHandler(m_service));
++ syncStateHandler->updateSyncStateRecord(serviceName, accountId, LoginCallbackInterface::LOGIN_FAILED, err);
++ }
++ else {
++ MojLogError(IMServiceApp::s_log, _T("loginResult: could not find account Id in cached login states map. No syncState record created."));
++ // can we do anything here??
++ }
++
++ return MojErrNone;
++}
++
++MojErr IMLoginStateHandler::getCredentialsResult(MojObject& payload, MojErr resultErr)
++{
++ MojLogError(IMServiceApp::s_log, _T("getCredentialsResult: wanConnected:%d wifiConnected:%d"),ConnectionState::wanConnected(),ConnectionState::wifiConnected());
++ if (resultErr != MojErrNone)
++ {
++ // Failed to get credentials, so log the issue and set the state to offlnie
++ MojString serviceName = m_workingLoginState.getServiceName();
++ MojString username = m_workingLoginState.getUsername();
++
++ MojString error;
++ MojErrToString(resultErr, error);
++ MojLogError(IMServiceApp::s_log, _T("Failed to get credentials on %s. error %d - %s"), serviceName.data(), resultErr, error.data());
++
++ handleBadCredentials(serviceName, username, ERROR_BAD_PASSWORD);
++ }
++ // About to login so this is the time to make a final check for internet connection
++ else if (!ConnectionState::hasInternetConnection())
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("No internet connection available!"));
++ // No internet so mark this activity complete and reset the watch
++ // which will fire next time there's a stable connection
++ completeAndResetWatch();
++ }
++ else
++ {
++ //TODO: get rid of m_workingLoginState: what in the credentials response can tie it back to its corresponding m_loginState?
++
++ // Now get the login params and request login
++ MojObject credentials;
++ MojErr err = payload.getRequired("credentials", credentials);
++
++ LoginParams loginParams;
++ MojString password;
++ err = credentials.getRequired("password", password);
++ if (password.empty())
++ {
++ MojLogError(IMServiceApp::s_log, _T("Password is empty. I think this is not ok."));
++ }
++
++ MojString serviceName = m_workingLoginState.getServiceName();
++ MojString username = m_workingLoginState.getUsername();
++
++ MojString connectionType, localIpAddress;
++ ConnectionState::getBestConnection(connectionType, localIpAddress);
++ // We're about to log in, so set the state to "logging in"
++ updateLoginStateNoResponse(serviceName, username, LOGIN_STATE_LOGGING_ON, localIpAddress.data());
++
++ loginParams.password = password.data();
++ loginParams.accountId = m_workingLoginState.getAccountId().data();
++ loginParams.username = username.data();
++ loginParams.serviceName = serviceName.data();
++ loginParams.availability = m_workingLoginState.getAvailability();
++ loginParams.customMessage = m_workingLoginState.getCustomMessage();
++ loginParams.connectionType = connectionType.data();
++ loginParams.localIpAddress = localIpAddress.data();
++
++ // Login may be asynchronous with the result callback in loginResult().
++ // Also deal with immediate results
++ LibpurpleAdapter::LoginResult result;
++ result = LibpurpleAdapter::login(&loginParams, m_loginStateController);
++ if (result == LibpurpleAdapter::FAILED)
++ {
++ handleBadCredentials(serviceName, username, ERROR_GENERIC_ERROR);
++ }
++ if (result == LibpurpleAdapter::INVALID_CREDENTIALS)
++ {
++ handleBadCredentials(serviceName, username, ERROR_AUTHENTICATION_FAILED);
++ }
++ else if (result == LibpurpleAdapter::ALREADY_LOGGED_IN)
++ {
++ MojDbQuery query;
++ query.where("serviceName", MojDbQuery::OpEq, serviceName);
++ query.where("username", MojDbQuery::OpEq, username);
++ query.from(IM_LOGINSTATE_KIND);
++ MojObject mergeProps;
++ mergeProps.putString("state", LOGIN_STATE_ONLINE);
++ mergeProps.putString("ipAddress", localIpAddress);
++ m_dbClient.merge(m_updateLoginStateSlot, query, mergeProps);
++
++ // update any imcommands that are in the "waiting-for-connection" status
++ moveWaitingCommandsToPending();
++ }
++ }
++ return MojErrNone;
++}
++
++
++MojErr IMLoginStateHandler::queryForContactsResult(MojObject& payload, MojErr resultErr)
++{
++ if (m_buddyListConsolidator == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("m_buddyListConsolidator is null on contact response"));
++ //TODO what to do here???
++ }
++ else
++ {
++ MojObject contactArray;
++ payload.getRequired("results", contactArray);
++ m_buddyListConsolidator->setContacts(contactArray);
++ if (m_buddyListConsolidator->isAllDataSet())
++ {
++ consolidateAllBuddyLists();
++ }
++ }
++ return MojErrNone;
++}
++
++
++MojErr IMLoginStateHandler::queryForBuddyStatusResult(MojObject& payload, MojErr resultErr)
++{
++ if (m_buddyListConsolidator == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("m_buddyListConsolidator is null on buddystatus response"));
++ //TODO what to do here???
++ }
++ else
++ {
++ MojObject buddyArray;
++ payload.getRequired("results", buddyArray);
++ m_buddyListConsolidator->setBuddyStatus(buddyArray);
++ if (m_buddyListConsolidator->isAllDataSet())
++ {
++ consolidateAllBuddyLists();
++ }
++ }
++ return MojErrNone;
++}
++
++
++MojErr IMLoginStateHandler::markBuddiesAsOffline(const MojString& serviceName, const MojString& username)
++{
++ MojErr err = MojErrNotFound;
++ LoginStateData state;
++ bool found = m_loginStateController->getLoginStateData(serviceName, username, state);
++ if (found)
++ {
++ err = markBuddiesAsOffline(state.getAccountId());
++ }
++ return err;
++}
++
++
++MojErr IMLoginStateHandler::markBuddiesAsOffline(const MojString& accountId)
++{
++ //MojLogInfo(IMServiceApp::s_log, _T("IMLoginStateHandler::markBuddiesAsOffline account=%s"), accountId.data());
++ // All buddies for this account get marked as offline
++ MojDbQuery query;
++ if (accountId.length() > 0)
++ {
++ query.where("accountId", MojDbQuery::OpEq, accountId);
++ }
++ else
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("markBuddiesAsOffline: no accountId - marking all buddies offline"));
++ }
++
++ query.from(IM_BUDDYSTATUS_KIND);
++ MojObject mergeProps;
++ mergeProps.putInt("availability", PalmAvailability::OFFLINE);
++ mergeProps.putString("status", "");
++ return m_tempdbClient.merge(m_markBuddiesAsOfflineSlot, query, mergeProps);
++}
++
++
++MojErr IMLoginStateHandler::markBuddiesAsOfflineResult(MojObject& payload, MojErr resultErr)
++{
++ // TODO: anything to do here???
++ return MojErrNone;
++}
++
++
++MojErr IMLoginStateHandler::moveWaitingMessagesToPending(const MojString& serviceName, const MojString& username)
++{
++ //MojLogInfo(IMServiceApp::s_log, _T("IMLoginStateHandler::moveWaitingMessagesToPending"));
++ MojDbQuery query;
++ MojString folderName, statusName;
++ folderName.assign(IMMessage::folderStrings[Outbox]);
++ query.where(MOJDB_FOLDER, MojDbQuery::OpEq, folderName);
++ statusName.assign(IMMessage::statusStrings[WaitingForConnection]);
++ query.where(MOJDB_STATUS, MojDbQuery::OpEq, statusName);
++
++ query.from(IM_IMMESSAGE_KIND);
++ MojObject mergeProps;
++ mergeProps.putString(MOJDB_STATUS, IMMessage::statusStrings[Pending]);
++ return m_dbClient.merge(m_moveMessagesToPendingSlot, query, mergeProps);
++}
++
++
++MojErr IMLoginStateHandler::moveMessagesToPendingResult(MojObject& payload, MojErr resultErr)
++{
++ return MojErrNone;
++}
++
++/*
++ * Find any imcommands that were left in the "waiting-for-connection" status and make them pending now that we are back online
++ */
++MojErr IMLoginStateHandler::moveWaitingCommandsToPending()
++{
++ MojLogInfo(IMServiceApp::s_log, _T("IMLoginStateHandler::moveWaitingCommandsToPending"));
++ MojDbQuery query;
++ MojString statusName;
++ statusName.assign(IMMessage::statusStrings[WaitingForConnection]);
++ query.where(MOJDB_STATUS, MojDbQuery::OpEq, statusName);
++
++ query.from(IM_IMCOMMAND_KIND);
++ MojObject mergeProps;
++ mergeProps.putString(MOJDB_STATUS, IMMessage::statusStrings[Pending]);
++ return m_dbClient.merge(m_moveCommandsToPendingSlot, query, mergeProps);
++}
++
++
++MojErr IMLoginStateHandler::moveCommandsToPendingResult(MojObject& payload, MojErr resultErr)
++{
++ return MojErrNone;
++}
++
++
++MojErr IMLoginStateHandler::queryLoginState()
++{
++ MojRefCountedPtr<MojServiceRequest> req;
++ MojErr err = m_service->createRequest(req);
++ if (err)
++ {
++ MojLogError(IMServiceApp::s_log, _T("IMLoginStateHandler: create activity manager adopt request failed"));
++ }
++ else
++ {
++ MojDbQuery query;
++ getLoginStateQuery(query);
++ err = m_dbClient.find(m_loginStateQuerySlot, query, false, false);
++ if (err)
++ {
++ MojLogError(IMServiceApp::s_log, _T("handleLoginStateChange query failed"));
++ }
++ }
++
++ return err;
++}
++
++
++void IMLoginStateHandler::logLoginStates(const LoginStateData& cachedState, const LoginStateData& newState)
++{
++ MojString stateString;
++ cachedState.toString(stateString);
++ //MojLogInfo(IMServiceApp::s_log, _T("cachedState: %s"), stateString.data());
++ newState.toString(stateString);
++ //MojLogInfo(IMServiceApp::s_log, _T("newState: %s"), stateString.data());
++}
++
++
++// resultArray should be an array of db objects of the form
++// {_id, _rev, username, serviceName, state, availability, customMessage}
++MojErr IMLoginStateHandler::processLoginStates(MojObject& loginStateArray)
++{
++ MojErr err = MojErrNone;
++
++ MojObject loginStateEntry;
++ MojObject::ConstArrayIterator loginStateItr = loginStateArray.arrayBegin();
++ // This shouldn't happen, but check if there's nothing to do.
++ if (loginStateItr == loginStateArray.arrayEnd())
++ {
++ MojLogError(IMServiceApp::s_log, _T("processLoginStates had empty result set"));
++ completeAndResetWatch();
++ }
++ else
++ {
++ // Just grab the first change and deal with it. If other records have changed, we'll
++ // pick that up when the watch is reset. This works because the query is on _rev in
++ // ascending order.
++ loginStateEntry = *loginStateItr;
++ LoginStateData newState;
++ newState.assignFromDbRecord(loginStateEntry);
++
++ // Update the revision number so this change is removed from the next query.
++ m_loginStateController->setLoginStateRevision(newState.getRevision());
++ //m_loginStateRevision = newState.getRevision();
++
++ LoginStateData cachedState;
++ bool found = m_loginStateController->getLoginStateData(newState.getKey(), cachedState);
++ if (!found)
++ {
++ // Key not found, so assign the new state to the cached state and
++ // let it proceed as normal for login (most likely) or logout
++ cachedState = newState;
++ }
++
++ logLoginStates(cachedState, newState);
++ m_workingLoginState = newState;
++ if (newState.needsToLogin(cachedState))
++ {
++ // Get account info & password then start the login process.
++ bool found = false;
++ MojString accountId;
++ err = loginStateEntry.get("accountId", accountId, found);
++ if (err != MojErrNone || !found)
++ {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("ERROR accountId missing from imloginstate. error %d - %s"), err, error.data());
++ // TODO: fail the record and ...
++ completeAndResetWatch();
++ }
++ else
++ {
++ MojRefCountedPtr<MojServiceRequest> req;
++ err = m_service->createRequest(req);
++ MojObject params;
++ err = params.put("accountId", accountId);
++ err = params.putString("name", "common");
++ err = req->send(m_getCredentialsSlot, "com.palm.service.accounts","readCredentials", params, 1);
++ }
++ }
++ else if (newState.needsToLogoff(cachedState))
++ {
++ // Now log out. The result will come in loginResult() with type=SIGNED_OFF
++ if (LibpurpleAdapter::logout(newState.getServiceName(), newState.getUsername(), m_loginStateController))
++ {
++ // Update state to show we're in the process of logging off
++ updateLoginStateNoResponse(newState.getServiceName(), newState.getUsername(), LOGIN_STATE_LOGGING_OFF, NULL);
++ }
++ else
++ {
++ // Logout returns false if the account is already logged out.
++ // TODO: update newState to offline to reflect the changed state
++ updateLoginStateNoResponse(newState.getServiceName(), newState.getUsername(), LOGIN_STATE_OFFLINE, NULL);
++ // Make the buddies for this account look offline to us
++ markBuddiesAsOffline(newState.getAccountId());
++ completeAndResetWatch();
++ }
++ }
++ else if (newState.needsToGetBuddies(cachedState))
++ {
++ getBuddyLists(newState.getServiceName(), newState.getUsername(), newState.getAccountId());
++ }
++ else if (newState.hasAvailabilityChanged(cachedState) || newState.hasCustomMessageChanged(cachedState))
++ {
++ if (newState.hasAvailabilityChanged(cachedState))
++ {
++ LibpurpleAdapter::setMyAvailability(newState.getServiceName().data(), newState.getUsername().data(), newState.getAvailability());
++ }
++
++ if (newState.hasCustomMessageChanged(cachedState))
++ {
++ LibpurpleAdapter::setMyCustomMessage(newState.getServiceName().data(), newState.getUsername().data(), newState.getCustomMessage().data());
++ }
++
++ completeAndResetWatch();
++ }
++ else
++ {
++ // Nothing needed to be done so just reset the watch.
++ completeAndResetWatch();
++ }
++
++ // Finally, add or update the cached entry to reflect whatever changed
++ m_loginStateController->putLoginStateData(newState.getKey(), newState);
++ }
++ return err;
++}
++
++
++// This fires off 3 asynchronous requests with the responses being stored in m_buddyListConsolidator
++// So whichever of the 3 returns last will continue the buddy list processing
++MojErr IMLoginStateHandler::getBuddyLists(const MojString& serviceName, const MojString& username, const MojString& accountId)
++{
++ m_buddyListConsolidator = new BuddyListConsolidator(m_service, accountId);
++
++ // Get a full list of buddies. The result is asynchronously returned via the buddyListResult() callback
++ bool ok = LibpurpleAdapter::getFullBuddyList(serviceName, username);
++ if (ok)
++ {
++ //TODO move these two slots into BuddyListConsolidator??
++ MojErr err;
++
++ // Get contacts that are buddies of this user
++ MojDbQuery contactsQuery;
++ contactsQuery.from(IM_CONTACT_KIND);
++ contactsQuery.where("accountId", MojDbQuery::OpEq, accountId);
++ err = m_dbClient.find(m_queryForContactsSlot, contactsQuery, false, false);
++ if (err)
++ MojLogError(IMServiceApp::s_log, _T("getBuddyLists: query for contacts returned err: %d"),err);
++
++ // Get buddies of this user
++ MojDbQuery buddiesQuery;
++ buddiesQuery.from(IM_BUDDYSTATUS_KIND);
++ buddiesQuery.where("accountId", MojDbQuery::OpEq, accountId);
++ err = m_tempdbClient.find(m_queryForBuddyStatusSlot, buddiesQuery, false, false);
++ if (err)
++ MojLogError(IMServiceApp::s_log, _T("getBuddyLists: query for buddies returned err: %d"),err);
++ }
++ else
++ {
++ //TODO: set the state to offline?? Perhaps loginstate needs a retry count.
++ MojLogError(IMServiceApp::s_log, _T("getBuddyLists: getFullBuddyList return false. This is not good"));
++
++ delete m_buddyListConsolidator;
++ m_buddyListConsolidator = NULL;
++
++
++ // Since it failed, we need to reset the watch ourself.
++ completeAndResetWatch();
++ }
++
++ return MojErrNone;
++}
++
++MojErr IMLoginStateHandler::consolidateAllBuddyLists()
++{
++ if (m_buddyListConsolidator == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("m_buddyListConsolidator is null in consolidateBuddyLists(). This shouldn't be possible"));
++ //TODO what to do here???
++ }
++ else
++ {
++ m_buddyListConsolidator->consolidateContacts();
++ m_buddyListConsolidator->consolidateBuddyStatus();
++ // The consolidator cleans itself up. Good kitty.
++ m_buddyListConsolidator = NULL;
++ }
++
++ // The user is online at this point despite any errors in buddy consolidation
++
++ // update any imcommands that are in the "waiting-for-connection" status
++ moveWaitingCommandsToPending();
++
++ MojString serviceName = m_workingLoginState.getServiceName();
++ MojString username = m_workingLoginState.getUsername();
++ MojDbQuery query;
++ query.where("serviceName", MojDbQuery::OpEq, serviceName);
++ query.where("username", MojDbQuery::OpEq, username);
++ query.from(IM_LOGINSTATE_KIND);
++ MojObject mergeProps;
++ mergeProps.putString("state", LOGIN_STATE_ONLINE);
++ return m_dbClient.merge(m_updateLoginStateSlot, query, mergeProps);
++}
++
++MojErr IMLoginStateHandler::adoptActivity()
++{
++ MojLogInfo(IMServiceApp::s_log, _T("Adopting activityId: %llu."), m_activityId);
++ MojErr err = MojErrNone;
++ if (m_activityId > 0)
++ {
++ MojRefCountedPtr<MojServiceRequest> req;
++ MojErr err = m_service->createRequest(req);
++ if (err)
++ {
++ MojLogError(IMServiceApp::s_log, _T("IMLoginStateHandler: create activity manager adopt request failed"));
++ }
++ else
++ {
++ MojObject adoptParams;
++ adoptParams.put(_T("activityId"), m_activityId);
++ adoptParams.putBool(_T("subscribe"), true);
++
++
++
++ MojLogInfo(IMServiceApp::s_log, _T("handleLoginStateChange adopting activity id: %llu."), m_activityId);
++ err = req->send(m_activityAdoptSlot, "com.palm.activitymanager", "adopt", adoptParams, MojServiceRequest::Unlimited);
++ if (err)
++ {
++ MojLogError(IMServiceApp::s_log, _T("IMLoginStateHandler::adoptActivity send activity manager adopt request failed"));
++ }
++ }
++ }
++
++ return err;
++}
++
++
++MojErr IMLoginStateHandler::completeAndResetWatch()
++{
++ //MojLogInfo(IMServiceApp::s_log, _T("Completing activityId: %llu."), m_activityId);
++ MojErr err = MojErrNone;
++
++ // If there is an active activity, mark it as completed with the "restart" flag
++ if (m_activityId > 0)
++ {
++ MojRefCountedPtr<MojServiceRequest> completeReq;
++ MojErr err = m_service->createRequest(completeReq);
++ MojErrCheck(err);
++ MojObject completeParams;
++ completeParams.put(_T("activityId"), m_activityId);
++ completeParams.put(_T("restart"), true); // Important, we need the activity to restart
++
++ // Build out the actvity's trigger
++ const char* triggerJson =
++ "{\"key\":\"fired\","
++ "\"method\":\"palm://com.palm.db/watch\","
++ "}";
++ MojObject trigger;
++ trigger.fromJson(triggerJson);
++ MojDbQuery dbQuery;
++ getLoginStateQuery(dbQuery);
++ MojObject queryDetails;
++ dbQuery.toObject(queryDetails);
++ MojObject query;
++ query.put("query", queryDetails);
++ trigger.put("params", query);
++
++ completeParams.put("trigger", trigger);
++
++ err = completeReq->send(m_activityCompleteSlot, "com.palm.activitymanager", "complete", completeParams, 1);
++ MojErrCheck(err);
++
++ m_activityId = -1; // clear this out so we don't keep using it.
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("completeAndResetWatch - no activity id to complete"));
++// setWatch();
++ }
++
++ return err;
++}
++
++/*
++ * NOT USED
++ */
++//MojErr IMLoginStateHandler::setWatch()
++//{
++// MojLogError(IMServiceApp::s_log, _T("setWatch 1"));
++// MojLogInfo(IMServiceApp::s_log, _T("Starting new loginstate watch"));
++// MojErr err = MojErrNone;
++//
++// // Reset the activity watch
++// MojRefCountedPtr<MojServiceRequest> watchReq;
++// err = m_service->createRequest(watchReq);
++// MojErrCheck(err);
++// MojLogDebug(IMServiceApp::s_log, _T("com.palm.activitymanager/create"));
++//
++// MojLogError(IMServiceApp::s_log, _T("setWatch 2"));
++// // A lot of the activity object is static data, so fill that in using hardcoded strings, then add the "trigger"
++// // Properties within the "activity" object
++//
++// // Build out the actvity's trigger
++// const char* triggerJson =
++// "{\"key\":\"fired\","
++// "\"method\":\"palm://com.palm.db/watch\","
++// "}";
++// MojObject trigger;
++// trigger.fromJson(triggerJson);
++// MojDbQuery dbQuery;
++// getLoginStateQuery(dbQuery);
++// MojObject queryDetails;
++// dbQuery.toObject(queryDetails);
++// MojObject query;
++// query.put("query", queryDetails);
++// trigger.put("params", query);
++//
++// // Build out the activity
++// const char* activityJson =
++// "{\"name\":\"Libpurple loginstate\","
++// "\"description\":\"Watch for changes to the imloginstate\","
++// "\"type\": {\"foreground\": true},"
++// "\"callback\":{\"method\":\"palm://com.palm.imlibpurple/loginStateChanged\"}"
++// "}";
++// MojObject activity;
++// activity.fromJson(activityJson);
++// activity.put("trigger", trigger);
++//
++// // requirements for internet:true
++// MojObject requirements;
++// err = requirements.put("internet", true);
++// MojErrCheck(err);
++// err = activity.put("requirements", requirements);
++// MojErrCheck(err);
++//
++// MojObject activityCreateParams;
++// activityCreateParams.putBool("start", true);
++// activityCreateParams.put("activity", activity);
++// //TODO should this be Unlimited or just one-off???
++// err = watchReq->send(m_setWatchSlot, "com.palm.activitymanager", "create", activityCreateParams, MojServiceRequest::Unlimited);
++// MojErrCheck(err);
++//
++// return err;
++//}
++
++
++// This is a callback from the LibpurpleAdapter for notification of login events (success, failed, disconnected)
++void IMLoginStateHandler::loginResult(const char* serviceName, const char* username, LoginCallbackInterface::LoginResult type, bool loggedOut, const char* errCode, bool noRetry)
++{
++ MojErr err = MojErrNone;
++ MojLogInfo(IMServiceApp::s_log, _T("loginResult: user %s, service %s, result=%d, code=%s, loggedOut=%d, noRetry=%d."), username, serviceName, type, errCode, loggedOut, noRetry);
++
++ MojString serviceNameMoj, usernameMoj, errorCodeMoj;
++ if (errCode == NULL)
++ {
++ // If no error code was specified, assume no error
++ errorCodeMoj.assign(ERROR_NO_ERROR);
++ }
++ else
++ {
++ errorCodeMoj.assign(errCode);
++ }
++
++ // Want to merge the new state and errorCode values for the given username and serviceName
++ MojDbQuery query;
++ serviceNameMoj.assign(serviceName);
++ usernameMoj.assign(username);
++ query.where("serviceName", MojDbQuery::OpEq, serviceNameMoj);
++ query.where("username", MojDbQuery::OpEq, usernameMoj);
++ query.from(IM_LOGINSTATE_KIND);
++
++ MojObject mergeProps;
++ if (noRetry)
++ {
++ if (type == LoginCallbackInterface::LOGIN_SUCCESS)
++ {
++ MojLogWarning(IMServiceApp::s_log, _T("loginResult WARNING: ignoring noRetry==true because type==login_success"));
++ }
++ else
++ {
++ mergeProps.putInt("availability", PalmAvailability::OFFLINE);
++ }
++ }
++
++ // Special case to handle getting logged off because of lost network connection.
++ // There's a race condition where the login fails before the network manager
++ // admits the connection is lost so the following prevents it from immediately
++ // logging off which then triggers the db watch, causing a login to start and
++ // quickly get stopped.
++ if (type == LoginCallbackInterface::LOGIN_FAILED && noRetry == false)
++ {
++ IMLoginFailRetryHandler* handler = new IMLoginFailRetryHandler(m_service);
++ mergeProps.putString("state", LOGIN_STATE_OFFLINE);
++ mergeProps.put("errorCode", errorCodeMoj);
++ handler->startTimerActivity(serviceNameMoj, query, mergeProps);
++ }
++ else
++ {
++ switch (type)
++ {
++ case LoginCallbackInterface::LOGIN_SUCCESS:
++ mergeProps.putString("state", LOGIN_STATE_GETTING_BUDDIES);
++ mergeProps.put("errorCode", errorCodeMoj);
++ // Since the login succeeded, move any waiting messages back to pending
++ moveWaitingMessagesToPending(serviceNameMoj, usernameMoj);
++ break;
++ case LoginCallbackInterface::LOGIN_SIGNED_OFF:
++ case LoginCallbackInterface::LOGIN_FAILED:
++ case LoginCallbackInterface::LOGIN_TIMEOUT:
++ mergeProps.putString("state", LOGIN_STATE_OFFLINE);
++ mergeProps.put("errorCode", errorCodeMoj);
++ // Make the buddies for this account look offline to us
++ markBuddiesAsOffline(serviceNameMoj, usernameMoj);
++ break;
++ }
++ err = m_dbClient.merge(m_updateLoginStateSlot, query, mergeProps);
++ }
++
++ // update the syncState record for this account so account dashboard can display errors
++ // first we need to find our account id
++ LoginStateData state;
++ MojString accountId, service, user;
++ service.assign(serviceName);
++ user.assign(username);
++ bool found = m_loginStateController->getLoginStateData(service, user, state);
++ if (found) {
++ accountId = state.getAccountId();
++ MojRefCountedPtr<IMLoginSyncStateHandler> syncStateHandler(new IMLoginSyncStateHandler(m_service));
++ syncStateHandler->updateSyncStateRecord(serviceName, accountId, type, errorCodeMoj);
++ }
++ else {
++ MojLogError(IMServiceApp::s_log, _T("loginResult: could not find account Id in cached login states map. No syncState record created."));
++ // can we do anything here??
++ }
++
++}
++
++
++// This is a callback from the LibpurpleAdapter for notification of buddyList changes
++void IMLoginStateHandler::fullBuddyListResult(const char* serviceName, const char* username, MojObject& buddyList)
++{
++
++ //TODO: Update com.palm.imbuddystatus (maybe just delete all and add all new)
++ if (m_buddyListConsolidator == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("m_buddyListConsolidator is null on fullBuddyListResult"));
++ //TODO what to do here???
++ }
++ else
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("fullBuddyListResult: setting new buddy list"));
++ m_buddyListConsolidator->setNewBuddyList(buddyList);
++ if (m_buddyListConsolidator->isAllDataSet())
++ {
++ consolidateAllBuddyLists();
++ }
++ }
++}
++
++
++MojErr IMLoginStateHandler::getLoginStateQuery(MojDbQuery& query)
++{
++ query.from(IM_LOGINSTATE_KIND);
++ query.where("_rev", MojDbQuery::OpGreaterThan, m_workingLoginStateRev);
++ MojObject queryObj;
++ query.toObject(queryObj);
++ IMServiceHandler::logMojObjectJsonString(_T("getLoginStateQuery: %s"), queryObj);
++ return MojErrNone;
++}
++
++
++MojErr IMLoginStateHandler::updateLoginStateNoResponse(const MojString& serviceName, const MojString& username, const char* state, const char* ipAddress)
++{
++ MojDbQuery query;
++ query.where("serviceName", MojDbQuery::OpEq, serviceName);
++ query.where("username", MojDbQuery::OpEq, username);
++ query.from(IM_LOGINSTATE_KIND);
++ MojObject mergeProps;
++ mergeProps.putString("state", state);
++ if (ipAddress != NULL)
++ {
++ mergeProps.putString("ipAddress", ipAddress);
++ }
++ return m_dbClient.merge(m_ignoreUpdateLoginStateSlot, query, mergeProps);
++}
++
++
++/*
++ * IMLoginState::LoginStateData
++ */
++MojErr LoginStateData::assignFromDbRecord(MojObject& record)
++{
++ MojErr err = MojErrNone;
++ bool found = false;
++
++ err = record.get("_id", m_recordId, found);
++ if (err != MojErrNone || found == false)
++ {
++ MojLogError(IMServiceApp::s_log, _T("LoginStateData::getFromDbRecord missing _id."));
++ return err;
++ }
++
++ err = record.get("_rev", m_revision, found);
++ if (err != MojErrNone || found == false)
++ {
++ MojLogError(IMServiceApp::s_log, _T("LoginStateData::getFromDbRecord missing _rev. err=%u"), err);
++ return err;
++ }
++
++ record.get("username", m_username, found);
++ record.get("accountId", m_accountId, found);
++ record.get("serviceName", m_serviceName, found);
++ err = record.get("state", m_state, found);
++ if (err != MojErrNone || found == false)
++ {
++ MojLogError(IMServiceApp::s_log, _T("LoginStateData::getFromDbRecord missing state, assuming offline."));
++ m_state.assign(LOGIN_STATE_OFFLINE);
++ }
++
++ // This may legitimately be missing
++ record.get("customMessage", m_customMessage, found);
++
++ err = record.get("availability", m_availability, found);
++ if (err != MojErrNone || found == false)
++ {
++ MojLogError(IMServiceApp::s_log, _T("LoginStateData::getFromDbRecord missing availability, assuming offline."));
++ m_availability = PalmAvailability::OFFLINE;
++ }
++ return err;
++}
++
++bool LoginStateData::needsToLogin(LoginStateData& oldState)
++{
++ return (m_state == LOGIN_STATE_OFFLINE && m_availability != PalmAvailability::OFFLINE && m_availability != PalmAvailability::NO_PRESENCE);
++}
++
++bool LoginStateData::needsToGetBuddies(LoginStateData& oldState)
++{
++ return (m_state == LOGIN_STATE_GETTING_BUDDIES && m_availability != PalmAvailability::OFFLINE && m_availability != PalmAvailability::NO_PRESENCE && (oldState.m_state == LOGIN_STATE_OFFLINE || oldState.m_state == LOGIN_STATE_LOGGING_ON));
++}
++
++bool LoginStateData::needsToLogoff(LoginStateData& oldState)
++{
++ return (m_state != LOGIN_STATE_OFFLINE && m_availability == PalmAvailability::OFFLINE);
++}
++
++bool LoginStateData::hasAvailabilityChanged(LoginStateData& oldState)
++{
++ return (m_availability != oldState.m_availability);
++}
++
++bool LoginStateData::hasCustomMessageChanged(LoginStateData& newState)
++{
++ return (m_customMessage != newState.m_customMessage);
++}
++
++
++/*
++ * IMLoginFailRetryHandler -- signal handler
++ */
++IMLoginFailRetryHandler::IMLoginFailRetryHandler(MojService* service)
++: m_activitySubscriptionSlot(this, &IMLoginFailRetryHandler::activitySubscriptionResult),
++ m_dbmergeSlot(this, &IMLoginFailRetryHandler::dbmergeResult),
++ m_activityCompleteSlot(this, &IMLoginFailRetryHandler::activityCompleteResult),
++ m_service(service),
++ m_dbClient(service, MojDbServiceDefs::ServiceName),
++ m_tempdbClient(service, MojDbServiceDefs::TempServiceName),
++ m_activityId(-1)
++{
++}
++
++MojErr IMLoginFailRetryHandler::startTimerActivity(const MojString& serviceName, const MojDbQuery& query, const MojObject& mergeProps)
++{
++ //MojLogError(IMServiceApp::s_log, _T("**********IMLoginFailRetryHandler::startTimerActivity"));
++ MojRefCountedPtr<MojServiceRequest> req;
++ MojErr err = m_service->createRequest(req);
++ if (err != MojErrNone)
++ {
++ MojLogError(IMServiceApp::s_log, _T("IMLoginFailRetryHandler createRequest failed. error %d"), err);
++ }
++ else
++ {
++ m_query = query;
++ m_mergeProps = mergeProps;
++ // This is what the activity looks like
++ // {
++ // "name":"Set IMLoginState Offline " + serviceName,
++ // "description":"Scheduled request to mark the service as offline",
++ // "type":{"foreground":true},
++ // "schedule":{
++ // "start":<current date/time> + 2s,
++ // }
++ // }
++ // activity
++ MojObject activity;
++ MojString activityName;
++ activityName.assign(_T("Set IMLoginState Offline "));
++ activityName.append(serviceName);
++ activity.putString("name", activityName);
++ activity.putString("description", _T("Scheduled request to mark the service as offline"));
++
++ // activity.type
++ MojObject activityType;
++ activityType.putBool("foreground", true);
++ activity.put("type", activityType);
++
++ // activity.schedule
++ time_t targetDate;
++ time(&targetDate);
++ targetDate += 2; // schedule for 2 seconds in the future
++ tm* ptm = gmtime(&targetDate);
++ char scheduleTime[50];
++ sprintf(scheduleTime, "%d-%02d-%02d %02d:%02d:%02dZ", ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
++ MojLogDebug(IMServiceApp::s_log, _T("IMLoginFailRetryHandler: com.palm.activitymanager/create date=%s"), scheduleTime);
++ MojObject scheduleObj;
++ scheduleObj.putString("start", scheduleTime);
++ activity.put("schedule", scheduleObj);
++
++ MojObject params;
++ params.putBool("start", true);
++ params.putBool("subscribe", true);
++ params.put("activity", activity);
++
++ err = req->send(m_activitySubscriptionSlot, "com.palm.activitymanager", "create", params, MojServiceRequest::Unlimited);
++ }
++
++ return err;
++}
++
++// Expected responses
++// {"activityId":78,"returnValue":true}
++// {"activityId":78,"event":"start","returnValue":true} <-- this occurs when the timeout fires
++// Error in case there is already an activity for the given serviceName
++// {"errorCode":17,"errorText":"Activity with that name already exists","returnValue":false}
++MojErr IMLoginFailRetryHandler::activitySubscriptionResult(MojObject& result, MojErr err)
++{
++ MojLogDebug(IMServiceApp::s_log, _T("IMLoginFailRetryHandler::activitySubscriptionResult begin err=%d"), err);
++
++ MojString event;
++ if (err == MojErrNone && result.getRequired(_T("event"), event) == MojErrNone)
++ {
++ if (event == "start")
++ {
++ bool found = result.get("activityId", m_activityId);
++ if (!found)
++ MojLogError(IMServiceApp::s_log, _T("activitySubscriptionResult: parameter has no activityId"));
++ err = m_dbClient.merge(m_dbmergeSlot, m_query, m_mergeProps);
++ //TODO also markBuddiesAsOffline? Shouldn't need to since lost connection should do that later
++ }
++ }
++ return err;
++}
++
++MojErr IMLoginFailRetryHandler::dbmergeResult(MojObject& result, MojErr err)
++{
++ if (m_activityId > 0)
++ {
++ MojRefCountedPtr<MojServiceRequest> req;
++ err = m_service->createRequest(req);
++ MojObject completeParams;
++ completeParams.put(_T("activityId"), m_activityId);
++ err = req->send(m_activityCompleteSlot, "com.palm.activitymanager", "complete", completeParams, 1);
++ }
++ return err;
++}
++
++MojErr IMLoginFailRetryHandler::activityCompleteResult(MojObject& result, MojErr err)
++{
++ m_activitySubscriptionSlot.cancel();
++ return err;
++}
++
++
++/*
++ * IMLoginSyncStateHandler -- signal handler to update syncState
++ */
++IMLoginSyncStateHandler::IMLoginSyncStateHandler(MojService* service)
++: m_removeSyncStateSlot(this, &IMLoginSyncStateHandler::removeSyncStateResult),
++ m_saveSyncStateSlot(this, &IMLoginSyncStateHandler::saveSyncStateResult),
++ m_service(service),
++ m_tempdbClient(service, MojDbServiceDefs::TempServiceName)
++{
++}
++
++/*
++ * Update the syncState record in temp DB
++ * delete any existing syncState record. Covers the case when login succeeds
++ * if the login failed due to bad password, need to add a syncState record with the correct account error code
++ */
++void IMLoginSyncStateHandler::updateSyncStateRecord(const char* serviceName, MojString accountId, LoginCallbackInterface::LoginResult type, const char* errCode)
++{
++ // syncState record:
++ /*
++ {
++ "_kind": "com.palm.account.syncstate:1",
++ "accountId": "++HBI8gkY38GlaVk", LoginStateData.getAccountId()
++ "busAddress": "com.palm.imlibpurple"
++ "capabilityProvider": "com.palm.google.talk" or "com.palm.aol.aim"
++ "errorCode": "401_UNAUTHORIZED",
++ "errorText": "Incorrect Password",
++ "syncState": "ERROR"
++ }
++ */
++
++
++ // get the capabilityProvidor id from the service
++ // TODO - should read this out of template ID...
++ if (strcmp(serviceName, SERVICENAME_AIM) == 0) {
++ m_capabilityId.assign(CAPABILITY_AIM);
++ }
++ else if (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0){
++ m_capabilityId.assign(CAPABILITY_FACEBOOK);
++ }
++ else if (strcmp(serviceName, SERVICENAME_GTALK) == 0){
++ m_capabilityId.assign(CAPABILITY_GTALK);
++ }
++ else if (strcmp(serviceName, SERVICENAME_GADU) == 0){
++ m_capabilityId.assign(CAPABILITY_GADU);
++ }
++ else if (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0){
++ m_capabilityId.assign(CAPABILITY_GROUPWISE);
++ }
++ else if (strcmp(serviceName, SERVICENAME_ICQ) == 0){
++ m_capabilityId.assign(CAPABILITY_ICQ);
++ }
++ else if (strcmp(serviceName, SERVICENAME_JABBER) == 0){
++ m_capabilityId.assign(CAPABILITY_JABBER);
++ }
++ else if (strcmp(serviceName, SERVICENAME_LIVE) == 0){
++ m_capabilityId.assign(CAPABILITY_LIVE);
++ }
++ else if (strcmp(serviceName, SERVICENAME_MYSPACE) == 0){
++ m_capabilityId.assign(CAPABILITY_MYSPACE);
++ }
++ else if (strcmp(serviceName, SERVICENAME_QQ) == 0){
++ m_capabilityId.assign(CAPABILITY_QQ);
++ }
++ else if (strcmp(serviceName, SERVICENAME_SAMETIME) == 0){
++ m_capabilityId.assign(CAPABILITY_SAMETIME);
++ }
++ else if (strcmp(serviceName, SERVICENAME_SIPE) == 0){
++ m_capabilityId.assign(CAPABILITY_SIPE);
++ }
++ else if (strcmp(serviceName, SERVICENAME_XFIRE) == 0){
++ m_capabilityId.assign(CAPABILITY_XFIRE);
++ }
++ else if (strcmp(serviceName, SERVICENAME_YAHOO) == 0){
++ m_capabilityId.assign(CAPABILITY_YAHOO);
++ }
++ else {
++ MojLogError(IMServiceApp::s_log, _T("updateSyncStateRecord: unknown serviceName %s. No syncState record created."), serviceName);
++ // can we do anything here??
++ return;
++ }
++
++ // save the input parameters
++ m_accountId = accountId;
++
++ // get the error code for the new record if we need it
++ if (LoginCallbackInterface::LOGIN_SUCCESS != type) {
++
++ // for now, we only show dashboard for authentication errors
++ if ((strcmp(errCode, ERROR_AUTHENTICATION_FAILED) == 0) ||
++ (strcmp(errCode, ERROR_BAD_USERNAME) == 0) ||
++ (strcmp(errCode, ERROR_BAD_PASSWORD) == 0)) {
++ m_errorCode.assign(ACCOUNT_401_UNAUTHORIZED);
++ }
++ }
++
++ // delete any existing record for this account
++ // construct our where clause - find by username, accountId and servicename
++ MojDbQuery query;
++ query.where("capabilityProvider", MojDbQuery::OpEq, m_capabilityId);
++ query.where("accountId", MojDbQuery::OpEq, m_accountId);
++ query.from(IM_SYNCSTATE_KIND);
++
++ // delete the old record. If none exists, that is OK - DB just returns a count of 0 in result
++ MojErr err = m_tempdbClient.del(m_removeSyncStateSlot, query);
++ if (err) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("updateSyncStateRecord: dbClient.del() failed: error %d - %s"), err, error.data());
++ }
++}
++
++/*
++ * Remove the old the syncState record result
++ */
++MojErr IMLoginSyncStateHandler::removeSyncStateResult(MojObject& result, MojErr resultErr)
++{
++ if (resultErr) {
++ MojString error;
++ MojErrToString(resultErr, error);
++ MojLogError(IMServiceApp::s_log, _T("removeSyncStateResult: DB del failed. error %d - %s"), resultErr, error.data());
++ }
++ else {
++ IMServiceHandler::logMojObjectJsonString(_T("removeSyncStateResult: syncState successfully removed: %s"), result);
++ }
++
++ // no error means we are done
++ if (m_errorCode.empty())
++ return MojErrNone;
++
++
++ // otherwise add the new record
++ MojObject props;
++ props.putString(_T("errorCode"), m_errorCode);
++ props.putString(_T("_kind"), IM_SYNCSTATE_KIND);
++ props.putString(_T("accountId"), m_accountId);
++ props.putString(_T("capabilityProvider"), m_capabilityId);
++
++ // bus address
++ MojString busAddr;
++ busAddr.assign(_T("org.webosinternals.imlibpurple"));
++ props.putString(_T("busAddress"), busAddr);
++
++ // sync state - error
++ MojString syncState;
++ syncState.assign(_T("ERROR"));
++ props.putString(_T("syncState"), syncState);
++
++ // log it
++ MojString json;
++ props.toJson(json);
++ MojLogInfo(IMServiceApp::s_log, _T("removeSyncStateResult: saving syncState to db: %s"), json.data());
++
++ MojErr err = m_tempdbClient.put(m_saveSyncStateSlot, props);
++ if (err) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("removeSyncStateResult: dbClient.put() failed: error %d - %s"), err, error.data());
++ }
++
++ return MojErrNone;
++}
++
++/*
++ * Save the new syncState record result
++ * Just log errors.
++ */
++MojErr IMLoginSyncStateHandler::saveSyncStateResult(MojObject& result, MojErr resultErr)
++{
++ if (resultErr) {
++ MojString error;
++ MojErrToString(resultErr, error);
++ MojLogError(IMServiceApp::s_log, _T("saveSyncStateResult: DB put failed. error %d - %s"), resultErr, error.data());
++ }
++ else {
++ IMServiceHandler::logMojObjectJsonString(_T("saveSyncStateResult: syncState successfully saved: %s"), result);
++ }
++
++ return MojErrNone;
++}
++
+diff -rupN imlibpurpleservice-1.0/src/IMServiceApp.cpp imlibpurpleservice-1.0-new//src/IMServiceApp.cpp
+--- imlibpurpleservice-1.0/src/IMServiceApp.cpp 2011-03-25 10:45:40.744552999 -0600
++++ imlibpurpleservice-1.0-new//src/IMServiceApp.cpp 2011-03-27 09:10:06.164553000 -0600
+@@ -32,6 +32,7 @@ MojLogger IMServiceApp::s_log(_T("org.we
+
+ int main(int argc, char** argv)
+ {
++purple_debug_set_enabled(TRUE);
+ IMServiceApp app;
+ LibpurpleAdapter::init();
+ int mainResult = app.main(argc, argv);
+diff -rupN imlibpurpleservice-1.0/src/LibpurpleAdapter.cpp imlibpurpleservice-1.0-new//src/LibpurpleAdapter.cpp
+--- imlibpurpleservice-1.0/src/LibpurpleAdapter.cpp 2011-03-25 10:45:40.760552999 -0600
++++ imlibpurpleservice-1.0-new//src/LibpurpleAdapter.cpp 2011-03-27 09:10:06.212553000 -0600
+@@ -378,6 +378,9 @@ static char* getMojoFriendlyTemplateID (
+ else if (strcmp(serviceName, SERVICENAME_LIVE) == 0){
+ return (char*)CAPABILITY_LIVE;
+ }
++ else if (strcmp(serviceName, SERVICENAME_WLM) == 0){
++ return (char*)CAPABILITY_WLM;
++ }
+ else if (strcmp(serviceName, SERVICENAME_MYSPACE) == 0){
+ return (char*)CAPABILITY_MYSPACE;
+ }
+@@ -554,7 +557,7 @@ static char* getPrplProtocolIdFromServic
+ }
+ GString* prplProtocolId = g_string_new("prpl-");
+
+- if ((strcmp(serviceName, SERVICENAME_GTALK) == 0) || (strcmp(serviceName, SERVICENAME_LIVE) == 0) || (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0) || (strcmp(serviceName, SERVICENAME_SAMETIME) == 0) || (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0) || (strcmp(serviceName, SERVICENAME_GADU) == 0))
++ if ((strcmp(serviceName, SERVICENAME_GTALK) == 0) || (strcmp(serviceName, SERVICENAME_LIVE) == 0) || (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0) || (strcmp(serviceName, SERVICENAME_SAMETIME) == 0) || (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0) || (strcmp(serviceName, SERVICENAME_GADU) == 0) || (strcmp(serviceName, SERVICENAME_WLM) == 0))
+ {
+ if (strcmp(serviceName, SERVICENAME_GTALK) == 0)
+ {
+@@ -566,6 +569,11 @@ static char* getPrplProtocolIdFromServic
+ // Special case for live where the mojo serviceName is "type_live" and the prpl protocol_id is "prpl-msn"
+ g_string_append(prplProtocolId, "msn");
+ }
++ if (strcmp(serviceName, SERVICENAME_WLM) == 0)
++ {
++ // Special case for live (pecan) where the mojo serviceName is "type_wlm" and the prpl protocol_id is "prpl-msn-pecan"
++ g_string_append(prplProtocolId, "msn-pecan");
++ }
+ if (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0)
+ {
+ // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim"
+@@ -647,6 +655,12 @@ static char* getServiceNameFromPrplProto
+ g_string_free(serviceName, TRUE);
+ serviceName = g_string_new("live");
+ }
++ if (strcmp(serviceName->str, "msn-pecan") == 0)
++ {
++ // Special case for live where the mojo serviceName is "type_wlm" and the prpl protocol_id is "prpl-msn-pecan"
++ g_string_free(serviceName, TRUE);
++ serviceName = g_string_new("wlm");
++ }
+ if (strcmp(serviceName->str, "bigbrownchunx-facebookim") == 0)
+ {
+ // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim"
+diff -rupN imlibpurpleservice-1.0/src/LibpurpleAdapter.cpp~ imlibpurpleservice-1.0-new//src/LibpurpleAdapter.cpp~
+--- imlibpurpleservice-1.0/src/LibpurpleAdapter.cpp~ 1969-12-31 18:00:00.000000000 -0600
++++ imlibpurpleservice-1.0-new//src/LibpurpleAdapter.cpp~ 2011-03-27 09:10:06.132553000 -0600
+@@ -0,0 +1,2817 @@
++/*
++ * LibpurpleAdapter.cpp
++ *
++ * Copyright 2010 Palm, Inc. All rights reserved.
++ *
++ * This program is free software and licensed under the terms of the GNU
++ * General Public License Version 2 as published by the Free
++ * Software Foundation;
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License,
++ * Version 2 along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-
++ * 1301, USA
++ *
++ * IMLibpurpleservice uses libpurple.so to implement a fully functional IM
++ * Transport service for use on a mobile device.
++ *
++ * The LibpurpleAdapter is a simple adapter between libpurple.so and the
++ * IMLibpurpleService transport
++ */
++
++/*
++ * This file includes code from pidgin (nullclient.c)
++ *
++ * pidgin
++ *
++ * Pidgin is the legal property of its developers, whose names are too numerous
++ * to list here. Please refer to the COPYRIGHT file distributed with this
++ * source distribution.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
++ *
++ */
++
++#include "purple.h"
++
++#include <glib.h>
++
++#include <signal.h>
++#include <string.h>
++#include <unistd.h>
++#include <stdlib.h>
++
++#include "LibpurpleAdapter.h"
++#include "LibpurpleAdapterPrefs.h"
++#include "PalmImCommon.h"
++#include "luna/MojLunaService.h"
++#include "IMServiceApp.h"
++#include "db/MojDbQuery.h"
++
++static const guint PURPLE_GLIB_READ_COND = (G_IO_IN | G_IO_HUP | G_IO_ERR);
++static const guint PURPLE_GLIB_WRITE_COND = (G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL);
++//static const guint CONNECT_TIMEOUT_SECONDS = 45; Moved to preferences
++//static const guint BUDDY_LIST_REFRESH = 10; Moved to preferences
++
++static LoginCallbackInterface* s_loginState = NULL;
++static IMServiceCallbackInterface* s_imServiceHandler = NULL;
++static LibpurplePrefsHandler* s_PrefsHandler = NULL;
++
++/**
++ * List of accounts that are online
++ */
++static GHashTable* s_onlineAccountData = NULL;
++/**
++ * List of accounts that are in the process of logging in
++ */
++static GHashTable* s_pendingAccountData = NULL;
++static GHashTable* s_offlineAccountData = NULL;
++static GHashTable* s_accountLoginTimers = NULL;
++static GHashTable* s_connectionTypeData = NULL;
++static GHashTable* s_AccountIdsData = NULL;
++static GHashTable* s_accountBuddyListTimers = NULL;
++
++/*
++ * list of pending authorization requests
++ */
++static GHashTable* s_AuthorizeRequests = NULL;
++
++static bool s_libpurpleInitialized = FALSE;
++static bool s_registeredForAccountSignals = FALSE;
++static bool s_registeredForPresenceUpdateSignals = FALSE;
++/**
++ * Keeps track of the local IP address that we bound to when logging in to individual accounts
++ * key: accountKey, value: IP address
++ */
++static GHashTable* s_ipAddressesBoundTo = NULL;
++
++typedef struct _IOClosure
++{
++ guint result;
++ gpointer data;
++ PurpleInputFunction function;
++} IOClosure;
++
++// used for processing buddy invite requests
++typedef struct _auth_and_add
++{
++ PurpleAccountRequestAuthorizationCb auth_cb;
++ PurpleAccountRequestAuthorizationCb deny_cb;
++ void *data;
++ char *remote_user;
++ char *alias;
++ PurpleAccount *account;
++} AuthRequest;
++
++static void incoming_message_cb(PurpleConversation *conv, const char *who, const char *alias, const char *message, PurpleMessageFlags flags, time_t mtime);
++static void adapterUIInit(void);
++static GHashTable* getClientInfo(void);
++static gboolean adapterInvokeIO(GIOChannel *source, GIOCondition condition, gpointer data);
++static guint adapterIOAdd(gint fd, PurpleInputCondition condition, PurpleInputFunction function, gpointer data);
++
++//Prompt for authorization when someone adds this account to their buddy list.
++//To authorize them to see this account's presence, call authorize_cb (user_data); otherwise call deny_cb (user_data);
++//Returns:
++// a UI-specific handle, as passed to close_account_request.
++// void(* _PurpleAccountUiOps::close_account_request)(void *ui_handle)
++// Close a pending request for authorization.
++// ui_handle is a handle as returned by request_authorize.
++//Parameters:
++// account The account that was added
++// remote_user The name of the user that added this account.
++// id The optional ID of the local account. Rarely used.
++// alias The optional alias of the remote user.
++// message The optional message sent by the user wanting to add you.
++// on_list Is the remote user already on the buddy list?
++// auth_cb The callback called when the local user accepts
++// deny_cb The callback called when the local user rejects
++// user_data Data to be passed back to the above callbacks
++static void *request_authorize_cb (PurpleAccount *account,
++ const char *remote_user,
++ const char *id,
++ const char *alias,
++ const char *message,
++ gboolean on_list,
++ PurpleAccountRequestAuthorizationCb authorize_cb,
++ PurpleAccountRequestAuthorizationCb deny_cb,
++ void *user_data);
++
++void request_add_cb (PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message);
++
++// AccountUIOps:
++// /** A buddy who is already on this account's buddy list added this account
++//- * to their buddy list.
++// */
++//- void (*notify_added)(PurpleAccount *account,
++//- const char *remote_user,
++//- const char *id,
++//- const char *alias,
++//- const char *message);
++//-
++//- /** This account's status changed. */
++//- void (*status_changed)(PurpleAccount *account,
++//- PurpleStatus *status);
++//-
++//- /** Someone we don't have on our list added us; prompt to add them. */
++//- void (*request_add)(PurpleAccount *account,
++//- const char *remote_user,
++//- const char *id,
++//- const char *alias,
++//- const char *message);
++//-
++//- /** Prompt for authorization when someone adds this account to their buddy
++//- * list. To authorize them to see this account's presence, call \a
++//- * authorize_cb (\a user_data); otherwise call \a deny_cb (\a user_data);
++//- * @return a UI-specific handle, as passed to #close_account_request.
++//- */
++//- void *(*request_authorize)(PurpleAccount *account,
++//- const char *remote_user,
++//- const char *id,
++//- const char *alias,
++//- const char *message,
++//- gboolean on_list,
++//- PurpleAccountRequestAuthorizationCb authorize_cb,
++//- PurpleAccountRequestAuthorizationCb deny_cb,
++//- void *user_data);
++//-
++//- /** Close a pending request for authorization. \a ui_handle is a handle
++//- * as returned by #request_authorize.
++//- */
++//- void (*close_account_request)(void *ui_handle);
++static PurpleAccountUiOps adapterAccountUIOps =
++{
++ NULL, // notify_added
++ NULL, // status_changed
++ request_add_cb, // request_add
++ request_authorize_cb, // request_authorize
++ NULL, // close_account_request,
++
++ // padding
++ NULL,
++ NULL,
++ NULL,
++ NULL,
++};
++
++static PurpleCoreUiOps adapterCoreUIOps =
++{
++ NULL, NULL, adapterUIInit, NULL, getClientInfo, NULL, NULL, NULL
++};
++
++static PurpleEventLoopUiOps adapterEventLoopUIOps =
++{
++ g_timeout_add, g_source_remove, adapterIOAdd, g_source_remove, NULL, g_timeout_add_seconds, NULL, NULL, NULL
++};
++
++static PurpleConversationUiOps adapterConversationUIOps =
++{
++ NULL, NULL, NULL, NULL, incoming_message_cb, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
++ NULL, NULL
++};
++
++// useful for debugging
++static void authRequest_log_func(gpointer key, gpointer value, gpointer ud)
++{
++ AuthRequest *aa = (AuthRequest *)value;
++ MojLogInfo(IMServiceApp::s_log, _T(" AuthRequest: key = %s, requester = %s"), (char*)key, aa->remote_user);
++}
++static void logAuthRequestTableValues()
++{
++ MojLogInfo(IMServiceApp::s_log, _T("Authorize Request Table:"));
++ g_hash_table_foreach(s_AuthorizeRequests, authRequest_log_func, NULL);
++}
++
++
++void adapterUIInit(void)
++{
++ purple_conversations_set_ui_ops(&adapterConversationUIOps);
++ purple_accounts_set_ui_ops(&adapterAccountUIOps);
++}
++
++void destroyNotify(gpointer dataToFree)
++{
++ g_free(dataToFree);
++}
++
++gboolean adapterInvokeIO(GIOChannel* ioChannel, GIOCondition ioCondition, gpointer data)
++{
++ IOClosure* ioClosure = (IOClosure*)data;
++ int purpleCondition = 0;
++
++ if (PURPLE_GLIB_READ_COND & ioCondition)
++ {
++ purpleCondition = purpleCondition | PURPLE_INPUT_READ;
++ }
++
++ if (PURPLE_GLIB_WRITE_COND & ioCondition)
++ {
++ purpleCondition = purpleCondition | PURPLE_INPUT_WRITE;
++ }
++
++ ioClosure->function(ioClosure->data, g_io_channel_unix_get_fd(ioChannel), (PurpleInputCondition)purpleCondition);
++
++ return TRUE;
++}
++
++guint adapterIOAdd(gint fd, PurpleInputCondition purpleCondition, PurpleInputFunction inputFunction, gpointer data)
++{
++ GIOChannel* ioChannel;
++ unsigned int ioCondition = 0;
++ IOClosure* ioClosure = g_new0(IOClosure, 1);
++
++ ioClosure->data = data;
++ ioClosure->function = inputFunction;
++
++ if (PURPLE_INPUT_READ & purpleCondition)
++ {
++ ioCondition = ioCondition | PURPLE_GLIB_READ_COND;
++ }
++
++ if (PURPLE_INPUT_WRITE & purpleCondition)
++ {
++ ioCondition = ioCondition | PURPLE_GLIB_WRITE_COND;
++ }
++
++ ioChannel = g_io_channel_unix_new(fd);
++ ioClosure->result = g_io_add_watch_full(ioChannel, G_PRIORITY_DEFAULT, (GIOCondition)ioCondition, adapterInvokeIO, ioClosure,
++ destroyNotify);
++
++ g_io_channel_unref(ioChannel);
++ return ioClosure->result;
++}
++
++/*
++ * Helper methods
++ */
++
++ /*
++ * The messaging service expects the username to be in the username@domain.com format, whereas the AIM prpl uses the username only
++ * Free the returned string when you're done with it
++ */
++static char* getMojoFriendlyUsername(const char* username, const char* serviceName)
++{
++ if (!username || !serviceName)
++ {
++ return strdup("");
++ }
++ GString* mojoFriendlyUsername = g_string_new(username);
++ if (strcmp(serviceName, SERVICENAME_AIM) == 0 && strchr(username, '@') == NULL)
++ {
++ g_string_append(mojoFriendlyUsername, "@aol.com");
++ }
++ else if (strcmp(serviceName, SERVICENAME_GTALK) == 0)
++ {
++ char* resource = (char*)memchr(username, '/', strlen(username));
++ if (resource != NULL)
++ {
++ int charsToKeep = resource - username;
++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1);
++ }
++ }
++ else if (strcmp(serviceName, SERVICENAME_JABBER) == 0)
++ {
++ if (strstr(username, "/") != NULL)
++ {
++ //If jabber resource is blank remove /
++ char *resource = (char*)memchr(username, '/', strlen(username));
++ if (resource != NULL)
++ {
++ int charsToKeep = resource - username;
++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1);
++ }
++ }
++ }
++ else if (strcmp(serviceName, SERVICENAME_SIPE) == 0)
++ {
++ char *resource = (char*)memchr(username, ',', strlen(username));
++ if (resource != NULL)
++ {
++ int charsToKeep = resource - username;
++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1);
++ }
++ }
++ char* mojoFriendlyUsernameToReturn = strdup(mojoFriendlyUsername->str);
++ g_string_free(mojoFriendlyUsername, TRUE);
++ return mojoFriendlyUsernameToReturn;
++}
++
++static char* getMojoFriendlyTemplateID (char* serviceName)
++{
++ if (strcmp(serviceName, SERVICENAME_AIM) == 0) {
++ return (char*)CAPABILITY_AIM;
++ }
++ else if (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0){
++ return (char*)CAPABILITY_FACEBOOK;
++ }
++ else if (strcmp(serviceName, SERVICENAME_GTALK) == 0){
++ return (char*)CAPABILITY_GTALK;
++ }
++ else if (strcmp(serviceName, SERVICENAME_GADU) == 0){
++ return (char*)CAPABILITY_GADU;
++ }
++ else if (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0){
++ return (char*)CAPABILITY_GROUPWISE;
++ }
++ else if (strcmp(serviceName, SERVICENAME_ICQ) == 0){
++ return (char*)CAPABILITY_ICQ;
++ }
++ else if (strcmp(serviceName, SERVICENAME_JABBER) == 0){
++ return (char*)CAPABILITY_JABBER;
++ }
++ else if (strcmp(serviceName, SERVICENAME_LIVE) == 0){
++ return (char*)CAPABILITY_LIVE;
++ }
++ else if (strcmp(serviceName, SERVICENAME_MYSPACE) == 0){
++ return (char*)CAPABILITY_MYSPACE;
++ }
++ else if (strcmp(serviceName, SERVICENAME_QQ) == 0){
++ return (char*)CAPABILITY_QQ;
++ }
++ else if (strcmp(serviceName, SERVICENAME_SAMETIME) == 0){
++ return (char*)CAPABILITY_SAMETIME;
++ }
++ else if (strcmp(serviceName, SERVICENAME_SIPE) == 0){
++ return (char*)CAPABILITY_SIPE;
++ }
++ else if (strcmp(serviceName, SERVICENAME_XFIRE) == 0){
++ return (char*)CAPABILITY_XFIRE;
++ }
++ else if (strcmp(serviceName, SERVICENAME_YAHOO) == 0){
++ return (char*)CAPABILITY_YAHOO;
++ }
++
++ return (char*)"";
++}
++
++/*
++ * This method handles special cases where the username passed by the mojo side does not satisfy a particular prpl's requirement
++ * (e.g. for logging into AIM, the mojo service uses "palmpre@aol.com", yet the aim prpl expects "palmpre"; same scenario with yahoo)
++ * Free the returned string when you're done with it
++ */
++static char* getPrplFriendlyUsername(const char* serviceName, const char* username)
++{
++ if (!username || !serviceName)
++ {
++ return strdup("");
++ }
++
++ char* transportFriendlyUsername = strdup(username);
++ if (strcmp(serviceName, SERVICENAME_AIM) == 0)
++ {
++ // Need to strip off @aol.com, but not @aol.com.mx
++ const char* extension = strstr(username, "@aol.com");
++ if (extension != NULL && strstr(extension, "aol.com.") == NULL)
++ {
++ strtok(transportFriendlyUsername, "@");
++ }
++ }
++
++ const char* SIPEServerLogin;
++ const char* JabberResource;
++ char* templateId = getMojoFriendlyTemplateID((char*)serviceName);
++
++ //Special Case for Office Communicator when DOMAIN\USER is set. Account name is USERNAME,DOMAIN\USER
++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0)
++ {
++ if (strstr(username, ",") != NULL)
++ {
++ //A "," exists in the sign in name already
++ //transportFriendlyUsername = malloc(strlen(username) + 1);
++ transportFriendlyUsername = strcpy(transportFriendlyUsername, username);
++ return transportFriendlyUsername;
++ }
++ else
++ {
++ SIPEServerLogin = s_PrefsHandler->GetStringPreference("SIPEServerLogin", templateId, username);
++ if (strcmp(SIPEServerLogin, "") != 0)
++ {
++ //transportFriendlyUsername = malloc(strlen(username) + 1);
++ transportFriendlyUsername = strcpy(transportFriendlyUsername, username);
++
++ //SIPE Account
++ char *SIPEFullLoginName = NULL;
++ SIPEFullLoginName = (char *)calloc(strlen(transportFriendlyUsername) + strlen(SIPEServerLogin) + 2, sizeof(char));
++ strcat(SIPEFullLoginName, transportFriendlyUsername);
++ strcat(SIPEFullLoginName, ",");
++ strcat(SIPEFullLoginName, SIPEServerLogin);
++
++ return SIPEFullLoginName;
++ }
++ }
++ }
++
++ //Special Case for jabber when resource is set. Account name is USERNAME/RESOURCE
++ if (strcmp(serviceName, SERVICENAME_JABBER) == 0)
++ {
++ if (strstr(username, "/") != NULL)
++ {
++ //A "/" exists in the sign in name already
++ transportFriendlyUsername = strcpy(transportFriendlyUsername, username);
++
++ return transportFriendlyUsername;
++ }
++ else
++ {
++ JabberResource = s_PrefsHandler->GetStringPreference("JabberResource", templateId, username);
++
++ if (strcmp(JabberResource, "") != 0)
++ {
++ transportFriendlyUsername = strcpy(transportFriendlyUsername, username);
++
++ //Jabber Account
++ char *JabberFullLoginName = NULL;
++ JabberFullLoginName = (char *)calloc(strlen(transportFriendlyUsername) + strlen(JabberResource) + 2, sizeof(char));
++ strcat(JabberFullLoginName, transportFriendlyUsername);
++ strcat(JabberFullLoginName, "/");
++ strcat(JabberFullLoginName, JabberResource);
++
++ return JabberFullLoginName;
++ }
++ }
++ }
++
++ MojLogInfo(IMServiceApp::s_log, _T("getPrplFriendlyUsername: username: %s, transportFriendlyUsername: %s"), username, transportFriendlyUsername);
++ return transportFriendlyUsername;
++}
++
++static char* stripResourceFromGtalkUsername(const char* username, const char* serviceName)
++{
++ if (!username)
++ {
++ return strdup("");
++ }
++ if ((strcmp(serviceName, SERVICENAME_GTALK) != 0) && (strcmp(serviceName, SERVICENAME_JABBER) != 0))
++ {
++ return strdup(username);
++ }
++
++ GString* mojoFriendlyUsername = g_string_new(username);
++ char* resource = (char*)memchr(username, '/', strlen(username));
++ if (resource != NULL)
++ {
++ int charsToKeep = resource - username;
++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1);
++ }
++ char* mojoFriendlyUsernameToReturn = strdup(mojoFriendlyUsername->str);
++ g_string_free(mojoFriendlyUsername, TRUE);
++ return mojoFriendlyUsernameToReturn;
++}
++
++static const char* getMojoFriendlyErrorCode(PurpleConnectionError type)
++{
++ const char* mojoFriendlyErrorCode;
++ if (type == PURPLE_CONNECTION_ERROR_INVALID_USERNAME)
++ {
++ mojoFriendlyErrorCode = ERROR_BAD_USERNAME;
++ }
++ else if (type == PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED)
++ {
++ mojoFriendlyErrorCode = ERROR_AUTHENTICATION_FAILED;
++ }
++ else if (type == PURPLE_CONNECTION_ERROR_NETWORK_ERROR)
++ {
++ mojoFriendlyErrorCode = ERROR_NETWORK_ERROR;
++ }
++ else if (type == PURPLE_CONNECTION_ERROR_NAME_IN_USE)
++ {
++ mojoFriendlyErrorCode = ERROR_NAME_IN_USE;
++ }
++ else
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("PurpleConnectionError was %i"), type);
++ mojoFriendlyErrorCode = ERROR_GENERIC_ERROR;
++ }
++ return mojoFriendlyErrorCode;
++}
++
++/*
++ * Given mojo-friendly serviceName, it will return prpl-specific protocol_id (e.g. given "type_aim", it will return "prpl-aim")
++ * Free the returned string when you're done with it
++ */
++static char* getPrplProtocolIdFromServiceName(const char* serviceName)
++{
++ if (!serviceName || serviceName[0] == 0)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getPrplProtocolIdFromServiceName called with empty serviceName"));
++ return strdup("");
++ }
++ GString* prplProtocolId = g_string_new("prpl-");
++
++ if ((strcmp(serviceName, SERVICENAME_GTALK) == 0) || (strcmp(serviceName, SERVICENAME_LIVE) == 0) || (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0) || (strcmp(serviceName, SERVICENAME_SAMETIME) == 0) || (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0) || (strcmp(serviceName, SERVICENAME_GADU) == 0))
++ {
++ if (strcmp(serviceName, SERVICENAME_GTALK) == 0)
++ {
++ // Special case for gtalk where the mojo serviceName is "type_gtalk" and the prpl protocol_id is "prpl-jabber"
++ g_string_append(prplProtocolId, "jabber");
++ }
++ if (strcmp(serviceName, SERVICENAME_LIVE) == 0)
++ {
++ // Special case for live where the mojo serviceName is "type_live" and the prpl protocol_id is "prpl-msn"
++ g_string_append(prplProtocolId, "msn");
++ }
++ if (strcmp(serviceName, SERVICENAME_FACEBOOK) == 0)
++ {
++ // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim"
++ g_string_append(prplProtocolId, "bigbrownchunx-facebookim");
++ }
++ if (strcmp(serviceName, SERVICENAME_SAMETIME) == 0)
++ {
++ // Special case for sametime where the mojo serviceName is "type_sametime" and the prpl protocol_id is "prpl-meanwhile"
++ g_string_append(prplProtocolId, "meanwhile");
++ }
++ if (strcmp(serviceName, SERVICENAME_GROUPWISE) == 0)
++ {
++ // Special case for groupwise where the mojo serviceName is "type_groupwise" and the prpl protocol_id is "prpl-novell"
++ g_string_append(prplProtocolId, "novell");
++ }
++ if (strcmp(serviceName, SERVICENAME_GADU) == 0)
++ {
++ // Special case for gadu gadu where the mojo serviceName is "type_gadu" and the prpl protocol_id is "prpl-gg"
++ g_string_append(prplProtocolId, "gg");
++ }
++ }
++ else
++ {
++ const char* stringChopper = serviceName;
++ stringChopper += strlen("type_");
++ g_string_append(prplProtocolId, stringChopper);
++ }
++ char* prplProtocolIdToReturn = strdup(prplProtocolId->str);
++ g_string_free(prplProtocolId, TRUE);
++ return prplProtocolIdToReturn;
++}
++
++/*
++ * Given the prpl-specific protocol_id, it will return mojo-friendly serviceName (e.g. given "prpl-aim", it will return "type_aim")
++ * Free the returned string when you're done with it
++ */
++static char* getServiceNameFromPrplProtocolId(PurpleAccount *account)
++{
++ char *prplProtocolId = account->protocol_id;
++ if (!prplProtocolId)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId called with empty protocolId"));
++ return strdup("type_default");
++ }
++ char* stringChopper = prplProtocolId;
++ stringChopper += strlen("prpl-");
++ GString* serviceName = g_string_new(stringChopper);
++
++ if (strcmp(serviceName->str, "jabber") == 0)
++ {
++ // Special case for gtalk where the mojo serviceName is "type_gtalk" and the prpl protocol_id is "prpl-jabber"
++ g_string_free(serviceName, TRUE);
++
++ const char *Alias = purple_account_get_alias (account);
++
++ if (Alias != NULL)
++ {
++ //Check account alias. gtalk for Gtalk, jabber for Jabber
++ if (strcmp(Alias, "gtalk") == 0)
++ {
++ // Special case for gtalk where the java serviceName is "gmail" and the prpl protocol_id is "prpl-jabber"
++ serviceName = g_string_new("gtalk");
++ }
++ else
++ {
++ // Special case for jabber where the java serviceName is "jabber" and the prpl protocol_id is "prpl-jabber"
++ serviceName = g_string_new("jabber");
++ }
++ }
++ else
++ {
++ //Account alias is blank for some reason. Guess gtalk
++ serviceName = g_string_new("gtalk");
++ }
++ }
++ if (strcmp(serviceName->str, "msn") == 0)
++ {
++ // Special case for live where the mojo serviceName is "type_live" and the prpl protocol_id is "prpl-msn"
++ g_string_free(serviceName, TRUE);
++ serviceName = g_string_new("live");
++ }
++ if (strcmp(serviceName->str, "bigbrownchunx-facebookim") == 0)
++ {
++ // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim"
++ g_string_free(serviceName, TRUE);
++ serviceName = g_string_new("facebook");
++ }
++ if (strcmp(serviceName->str, "meanwhile") == 0)
++ {
++ // Special case for sametime where the mojo serviceName is "type_sametime" and the prpl protocol_id is "prpl-meanwhile"
++ g_string_free(serviceName, TRUE);
++ serviceName = g_string_new("sametime");
++ }
++ if (strcmp(serviceName->str, "novell") == 0)
++ {
++ // Special case for groupwise where the mojo serviceName is "type_groupwise" and the prpl protocol_id is "prpl-novell"
++ g_string_free(serviceName, TRUE);
++ serviceName = g_string_new("groupwise");
++ }
++ if (strcmp(serviceName->str, "gg") == 0)
++ {
++ // Special case for gadu where the mojo serviceName is "type_gadu" and the prpl protocol_id is "prpl-gg"
++ g_string_free(serviceName, TRUE);
++ serviceName = g_string_new("gadu");
++ }
++ char* serviceNameToReturn = NULL;
++ // asprintf allocates appropriate-sized buffer
++ asprintf(&serviceNameToReturn, "type_%s", serviceName->str);
++ g_string_free(serviceName, TRUE);
++ return serviceNameToReturn;
++}
++
++static char* getAccountKey(const char* username, const char* serviceName)
++{
++ if (!username || !serviceName)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getAccountKey called with username=\"%s\" and serviceName=\"%s\""), username, serviceName);
++ return strdup("");
++ }
++ char *accountKey = NULL;
++ // asprintf allocates appropriate-sized buffer
++ asprintf(&accountKey, "%s_%s", username, serviceName);
++ return accountKey;
++}
++
++static char* getAuthRequestKey(const char* username, const char* serviceName, const char* remoteUsername)
++{
++ if (!username || !serviceName || !remoteUsername)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getAuthRequestKey - empty input parameter. username: %s, serviceName: %s, remoteUsername: %s"), username, serviceName, remoteUsername);
++ return strdup("");
++ }
++ char *authRequestKey = NULL;
++ // asprintf allocates appropriate-sized buffer
++ asprintf(&authRequestKey, "%s_%s_%s", username, serviceName, remoteUsername);
++ MojLogInfo(IMServiceApp::s_log, _T("getAuthRequestKey - username: %s, serviceName: %s, remoteUser: %s key: %s"), username, serviceName, remoteUsername, authRequestKey);
++ return authRequestKey;
++}
++
++static char* getAccountKeyFromPurpleAccount(PurpleAccount* account)
++{
++ if (!account)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getAccountKeyFromPurpleAccount called with empty account"));
++ return strdup("");
++ }
++ char* serviceName = getServiceNameFromPrplProtocolId(account);
++ char* username = getMojoFriendlyUsername(account->username, serviceName);
++ char* accountKey = getAccountKey(username, serviceName);
++
++ free(serviceName);
++ free(username);
++
++ return accountKey;
++}
++
++/**
++ * Returns a GString containing the special stanza to enable server-side presence update queue
++ * Clean up after yourself using g_string_free when you're done with the return value
++ */
++static GString* getEnableQueueStanza(PurpleAccount* account)
++{
++ GString* stanza = NULL;
++ if (account != NULL)
++ {
++ if (strcmp(account->protocol_id, "prpl-jabber") == 0)
++ {
++ stanza = g_string_new("");
++ PurpleConnection* pc = purple_account_get_connection(account);
++ if (pc == NULL)
++ {
++ return NULL;
++ }
++ const char* displayName = purple_connection_get_display_name(pc);
++ if (displayName == NULL)
++ {
++ return NULL;
++ }
++ g_string_append(stanza, "<iq from='");
++ g_string_append(stanza, displayName);
++ g_string_append(stanza, "' type='set'><query xmlns='google:queue'><enable/></query></iq>");
++ }
++ else if (strcmp(account->protocol_id, "prpl-aim") == 0)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("getEnableQueueStanza for AIM"));
++ stanza = g_string_new("true");
++ }
++ }
++ return stanza;
++}
++
++/**
++ * Returns a GString containing the special stanza to disable and flush the server-side presence update queue
++ * Clean up after yourself using g_string_free when you're done with the return value
++ */
++static GString* getDisableQueueStanza(PurpleAccount* account)
++{
++ GString* stanza = NULL;
++ if (account != NULL)
++ {
++ if (strcmp(account->protocol_id, "prpl-jabber") == 0)
++ {
++ stanza = g_string_new("");
++ PurpleConnection* pc = purple_account_get_connection(account);
++ if (pc == NULL)
++ {
++ return NULL;
++ }
++ const char* displayName = purple_connection_get_display_name(pc);
++ if (displayName == NULL)
++ {
++ return NULL;
++ }
++ g_string_append(stanza, "<iq from='");
++ g_string_append(stanza, displayName);
++ g_string_append(stanza, "' type='set'><query xmlns='google:queue'><disable/><flush/></query></iq>");
++ }
++ else if (strcmp(account->protocol_id, "prpl-aim") == 0)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("getDisableQueueStanza for AIM"));
++ stanza = g_string_new("false");
++ }
++ }
++ return stanza;
++}
++
++static void enableServerQueueForAccount(PurpleAccount* account)
++{
++ if (!account)
++ {
++ MojLogError(IMServiceApp::s_log, _T("enableServerQueueForAccount called with empty account"));
++ return;
++ }
++
++ PurplePluginProtocolInfo* prpl_info = NULL;
++ PurpleConnection* gc = purple_account_get_connection(account);
++ PurplePlugin* prpl = NULL;
++
++ if (gc != NULL)
++ {
++ prpl = purple_connection_get_prpl(gc);
++ }
++
++ if (prpl != NULL)
++ {
++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
++ }
++
++ if (prpl_info && prpl_info->send_raw)
++ {
++ GString* enableQueueStanza = getEnableQueueStanza(account);
++ if (enableQueueStanza != NULL)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("Enabling server queue for %s"), account->protocol_id);
++ prpl_info->send_raw(gc, enableQueueStanza->str, enableQueueStanza->len);
++ g_string_free(enableQueueStanza, TRUE);
++ }
++ }
++}
++
++static void disableServerQueueForAccount(PurpleAccount* account)
++{
++ if (!account)
++ {
++ MojLogError(IMServiceApp::s_log, _T("disableServerQueueForAccount called with empty account"));
++ return;
++ }
++ PurplePluginProtocolInfo* prpl_info = NULL;
++ PurpleConnection* gc = purple_account_get_connection(account);
++ PurplePlugin* prpl = NULL;
++
++ if (gc != NULL)
++ {
++ prpl = purple_connection_get_prpl(gc);
++ }
++
++ if (prpl != NULL)
++ {
++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
++ }
++
++ if (prpl_info && prpl_info->send_raw)
++ {
++ GString* disableQueueStanza = getDisableQueueStanza(account);
++ if (disableQueueStanza != NULL)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("Disabling server queue"));
++ prpl_info->send_raw(gc, disableQueueStanza->str, disableQueueStanza->len);
++ g_string_free(disableQueueStanza, TRUE);
++ }
++ }
++}
++
++/**
++ * Asking the gtalk server to enable/disable queueing of presence updates
++ * This is called when the screen is turned off (enable:true) or turned on (enable:false)
++ */
++bool LibpurpleAdapter::queuePresenceUpdates(bool enable)
++{
++ PurpleAccount* account;
++ GList* onlineAccountKeys = NULL;
++ GList* iterator = NULL;
++ char* accountKey = NULL;
++
++ onlineAccountKeys = g_hash_table_get_keys(s_onlineAccountData);
++ for (iterator = onlineAccountKeys; iterator != NULL; iterator = g_list_next(iterator))
++ {
++ accountKey = (char*)iterator->data;
++ account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ if (account)
++ {
++ /*
++ * enabling/disabling server queue is supported by gtalk (jabber) or aim
++ */
++ if ((strcmp(account->protocol_id, "prpl-jabber") == 0) ||
++ (strcmp(account->protocol_id, "prpl-aim") == 0))
++ {
++ if (enable)
++ {
++ enableServerQueueForAccount(account);
++ }
++ else
++ {
++ disableServerQueueForAccount(account);
++ }
++ }
++ }
++ }
++ return TRUE;
++}
++
++
++static int getPalmAvailabilityFromPurpleAvailability(int prplAvailability)
++{
++ switch (prplAvailability)
++ {
++ case PURPLE_STATUS_UNSET:
++ return PalmAvailability::NO_PRESENCE;
++ case PURPLE_STATUS_OFFLINE:
++ return PalmAvailability::OFFLINE;
++ case PURPLE_STATUS_AVAILABLE:
++ return PalmAvailability::ONLINE;
++ case PURPLE_STATUS_UNAVAILABLE:
++ return PalmAvailability::IDLE;
++ case PURPLE_STATUS_INVISIBLE:
++ return PalmAvailability::INVISIBLE;
++ case PURPLE_STATUS_AWAY:
++ return PalmAvailability::IDLE;
++ case PURPLE_STATUS_EXTENDED_AWAY:
++ return PalmAvailability::IDLE;
++ case PURPLE_STATUS_MOBILE:
++ return PalmAvailability::MOBILE;
++ case PURPLE_STATUS_TUNE:
++ return PalmAvailability::ONLINE;
++ default:
++ return PalmAvailability::OFFLINE;
++ }
++}
++
++
++static PurpleStatusPrimitive getPurpleAvailabilityFromPalmAvailability(int palmAvailability)
++{
++ switch (palmAvailability)
++ {
++ case PalmAvailability::ONLINE:
++ return PURPLE_STATUS_AVAILABLE;
++ case PalmAvailability::MOBILE:
++ return PURPLE_STATUS_MOBILE;
++ case PalmAvailability::IDLE:
++ return PURPLE_STATUS_AWAY;
++ case PalmAvailability::INVISIBLE:
++ return PURPLE_STATUS_INVISIBLE;
++ case PalmAvailability::OFFLINE:
++ return PURPLE_STATUS_OFFLINE;
++ default:
++ return PURPLE_STATUS_OFFLINE;
++ }
++}
++
++/*
++ * End of helper methods
++ */
++
++/*
++ * Callbacks
++ */
++
++static void buddy_signed_on_off_cb(PurpleBuddy* buddy, gpointer data)
++{
++ LSError lserror;
++ LSErrorInit(&lserror);
++
++ PurpleAccount* account = purple_buddy_get_account(buddy);
++ char* accountKey = getAccountKeyFromPurpleAccount(account);
++ const char* accountId = (const char*)g_hash_table_lookup(s_AccountIdsData, accountKey);
++ if (NULL == accountId) {
++ MojLogError(IMServiceApp::s_log, _T("buddy_signed_on_off_cb: accountId not found in table. accountKey: %s"), accountKey);
++ free(accountKey);
++ return;
++ }
++
++ char* serviceName = getServiceNameFromPrplProtocolId(account);
++ PurpleStatus* activeStatus = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
++ /*
++ * Getting the new availability
++ */
++ int newStatusPrimitive = purple_status_type_get_primitive(purple_status_get_type(activeStatus));
++ int newAvailabilityValue = getPalmAvailabilityFromPurpleAvailability(newStatusPrimitive);
++ PurpleBuddyIcon* icon = purple_buddy_get_icon(buddy);
++ const char* customMessage = "";
++ char* buddyAvatarLocation = NULL;
++
++ char* UserName = getMojoFriendlyUsername(account->username, serviceName);
++ char* templateId = getMojoFriendlyTemplateID(serviceName);
++
++ if (s_PrefsHandler->GetBoolPreference("Avatar", templateId, UserName) == true)
++ {
++ if (icon != NULL)
++ {
++ buddyAvatarLocation = purple_buddy_icon_get_full_path(icon);
++ }
++ }
++ else
++ {
++ buddyAvatarLocation = (char*)"";
++ }
++
++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, UserName) == true)
++ {
++ if (buddy->server_alias == NULL)
++ {
++ buddy->server_alias = (char*)"";
++ }
++ }
++ else
++ {
++ if (buddy->alias == NULL)
++ {
++ buddy->alias = (char*)"";
++ }
++ }
++
++ customMessage = purple_status_get_attr_string(activeStatus, "message");
++ if (customMessage == NULL)
++ {
++ customMessage = "";
++ }
++
++ PurpleGroup* group = purple_buddy_get_group(buddy);
++ const char* groupName = purple_group_get_name(group);
++ if (groupName == NULL)
++ {
++ groupName = "";
++ }
++
++ //Special for Office Communicator. (Remove 'sip:' prefix)
++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0)
++ {
++ if (strstr(buddy->name, "sip:") != NULL)
++ {
++ GString *SIPBuddyAlias = g_string_new(buddy->name);
++ g_string_erase(SIPBuddyAlias, 0, 4);
++ buddy->name = SIPBuddyAlias->str;
++ }
++ }
++
++ // call into the imlibpurpletransport
++ // buddy->name is stored in the imbuddyStatus DB kind in the libpurple format - ie. for AIM without the "@aol.com" so that is how we need to search for it
++ s_imServiceHandler->updateBuddyStatus(accountId, serviceName, buddy->name, newAvailabilityValue,customMessage, groupName, buddyAvatarLocation);
++
++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, UserName) == true)
++ {
++ g_message(
++ "%s says: %s's presence: availability: '%i', custom message: '%s', avatar location: '%s', display name: '%s', group name: '%s'",
++ __FUNCTION__, buddy->name, newAvailabilityValue, customMessage, buddyAvatarLocation, buddy->server_alias, groupName);
++ }
++ else
++ {
++ g_message(
++ "%s says: %s's presence: availability: '%i', custom message: '%s', avatar location: '%s', display name: '%s', group name: '%s'",
++ __FUNCTION__, buddy->name, newAvailabilityValue, customMessage, buddyAvatarLocation, buddy->alias, groupName);
++ }
++
++ free(serviceName);
++ free(accountKey);
++
++ /* if (buddyAvatarLocation)
++ {
++ g_free(buddyAvatarLocation);
++ } */
++}
++
++static void buddy_status_changed_cb(PurpleBuddy* buddy, PurpleStatus* old_status, PurpleStatus* new_status,
++ gpointer unused)
++{
++ /*
++ * Getting the new availability
++ */
++ int newStatusPrimitive = purple_status_type_get_primitive(purple_status_get_type(new_status));
++ int newAvailabilityValue = getPalmAvailabilityFromPurpleAvailability(newStatusPrimitive);
++
++ /*
++ * Getting the new custom message
++ */
++ const char* customMessage = purple_status_get_attr_string(new_status, "message");
++ if (customMessage == NULL)
++ {
++ customMessage = "";
++ }
++
++ LSError lserror;
++ LSErrorInit(&lserror);
++
++ PurpleAccount* account = purple_buddy_get_account(buddy);
++ char* accountKey = getAccountKeyFromPurpleAccount(account);
++ const char* accountId = (const char*)g_hash_table_lookup(s_AccountIdsData, accountKey);
++ if (NULL == accountId) {
++ MojLogError(IMServiceApp::s_log, _T("buddy_status_changed_cb: accountId not found in table. accountKey: %s"), accountKey);
++ free(accountKey);
++ return;
++ }
++ char* serviceName = getServiceNameFromPrplProtocolId(account);
++
++ PurpleBuddyIcon* icon = purple_buddy_get_icon(buddy);
++ char* buddyAvatarLocation = NULL;
++
++ char* UserName = getMojoFriendlyUsername(account->username, serviceName);
++ char* templateId = getMojoFriendlyTemplateID(serviceName);
++
++ if (s_PrefsHandler->GetBoolPreference("Avatar", templateId, UserName) == true)
++ {
++ if (icon != NULL)
++ {
++ buddyAvatarLocation = purple_buddy_icon_get_full_path(icon);
++ }
++ }
++ else
++ {
++ buddyAvatarLocation = (char*)"";
++ }
++
++ PurpleGroup* group = purple_buddy_get_group(buddy);
++ const char* groupName = purple_group_get_name(group);
++ if (groupName == NULL)
++ {
++ groupName = "";
++ }
++
++ //Special for Office Communicator. (Remove 'sip:' prefix)
++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0)
++ {
++ if (strstr(buddy->name, "sip:") != NULL)
++ {
++ GString *SIPBuddyAlias = g_string_new(buddy->name);
++ g_string_erase(SIPBuddyAlias, 0, 4);
++ buddy->name = SIPBuddyAlias->str;
++ }
++ }
++
++ // call into the imlibpurpletransport
++ // buddy->name is stored in the imbuddyStatus DB kind in the libpurple format - ie. for AIM without the "@aol.com" so that is how we need to search for it
++ s_imServiceHandler->updateBuddyStatus(accountId, serviceName, buddy->name, newAvailabilityValue, customMessage, groupName, buddyAvatarLocation);
++
++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, UserName) == true)
++ {
++ g_message(
++ "%s says: %s's presence: availability: '%i', custom message: '%s', avatar location: '%s', display name: '%s', group name: '%s'",
++ __FUNCTION__, buddy->name, newAvailabilityValue, customMessage, buddyAvatarLocation, buddy->server_alias, groupName);
++ }
++ else
++ {
++ g_message(
++ "%s says: %s's presence: availability: '%i', custom message: '%s', avatar location: '%s', display name: '%s', group name: '%s'",
++ __FUNCTION__, buddy->name, newAvailabilityValue, customMessage, buddyAvatarLocation, buddy->alias, groupName);
++ }
++
++ if (serviceName)
++ {
++ free(serviceName);
++ }
++ free(accountKey);
++
++ /* if (buddyAvatarLocation)
++ {
++ g_free(buddyAvatarLocation);
++ } */
++}
++
++static void buddy_avatar_changed_cb(PurpleBuddy* buddy)
++{
++ PurpleStatus* activeStatus = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
++ MojLogInfo(IMServiceApp::s_log, _T("buddy avatar changed for %s"), buddy->name);
++ buddy_status_changed_cb(buddy, activeStatus, activeStatus, NULL);
++}
++
++/*
++ * Called after we remove a buddy from out list
++ */
++static void buddy_removed_cb(PurpleBuddy* buddy)
++{
++ // nothing to do...
++ MojLogInfo(IMServiceApp::s_log, _T("buddy removed %s"), buddy->name);
++}
++
++/*
++ * Called both after we add a buddy to our list and when we accept a remote users' invitation to add us to their list
++ * buddy is the new buddy
++ */
++static void buddy_added_cb(PurpleBuddy* buddy)
++{
++ // nothing to do...
++ MojLogInfo(IMServiceApp::s_log, _T("buddy added %s"), buddy->name);
++}
++
++// testing. Never gets called...
++//static void sent_message_cb (PurpleAccount *account, const char *receiver, const char *message)
++//{
++// MojLogInfo(IMServiceApp::s_log, _T("im message sent: %s, receiver %s"), message, receiver);
++//}
++
++bool LibpurpleAdapter::getFullBuddyList(const char* serviceName, const char* username)
++{
++ MojLogInfo(IMServiceApp::s_log, "%s called.", __FUNCTION__);
++
++ if (!serviceName || !username)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getBuddyList: Invalid parameter. Please double check the passed parameters."));
++ return FALSE;
++ }
++
++ if (s_loginState == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getBuddyList: s_loginState still null."));
++ return FALSE;
++ }
++
++ MojLogInfo(IMServiceApp::s_log, _T("getFullBuddyList: Parameters: serviceName %s, username %s"), serviceName, username);
++
++ /*
++ * Send over the full buddy list if the account is already logged in
++ */
++ bool success;
++ char* accountKey = getAccountKey(username, serviceName);
++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ if (account == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getFullBuddyList: ERROR: No account for %s on %s"), username, serviceName);
++ MojLogError(IMServiceApp::s_log, _T("getFullBuddyList !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! accountKey: %s"), accountKey);
++ success = FALSE;
++ }
++ else
++ {
++ success = TRUE;
++ GSList* buddyList = purple_find_buddies(account, NULL);
++ MojObject buddyListObj;
++ if (!buddyList)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getFullBuddyList: WARNING: the buddy list was NULL, returning empty buddy list. accountKey %s"), accountKey);
++ s_loginState->buddyListResult(serviceName, username, buddyListObj, true);
++ }
++
++ GSList* buddyIterator = NULL;
++ PurpleBuddy* buddyToBeAdded = NULL;
++ PurpleGroup* group = NULL;
++ char* buddyAvatarLocation = NULL;
++
++ //printf("\n\n\n\n\n\n ---------------- BUDDY LIST SIZE: %d----------------- \n\n\n\n\n\n", g_slist_length(buddyList));
++ for (buddyIterator = buddyList; buddyIterator != NULL; buddyIterator = buddyIterator->next)
++ {
++ MojObject buddyObj;
++ buddyObj.clear(MojObject::TypeArray);
++ buddyToBeAdded = (PurpleBuddy*)buddyIterator->data;
++
++ buddyObj.putString("username", buddyToBeAdded->name);
++ buddyObj.putString("serviceName", serviceName);
++
++ group = purple_buddy_get_group(buddyToBeAdded);
++ const char* groupName = purple_group_get_name(group);
++ buddyObj.putString("group", groupName);
++
++ /*
++ * Getting the availability
++ */
++ PurpleStatus* status = purple_presence_get_active_status(purple_buddy_get_presence(buddyToBeAdded));
++ int newStatusPrimitive = purple_status_type_get_primitive(purple_status_get_type(status));
++ int availability = getPalmAvailabilityFromPurpleAvailability(newStatusPrimitive);
++ buddyObj.putInt("availability", availability);
++
++ char* templateId = getMojoFriendlyTemplateID((char*)serviceName);
++
++ //Special for Office Communicator. (Remove 'sip:' prefix)
++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0)
++ {
++ if (strstr(buddyToBeAdded->name, "sip:") != NULL)
++ {
++ GString *SIPBuddyAlias = g_string_new(buddyToBeAdded->name);
++ g_string_erase(SIPBuddyAlias, 0, 4);
++ buddyObj.putString("displayName", SIPBuddyAlias->str);
++ }
++ else
++ {
++ buddyObj.putString("displayName", buddyToBeAdded->name);
++ }
++ }
++ else
++ {
++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, username) == true)
++ {
++ if (buddyToBeAdded->server_alias != NULL)
++ {
++ buddyObj.putString("displayName", buddyToBeAdded->server_alias);
++ }
++ }
++ else
++ {
++ if (buddyToBeAdded->alias != NULL)
++ {
++ buddyObj.putString("displayName", buddyToBeAdded->alias);
++ }
++ }
++ }
++
++ PurpleBuddyIcon* icon = purple_buddy_get_icon(buddyToBeAdded);
++
++ if (s_PrefsHandler->GetBoolPreference("Avatar", templateId, username) == true)
++ {
++ if (icon != NULL)
++ {
++ buddyAvatarLocation = purple_buddy_icon_get_full_path(icon);
++ buddyObj.putString("avatar", buddyAvatarLocation);
++ }
++ else
++ {
++ buddyObj.putString("avatar", "");
++ }
++ }
++ else
++ {
++ buddyObj.putString("avatar", "");
++ }
++
++ const char* customMessage = purple_status_get_attr_string(status, "message");
++ if (customMessage != NULL)
++ {
++ buddyObj.putString("status", customMessage);
++ }
++
++ if (s_PrefsHandler->GetBoolPreference("Alias", templateId, username) == true)
++ {
++ g_message("%s says: %s's presence: availability: '%d', custom message: '%s', avatar location: '%s', display name: '%s', group name:'%s'",
++ __FUNCTION__, buddyToBeAdded->name, availability, customMessage, buddyAvatarLocation, buddyToBeAdded->server_alias, groupName);
++ }
++ else
++ {
++ g_message("%s says: %s's presence: availability: '%d', custom message: '%s', avatar location: '%s', display name: '%s', group name:'%s'",
++ __FUNCTION__, buddyToBeAdded->name, availability, customMessage, buddyAvatarLocation, buddyToBeAdded->alias, groupName);
++ }
++
++ if (buddyAvatarLocation)
++ {
++ g_free(buddyAvatarLocation);
++ buddyAvatarLocation = NULL;
++ }
++
++ buddyListObj.push(buddyObj);
++ }
++
++ s_loginState->buddyListResult(serviceName, username, buddyListObj, true);
++
++ //TODO free the buddyList object???
++ }
++
++ if (accountKey)
++ {
++ free(accountKey);
++ }
++ return success;
++}
++
++typedef struct _LoginDetails
++{
++ PurpleAccount* account;
++ char* accountKey;
++ char* serviceName;
++ char* username;
++ gpointer loginState;
++} LoginDetails;
++
++gboolean BuddyListRefreshCallback(gpointer data)
++{
++ LoginDetails *logindetails = (LoginDetails*)data;
++
++ void* blist_handle = purple_blist_get_handle();
++ static int handle;
++ gpointer loginState = logindetails->loginState;
++
++ char* accountKey = logindetails->accountKey;
++ PurpleAccount* loggedInAccount = (PurpleAccount*)logindetails->account;
++ char* serviceName = logindetails->serviceName;
++ char* username = logindetails->username;
++
++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL)
++ {
++ /*
++ * If the account is not pending anymore (which means login either already failed or succeeded)
++ * then we shouldn't have gotten to this point since we should have cancelled the timer
++ */
++ MojLogError(IMServiceApp::s_log,
++ _T("WARNING: we shouldn't have gotten to BuddyListRefreshCallback since the account is not logged in. accountKey %s"), accountKey);
++ free(accountKey);
++ return TRUE;
++ }
++
++ /*
++ * Remove the timer
++ */
++ guint BuddyListtimerHandle = (guint)g_hash_table_lookup(s_accountBuddyListTimers, accountKey);
++ purple_timeout_remove(BuddyListtimerHandle);
++ g_hash_table_remove(s_accountBuddyListTimers, accountKey);
++
++ // Don't free accountKey because s_onlineAccountData has a reference to it.
++ MojLogInfo(IMServiceApp::s_log, _T("account_logged_in_cb: inserting account into onlineAccountData hash table. accountKey %s"), accountKey);
++ g_hash_table_insert(s_onlineAccountData, accountKey, loggedInAccount);
++ g_hash_table_remove(s_pendingAccountData, accountKey);
++
++ MojLogInfo(IMServiceApp::s_log, _T("Account connected..."));
++
++ //reply with login success
++ if (loginState)
++ {
++ ((LoginCallbackInterface*)loginState)->loginResult(serviceName, username, LoginCallbackInterface::LOGIN_SUCCESS, false, ERROR_NO_ERROR, false);
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("ERROR: account_logged_in_cb called with loginState=NULL"));
++ }
++
++ if (s_registeredForPresenceUpdateSignals == FALSE)
++ {
++ purple_signal_connect(blist_handle, "buddy-status-changed", &handle, PURPLE_CALLBACK(buddy_status_changed_cb),
++ loginState);
++ purple_signal_connect(blist_handle, "buddy-signed-on", &handle, PURPLE_CALLBACK(buddy_signed_on_off_cb),
++ GINT_TO_POINTER(TRUE));
++ purple_signal_connect(blist_handle, "buddy-signed-off", &handle, PURPLE_CALLBACK(buddy_signed_on_off_cb),
++ GINT_TO_POINTER(FALSE));
++ purple_signal_connect(blist_handle, "buddy-icon-changed", &handle, PURPLE_CALLBACK(buddy_avatar_changed_cb),
++ GINT_TO_POINTER(FALSE));
++ purple_signal_connect(blist_handle, "buddy-removed", &handle, PURPLE_CALLBACK(buddy_removed_cb),
++ GINT_TO_POINTER(FALSE));
++ purple_signal_connect(blist_handle, "buddy-added", &handle, PURPLE_CALLBACK(buddy_added_cb),
++ GINT_TO_POINTER(FALSE));
++
++ // testing. Doesn't work: error - "Signal data for sent-im-msg not found". Need to figure out the right handle
++// purple_signal_connect(purple_connections_get_handle(), "sent-im-msg", &handle, PURPLE_CALLBACK(sent_message_cb),
++// GINT_TO_POINTER(FALSE));
++ s_registeredForPresenceUpdateSignals = TRUE;
++ }
++
++ //Refresh the full buddy list
++ MojLogError(IMServiceApp::s_log, _T("BuddyListRefreshCallback accountKey: %s"), accountKey);
++
++ return TRUE;
++}
++
++static void account_logged_in_cb(PurpleConnection* gc, gpointer loginState)
++{
++ PurpleAccount* loggedInAccount = purple_connection_get_account(gc);
++ g_return_if_fail(loggedInAccount != NULL);
++
++ char* serviceName = getServiceNameFromPrplProtocolId(loggedInAccount);
++ char* username = getMojoFriendlyUsername(loggedInAccount->username, serviceName);
++ char* accountKey = getAccountKey(username, serviceName);
++
++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL)
++ {
++ // we were online. why are we getting notified that we're connected again? we were never disconnected.
++ // mark the account online just to be sure?
++ MojLogError(IMServiceApp::s_log, _T("account_logged_in_cb: account already online. why are we getting notified? accountKey %s"), accountKey);
++ free(serviceName);
++ free(username);
++ free(accountKey);
++ return;
++ }
++
++ /*
++ * cancel the connect timeout for this account
++ */
++ guint timerHandle = (guint)g_hash_table_lookup(s_accountLoginTimers, accountKey);
++ purple_timeout_remove(timerHandle);
++ g_hash_table_remove(s_accountLoginTimers, accountKey);
++
++ //Create timer to refresh the buddy list after login
++ LoginDetails* logindetails = g_new0(LoginDetails, 1);
++
++ logindetails->accountKey = accountKey;
++ logindetails->serviceName = serviceName;
++ logindetails->username = username;
++ logindetails->loginState = loginState;
++ logindetails->account = loggedInAccount;
++
++ char* templateId = getMojoFriendlyTemplateID((char*)serviceName);
++ char* BuddyListTimeOut = s_PrefsHandler->GetStringPreference("BuddyListTimeOut", templateId, username);
++ static const guint BUDDY_LIST_REFRESH = (guint)atoi(BuddyListTimeOut);
++ guint BuddyListtimerHandle = purple_timeout_add_seconds(BUDDY_LIST_REFRESH, BuddyListRefreshCallback, logindetails);
++ g_hash_table_insert(s_accountBuddyListTimers, accountKey, (gpointer)BuddyListtimerHandle);
++
++ MojLogError(IMServiceApp::s_log, _T("Login successful. Waiting %s seconds to ensure successful buddy list retrieval."),BuddyListTimeOut);
++}
++
++static void account_signed_off_cb(PurpleConnection* gc, gpointer loginState)
++{
++ PurpleAccount* account = purple_connection_get_account(gc);
++ g_return_if_fail(account != NULL);
++
++ char* accountKey = getAccountKeyFromPurpleAccount(account);
++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("account_signed_off_cb: removing account from onlineAccountData hash table. accountKey %s"), accountKey);
++ g_hash_table_remove(s_onlineAccountData, accountKey);
++ }
++ else if (g_hash_table_lookup(s_pendingAccountData, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_pendingAccountData, accountKey);
++ }
++ else
++ {
++ // Already signed off this account (or never signed in) so just return
++ free(accountKey);
++ return;
++ }
++
++ g_hash_table_remove(s_ipAddressesBoundTo, accountKey);
++ //g_hash_table_remove(connectionTypeData, accountKey);
++
++ MojLogInfo(IMServiceApp::s_log, _T("Account disconnected..."));
++
++ if (g_hash_table_lookup(s_offlineAccountData, accountKey) == NULL)
++ {
++ /*
++ * Keep the PurpleAccount struct to reuse in future logins
++ */
++ g_hash_table_insert(s_offlineAccountData, accountKey, account);
++ }
++
++ // reply with signed off
++ if (loginState)
++ {
++ char* serviceName = getServiceNameFromPrplProtocolId(account);
++ char* myMojoFriendlyUsername = getMojoFriendlyUsername(account->username, serviceName);
++ ((LoginCallbackInterface*)loginState)->loginResult(serviceName, myMojoFriendlyUsername, LoginCallbackInterface::LOGIN_SIGNED_OFF, false, ERROR_NO_ERROR, true);
++ free(serviceName);
++ free(myMojoFriendlyUsername);
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("ERROR: account_logged_in_cb called with loginState=NULL"));
++ }
++
++ free(accountKey);
++}
++
++/*
++ * This callback is called if a) the login attempt failed, or b) login was successful but the session was closed
++ * (e.g. connection problems, etc).
++ */
++static void account_login_failed_cb(PurpleConnection* gc, PurpleConnectionError type, const gchar* description,
++ gpointer loginState)
++{
++ MojLogInfo(IMServiceApp::s_log, _T("account_login_failed is called with description %s"), description);
++
++ PurpleAccount* account = purple_connection_get_account(gc);
++ g_return_if_fail(account != NULL);
++
++ gboolean loggedOut = FALSE;
++ bool noRetry = true;
++ char* serviceName = getServiceNameFromPrplProtocolId(account);
++ char* username = getMojoFriendlyUsername(account->username, serviceName);
++ char* accountKey = getAccountKey(username, serviceName);
++
++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL)
++ {
++ /*
++ * We were online on this account and are now disconnected because either a) the data connection is dropped,
++ * b) the server is down, or c) the user has logged in from a different location and forced this session to
++ * get closed.
++ */
++ // Special handling for broken network connection errors (due to bad coverage or flight mode)
++ if (type == 0)//PURPLE_CONNECTION_ERROR_NETWORK_ERROR)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("We had a network error. Reason: %s, prpl error code: %i"), description, type);
++ noRetry = false;
++ }
++ else
++ {
++ loggedOut = TRUE;
++ MojLogInfo(IMServiceApp::s_log, _T("We were logged out. Reason: %s, prpl error code: %i"), description, type);
++ }
++ }
++ else
++ {
++ /*
++ * cancel the connect timeout for this account
++ */
++ guint timerHandle = (guint)g_hash_table_lookup(s_accountLoginTimers, accountKey);
++ purple_timeout_remove(timerHandle);
++ g_hash_table_remove(s_accountLoginTimers, accountKey);
++
++ //Cancel the buddy list timer
++ guint BuddyListtimerHandle = (guint)g_hash_table_lookup(s_accountBuddyListTimers, accountKey);
++ purple_timeout_remove(BuddyListtimerHandle);
++ g_hash_table_remove(s_accountBuddyListTimers, accountKey);
++
++ if (g_hash_table_lookup(s_pendingAccountData, accountKey) == NULL)
++ {
++ /*
++ * This account was in neither of the account data lists (online or pending). We must have logged it out
++ * and not cared about letting mojo know about it (probably because mojo went down and came back up and
++ * thought that the account was logged out anyways)
++ */
++ MojLogError(IMServiceApp::s_log, _T("account_login_failed_cb: account in neither online or pending list. Why are we getting logged out? accountKey: %s description %s:"),
++ accountKey, description);
++ // don't leak!
++ free(serviceName);
++ free(username);
++ free(accountKey);
++ return;
++ }
++ else
++ {
++ g_hash_table_remove(s_pendingAccountData, accountKey);
++ }
++ }
++
++ const char* mojoFriendlyErrorCode = getMojoFriendlyErrorCode(type);
++ const char* accountBoundToIpAddress = (char*)g_hash_table_lookup(s_ipAddressesBoundTo, accountKey);
++ const char* connectionType = (char*)g_hash_table_lookup(s_connectionTypeData, accountKey);
++
++ if (accountBoundToIpAddress == NULL)
++ {
++ accountBoundToIpAddress = "";
++ }
++
++ if (connectionType == NULL)
++ {
++ connectionType = "";
++ }
++ MojLogInfo(IMServiceApp::s_log, _T("account_login_failed_cb: removing account from onlineAccountData hash table. accountKey %s"), accountKey);
++ g_hash_table_remove(s_onlineAccountData, accountKey);
++ g_hash_table_remove(s_ipAddressesBoundTo, accountKey);
++ g_hash_table_remove(s_connectionTypeData, accountKey);
++
++ if (g_hash_table_lookup(s_offlineAccountData, accountKey) == NULL)
++ {
++ /*
++ * Keep the PurpleAccount struct to reuse in future logins
++ */
++ g_hash_table_insert(s_offlineAccountData, accountKey, account);
++ // don't free the accountKey in this case since now s_offlineAccountData points to it
++ accountKey = NULL;
++ }
++
++ // reply with login failed
++ if (loginState != NULL)
++ {
++ //TODO: determine if there are cases where noRetry should be false
++ //TODO: include "description" parameter because it had useful details?
++ ((LoginCallbackInterface*)loginState)->loginResult(serviceName, username, LoginCallbackInterface::LOGIN_FAILED, loggedOut, mojoFriendlyErrorCode, noRetry);
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("ERROR: account_login_failed_cb called with loginState=NULL"));
++ }
++ free(serviceName);
++ free(username);
++ if (NULL != accountKey)
++ free(accountKey);
++}
++
++static void account_status_changed(PurpleAccount* account, PurpleStatus* oldStatus, PurpleStatus* newStatus, gpointer loginState)
++{
++ printf("\n\n ACCOUNT STATUS CHANGED \n\n");
++}
++
++/*
++ * This gets called then we decline a remote user's buddy invite
++ * TODO - what signal gets emitted when a remote user declines our invitation???
++ */
++static void account_auth_deny_cb(PurpleAccount* account, const char* remote_user)
++{
++ MojLogInfo(IMServiceApp::s_log, _T("account_auth_deny_cb called. account: %s, remote_user: %s"), account->username, remote_user);
++
++ // TODO this needs to happen when remote user declines our invite, not here...
++// char* serviceName = getServiceNameFromPrplProtocolId(account);
++// char* username = getMojoFriendlyUsername(account->username, serviceName);
++// char* usernameFromStripped = stripResourceFromGtalkUsername(remote_user, serviceName);
++//
++// // tell transport to delete the buddy and contacts
++// s_imServiceHandler->buddyInviteDeclined(serviceName, username, usernameFromStripped);
++//
++// // clean up
++// free(serviceName);
++// free(username);
++// free(usernameFromStripped);
++
++
++}
++
++/*
++ * This gets called then we accept a remote user's buddy invite
++ * log: account_auth_accept_cb called. account: palm@gmail.com/Home, buddy: palm3@gmail.com
++ */
++static void account_auth_accept_cb(PurpleAccount* account, const char* remote_user)
++{
++ // nothing to do
++ MojLogInfo(IMServiceApp::s_log, _T("account_auth_accept_cb called. account: %s, remote_user: %s"), account->username, remote_user);
++}
++
++
++void incoming_message_cb(PurpleConversation* conv, const char* who, const char* alias, const char* message,
++ PurpleMessageFlags flags, time_t mtime)
++{
++ /*
++ * snippet taken from nullclient
++ */
++ const char* usernameFrom;
++ if (who &&* who)
++ usernameFrom = who;
++ else if (alias &&* alias)
++ usernameFrom = alias;
++ else
++ usernameFrom = "";
++
++ if ((flags & PURPLE_MESSAGE_RECV) != PURPLE_MESSAGE_RECV)
++ {
++ /* this is a sent message. ignore it. */
++ return;
++ }
++
++ PurpleAccount* account = purple_conversation_get_account(conv);
++
++ // these never return null...
++ char* serviceName = getServiceNameFromPrplProtocolId(account);
++ char* username = getMojoFriendlyUsername(account->username, serviceName);
++
++ if (strcmp(username, usernameFrom) == 0) // TODO: should this compare use account->username?
++ {
++ /* We get notified even though we sent the message. Just ignore it */
++ free(serviceName);
++ free(username);
++ return;
++ }
++
++ if (strcmp(serviceName, SERVICENAME_AIM) == 0 && (strcmp(usernameFrom, "aolsystemmsg") == 0 || strcmp(usernameFrom,
++ "AOL System Msg") == 0))
++ {
++ /*
++ * ignore messages from aolsystemmsg telling us that we're logged in somewhere else
++ */
++ free(serviceName);
++ free(username);
++ return;
++ }
++
++ char* usernameFromStripped = stripResourceFromGtalkUsername(usernameFrom, serviceName);
++
++ //Special for Office Communicator. (Remove 'sip:' prefix)
++ if (strcmp(serviceName, SERVICENAME_SIPE) == 0)
++ {
++ if (strstr(usernameFromStripped, "sip:") != NULL)
++ {
++ GString *SIPBuddyAlias = g_string_new(usernameFromStripped);
++ g_string_erase(SIPBuddyAlias, 0, 4);
++ usernameFromStripped = (char*)SIPBuddyAlias->str;
++ }
++ }
++
++ // call the transport service incoming message handler
++ s_imServiceHandler->incomingIM(serviceName, username, usernameFromStripped, message);
++
++ free(serviceName);
++ free(username);
++ free(usernameFromStripped);
++}
++
++/*
++ * Called when a remote user requests authorization to be our buddy
++ *
++ * Note: this method will get called every time user logs in if there is a pending invitation.
++ */
++static void *request_authorize_cb (PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message,
++ gboolean on_list, PurpleAccountRequestAuthorizationCb authorize_cb, PurpleAccountRequestAuthorizationCb deny_cb,
++ void *user_data)
++{
++
++ MojLogInfo(IMServiceApp::s_log, _T("request_authorize_cb called. remote user: %s, id: %s, message: %s"), remote_user, id, message);
++
++ // these never return null...
++ char* serviceName = getServiceNameFromPrplProtocolId(account);
++ char* username = getMojoFriendlyUsername(account->username, serviceName);
++ char* usernameFromStripped = stripResourceFromGtalkUsername(remote_user, serviceName);
++
++ // Save off the authorize/deny callbacks to use later
++ AuthRequest *aa = g_new0(AuthRequest, 1);
++ aa->auth_cb = authorize_cb;
++ aa->deny_cb = deny_cb;
++ aa->data = user_data;
++ aa->remote_user = g_strdup(remote_user);
++ aa->alias = g_strdup(alias);
++ aa->account = account;
++
++ char *authRequestKey = getAuthRequestKey(username, serviceName, usernameFromStripped);
++ // if there is already an entry for this, we need to replace it since callback function pointers will change on login
++ // old object gets deleted by our destroy functions specified in the hash table construction
++ g_hash_table_replace(s_AuthorizeRequests, authRequestKey, aa);
++ // log the table
++ logAuthRequestTableValues();
++
++ // call back into IMServiceHandler to create a receivedBuddyInvite imCommand.
++ s_imServiceHandler->receivedBuddyInvite(serviceName, username, usernameFromStripped, message);
++
++ free(serviceName);
++ free(username);
++ free(usernameFromStripped);
++ // don't free the authRequestKey - it is not copied, but held onto for the life of the hash table once inserted
++
++ return NULL;
++
++}
++
++/*
++ * Not used
++ */
++void request_add_cb(PurpleAccount *account, const char *remote_user, const char *id, const char *alias, const char *message) {
++
++ MojLogInfo(IMServiceApp::s_log, _T("request_add_cb called. remote user: %s"), remote_user);
++}
++
++gboolean connectTimeoutCallback(gpointer data)
++{
++ char* accountKey = (char*)data;
++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_pendingAccountData, accountKey);
++ if (account == NULL)
++ {
++ /*
++ * If the account is not pending anymore (which means login either already failed or succeeded)
++ * then we shouldn't have gotten to this point since we should have cancelled the timer
++ */
++ MojLogError(IMServiceApp::s_log,
++ _T("WARNING: we shouldn't have gotten to connectTimeoutCallback since login had already failed/succeeded. accountKey %s"), accountKey);
++ free(accountKey);
++ return FALSE;
++ }
++
++ /*
++ * abort logging in since our connect timeout has hit before login either failed or succeeded
++ */
++ g_hash_table_remove(s_accountLoginTimers, accountKey);
++ g_hash_table_remove(s_pendingAccountData, accountKey);
++ g_hash_table_remove(s_ipAddressesBoundTo, accountKey);
++ g_hash_table_remove(s_accountBuddyListTimers, accountKey);
++
++ purple_account_disconnect(account);
++
++ if (s_loginState)
++ {
++ char* serviceName = getServiceNameFromPrplProtocolId(account);
++ char* username = getMojoFriendlyUsername(account->username, serviceName);
++
++ s_loginState->loginResult(serviceName, username, LoginCallbackInterface::LOGIN_TIMEOUT, false, ERROR_NETWORK_ERROR, true);
++
++ free(serviceName);
++ free(username);
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("ERROR: connectTimeoutCallback called with s_loginState=NULL. accountKey %s"), accountKey);
++ }
++
++ free(accountKey);
++ return FALSE;
++}
++
++/*
++ * End of callbacks
++ */
++
++/*
++ * libpurple initialization methods
++ */
++
++static GHashTable* getClientInfo(void)
++{
++ GHashTable* clientInfo = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ g_hash_table_insert(clientInfo, (void*)"name", (void*)"Palm Messaging");
++ g_hash_table_insert(clientInfo, (void*)"version", (void*)"");
++
++ return clientInfo;
++}
++
++static void initializeLibpurple()
++{
++ signal(SIGCHLD, SIG_IGN);
++
++ /* Set a custom user directory (optional) */
++ purple_util_set_user_dir(CUSTOM_USER_DIRECTORY);
++
++ /* We do not want any debugging for now to keep the noise to a minimum. */
++ purple_debug_set_enabled(TRUE);
++
++ /* Set the core-uiops, which is used to
++ * - initialize the ui specific preferences.
++ * - initialize the debug ui.
++ * - initialize the ui components for all the modules.
++ * - uninitialize the ui components for all the modules when the core terminates.
++ */
++ purple_core_set_ui_ops(&adapterCoreUIOps);
++
++ /* Set path to search for plugins. The core (libpurple) takes care of loading the
++ * core-plugins, which includes the protocol-plugins. So it is not essential to add
++ * any path here, but it might be desired, especially for ui-specific plugins. */
++ purple_plugins_add_search_path(CUSTOM_PLUGIN_PATH);
++
++ purple_eventloop_set_ui_ops(&adapterEventLoopUIOps);
++
++ // TODO - there is a memory leak in here...at least on desktop
++ if (!purple_core_init(UI_ID))
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("libpurple initialization failed."));
++ abort();
++ }
++
++ /* Create and load the buddylist. */
++ purple_set_blist(purple_blist_new());
++ purple_blist_load();
++
++ purple_buddy_icons_set_cache_dir("/var/luna/data/im-avatars");
++
++ s_libpurpleInitialized = TRUE;
++ MojLogInfo(IMServiceApp::s_log, _T("libpurple initialized.\n"));
++}
++/*
++ * End of libpurple initialization methods
++ */
++
++/*
++ * Service methods
++ */
++LibpurpleAdapter::LoginResult LibpurpleAdapter::login(LoginParams* params, LoginCallbackInterface* loginState)
++{
++ LoginResult result = LibpurpleAdapter::OK;
++
++ PurpleAccount* account;
++ char* prplProtocolId = NULL;
++ char* transportFriendlyUserName = NULL;
++ char* accountKey = NULL;
++ bool accountIsAlreadyOnline = FALSE;
++ bool accountIsAlreadyPending = FALSE;
++
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ PurpleAccount* alreadyActiveAccount = NULL;
++
++ if (!params || !params->serviceName || params->serviceName[0] == 0 || !params->username || params->username[0] == 0)
++ {
++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login with empty username or serviceName"));
++ result = LibpurpleAdapter::INVALID_CREDENTIALS;
++ goto error;
++ }
++
++ if (!params->localIpAddress)
++ {
++ params->localIpAddress = "";
++ }
++
++ if (!params->connectionType)
++ {
++ params->connectionType = "";
++ }
++
++ MojLogInfo(IMServiceApp::s_log, _T("Parameters: accountId %s, servicename %s, connectionType %s"), params->accountId, params->serviceName, params->connectionType);
++
++ if (s_libpurpleInitialized == FALSE)
++ {
++ initializeLibpurple();
++ }
++
++ /* libpurple variables */
++ accountKey = getAccountKey(params->username, params->serviceName);
++
++ // If this account id isn't yet stored, then keep track of it now.
++ if (g_hash_table_lookup(s_AccountIdsData, accountKey) == NULL)
++ {
++ g_hash_table_insert(s_AccountIdsData, accountKey, strdup(params->accountId));
++ }
++
++ /*
++ * Let's check to see if we're already logged in to this account or that we're already in the process of logging in
++ * to this account. This can happen when mojo goes down and comes back up.
++ */
++ alreadyActiveAccount = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ if (alreadyActiveAccount != NULL)
++ {
++ accountIsAlreadyOnline = TRUE;
++ }
++ else
++ {
++ alreadyActiveAccount = (PurpleAccount*)g_hash_table_lookup(s_pendingAccountData, accountKey);
++ if (alreadyActiveAccount != NULL)
++ {
++ accountIsAlreadyPending = TRUE;
++ }
++ }
++
++ if (alreadyActiveAccount != NULL)
++ {
++ /*
++ * We're either already logged in to this account or we're already in the process of logging in to this account
++ * (i.e. it's pending; waiting for server response)
++ */
++ char* accountBoundToIpAddress = (char*)g_hash_table_lookup(s_ipAddressesBoundTo, accountKey);
++ if (accountBoundToIpAddress != NULL && strcmp(params->localIpAddress, accountBoundToIpAddress) == 0)
++ {
++ /*
++ * We're using the right interface for this account
++ */
++ if (accountIsAlreadyPending)
++ {
++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login: We were already in the process of logging in. accountKey %s"), accountKey);
++ return LibpurpleAdapter::OK;
++ }
++ else if (accountIsAlreadyOnline)
++ {
++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login: We were already logged in to the requested account. accountKey %s"), accountKey);
++ return LibpurpleAdapter::ALREADY_LOGGED_IN;
++ }
++ }
++ else
++ {
++ /*
++ * We're not using the right interface. Close the current connection for this account and create a new one
++ */
++ MojLogError(IMServiceApp::s_log,
++ _T("LibpurpleAdapter::login: We have to logout and login again since the local IP address has changed. Logging out from account. accountKey %s"), accountKey);
++ /*
++ * Once the current connection is closed we don't want to let mojo know that the account was disconnected.
++ * Since mojo went down and came back up it didn't know that the account was connected anyways.
++ * So let's take the account out of the account data hash and then disconnect it.
++ */
++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("LibpurpleAdapter::login: removing account from onlineAccountData hash table. accountKey %s"), accountKey);
++ g_hash_table_remove(s_onlineAccountData, accountKey);
++ }
++ if (g_hash_table_lookup(s_pendingAccountData, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_pendingAccountData, accountKey);
++ }
++ purple_account_disconnect(alreadyActiveAccount);
++ }
++ }
++
++ /*
++ * Let's go through our usual login process
++ */
++
++ // TODO this currently ignores authentication token, but should check it as well when support for auth token is added
++ if (params->password == NULL || params->password[0] == 0)
++ {
++ MojLogError(IMServiceApp::s_log, _T("Error: null or empty password trying to log in to servicename %s"), params->serviceName);
++ result = LibpurpleAdapter::INVALID_CREDENTIALS;
++ goto error;
++ }
++ else
++ {
++ /* save the local IP address that we need to use */
++ // TODO - move this to #ifdef. If you are running imlibpurpletransport on desktop, but tethered to device, params->localIpAddress needs to be set to
++ // NULL otherwise login will fail...
++ if (params->localIpAddress != NULL && params->localIpAddress[0] != 0)
++ {
++ purple_prefs_remove("/purple/network/preferred_local_ip_address");
++ purple_prefs_add_string("/purple/network/preferred_local_ip_address", params->localIpAddress);
++ }
++ else
++ {
++#ifdef DEVICE
++ /*
++ * If we're on device you should not accept an empty ipAddress; it's mandatory to be provided
++ */
++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login with missing localIpAddress"));
++ result = FAILED;
++ goto error;
++#endif
++ }
++
++ /* save the local IP address that we need to use */
++ if (params->connectionType != NULL && params->connectionType[0] != 0)
++ {
++ g_hash_table_insert(s_connectionTypeData, accountKey, strdup(params->connectionType));
++ }
++
++ prplProtocolId = getPrplProtocolIdFromServiceName(params->serviceName);
++ /*
++ * If we've already logged in to this account before then re-use the old PurpleAccount struct
++ */
++ transportFriendlyUserName = getPrplFriendlyUsername(params->serviceName, params->username);
++ account = (PurpleAccount*)g_hash_table_lookup(s_offlineAccountData, accountKey);
++ if (!account)
++ {
++ /* Create the account */
++ account = purple_account_new(transportFriendlyUserName, prplProtocolId);
++ if (!account)
++ {
++ MojLogError(IMServiceApp::s_log, _T("LibpurpleAdapter::login failed to create new Purple account"));
++ result = LibpurpleAdapter::FAILED;
++ goto error;
++ }
++ }
++
++ //Load account preferences
++ char* templateId = getMojoFriendlyTemplateID((char*)params->serviceName);
++
++ if (strcmp((char*)params->serviceName, SERVICENAME_JABBER) == 0)
++ {
++ //Set Account Alias to jabber
++ purple_account_set_alias (account,"jabber");
++ }
++ if (strcmp((char*)params->serviceName,SERVICENAME_GTALK) == 0)
++ {
++ //Set Account Alias to gtalk
++ purple_account_set_alias (account,"gtalk");
++ }
++
++ MojString m_templateId;
++ m_templateId.assign(templateId);
++ s_PrefsHandler->setaccountprefs(m_templateId,account);
++ //Load account preferences
++ MojLogInfo(IMServiceApp::s_log, _T("Logging in..."));
++
++ purple_account_set_password(account, params->password);
++ }
++
++ if (result == LibpurpleAdapter::OK)
++ {
++ /* mark the account as pending */
++ g_hash_table_insert(s_pendingAccountData, accountKey, account);
++
++ if (params->localIpAddress != NULL && params->localIpAddress[0] != 0)
++ {
++ /* keep track of the local IP address that we bound to when logging in to this account */
++ g_hash_table_insert(s_ipAddressesBoundTo, accountKey, strdup(params->localIpAddress));
++ }
++
++ /* It's necessary to enable the account first. */
++ purple_account_set_enabled(account, UI_ID, TRUE);
++
++ /* Now, to connect the account, create a status and activate it. */
++
++ /*
++ * Create a timer for this account's login so it can fail the login after a timeout.
++ */
++ char* UserName = getMojoFriendlyUsername(params->username, params->serviceName);
++ char* templateId = getMojoFriendlyTemplateID((char*)params->serviceName);
++ static const guint CONNECT_TIMEOUT_SECONDS = (guint)atoi(s_PrefsHandler->GetStringPreference("LoginTimeOut", templateId, UserName));
++ guint timerHandle = purple_timeout_add_seconds(CONNECT_TIMEOUT_SECONDS, connectTimeoutCallback, accountKey);
++ g_hash_table_insert(s_accountLoginTimers, accountKey, (gpointer)timerHandle);
++
++ PurpleStatusPrimitive prim = getPurpleAvailabilityFromPalmAvailability(params->availability);
++ PurpleSavedStatus* savedStatus = purple_savedstatus_new(NULL, prim);
++ if (params->customMessage && params->customMessage[0])
++ {
++ purple_savedstatus_set_message(savedStatus, params->customMessage);
++ }
++ purple_savedstatus_activate_for_account(savedStatus, account);
++ }
++
++ error:
++
++ if (prplProtocolId)
++ {
++ free(prplProtocolId);
++ }
++ if (transportFriendlyUserName)
++ {
++ free(transportFriendlyUserName);
++ }
++
++ return result;
++}
++
++bool LibpurpleAdapter::logout(const char* serviceName, const char* username, LoginCallbackInterface* loginState)
++{
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ if (!username || !serviceName)
++ {
++ MojLogError(IMServiceApp::s_log, _T("Invalid logout parameter. Please double check the passed parameters."));
++ return FALSE;
++ }
++
++ bool success = TRUE;
++
++ MojLogInfo(IMServiceApp::s_log, _T("Parameters: servicename %s"), serviceName);
++
++ char* accountKey = getAccountKey(username, serviceName);
++
++ // Remove the accountId since a logout could be from the user removing the account
++ g_hash_table_remove(s_AccountIdsData, accountKey);
++
++ PurpleAccount* accountTologoutFrom = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ if (accountTologoutFrom == NULL)
++ {
++ accountTologoutFrom = (PurpleAccount*)g_hash_table_lookup(s_pendingAccountData, accountKey);
++ if (accountTologoutFrom == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("Trying to logout from an account that is not logged in. username %s, service name %s, accountKey %s"), username, serviceName, accountKey);
++ success = FALSE;
++ }
++ }
++
++ if (accountTologoutFrom != NULL)
++ {
++ purple_account_disconnect(accountTologoutFrom);
++ }
++
++ if (accountKey)
++ {
++ free(accountKey);
++ }
++ return success;
++}
++
++bool LibpurpleAdapter::setMyAvailability(const char* serviceName, const char* username, int availability)
++{
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ if (!serviceName || !username)
++ {
++ MojLogError(IMServiceApp::s_log, _T("setMyAvailability: Invalid parameter. Please double check the passed parameters."));
++ return FALSE;
++ }
++
++ MojLogInfo(IMServiceApp::s_log, _T("Parameters: serviceName %s, availability %i"), serviceName, availability);
++
++ bool retVal = FALSE;
++ char* accountKey = getAccountKey(username, serviceName);
++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ if (account == NULL)
++ {
++ //this should never happen based on MessagingService's logic
++ MojLogError(IMServiceApp::s_log,
++ _T("setMyAvailability was called on an account that wasn't logged in. serviceName: %s, availability: %i"),
++ serviceName, availability);
++ retVal = FALSE;
++ }
++ else
++ {
++ retVal = TRUE;
++ /*
++ * Let's get the current custom message and set it as well so that we don't overwrite it with ""
++ */
++ PurplePresence* presence = purple_account_get_presence(account);
++ const PurpleStatus* status = purple_presence_get_active_status(presence);
++ const PurpleValue* value = purple_status_get_attr_value(status, "message");
++ const char* customMessage = NULL;
++ if (value != NULL)
++ {
++ customMessage = purple_value_get_string(value);
++ }
++ if (customMessage == NULL)
++ {
++ customMessage = "";
++ }
++
++ PurpleStatusPrimitive prim = getPurpleAvailabilityFromPalmAvailability(availability);
++ PurpleStatusType* type = purple_account_get_status_type_with_primitive(account, prim);
++ GList* attrs = NULL;
++ attrs = g_list_append(attrs, (void*)"message");
++ attrs = g_list_append(attrs, (char*)customMessage);
++ purple_account_set_status_list(account, purple_status_type_get_id(type), TRUE, attrs);
++ }
++
++ // delete the key since it was just for lookup
++ free(accountKey);
++ return retVal;
++}
++
++bool LibpurpleAdapter::setMyCustomMessage(const char* serviceName, const char* username, const char* customMessage)
++{
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ if (!serviceName || !username || !customMessage)
++ {
++ MojLogError(IMServiceApp::s_log, _T("setMyCustomMessage: Invalid parameter. Please double check the passed parameters."));
++ return FALSE;
++ }
++
++ MojLogInfo(IMServiceApp::s_log, _T("Parameters: serviceName %s"), serviceName);
++
++ bool retVal = FALSE;
++ char* accountKey = getAccountKey(username, serviceName);
++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ if (account == NULL)
++ {
++ //this should never happen based on MessagingService's logic
++ MojLogError(IMServiceApp::s_log,
++ _T("setMyCustomMessage was called on an account that wasn't logged in. serviceName: %s"),
++ serviceName);
++ retVal = FALSE;
++ }
++ else
++ {
++ retVal = TRUE;
++ // get the account's current status type
++ PurpleStatusType* type = purple_status_get_type(purple_account_get_active_status(account));
++ GList* attrs = NULL;
++ attrs = g_list_append(attrs, (void*)"message");
++ attrs = g_list_append(attrs, (char*)customMessage);
++ purple_account_set_status_list(account, purple_status_type_get_id(type), TRUE, attrs);
++ }
++
++ // delete the key since it was just for lookup
++ free(accountKey);
++ return retVal;
++}
++
++/*
++ * Block this user from sending us messages
++ */
++LibpurpleAdapter::SendResult LibpurpleAdapter::blockBuddy(const char* serviceName, const char* username, const char* buddyUsername, bool block)
++{
++ bool success = FALSE;
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ if (!serviceName || !username || !buddyUsername)
++ {
++ MojLogError(IMServiceApp::s_log, _T("blockBuddy: null parameter. username %s, service name %s, buddyUsername %s"), username, serviceName, buddyUsername);
++ return INVALID_PARAMS;
++ }
++
++ char* accountKey = getAccountKey(username, serviceName);
++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ success = (account != NULL);
++ if (success)
++ {
++ // strip off the "@aol.com" if needed
++ char *transportFriendlyUserName = getPrplFriendlyUsername(serviceName, buddyUsername);
++ if (block)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("blockBuddy: deny %s"), transportFriendlyUserName);
++ purple_privacy_deny(account, transportFriendlyUserName, false, true);
++ }
++ else
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("blockBuddy: allow %s"), transportFriendlyUserName);
++ purple_privacy_allow(account, transportFriendlyUserName, false, true);
++ }
++ free(transportFriendlyUserName);
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, "blockBuddy: Trying to send from an account that is not logged in. username %s, service name %s, accountKey %s", username, serviceName, accountKey);
++ }
++
++ if (accountKey)
++ {
++ free(accountKey);
++ }
++
++ if (success)
++ return SENT;
++ else return USER_NOT_LOGGED_IN;
++}
++
++/*
++ * Remove a buddy from our account
++ */
++LibpurpleAdapter::SendResult LibpurpleAdapter::removeBuddy(const char* serviceName, const char* username, const char* buddyUsername)
++{
++ bool success = FALSE;
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ if (!serviceName || !username || !buddyUsername)
++ {
++ MojLogError(IMServiceApp::s_log, _T("removeBuddy: null parameter. username %s, service name %s, buddyUsername %s"), username, serviceName, buddyUsername);
++ return INVALID_PARAMS;
++ }
++
++ char* accountKey = getAccountKey(username, serviceName);
++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ success = (account != NULL);
++ if (success)
++ {
++ // strip off the "@aol.com" if needed
++ char *transportFriendlyUserName = getPrplFriendlyUsername(serviceName, buddyUsername);
++ MojLogInfo(IMServiceApp::s_log, _T("removeBuddy: %s"), transportFriendlyUserName);
++
++ PurpleBuddy* buddy = purple_find_buddy(account, transportFriendlyUserName);
++ if (NULL == buddy) {
++ MojLogError(IMServiceApp::s_log, _T("could not find buddy %s in list - cannot remove"), transportFriendlyUserName);
++ success = FALSE;
++ }
++ else {
++ PurpleGroup* group = purple_buddy_get_group(buddy);
++ // remove from server list
++ purple_account_remove_buddy(account, buddy, group);
++
++ // remove from buddy list - generates a "buddy-removed" signal
++ purple_blist_remove_buddy(buddy);
++ }
++
++ free(transportFriendlyUserName);
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, "removeBuddy: Trying to send from an account that is not logged in. username %s, service name %s, accountKey %s", username, serviceName, accountKey);
++ }
++
++ if (accountKey)
++ {
++ free(accountKey);
++ }
++
++ if (success)
++ return SENT;
++ else return USER_NOT_LOGGED_IN;
++}
++
++/*
++ * Add a buddy to our buddy list. Some services (GTalk) will require the buddy to authorize us to add them
++ */
++LibpurpleAdapter::SendResult LibpurpleAdapter::addBuddy(const char* serviceName, const char* username, const char* buddyUsername, const char* groupName)
++{
++ bool success = FALSE;
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ if (!serviceName || !username || !buddyUsername)
++ {
++ MojLogError(IMServiceApp::s_log, _T("addBuddy: null parameter. username %s, service name %s, buddyUsername %s"), username, serviceName, buddyUsername);
++ return INVALID_PARAMS;
++ }
++
++ char* accountKey = getAccountKey(username, serviceName);
++ PurpleAccount* account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ success = (account != NULL);
++ if (success)
++ {
++ // strip off the "@aol.com" if needed
++ char *transportFriendlyUserName = getPrplFriendlyUsername(serviceName, buddyUsername);
++ MojLogInfo(IMServiceApp::s_log, _T("addBuddy: %s"), transportFriendlyUserName);
++
++ PurpleBuddy* buddy = purple_buddy_new(account, transportFriendlyUserName, /*alias*/ NULL);
++
++ // add buddy to list
++ /*
++ void purple_blist_add_buddy ( PurpleBuddy * buddy,
++ PurpleContact * contact,
++ PurpleGroup * group,
++ PurpleBlistNode * node
++ )
++
++ Adds a new buddy to the buddy list.
++ The buddy will be inserted right after node or prepended to the group if node is NULL. If both are NULL, the buddy will be added to the "Buddies" group.
++ */
++ PurpleGroup *group = NULL;
++ if (NULL != groupName && *groupName != '\0') {
++ group = purple_find_group(groupName);
++ if (NULL == group){
++ // group not there - add it
++ MojLogInfo(IMServiceApp::s_log, _T("addBuddy: adding new group %s"), groupName);
++ group = purple_group_new(groupName);
++ }
++ }
++ purple_blist_add_buddy(buddy, NULL, group, NULL);
++
++ // add to server - note: this has to be called AFTER the purple_blist_add_buddy() call otherwise it seg faults...
++ purple_account_add_buddy(account, buddy);
++
++ // note - there seems to be an inconsistency in libpurple where AIM buddies added via purple appear offline until the account is logged off and on...
++ // see http://pidgin.im/pipermail/devel/2007-June/001517.html:
++ /* Such is not the case on AIM, however. The behavior I see here is that the
++ buddies appear in the Pidgin blist but always have an offline status, even
++ though I know at least four of these people are online. Looking at blist.xml
++ after the next shown flush in the debug window shows that nothing has been added
++ to the local list. The alias and buddy notes are not added, either. The status
++ remains incorrect until I restart Pidgin, disable and enable the account, or
++ switch to the offline status and then back to an online status. At this point,
++ the buddies are finally shown in blist.xml and statuses are correct; however,
++ the alias and notes string that were set at import are missing. Behavior is
++ identical on ICQ when importing a list of AIM buddies (which to my limited
++ knowledge does not require authorization).
++ */
++
++ free(transportFriendlyUserName);
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, "addBuddy: Trying to send from an account that is not logged in. username %s, service name %s, accountKey %s", username, serviceName, accountKey);
++ }
++
++ if (accountKey)
++ {
++ free(accountKey);
++ }
++
++ if (success)
++ return SENT;
++ else return USER_NOT_LOGGED_IN;
++}
++
++/*
++ * Authorize the remote user to be our buddy
++ */
++LibpurpleAdapter::SendResult LibpurpleAdapter::authorizeBuddy(const char* serviceName, const char* username, const char* fromUsername)
++{
++ MojLogInfo(IMServiceApp::s_log, _T("authorizeBuddy: username: %s, serviceName: %s, buddyUsername: %s"), username, serviceName, fromUsername);
++
++ if (!serviceName || !username || !fromUsername)
++ {
++ MojLogError(IMServiceApp::s_log, _T("authorizeBuddy: null parameter. cannot process command"));
++ return INVALID_PARAMS;
++ }
++
++ // if we got here, we need to be online...
++ char *accountKey = getAccountKey(username, serviceName);
++ PurpleAccount* onlineAccount = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++
++ if (onlineAccount == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("authorizeBuddy: account not online. accountKey: %s"), accountKey);
++ free (accountKey);
++ return USER_NOT_LOGGED_IN;
++ }
++ // free key since it was only used for lookup
++ free (accountKey);
++
++ // create the key and find the auth_and_add object in the s_AuthorizeRequests table
++ char *authRequestKey = getAuthRequestKey(username, serviceName, fromUsername);
++ AuthRequest *aa = (AuthRequest *)g_hash_table_lookup(s_AuthorizeRequests, authRequestKey);
++ if (NULL == aa)
++ {
++ MojLogError(IMServiceApp::s_log, "authorizeBuddy: cannot find auth request object - authRequestKey %s", authRequestKey);
++ // log the table
++ logAuthRequestTableValues();
++ free (authRequestKey);
++ return SEND_FAILED;
++ }
++
++ // authorize account
++ aa->auth_cb(aa->data);
++
++ // TODO - do we need to add this user to our buddy list? Libpurple seems to add it automatically - appears at next login.
++
++ // we are done with this request - remove it from list
++ // object and key held by table get deleted by our destroy functions specified in the hash table construction
++ g_hash_table_remove(s_AuthorizeRequests, authRequestKey);
++
++ // free key used for look-up
++ free (authRequestKey);
++
++ return SENT;
++}
++
++/*
++ * Decline the request from the remote user to be our buddy
++ */
++LibpurpleAdapter::SendResult LibpurpleAdapter::declineBuddy(const char* serviceName, const char* username, const char* fromUsername)
++{
++ MojLogError(IMServiceApp::s_log, _T("declineBuddy: username: %s, serviceName: %s, buddyUsername: %s"), username, serviceName, fromUsername);
++ if (!serviceName || !username || !fromUsername)
++ {
++ MojLogError(IMServiceApp::s_log, _T("declineBuddy: null parameter. cannot process command"));
++ return INVALID_PARAMS;
++ }
++
++ // if we got here, we need to be online...
++ char *accountKey = getAccountKey(username, serviceName);
++ PurpleAccount* onlineAccount = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ if (onlineAccount == NULL)
++ {
++ MojLogError(IMServiceApp::s_log, _T("declineBuddy: account not online. accountKey: %s"), accountKey);
++ free (accountKey);
++ return USER_NOT_LOGGED_IN;
++ }
++ // free key since it was only used for lookup
++ free (accountKey);
++
++ // create the key and find the auth_and_add object in the s_AuthorizeRequests table
++ char *authRequestKey = getAuthRequestKey(username, serviceName, fromUsername);
++ AuthRequest *aa = (AuthRequest *)g_hash_table_lookup(s_AuthorizeRequests, authRequestKey);
++ if (NULL == aa)
++ {
++ MojLogError(IMServiceApp::s_log, "declineBuddy: cannot find auth request object - authRequestKey %s", authRequestKey);
++ // log the table
++ logAuthRequestTableValues();
++ free (authRequestKey);
++ return SEND_FAILED;
++ }
++
++ aa->deny_cb(aa->data);
++
++ // we are done with this request - remove it from list
++ // object gets deleted by our destroy functions specified in the hash table construction
++ g_hash_table_remove(s_AuthorizeRequests, authRequestKey);
++
++ // free key used for look-up
++ free (authRequestKey);
++ return SENT;
++}
++
++LibpurpleAdapter::SendResult LibpurpleAdapter::sendMessage(const char* serviceName, const char* username, const char* usernameTo, const char* messageText)
++{
++ if (!serviceName || !username || !usernameTo || !messageText)
++ {
++ MojLogError(IMServiceApp::s_log, _T("sendMessage: Invalid parameter. Please double check the passed parameters."));
++ return LibpurpleAdapter::INVALID_PARAMS;
++ }
++
++ LibpurpleAdapter::SendResult retVal = LibpurpleAdapter::SENT;
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ char* accountKey = getAccountKey(username, serviceName);
++ PurpleAccount* accountToSendFrom = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++
++ if (accountToSendFrom == NULL)
++ {
++ retVal = LibpurpleAdapter::USER_NOT_LOGGED_IN;
++ MojLogError(IMServiceApp::s_log, _T("sendMessage: Trying to send from an account that is not logged in. username %s, service name %s, accountKey %s"), username, serviceName, accountKey);
++ }
++ else
++ {
++ // strip off the "@aol.com" if needed
++ char *transportFriendlyUserName = getPrplFriendlyUsername(serviceName, usernameTo);
++ PurpleConversation* purpleConversation = purple_conversation_new(PURPLE_CONV_TYPE_IM, accountToSendFrom, transportFriendlyUserName);
++ char* messageTextUnescaped = g_strcompress(messageText);
++
++ // replace this with the lower level call so we can try to get an error code back...
++ // calls common_send which calls serv_send_im (conversation.c)
++// purple_conv_im_send(purple_conversation_get_im_data(purpleConversation), messageTextUnescaped);
++// common_send(PurpleConversation *conv, const char *message, PurpleMessageFlags msgflags)
++// gc = purple_conversation_get_gc(conv);
++// err = serv_send_im(gc, purple_conversation_get_name(conv), sent, msgflags);
++ // we still don't seem to get an error value back there...returns 1, even for an invalid recipient
++ int err = serv_send_im(purple_conversation_get_gc(purpleConversation), purple_conversation_get_name(purpleConversation), messageTextUnescaped, (PurpleMessageFlags)0);
++ if (err < 0) {
++ retVal = LibpurpleAdapter::SEND_FAILED;
++ MojLogError(IMServiceApp::s_log, _T("sendMessage: serv_send_im returned err %d"), err);
++ }
++
++ free(messageTextUnescaped);
++ free(transportFriendlyUserName);
++ }
++ if (accountKey)
++ free(accountKey);
++ return retVal;
++}
++
++
++// Called by IMLoginState whenever a connection interface goes down.
++// If all==true, then all interfaces went down.
++bool LibpurpleAdapter::deviceConnectionClosed(bool all, const char* ipAddress)
++{
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ if (!ipAddress)
++ {
++ return FALSE;
++ }
++
++ PurpleAccount* account;
++ GSList* accountToLogoutList = NULL;
++ GList* iterator = NULL;
++
++ GList* onlineAndPendingAccountKeys = g_hash_table_get_keys(s_ipAddressesBoundTo);
++ for (iterator = onlineAndPendingAccountKeys; iterator != NULL; iterator = g_list_next(iterator))
++ {
++ char* accountKey = (char*)iterator->data;
++ char* accountBoundToIpAddress = (char*)g_hash_table_lookup(s_ipAddressesBoundTo, accountKey);
++ if (all == true || (accountBoundToIpAddress != NULL && strcmp(ipAddress, accountBoundToIpAddress) == 0))
++ {
++ bool accountWasLoggedIn = FALSE;
++
++ account = (PurpleAccount*)g_hash_table_lookup(s_onlineAccountData, accountKey);
++ if (account == NULL)
++ {
++ account = (PurpleAccount*)g_hash_table_lookup(s_pendingAccountData, accountKey);
++ if (account == NULL)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("account was not found in the hash"));
++ continue;
++ }
++ MojLogInfo(IMServiceApp::s_log, _T("Abandoning login"));
++ }
++ else
++ {
++ accountWasLoggedIn = TRUE;
++ MojLogInfo(IMServiceApp::s_log, _T("Logging out"));
++ }
++
++ if (g_hash_table_lookup(s_onlineAccountData, accountKey) != NULL)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("deviceConnectionClosed: removing account from onlineAccountData hash table. accountKey %s"), accountKey);
++ g_hash_table_remove(s_onlineAccountData, accountKey);
++ }
++ if (g_hash_table_lookup(s_pendingAccountData, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_pendingAccountData, accountKey);
++ }
++ if (g_hash_table_lookup(s_offlineAccountData, accountKey) == NULL)
++ {
++ /*
++ * Keep the PurpleAccount struct to reuse in future logins
++ */
++ g_hash_table_insert(s_offlineAccountData, accountKey, account);
++ }
++
++ purple_account_disconnect(account);
++
++ accountToLogoutList = g_slist_append(accountToLogoutList, account);
++
++ MojLogInfo(IMServiceApp::s_log, _T("deviceConnectionClosed: removing account from onlineAccountData hash table. accountKey %s"), accountKey);
++ g_hash_table_remove(s_onlineAccountData, accountKey);
++ // We can't remove this guy since we're iterating through its keys. We'll remove it after the break
++ //g_hash_table_remove(ipAddressesBoundTo, accountKey);
++ }
++ }
++
++ if (g_slist_length(accountToLogoutList) == 0)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("No accounts were connected on the requested ip address"));
++ }
++ else
++ {
++ GSList* accountIterator = NULL;
++ for (accountIterator = accountToLogoutList; accountIterator != NULL; accountIterator = accountIterator->next)
++ {
++ account = (PurpleAccount*) accountIterator->data;
++ char* serviceName = getServiceNameFromPrplProtocolId(account);
++ char* username = getMojoFriendlyUsername(account->username, serviceName);
++ char* accountKey = getAccountKey(username, serviceName);
++
++ g_hash_table_remove(s_ipAddressesBoundTo, accountKey);
++
++ free(serviceName);
++ free(username);
++ free(accountKey);
++ }
++ }
++
++ return TRUE;
++}
++
++void LibpurpleAdapter::assignIMLoginState(LoginCallbackInterface* loginState)
++{
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ static int handle = 0x1AD;
++ s_loginState = loginState;
++
++ if (s_registeredForAccountSignals == TRUE)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("Disconnecting old signals."));
++ s_registeredForAccountSignals = FALSE;
++
++ purple_signal_disconnect(purple_connections_get_handle(), "signed-on", &handle,
++ PURPLE_CALLBACK(account_logged_in_cb));
++ purple_signal_disconnect(purple_connections_get_handle(), "signed-off", &handle,
++ PURPLE_CALLBACK(account_signed_off_cb));
++ purple_signal_disconnect(purple_connections_get_handle(), "connection-error", &handle,
++ PURPLE_CALLBACK(account_login_failed_cb));
++
++ purple_signal_disconnect(purple_accounts_get_handle(), "account-status-changed", &handle,
++ PURPLE_CALLBACK(account_status_changed));
++ purple_signal_disconnect(purple_accounts_get_handle(), "account-authorization-denied", &handle,
++ PURPLE_CALLBACK(account_auth_deny_cb));
++ purple_signal_disconnect(purple_accounts_get_handle(), "account-authorization-granted", &handle,
++ PURPLE_CALLBACK(account_auth_accept_cb));
++ }
++
++ if (loginState != NULL)
++ {
++ MojLogInfo(IMServiceApp::s_log, _T("Connecting new signals."));
++ s_registeredForAccountSignals = TRUE;
++ /*
++ * Listen for a number of different signals:
++ */
++ purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
++ PURPLE_CALLBACK(account_logged_in_cb), loginState);
++ purple_signal_connect(purple_connections_get_handle(), "signed-off", &handle,
++ PURPLE_CALLBACK(account_signed_off_cb), loginState);
++ purple_signal_connect(purple_connections_get_handle(), "connection-error", &handle,
++ PURPLE_CALLBACK(account_login_failed_cb), loginState);
++
++ // accounts signals
++ purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", &handle,
++ PURPLE_CALLBACK(account_status_changed), loginState);
++ purple_signal_connect(purple_accounts_get_handle(), "account-authorization-denied", &handle,
++ PURPLE_CALLBACK(account_auth_deny_cb), loginState);
++ purple_signal_connect(purple_accounts_get_handle(), "account-authorization-granted", &handle,
++ PURPLE_CALLBACK(account_auth_accept_cb), loginState);
++ }
++}
++
++void LibpurpleAdapter::assignIMServiceHandler(IMServiceCallbackInterface* imServiceHandler)
++{
++ MojLogInfo(IMServiceApp::s_log, _T("%s called."), __FUNCTION__);
++
++ s_imServiceHandler = imServiceHandler;
++}
++
++void LibpurpleAdapter::assignPrefsHandler(LibpurplePrefsHandler* PrefsHandler)
++{
++ s_PrefsHandler = PrefsHandler;
++}
++
++/*
++ * Value destroy function for s_AuthorizeRequests
++ */
++static void deleteAuthRequest(void* obj)
++{
++ AuthRequest *aa = (AuthRequest *)obj;
++ MojLogInfo(IMServiceApp::s_log, _T("deleteAuthRequest: deleting auth request object. account: %s, remote_user: %s"), aa->account->username, aa->remote_user);
++ g_free(aa->remote_user);
++ g_free(aa->alias);
++ g_free(aa);
++}
++
++void LibpurpleAdapter::init()
++{
++ //TODO: replace the NULLs with real functions to prevent memory leaks
++ /* g_hash_table_new_full() Creates a new GHashTable like g_hash_table_new() and allows one to specify functions to free the memory allocated for the key and value that get called when removing the entry from the GHashTable.
++ * Parameters:
++ hash_func: a function to create a hash value from a key.
++ key_equal_func: a function to check two keys for equality.
++ key_destroy_func: a function to free the memory allocated for the key used when removing the entry from the GHashTable or NULL if you don't want to supply such a function.
++ value_destroy_func: a function to free the memory allocated for the value used when removing the entry from the GHashTable or NULL if you don't want to supply such a function.
++ Returns: a new GHashTable.
++ */
++ s_onlineAccountData = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL);
++ s_pendingAccountData = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_accountLoginTimers = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_ipAddressesBoundTo = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free);
++ s_connectionTypeData = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free);
++ s_AccountIdsData = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free);
++ s_offlineAccountData = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_AuthorizeRequests = g_hash_table_new_full(g_str_hash, g_str_equal, free, deleteAuthRequest);
++ s_accountBuddyListTimers = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++
++ initializeLibpurple();
++}
+diff -rupN imlibpurpleservice-1.0/src/LibpurpleAdapterPrefs.cpp imlibpurpleservice-1.0-new//src/LibpurpleAdapterPrefs.cpp
+--- imlibpurpleservice-1.0/src/LibpurpleAdapterPrefs.cpp 2011-03-25 10:45:40.752552999 -0600
++++ imlibpurpleservice-1.0-new//src/LibpurpleAdapterPrefs.cpp 2011-03-27 09:10:06.196553000 -0600
+@@ -139,6 +139,9 @@ static char* getMojoFriendlyServiceName
+ else if (strcmp(templateId, CAPABILITY_LIVE) == 0){
+ return (char*)SERVICENAME_LIVE;
+ }
++ else if (strcmp(templateId, CAPABILITY_WLM) == 0){
++ return (char*)SERVICENAME_WLM;
++ }
+ else if (strcmp(templateId, CAPABILITY_MYSPACE) == 0){
+ return (char*)SERVICENAME_MYSPACE;
+ }
+@@ -627,6 +630,7 @@ static char* getServiceNameFromPrplProto
+ MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId called with empty protocolId"));
+ return strdup("type_default");
+ }
++ MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId, prplProtocolId = %s"), prplProtocolId);
+ char* stringChopper = prplProtocolId;
+ stringChopper += strlen("prpl-");
+ GString* serviceName = g_string_new(stringChopper);
+@@ -643,6 +647,12 @@ static char* getServiceNameFromPrplProto
+ g_string_free(serviceName, true);
+ serviceName = g_string_new("live");
+ }
++ if (strcmp(serviceName->str, "msn-pecan") == 0)
++ {
++ // Special case for wlm where the mojo serviceName is "type_wlm" and the prpl protocol_id is "prpl-msn-pecan"
++ g_string_free(serviceName, true);
++ serviceName = g_string_new("wlm");
++ }
+ if (strcmp(serviceName->str, "bigbrownchunx-facebookim") == 0)
+ {
+ // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim"
+@@ -667,6 +677,7 @@ static char* getServiceNameFromPrplProto
+ g_string_free(serviceName, true);
+ serviceName = g_string_new("gadu");
+ }
++ MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId, serviceName = %s"),serviceName->str);
+ char* serviceNameToReturn = NULL;
+ // asprintf allocates appropriate-sized buffer
+ asprintf(&serviceNameToReturn, "type_%s", serviceName->str);
+diff -rupN imlibpurpleservice-1.0/src/LibpurpleAdapterPrefs.cpp~ imlibpurpleservice-1.0-new//src/LibpurpleAdapterPrefs.cpp~
+--- imlibpurpleservice-1.0/src/LibpurpleAdapterPrefs.cpp~ 1969-12-31 18:00:00.000000000 -0600
++++ imlibpurpleservice-1.0-new//src/LibpurpleAdapterPrefs.cpp~ 2011-03-27 09:10:06.180553000 -0600
+@@ -0,0 +1,821 @@
++/*
++ * LibpurpleAdapterPrefs.cpp
++ *
++ */
++
++#include "LibpurpleAdapterPrefs.h"
++#include "LibpurpleAdapter.h"
++#include "PalmImCommon.h"
++#include "IMServiceApp.h"
++#include "db/MojDbServiceClient.h"
++
++/*
++ * list of account preferences
++ */
++static GHashTable* s_AccountAliases = NULL;
++static GHashTable* s_AccountAvatars = NULL;
++static GHashTable* s_AccountBadCert = NULL;
++static GHashTable* s_ServerName = NULL;
++static GHashTable* s_ServerPort = NULL;
++static GHashTable* s_ServerTLS = NULL;
++static GHashTable* s_LoginTimeOut = NULL;
++static GHashTable* s_BuddyListTimeOut = NULL;
++
++static GHashTable* s_SametimeHideID = NULL;
++static GHashTable* s_SIPEServerProxy = NULL;
++static GHashTable* s_SIPEUserAgent = NULL;
++static GHashTable* s_XFireVersion = NULL;
++static GHashTable* s_SIPEServerLogin = NULL;
++static GHashTable* s_JabberResource = NULL;
++
++LibpurpleAdapterPrefs::LibpurpleAdapterPrefs(MojService* service)
++: m_findCommandSlot(this, &LibpurpleAdapterPrefs::findCommandResult),
++ m_service(service),
++ m_dbClient(service)
++{
++
++}
++
++LibpurpleAdapterPrefs::~LibpurpleAdapterPrefs() {
++
++}
++
++inline const char * const BoolToString(bool b)
++{
++ return b ? "true" : "false";
++}
++
++inline bool StringToBool(char* c)
++{
++ if(c == NULL)
++ {
++ return false;
++ }
++ if (strcmp(c, "false") == 0)
++ {
++ return false;
++ }
++ if (strcmp(c, "true") == 0)
++ {
++ return true;
++ }
++ return false;
++}
++
++/*
++ * The messaging service expects the username to be in the username@domain.com format, whereas the AIM prpl uses the username only
++ * Free the returned string when you're done with it
++ */
++static char* getMojoFriendlyUsername(const char* username, const char* serviceName)
++{
++ if (!username || !serviceName)
++ {
++ return strdup("");
++ }
++ GString* mojoFriendlyUsername = g_string_new(username);
++ if (strcmp(serviceName, SERVICENAME_AIM) == 0 && strchr(username, '@') == NULL)
++ {
++ g_string_append(mojoFriendlyUsername, "@aol.com");
++ }
++ else if (strcmp(serviceName, SERVICENAME_GTALK) == 0)
++ {
++ char* resource = (char*)memchr(username, '/', strlen(username));
++ if (resource != NULL)
++ {
++ int charsToKeep = resource - username;
++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1);
++ }
++ }
++ else if (strcmp(serviceName, SERVICENAME_JABBER) == 0)
++ {
++ if (strstr(username, "/") != NULL)
++ {
++ //If jabber resource is blank remove /
++ char *resource = (char*)memchr(username, '/', strlen(username));
++ if (resource != NULL)
++ {
++ int charsToKeep = resource - username;
++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1);
++ }
++ }
++ }
++ else if (strcmp(serviceName, SERVICENAME_SIPE) == 0)
++ {
++ char *resource = (char*)memchr(username, ',', strlen(username));
++ if (resource != NULL)
++ {
++ int charsToKeep = resource - username;
++ g_string_erase(mojoFriendlyUsername, charsToKeep, -1);
++ }
++ }
++ char* mojoFriendlyUsernameToReturn = strdup(mojoFriendlyUsername->str);
++ g_string_free(mojoFriendlyUsername, true);
++ return mojoFriendlyUsernameToReturn;
++}
++
++static char* getMojoFriendlyServiceName (const char* templateId)
++{
++ if (strcmp(templateId, CAPABILITY_AIM) == 0) {
++ return (char*)SERVICENAME_AIM;
++ }
++ else if (strcmp(templateId, CAPABILITY_FACEBOOK) == 0){
++ return (char*)SERVICENAME_FACEBOOK;
++ }
++ else if (strcmp(templateId, CAPABILITY_GTALK) == 0){
++ return (char*)SERVICENAME_GTALK;
++ }
++ else if (strcmp(templateId, CAPABILITY_GADU) == 0){
++ return (char*)SERVICENAME_GADU;
++ }
++ else if (strcmp(templateId, CAPABILITY_GROUPWISE) == 0){
++ return (char*)SERVICENAME_GROUPWISE;
++ }
++ else if (strcmp(templateId, CAPABILITY_ICQ) == 0){
++ return (char*)SERVICENAME_ICQ;
++ }
++ else if (strcmp(templateId, CAPABILITY_JABBER) == 0){
++ return (char*)SERVICENAME_JABBER;
++ }
++ else if (strcmp(templateId, CAPABILITY_LIVE) == 0){
++ return (char*)SERVICENAME_LIVE;
++ }
++ else if (strcmp(templateId, CAPABILITY_WLM) == 0){
++ return (char*)SERVICENAME_WLM;
++ }
++ else if (strcmp(templateId, CAPABILITY_MYSPACE) == 0){
++ return (char*)SERVICENAME_MYSPACE;
++ }
++ else if (strcmp(templateId, CAPABILITY_QQ) == 0){
++ return (char*)SERVICENAME_QQ;
++ }
++ else if (strcmp(templateId, CAPABILITY_SAMETIME) == 0){
++ return (char*)SERVICENAME_SAMETIME;
++ }
++ else if (strcmp(templateId, CAPABILITY_SIPE) == 0){
++ return (char*)SERVICENAME_SIPE;
++ }
++ else if (strcmp(templateId, CAPABILITY_XFIRE) == 0){
++ return (char*)SERVICENAME_XFIRE;
++ }
++ else if (strcmp(templateId, CAPABILITY_YAHOO) == 0){
++ return (char*)SERVICENAME_YAHOO;
++ }
++
++ return (char*)"";
++}
++
++//Preferences methods
++MojErr LibpurpleAdapterPrefs::LoadAccountPreferences(const char* templateId, const char* UserName)
++{
++ MojString m_templateId;
++ MojString m_UserName;
++ m_templateId.assign(templateId);
++ m_UserName.assign(UserName);
++
++ // Create the query
++ MojDbQuery query;
++ MojErr err;
++
++ //If the template is empty load everything
++ if (!strcmp(m_templateId, "") == 0)
++ {
++ err = query.where("templateId", MojDbQuery::OpEq, m_templateId);
++ MojErrCheck(err);
++
++ err = query.where("UserName", MojDbQuery::OpEq, m_UserName);
++ MojErrCheck(err);
++ }
++
++ // add our kind to the object
++ err = query.from("org.webosinternals.messaging.prefs:1");
++ MojErrCheck(err);
++
++ // log the query
++ MojObject queryObject;
++ err = query.toObject(queryObject);
++ MojErrCheck(err);
++
++ MojString json;
++ queryObject.toJson(json);
++ MojLogError(IMServiceApp::s_log, _T("LoadAccountPreference Query: %s"), json.data());
++
++ if (err) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("LoadAccountPreference Query failed: error %d - %s"), err, error.data());
++ return err;
++ }
++
++ // query DB
++ err = m_dbClient.find(this->m_findCommandSlot, query, true);
++
++ if (err) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("LoadAccountPreference Query find failed: error %d - %s"), err, error.data());
++ return err;
++ }
++
++ return MojErrNone;
++}
++
++/*
++ * Callback for the prefs query
++ */
++MojErr LibpurpleAdapterPrefs::findCommandResult(MojObject& result, MojErr findErr)
++{
++ MojLogTrace(IMServiceApp::s_log);
++
++ if (findErr) {
++ MojString error;
++ MojErrToString(findErr, error);
++ MojLogError(IMServiceApp::s_log, _T("findCommandResult failed: error %d - %s"), findErr, error.data());
++
++ } else {
++ // get the results
++ MojString mojStringJson;
++ result.toJson(mojStringJson);
++ MojLogError(IMServiceApp::s_log, _T("findCommandResult result: %s"), mojStringJson.data());
++
++ MojObject results;
++
++ // get results array in "results"
++ result.get(_T("results"), results);
++
++ //Are there results?
++ if (!results.empty())
++ {
++ bool BadCert = false;
++ bool EnableAlias = false;
++ bool EnableAvatar = false;
++ bool ServerTLS = false;
++ MojString Preferences;
++ MojString ServerName;
++ MojString ServerPort;
++ MojString LoginTimeOut;
++ MojString BuddyListTimeOut;
++ MojString UserName;
++ MojString templateId;
++ MojObject m_ServerName;
++ MojObject m_ServerPort;
++ MojObject m_UserName;
++ MojObject m_templateId;
++ MojObject accountPreferences;
++
++ bool SametimeHideID = false;
++ bool SIPEServerProxy = false;
++ MojString SIPEUserAgent;
++ MojString XFireVersion;
++ MojString SIPEServerLogin;
++ MojString JabberResource;
++ MojObject m_SIPEUserAgent;
++ MojObject m_XFireVersion;
++ MojObject m_SIPEServerLogin;
++ MojObject m_JabberResource;
++
++ MojObject::ConstArrayIterator PrefslItr = results.arrayBegin();
++ while (PrefslItr != results.arrayEnd()) {
++ accountPreferences = *PrefslItr;
++
++ accountPreferences.toJson(Preferences);
++ MojLogError(IMServiceApp::s_log, _T("findCommandResult 'Preferences' result: %s"), Preferences.data());
++
++ //Get the results
++ accountPreferences.get ("BadCert", BadCert);
++ accountPreferences.get ("EnableAlias", EnableAlias);
++ accountPreferences.get ("EnableAvatar", EnableAvatar);
++ accountPreferences.getRequired ("ServerName", ServerName);
++ accountPreferences.getRequired ("ServerPort", ServerPort);
++ accountPreferences.getRequired ("LoginTimeOut", LoginTimeOut);
++ accountPreferences.getRequired ("BuddyListTimeOut", BuddyListTimeOut);
++ accountPreferences.get ("ServerTLS", ServerTLS);
++ accountPreferences.getRequired ("UserName", UserName);
++ accountPreferences.getRequired ("templateId", templateId);
++
++ //Get non standard prefs
++ if (0 == templateId.compare(CAPABILITY_SAMETIME))
++ {
++ accountPreferences.get ("SametimeHideID", SametimeHideID);
++ }
++ if (0 == templateId.compare(CAPABILITY_SIPE))
++ {
++ accountPreferences.get ("SIPEServerProxy", SIPEServerProxy);
++ accountPreferences.getRequired ("SIPEUserAgent", SIPEUserAgent);
++ accountPreferences.getRequired ("SIPEServerLogin", SIPEServerLogin);
++ }
++ if (0 == templateId.compare(CAPABILITY_JABBER))
++ {
++ accountPreferences.getRequired ("JabberResource", JabberResource);
++ }
++ if (0 == templateId.compare(CAPABILITY_XFIRE))
++ {
++ accountPreferences.getRequired ("XFireversion", XFireVersion);
++ }
++
++ //Set AccountID
++ char *accountKey = NULL;
++ const char* ServiceType = getMojoFriendlyServiceName (templateId.data());
++ const char* mojUserName = getMojoFriendlyUsername ((const char*)UserName.data(),ServiceType);
++ asprintf(&accountKey, "%s_%s", mojUserName, ServiceType);
++
++ //Remove any entry in the hash tables
++ if (g_hash_table_lookup(s_AccountAliases, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_AccountAliases, accountKey);
++ }
++ if (g_hash_table_lookup(s_AccountAvatars, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_AccountAvatars, accountKey);
++ }
++ if (g_hash_table_lookup(s_AccountBadCert, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_AccountBadCert, accountKey);
++ }
++ if (g_hash_table_lookup(s_ServerName, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_ServerName, accountKey);
++ }
++ if (g_hash_table_lookup(s_ServerPort, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_ServerPort, accountKey);
++ }
++ if (g_hash_table_lookup(s_LoginTimeOut, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_LoginTimeOut, accountKey);
++ }
++ if (g_hash_table_lookup(s_BuddyListTimeOut, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_BuddyListTimeOut, accountKey);
++ }
++ if (g_hash_table_lookup(s_ServerTLS, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_ServerTLS, accountKey);
++ }
++ if (g_hash_table_lookup(s_SametimeHideID, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_SametimeHideID, accountKey);
++ }
++ if (g_hash_table_lookup(s_SIPEServerProxy, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_SIPEServerProxy, accountKey);
++ }
++ if (g_hash_table_lookup(s_SIPEUserAgent, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_SIPEUserAgent, accountKey);
++ }
++ if (g_hash_table_lookup(s_SIPEServerLogin, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_SIPEServerLogin, accountKey);
++ }
++ if (g_hash_table_lookup(s_XFireVersion, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_XFireVersion, accountKey);
++ }
++ if (g_hash_table_lookup(s_JabberResource, accountKey) != NULL)
++ {
++ g_hash_table_remove(s_JabberResource, accountKey);
++ }
++
++ //Show results
++ //findCommandResult result: {"results":[{"BadCert":false,"EnableAlias":true,"EnableAvatar":true,"ServerName":"messenger.hotmail.com","ServerPort":"1863","UserName":"SOMEONE@HOTMAIL.COM","_id":"++HRyXbD3r+pt8M2","_kind":"org.webosinternals.messaging.prefs:1","_rev":41856,"_sync":true,"templateId":"ORG.WEBOSINTERNALS.MESSAGING.LIVE"}],"returnValue":true}
++ if (BadCert)
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' BadCert: true"));
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' BadCert: false"));
++ }
++ if (EnableAlias)
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' EnableAlias: true"));
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' EnableAlias: false"));
++ }
++ if (EnableAvatar)
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' EnableAvatar: true"));
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' EnableAvatar: false"));
++ }
++ if (ServerTLS)
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' ServerTLS: true"));
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' ServerTLS: false"));
++ }
++ if (0 == templateId.compare(CAPABILITY_SAMETIME))
++ {
++ //Show results
++ if (SametimeHideID)
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' SametimeHideID: true"));
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' SametimeHideID: false"));
++ }
++ //Add the results to the hash tables
++ g_hash_table_insert(s_SametimeHideID, accountKey, strdup(BoolToString(SametimeHideID)));
++ }
++ if (0 == templateId.compare(CAPABILITY_SIPE))
++ {
++ //Show results
++ if (SIPEServerProxy)
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' SIPEServerProxy: true"));
++ }
++ else
++ {
++ MojLogError(IMServiceApp::s_log, _T("'Preference' SIPEServerProxy: false"));
++ }
++ MojLogError(IMServiceApp::s_log, _T("'Preference' SIPEUserAgent: %s"), SIPEUserAgent.data());
++ MojLogError(IMServiceApp::s_log, _T("'Preference' SIPEServerLogin: %s"), SIPEServerLogin.data());
++
++ //Add the results to the hash tables
++ g_hash_table_insert(s_SIPEServerLogin, accountKey, strdup(SIPEServerLogin.data()));
++ g_hash_table_insert(s_SIPEUserAgent, accountKey, strdup(SIPEUserAgent.data()));
++ g_hash_table_insert(s_SIPEServerProxy, accountKey, strdup(BoolToString(SIPEServerProxy)));
++ }
++ MojLogError(IMServiceApp::s_log, _T("'Preference' ServerName: %s"), ServerName.data());
++ MojLogError(IMServiceApp::s_log, _T("'Preference' ServerPort: %s"), ServerPort.data());
++ MojLogError(IMServiceApp::s_log, _T("'Preference' LoginTimeOut: %s"), LoginTimeOut.data());
++ MojLogError(IMServiceApp::s_log, _T("'Preference' BuddyListTimeOut: %s"), BuddyListTimeOut.data());
++ MojLogError(IMServiceApp::s_log, _T("'Preference' UserName: %s"), UserName.data());
++ MojLogError(IMServiceApp::s_log, _T("'Preference' templateId: %s"), templateId.data());
++
++ if (0 == templateId.compare(CAPABILITY_XFIRE))
++ {
++ //Show results
++ MojLogError(IMServiceApp::s_log, _T("'Preference' XFireVersion: %s"), XFireVersion.data());
++ //Add the results to the hash tables
++ g_hash_table_insert(s_XFireVersion, accountKey, strdup(XFireVersion.data()));
++ }
++ if (0 == templateId.compare(CAPABILITY_JABBER))
++ {
++ //Show results
++ MojLogError(IMServiceApp::s_log, _T("'Preference' JabberResource: %s"), JabberResource.data());
++ //Add the results to the hash tables
++ g_hash_table_insert(s_JabberResource, accountKey, strdup(JabberResource.data()));
++ }
++
++ //Add the results to the hash tables
++ g_hash_table_insert(s_AccountAliases, accountKey, strdup(BoolToString(EnableAlias)));
++ g_hash_table_insert(s_AccountAvatars, accountKey, strdup(BoolToString(EnableAvatar)));
++ g_hash_table_insert(s_AccountBadCert, accountKey, strdup(BoolToString(BadCert)));
++ g_hash_table_insert(s_ServerName, accountKey, strdup(ServerName.data()));
++ g_hash_table_insert(s_ServerPort, accountKey, strdup(ServerPort.data()));
++ g_hash_table_insert(s_ServerTLS, accountKey, strdup(BoolToString(ServerTLS)));
++ g_hash_table_insert(s_LoginTimeOut, accountKey, strdup(LoginTimeOut.data()));
++ g_hash_table_insert(s_BuddyListTimeOut, accountKey, strdup(BuddyListTimeOut.data()));
++
++ //Loop
++ PrefslItr++;
++ }
++ }
++ }
++
++ return MojErrNone;
++}
++
++MojErr LibpurpleAdapterPrefs::GetServerPreferences(const char* templateId, const char* UserName, char*& ServerName, char*& ServerPort, bool& ServerTLS, bool& BadCert)
++{
++ //Set AccountID
++ char *accountKey = NULL;
++ const char* ServiceType = getMojoFriendlyServiceName (templateId);
++ const char* mojUserName = getMojoFriendlyUsername (UserName,ServiceType);
++ asprintf(&accountKey, "%s_%s", mojUserName, ServiceType);
++
++ //Get Server Name
++ ServerName = (char*)g_hash_table_lookup(s_ServerName, accountKey);
++ if (ServerName == NULL)
++ {
++ ServerName = (char*)"";
++ }
++
++ //Get Server Port
++ ServerPort = (char*)g_hash_table_lookup(s_ServerPort, accountKey);
++ if (ServerPort == NULL)
++ {
++ ServerPort = (char*)"0";
++ }
++
++ //Get Server TLS
++ ServerTLS = StringToBool((char*)g_hash_table_lookup(s_ServerTLS, accountKey));
++
++ //Get Server Bad Cert Setting
++ BadCert = StringToBool((char*)g_hash_table_lookup(s_AccountBadCert, accountKey));
++
++ return MojErrNone;
++}
++
++char* LibpurpleAdapterPrefs::GetStringPreference(const char* Preference, const char* templateId, const char* UserName)
++{MojLogError(IMServiceApp::s_log, _T("In GetStringPrefs: %s"),Preference);
++ // Set AccountID
++ char *accountKey = NULL;
++ const char* ServiceType = getMojoFriendlyServiceName (templateId);
++ const char* mojUserName = getMojoFriendlyUsername (UserName,ServiceType);
++ asprintf(&accountKey, "%s_%s", mojUserName, ServiceType);
++
++ char* Setting = NULL;
++
++ if (strcmp(Preference, "SIPEUserAgent") == 0)
++ {
++ //Get SIPEUserAgent Setting
++ Setting = (char*)g_hash_table_lookup(s_SIPEUserAgent, accountKey);
++ }
++ if (strcmp(Preference, "XFireVersion") == 0)
++ {
++ //Get XFireVersion Setting
++ Setting = (char*)g_hash_table_lookup(s_XFireVersion, accountKey);
++
++ //If Xfire version is blank default to 132
++ if (Setting == NULL)
++ {
++ Setting = (char*)"132";
++ }
++ }
++ if (strcmp(Preference, "SIPEServerLogin") == 0)
++ {
++ //Get SIPEServerLogin Setting
++ Setting = (char*)g_hash_table_lookup(s_SIPEServerLogin, accountKey);
++ }
++ if (strcmp(Preference, "JabberResource") == 0)
++ {
++ //Get JabberResource Setting
++ Setting = (char*)g_hash_table_lookup(s_JabberResource, accountKey);
++ }
++
++ if (strcmp(Preference, "LoginTimeOut") == 0)
++ {
++ //Get Server Timeout
++ Setting = (char*)g_hash_table_lookup(s_LoginTimeOut, accountKey);
++ if (Setting == NULL)
++ {
++ Setting = (char*)"45";
++ }
++ }
++
++ if (strcmp(Preference, "BuddyListTimeOut") == 0)
++ {
++ //Get BuddyList Timeout
++ Setting = (char*)g_hash_table_lookup(s_BuddyListTimeOut, accountKey);
++ if (Setting == NULL)
++ {
++ Setting = (char*)"10";
++ }
++ }
++
++ if (Setting == NULL)
++ {
++ return (char*)"";
++ }
++ else
++ {
++ return Setting;
++ }
++}
++
++bool LibpurpleAdapterPrefs::GetBoolPreference(const char* Preference, const char* templateId, const char* UserName)
++{
++ //Set AccountID
++ char *accountKey = NULL;
++ const char* ServiceType = getMojoFriendlyServiceName (templateId);
++ const char* mojUserName = getMojoFriendlyUsername (UserName,ServiceType);
++ asprintf(&accountKey, "%s_%s", mojUserName, ServiceType);
++
++ bool Setting = false;
++
++ if (strcmp(Preference, "Avatar") == 0)
++ {
++ //Get Avatar Setting
++ Setting = StringToBool((char*)g_hash_table_lookup(s_AccountAvatars, accountKey));
++ return Setting;
++ }
++ if (strcmp(Preference, "Alias") == 0)
++ {
++ //Get Alias Setting
++ Setting = StringToBool((char*)g_hash_table_lookup(s_AccountAliases, accountKey));
++ return Setting;
++ }
++ if (strcmp(Preference, "SametimeHideID") == 0)
++ {
++ //Get SametimeHideID Setting
++ Setting = StringToBool((char*)g_hash_table_lookup(s_SametimeHideID, accountKey));
++ return Setting;
++ }
++ if (strcmp(Preference, "SIPEServerProxy") == 0)
++ {
++ //Get SIPEServerProxy Setting
++ Setting = StringToBool((char*)g_hash_table_lookup(s_SIPEServerProxy, accountKey));
++ return Setting;
++ }
++ return false;
++}
++
++/*
++ * Given the prpl-specific protocol_id, it will return mojo-friendly serviceName (e.g. given "prpl-aim", it will return "type_aim")
++ * Free the returned string when you're done with it
++ */
++static char* getServiceNameFromPrplProtocolId(char* prplProtocolId)
++{
++ if (!prplProtocolId)
++ {
++ MojLogError(IMServiceApp::s_log, _T("getServiceNameFromPrplProtocolId called with empty protocolId"));
++ return strdup("type_default");
++ }
++ char* stringChopper = prplProtocolId;
++ stringChopper += strlen("prpl-");
++ GString* serviceName = g_string_new(stringChopper);
++
++ if (strcmp(serviceName->str, "jabber") == 0)
++ {
++ // Special case for gtalk where the mojo serviceName is "type_gtalk" and the prpl protocol_id is "prpl-jabber"
++ g_string_free(serviceName, true);
++ serviceName = g_string_new("gtalk");
++ }
++ if (strcmp(serviceName->str, "msn") == 0)
++ {
++ // Special case for live where the mojo serviceName is "type_live" and the prpl protocol_id is "prpl-msn"
++ g_string_free(serviceName, true);
++ serviceName = g_string_new("live");
++ }
++ if (strcmp(serviceName->str, "bigbrownchunx-facebookim") == 0)
++ {
++ // Special case for facebook where the mojo serviceName is "type_facebook" and the prpl protocol_id is "prpl-bigbrownchunx-facebookim"
++ g_string_free(serviceName, true);
++ serviceName = g_string_new("facebook");
++ }
++ if (strcmp(serviceName->str, "meanwhile") == 0)
++ {
++ // Special case for sametime where the mojo serviceName is "type_sametime" and the prpl protocol_id is "prpl-meanwhile"
++ g_string_free(serviceName, true);
++ serviceName = g_string_new("sametime");
++ }
++ if (strcmp(serviceName->str, "novell") == 0)
++ {
++ // Special case for groupwise where the mojo serviceName is "type_groupwise" and the prpl protocol_id is "prpl-novell"
++ g_string_free(serviceName, true);
++ serviceName = g_string_new("groupwise");
++ }
++ if (strcmp(serviceName->str, "gg") == 0)
++ {
++ // Special case for gadu where the mojo serviceName is "type_gadu" and the prpl protocol_id is "prpl-gg"
++ g_string_free(serviceName, true);
++ serviceName = g_string_new("gadu");
++ }
++ char* serviceNameToReturn = NULL;
++ // asprintf allocates appropriate-sized buffer
++ asprintf(&serviceNameToReturn, "type_%s", serviceName->str);
++ g_string_free(serviceName, true);
++ return serviceNameToReturn;
++}
++
++void LibpurpleAdapterPrefs::setaccountprefs(MojString templateId, PurpleAccount* account)
++{
++ //Load the preferences
++ char* ServerName;
++ char* ServerPort;
++ bool ServerTLS = false;
++ bool BadCert = false;
++ char* SIPEUserAgent;
++ char* XFireversion;
++ bool SIPEServerProxy = false;
++ bool SametimehideID = false;
++
++ //Load account preferences
++ char* serviceName = getServiceNameFromPrplProtocolId(account->protocol_id);
++ char* username = getMojoFriendlyUsername(account->username, serviceName);
++ GetServerPreferences((const char*)templateId.data(), (const char*)username, ServerName, ServerPort, ServerTLS, BadCert);
++
++ //If no server blank
++ if (strcmp(ServerName, "") == 0)
++ {
++ ServerName = (char*)"";
++ }
++ //If no port quit
++ if (strcmp(ServerPort, "") == 0)
++ {
++ return;
++ }
++
++ //Bad Cert Accept?
++ purple_prefs_remove("/purple/acceptbadcert");
++ if (BadCert)
++ {
++ MojLogError(IMServiceApp::s_log, "Accepting Bad Certificates");
++ purple_prefs_add_string("/purple/acceptbadcert", "true");
++ }
++
++ //Set account preferences
++ purple_account_set_string(account, "server", ServerName);
++ purple_account_set_string(account, "connect_server", ServerName);
++ if (0 == templateId.compare(CAPABILITY_GADU))
++ {
++ //Set connect server
++ purple_account_set_string(account, "gg_server", ServerName);
++ purple_account_set_int(account, "server_port", atoi(ServerPort));
++ }
++ purple_account_set_int(account, "port", atoi(ServerPort));
++ purple_account_set_bool(account, "require_tls", ServerTLS);
++
++ if (ServerTLS)
++ {
++ purple_account_set_string(account, "transport", "tls");
++ }
++ else
++ {
++ purple_account_set_string(account, "transport", "auto");
++ }
++ if (0 == templateId.compare(CAPABILITY_QQ))
++ {
++ purple_account_set_string(account, "client_version", "qq2008");
++
++ //Set server in servername:port format for QQ
++ char *qqServer = NULL;
++ qqServer = (char *)calloc(strlen(ServerName) + strlen(ServerPort) + 1, sizeof(char));
++ strcat(qqServer, ServerName);
++ strcat(qqServer, ":");
++ strcat(qqServer, ServerPort);
++ purple_account_set_string(account, "server", qqServer);
++ }
++ if (0 == templateId.compare(CAPABILITY_FACEBOOK))
++ {
++ //Don't load chat history
++ purple_account_set_bool(account, "facebook_show_history", false);
++ }
++ if (0 == templateId.compare(CAPABILITY_XFIRE))
++ {
++ XFireversion = GetStringPreference("XFireVersion", templateId, username);
++ purple_account_set_int(account, "version", atoi(XFireversion));
++ }
++
++ if (0 == templateId.compare(CAPABILITY_SIPE))
++ {
++ SIPEUserAgent = GetStringPreference("SIPEUserAgent", templateId, username);
++ SIPEServerProxy = GetBoolPreference("SIPEServerProxy", templateId, username);
++
++ //Set ServerName
++ if(strcmp(ServerName, "") != 0)
++ {
++ char *SIPEFullServerName = NULL;
++ SIPEFullServerName = (char *)calloc(strlen(ServerName) + strlen(ServerPort) + 1, sizeof(char));
++ strcat(SIPEFullServerName, ServerName);
++ strcat(SIPEFullServerName, ":");
++ strcat(SIPEFullServerName, ServerPort);
++ purple_account_set_string(account, "server", SIPEFullServerName);
++
++ if (SIPEFullServerName)
++ {
++ free(SIPEFullServerName);
++ }
++ }
++
++ //Proxy?
++ if (!SIPEServerProxy)
++ {
++ //Disable Proxy
++ PurpleProxyInfo *info = purple_proxy_info_new();
++ purple_proxy_info_set_type(info, PURPLE_PROXY_NONE);
++ }
++
++ //User Agent
++ if(strcmp(SIPEUserAgent, "") != 0)
++ {
++ purple_account_set_string(account, "useragent", SIPEUserAgent);
++ }
++ }
++ if (0 == templateId.compare(CAPABILITY_SAMETIME))
++ {
++ SametimehideID = GetBoolPreference("SametimeHideID", templateId, username);
++ purple_account_set_bool(account, "fake_client_id", SametimehideID);
++ }
++}
++
++void LibpurpleAdapterPrefs::init()
++{
++ //Initialise hash tables
++ s_AccountAliases = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_AccountAvatars = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_AccountBadCert = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_ServerName = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_ServerPort = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_ServerTLS = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_LoginTimeOut = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_BuddyListTimeOut = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++
++ s_SametimeHideID = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_SIPEServerProxy = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_SIPEUserAgent = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_XFireVersion = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_SIPEServerLogin = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++ s_JabberResource = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
++
++ LibpurpleAdapter::assignPrefsHandler(this);
++}
+diff -rupN imlibpurpleservice-1.0/src/OnEnabledHandler.cpp imlibpurpleservice-1.0-new//src/OnEnabledHandler.cpp
+--- imlibpurpleservice-1.0/src/OnEnabledHandler.cpp 2011-03-25 10:45:40.704552999 -0600
++++ imlibpurpleservice-1.0-new//src/OnEnabledHandler.cpp 2011-03-27 09:10:06.136552999 -0600
+@@ -162,6 +162,8 @@ MojErr OnEnabledHandler::getDefaultServi
+ serviceName.assign(SERVICENAME_JABBER);
+ else if (templateId == "org.webosinternals.messaging.live")
+ serviceName.assign(SERVICENAME_LIVE);
++ else if (templateId == "org.webosinternals.messaging.wlm")
++ serviceName.assign(SERVICENAME_WLM);
+ else if (templateId == "org.webosinternals.messaging.myspace")
+ serviceName.assign(SERVICENAME_MYSPACE);
+ else if (templateId == "org.webosinternals.messaging.qq")
+@@ -198,6 +200,8 @@ void OnEnabledHandler::getServiceNameFro
+ serviceName.assign(SERVICENAME_JABBER);
+ else if (m_capabilityProviderId == CAPABILITY_LIVE)
+ serviceName.assign(SERVICENAME_LIVE);
++ else if (m_capabilityProviderId == CAPABILITY_WLM)
++ serviceName.assign(SERVICENAME_WLM);
+ else if (m_capabilityProviderId == CAPABILITY_MYSPACE)
+ serviceName.assign(SERVICENAME_MYSPACE);
+ else if (m_capabilityProviderId == CAPABILITY_QQ)
+diff -rupN imlibpurpleservice-1.0/src/OnEnabledHandler.cpp~ imlibpurpleservice-1.0-new//src/OnEnabledHandler.cpp~
+--- imlibpurpleservice-1.0/src/OnEnabledHandler.cpp~ 1969-12-31 18:00:00.000000000 -0600
++++ imlibpurpleservice-1.0-new//src/OnEnabledHandler.cpp~ 2011-03-27 09:10:06.148553000 -0600
+@@ -0,0 +1,434 @@
++/*
++ * OnEnabledHandler.cpp
++ *
++ * Copyright 2010 Palm, Inc. All rights reserved.
++ *
++ * This program is free software and licensed under the terms of the GNU
++ * General Public License Version 2 as published by the Free
++ * Software Foundation;
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License,
++ * Version 2 along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-
++ * 1301, USA
++ *
++ * IMLibpurpleservice uses libpurple.so to implement a fully functional IM
++ * Transport service for use on a mobile device.
++ *
++ * OnEnabledHandler class handles enabling and disabling the IM account
++ */
++
++#include "OnEnabledHandler.h"
++#include "db/MojDbQuery.h"
++#include "IMServiceApp.h"
++#include "IMDefines.h"
++
++OnEnabledHandler::OnEnabledHandler(MojService* service)
++: m_getAccountInfoSlot(this, &OnEnabledHandler::getAccountInfoResult),
++ m_addImLoginStateSlot(this, &OnEnabledHandler::addImLoginStateResult),
++ m_deleteImLoginStateSlot(this, &OnEnabledHandler::deleteImLoginStateResult),
++ m_deleteImMessagesSlot(this, &OnEnabledHandler::deleteImMessagesResult),
++ m_deleteImCommandsSlot(this, &OnEnabledHandler::deleteImCommandsResult),
++ m_deleteContactsSlot(this, &OnEnabledHandler::deleteContactsResult),
++ m_deleteImBuddyStatusSlot(this, &OnEnabledHandler::deleteImBuddyStatusResult),
++ m_service(service),
++ m_dbClient(service, MojDbServiceDefs::ServiceName),
++ m_tempdbClient(service, MojDbServiceDefs::TempServiceName),
++ m_enable(false)
++{
++}
++
++OnEnabledHandler::~OnEnabledHandler()
++{
++}
++
++/*
++ * 1. get account details from accountservices or db?
++ */
++MojErr OnEnabledHandler::start(const MojObject& payload)
++{
++
++ IMServiceHandler::logMojObjectJsonString(_T("OnEnabledHandler payload: %s"), payload);
++
++ MojString accountId;
++ MojErr err = payload.getRequired(_T("accountId"), accountId);
++ if (err != MojErrNone) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler accountId missing so bailing. error %d"), err);
++ return err;
++ }
++
++ err = payload.getRequired(_T("enabled"), m_enable);
++ if (err != MojErrNone) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler enabled missing, assuming 'false'"));
++ m_enable = false;
++ }
++
++ err = payload.getRequired(_T("capabilityProviderId"), m_capabilityProviderId);
++ if (err != MojErrNone) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler capabilityProviderId missing so bailing. error %d"), err);
++ return err;
++ }
++
++ MojRefCountedPtr<MojServiceRequest> req;
++ err = m_service->createRequest(req);
++ if (err != MojErrNone) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::start createRequest failed. error %d"), err);
++ } else {
++ MojObject params;
++ params.put(_T("accountId"), accountId);
++ err = req->send(m_getAccountInfoSlot, "com.palm.service.accounts", "getAccountInfo", params, 1);
++ if (err) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::start: getAccountInfo id %s failed. error %d"), accountId.data(), err);
++ }
++ }
++
++ return MojErrNone;
++}
++
++/*
++ */
++MojErr OnEnabledHandler::getAccountInfoResult(MojObject& payload, MojErr resultErr)
++{
++ MojLogTrace(IMServiceApp::s_log);
++ IMServiceHandler::logMojObjectJsonString(_T("OnEnabledHandler::getAccountInfoResult payload: %s"), payload);
++
++ if (resultErr != MojErrNone) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler getAccountInfo result error %d"), resultErr);
++ return resultErr;
++ }
++
++ MojObject result;
++ MojErr err = payload.getRequired("result", result);
++ if (err != MojErrNone || result.empty()) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::getAccountInfoResult result empty or error %d"), err);
++ return err;
++ }
++
++ MojString accountId;
++ err = result.getRequired("_id", accountId);
++ if (err != MojErrNone || accountId.empty()) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::getAccountInfoResult accountId empty or error %d"), err);
++ return err;
++ }
++
++ MojString username;
++ err = result.getRequired("username", username);
++ if (err != MojErrNone || username.empty()) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::getAccountInfoResult username empty or error %d"), err);
++ return err;
++ }
++
++ MojString serviceName;
++ getServiceNameFromCapabilityId(serviceName);
++ if (serviceName.empty()) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler::getAccountInfoResult serviceName empty"));
++ return err;
++ }
++
++ if (m_enable) {
++ err = accountEnabled(accountId, serviceName, username);
++ } else {
++ err = accountDisabled(accountId, serviceName, username);
++ }
++
++ return err;
++}
++
++MojErr OnEnabledHandler::getDefaultServiceName(const MojObject& accountResult, MojString& serviceName)
++{
++ MojString templateId;
++ MojErr err = accountResult.getRequired("templateId", templateId);
++ if (err != MojErrNone) {
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler templateId empty or error %d"), err);
++ } else {
++ if (templateId == "org.webosinternals.messaging.aol.aim")
++ serviceName.assign(SERVICENAME_AIM);
++ else if (templateId == "org.webosinternals.messaging.facebook")
++ serviceName.assign(SERVICENAME_FACEBOOK);
++ else if (templateId == "org.webosinternals.messaging.google.talk")
++ serviceName.assign(SERVICENAME_GTALK);
++ else if (templateId == "org.webosinternals.messaging.gadu")
++ serviceName.assign(SERVICENAME_GADU);
++ else if (templateId == "org.webosinternals.messaging.groupwise")
++ serviceName.assign(SERVICENAME_GROUPWISE);
++ else if (templateId == "org.webosinternals.messaging.icq")
++ serviceName.assign(SERVICENAME_ICQ);
++ else if (templateId == "org.webosinternals.messaging.jabber")
++ serviceName.assign(SERVICENAME_JABBER);
++ else if (templateId == "org.webosinternals.messaging.live")
++ serviceName.assign(SERVICENAME_LIVE);
++ else if (templateId == "org.webosinternals.messaging.myspace")
++ serviceName.assign(SERVICENAME_MYSPACE);
++ else if (templateId == "org.webosinternals.messaging.qq")
++ serviceName.assign(SERVICENAME_QQ);
++ else if (templateId == "org.webosinternals.messaging.sametime")
++ serviceName.assign(SERVICENAME_SAMETIME);
++ else if (templateId == "org.webosinternals.messaging.sipe")
++ serviceName.assign(SERVICENAME_SIPE);
++ else if (templateId == "org.webosinternals.messaging.xfire")
++ serviceName.assign(SERVICENAME_XFIRE);
++ else if (templateId == "org.webosinternals.messaging.yahoo")
++ serviceName.assign(SERVICENAME_YAHOO);
++ else
++ err = MojErrNotImpl;
++ }
++ return err;
++}
++
++void OnEnabledHandler::getServiceNameFromCapabilityId(MojString& serviceName)
++{
++ if (m_capabilityProviderId == CAPABILITY_AIM)
++ serviceName.assign(SERVICENAME_AIM);
++ else if (m_capabilityProviderId == CAPABILITY_FACEBOOK)
++ serviceName.assign(SERVICENAME_FACEBOOK);
++ else if (m_capabilityProviderId == CAPABILITY_GTALK)
++ serviceName.assign(SERVICENAME_GTALK);
++ else if (m_capabilityProviderId == CAPABILITY_GADU)
++ serviceName.assign(SERVICENAME_GADU);
++ else if (m_capabilityProviderId == CAPABILITY_GROUPWISE)
++ serviceName.assign(SERVICENAME_GROUPWISE);
++ else if (m_capabilityProviderId == CAPABILITY_ICQ)
++ serviceName.assign(SERVICENAME_ICQ);
++ else if (m_capabilityProviderId == CAPABILITY_JABBER)
++ serviceName.assign(SERVICENAME_JABBER);
++ else if (m_capabilityProviderId == CAPABILITY_LIVE)
++ serviceName.assign(SERVICENAME_LIVE);
++ else if (m_capabilityProviderId == CAPABILITY_MYSPACE)
++ serviceName.assign(SERVICENAME_MYSPACE);
++ else if (m_capabilityProviderId == CAPABILITY_QQ)
++ serviceName.assign(SERVICENAME_QQ);
++ else if (m_capabilityProviderId == CAPABILITY_SAMETIME)
++ serviceName.assign(SERVICENAME_SAMETIME);
++ else if (m_capabilityProviderId == CAPABILITY_SIPE)
++ serviceName.assign(SERVICENAME_SIPE);
++ else if (m_capabilityProviderId == CAPABILITY_XFIRE)
++ serviceName.assign(SERVICENAME_XFIRE);
++ else if (m_capabilityProviderId == CAPABILITY_YAHOO)
++ serviceName.assign(SERVICENAME_YAHOO);
++}
++
++/*
++ * Example messaging capability provider
++ * "capabilityProviders": [{
++ "_id": "2+MR",
++ "capability": "MESSAGING",
++ "id": "com.palm.google.talk",
++ "capabilitySubtype": "IM",
++ "loc_name": "Google Talk",
++ "icon": {
++ "loc_32x32": "/usr/palm/public/accounts/com.palm.google/images/gtalk32x32.png",
++ "loc_48x48": "/usr/palm/public/accounts/com.palm.google/images/gtalk48x48.png",
++ "splitter": "/usr/palm/public/accounts/com.palm.google/images/gtalk_transport_splitter.png"
++ },
++ "implementation": "palm://com.palm.imlibpurple/",
++ "onEnabled": "palm://com.palm.imlibpurple/onEnabled"
++ "serviceName":"type_aim",
++ "dbkinds": {
++ "immessage":"com.palm.immessage.libpurple:1",
++ "imcommand":"com.palm.imcommand.libpurple:1"
++ }
++ }],
++ */
++MojErr OnEnabledHandler::getMessagingCapabilityObject(const MojObject& capabilityProviders, MojObject& messagingObj)
++{
++ // iterate thru the capabilities array
++ MojErr err = MojErrRequiredPropNotFound;
++ MojString capability;
++ MojObject::ConstArrayIterator itr = capabilityProviders.arrayBegin();
++ // This shouldn't happen, but check if there's nothing to do.
++ while (itr != capabilityProviders.arrayEnd()) {
++ messagingObj = *itr;
++ err = messagingObj.getRequired("capability", capability);
++ if (capability == "MESSAGING") {
++ err = MojErrNone;
++ break;
++ }
++ ++itr;
++ }
++
++ return err;
++}
++
++/*
++ * Enabling an IM account requires the following
++ * add com.palm.imloginstate.libpurple record
++ */
++MojErr OnEnabledHandler::accountEnabled(const MojString& accountId, const MojString& serviceName, const MojString& username)
++{
++ //TODO: first issue a merge in case the account already exists?
++ MojLogTrace(IMServiceApp::s_log);
++ MojLogInfo(IMServiceApp::s_log, _T("accountEnabled id=%s, serviceName=%s"), accountId.data(), serviceName.data());
++
++ MojObject imLoginState;
++ imLoginState.putString(_T("_kind"), IM_LOGINSTATE_KIND);
++ imLoginState.put(_T("accountId"), accountId);
++ imLoginState.put(_T("serviceName"), serviceName);
++ imLoginState.put(_T("username"), username);
++ imLoginState.putString(_T("state"), LOGIN_STATE_OFFLINE);
++ imLoginState.putInt(_T("availability"), PalmAvailability::ONLINE); //default to online so we automatically login at first
++ MojErr err = m_dbClient.put(m_addImLoginStateSlot, imLoginState);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.put() failed: error %d - %s"), err, error.data());
++ }
++
++ return err;
++}
++
++/*
++ * Disabling an account means doing the following
++ * delete com.palm.imloginstate.libpurple record
++ * delete com.palm.immessage.libpurple records
++ * delete com.palm.imcommand.libpurple records
++ * delete com.palm.contact.libpurple records
++ * delete com.palm.imbuddystatus.libpurple records
++ * delete com.palm.imgroupchat.libpurple records -- groupchats not currently supported
++ * Note: the ChatThreader service takes care of removing empty chats
++ */
++MojErr OnEnabledHandler::accountDisabled(const MojString& accountId, const MojString& serviceName, const MojString& username)
++{
++ MojLogTrace(IMServiceApp::s_log);
++ MojLogInfo(IMServiceApp::s_log, _T("accountDisabled id=%s, serviceName=%s"), accountId.data(), serviceName.data());
++
++ // delete com.palm.imloginstate.libpurple record
++ MojDbQuery queryLoginState;
++ queryLoginState.from(IM_LOGINSTATE_KIND);
++ queryLoginState.where(_T("serviceName"), MojDbQuery::OpEq, serviceName);
++ queryLoginState.where(_T("username"), MojDbQuery::OpEq, username);
++ MojErr err = m_dbClient.del(m_deleteImLoginStateSlot, queryLoginState);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(imloginstate) failed: error %d - %s"), err, error.data());
++ }
++
++ // delete com.palm.immessage.libpurple records
++ //TODO: need to query both from & recipient addresses. Simplify this???
++ MojDbQuery queryMessage;
++ queryMessage.from(IM_IMMESSAGE_KIND);
++ queryLoginState.where(_T("serviceName"), MojDbQuery::OpEq, serviceName);
++ queryLoginState.where(_T("username"), MojDbQuery::OpEq, username);
++ err = m_dbClient.del(m_deleteImMessagesSlot, queryMessage);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(immessage) failed: error %d - %s"), err, error.data());
++ }
++
++ // delete com.palm.imcommand.libpurple records
++ MojDbQuery queryCommand;
++ queryCommand.from(IM_IMCOMMAND_KIND);
++ queryLoginState.where(_T("serviceName"), MojDbQuery::OpEq, serviceName);
++ queryLoginState.where(_T("username"), MojDbQuery::OpEq, username);
++ err = m_dbClient.del(m_deleteImMessagesSlot, queryCommand);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(imcommand) failed: error %d - %s"), err, error.data());
++ }
++
++ // delete com.palm.contact.libpurple record
++ MojDbQuery queryContact;
++ queryContact.from(IM_CONTACT_KIND);
++ queryContact.where(_T("accountId"), MojDbQuery::OpEq, accountId);
++ err = m_dbClient.del(m_deleteContactsSlot, queryContact);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(contact) failed: error %d - %s"), err, error.data());
++ }
++
++ // delete com.palm.imbuddystatus.libpurple record
++ MojDbQuery queryBuddyStatus;
++ queryBuddyStatus.from(IM_BUDDYSTATUS_KIND);
++ queryBuddyStatus.where(_T("accountId"), MojDbQuery::OpEq, accountId);
++ err = m_tempdbClient.del(m_deleteImBuddyStatusSlot, queryBuddyStatus);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogError(IMServiceApp::s_log, _T("OnEnabledHandler db.del(imbuddystatus) failed: error %d - %s"), err, error.data());
++ }
++
++ // now we need to tell libpurple to disconnect the account so we don't get more messages for it
++ // LoginCallbackInterface is null because we don't need to do any processing on the callback - all the DB kinds are already gone.
++ LibpurpleAdapter::logout(serviceName, username, NULL);
++
++ return MojErrNone;
++}
++
++MojErr OnEnabledHandler::addImLoginStateResult(MojObject& payload, MojErr err)
++{
++ MojLogTrace(IMServiceApp::s_log);
++
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogCritical(IMServiceApp::s_log, _T("addLoginStateResult failed: error %d - %s"), err, error.data());
++ //TODO retry adding the record. Not worrying about this for now because the add would only fail in
++ // extreme conditions.
++ }
++ return err;
++}
++
++
++MojErr OnEnabledHandler::deleteImLoginStateResult(MojObject& payload, MojErr err)
++{
++ MojLogTrace(IMServiceApp::s_log);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogCritical(IMServiceApp::s_log, _T("deleteImLoginStateResult failed: error %d - %s"), err, error.data());
++ }
++ return err;
++}
++
++MojErr OnEnabledHandler::deleteImMessagesResult(MojObject& payload, MojErr err)
++{
++ MojLogTrace(IMServiceApp::s_log);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogCritical(IMServiceApp::s_log, _T("deleteImMessagesResult failed: error %d - %s"), err, error.data());
++ }
++ return err;
++}
++
++MojErr OnEnabledHandler::deleteImCommandsResult(MojObject& payload, MojErr err)
++{
++ MojLogTrace(IMServiceApp::s_log);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogCritical(IMServiceApp::s_log, _T("deleteImCommandsResult failed: error %d - %s"), err, error.data());
++ }
++ return err;
++}
++
++MojErr OnEnabledHandler::deleteContactsResult(MojObject& payload, MojErr err)
++{
++ MojLogTrace(IMServiceApp::s_log);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogCritical(IMServiceApp::s_log, _T("deleteContactsResult failed: error %d - %s"), err, error.data());
++ }
++ return err;
++}
++
++MojErr OnEnabledHandler::deleteImBuddyStatusResult(MojObject& payload, MojErr err)
++{
++ MojLogTrace(IMServiceApp::s_log);
++ if (err != MojErrNone) {
++ MojString error;
++ MojErrToString(err, error);
++ MojLogCritical(IMServiceApp::s_log, _T("deleteImBuddyStatusResult failed: error %d - %s"), err, error.data());
++ }
++ return err;
++}
++
diff --git a/3.0/Compiler Source Files/Patches/msn-pecan-patches b/3.0/Compiler Source Files/Patches/msn-pecan-patches
new file mode 100644
index 0000000..78db5f6
--- a/dev/null
+++ b/3.0/Compiler Source Files/Patches/msn-pecan-patches
@@ -0,0 +1,495 @@
+diff -rupN msn-pecan-0.1.1/Makefile msn-pecan-0.1.1-new//Makefile
+--- msn-pecan-0.1.1/Makefile 2010-08-28 10:48:09.000000000 -0500
++++ msn-pecan-0.1.1-new//Makefile 2011-03-27 09:01:42.480553000 -0600
+@@ -17,9 +17,9 @@ GIO_LIBS := $(shell pkg-config --libs gi
+
+ # default configuration options
+ CVR := y
+-LIBSIREN := y
+-LIBMSPACK := y
+-PLUS_SOUNDS := y
++LIBSIREN := n
++LIBMSPACK := n
++PLUS_SOUNDS := n
+ DEBUG := y
+ DIRECTCONN := y
+
+diff -rupN msn-pecan-0.1.1/Makefile~ msn-pecan-0.1.1-new//Makefile~
+--- msn-pecan-0.1.1/Makefile~ 1969-12-31 18:00:00.000000000 -0600
++++ msn-pecan-0.1.1-new//Makefile~ 2011-03-27 09:01:42.480553000 -0600
+@@ -0,0 +1,279 @@
++CC := $(CROSS_COMPILE)gcc
++WINDRES := $(CROSS_COMPILE)windres
++
++XGETTEXT := xgettext
++MSGFMT := msgfmt
++
++PLATFORM := $(shell $(CC) -dumpmachine | cut -f 3 -d -)
++
++PURPLE_CFLAGS := $(shell pkg-config --cflags purple)
++PURPLE_LIBS := $(shell pkg-config --libs purple)
++PURPLE_LIBDIR := $(shell pkg-config --variable=libdir purple)
++PURPLE_DATADIR := $(shell pkg-config --variable=datadir purple)
++PURPLE_PLUGINDIR := $(PURPLE_LIBDIR)/purple-2
++
++GIO_CFLAGS := $(shell pkg-config --cflags gio-2.0)
++GIO_LIBS := $(shell pkg-config --libs gio-2.0)
++
++# default configuration options
++CVR := n
++LIBSIREN := n
++LIBMSPACK := n
++PLUS_SOUNDS := n
++DEBUG := n
++DIRECTCONN := y
++
++CFLAGS := -O2
++
++EXTRA_WARNINGS := -Wall -Wextra -Wformat-nonliteral -Wcast-align -Wpointer-arith \
++ -Wbad-function-cast -Wmissing-prototypes -Wstrict-prototypes \
++ -Wmissing-declarations -Winline -Wundef -Wnested-externs -Wcast-qual \
++ -Wshadow -Wwrite-strings -Wno-unused-parameter -Wfloat-equal -ansi -std=c99
++
++SIMPLE_WARNINGS := -Wextra -ansi -std=c99 -Wno-unused-parameter
++
++OTHER_WARNINGS := -D_FORTIFY_SOURCE=2 -fstack-protector -g3 -Wdisabled-optimization \
++ -Wendif-labels -Wformat=2 -Wstack-protector -Wswitch
++
++CFLAGS += -Wall # $(EXTRA_WARNINGS)
++
++ifdef DEBUG
++ override CFLAGS += -ggdb
++endif
++
++ifdef DEVEL
++ override CFLAGS += -DPECAN_DEVEL
++endif
++
++override CFLAGS += -D_XOPEN_SOURCE
++#override CFLAGS += -I. -DENABLE_NLS -DHAVE_LIBPURPLE -DPURPLE_DEBUG
++override CFLAGS += -I. -DENABLE_NLS -DHAVE_LIBPURPLE
++
++ifdef CVR
++ override CFLAGS += -DPECAN_CVR
++endif
++
++ifndef DO_NOT_USE_PSM
++ override CFLAGS += -DPECAN_USE_PSM
++endif
++
++ifdef LIBSIREN
++ override CFLAGS += -DPECAN_LIBSIREN
++ LIBSIREN_LIBS := -lm
++endif
++
++ifdef LIBMSPACK
++ override CFLAGS += -DPECAN_LIBMSPACK
++ LIBMSPACK_LIBS := -lm
++endif
++
++ifdef PLUS_SOUNDS
++ override CFLAGS += -DRECEIVE_PLUS_SOUNDS
++endif
++
++ifdef GIO
++ override CFLAGS += -DUSE_GIO
++endif
++
++# extra debugging
++override CFLAGS += -DPECAN_DEBUG_SLP
++
++LDFLAGS := -Wl,--no-undefined
++
++objects := msn.o \
++ nexus.o \
++ notification.o \
++ page.o \
++ session.o \
++ switchboard.o \
++ sync.o \
++ pn_log.o \
++ pn_printf.o \
++ pn_util.o \
++ pn_buffer.o \
++ pn_error.o \
++ pn_status.o \
++ pn_oim.o \
++ pn_dp_manager.o \
++ cmd/cmdproc.o \
++ cmd/command.o \
++ cmd/msg.o \
++ cmd/table.o \
++ cmd/transaction.o \
++ io/pn_parser.o \
++ ab/pn_group.o \
++ ab/pn_contact.o \
++ ab/pn_contactlist.o \
++ io/pn_stream.o \
++ io/pn_node.o \
++ io/pn_cmd_server.o \
++ io/pn_http_server.o \
++ io/pn_ssl_conn.o \
++ fix_purple.o
++
++ifdef CVR
++ objects += cvr/pn_peer_call.o \
++ cvr/pn_peer_link.o \
++ cvr/pn_peer_msg.o \
++ cvr/pn_msnobj.o
++ objects += libpurple/xfer.o
++endif
++
++ifdef DIRECTCONN
++ objects += cvr/pn_direct_conn.o
++ objects += io/pn_dc_conn.o
++ override CFLAGS += -DMSN_DIRECTCONN
++endif
++
++ifdef LIBSIREN
++ objects += ext/libsiren/common.o \
++ ext/libsiren/dct4.o \
++ ext/libsiren/decoder.o \
++ ext/libsiren/huffman.o \
++ ext/libsiren/rmlt.o \
++ pn_siren7.o
++endif
++
++ifdef LIBMSPACK
++ objects += ext/libmspack/cabd.o \
++ ext/libmspack/mszipd.o \
++ ext/libmspack/lzxd.o \
++ ext/libmspack/qtmd.o \
++ ext/libmspack/system.o
++endif
++
++sources := $(objects:.o=.c)
++deps := $(objects:.o=.d)
++
++CATALOGS := ar da de eo es fi fr tr hu it nb nl pt_BR pt sr sv tr zh_CN zh_TW
++
++ifeq ($(PLATFORM),darwin)
++ SHLIBEXT := dylib
++else
++ifeq ($(PLATFORM),mingw32)
++ SHLIBEXT := dll
++ LDFLAGS += -Wl,--enable-auto-image-base -L./win32
++ objects += win32/resource.res
++else
++ SHLIBEXT := so
++endif
++endif
++
++ifdef STATIC
++ SHLIBEXT := a
++ override CFLAGS += -DSTATIC_PECAN
++else
++ifneq ($(PLATFORM),mingw32)
++ override CFLAGS += -fPIC
++endif
++endif
++
++plugin := libmsn-pecan.$(SHLIBEXT)
++
++.PHONY: clean
++
++all: $(plugin)
++
++version := $(shell ./get-version)
++
++# pretty print
++ifndef V
++QUIET_CC = @echo ' CC '$@;
++QUIET_LINK = @echo ' LINK '$@;
++QUIET_CLEAN = @echo ' CLEAN '$@;
++QUIET_MO = @echo ' MSGFMT '$@;
++QUIET_WR = @echo ' WINDRES '$@;
++endif
++
++D = $(DESTDIR)
++
++plugin_libs := $(PURPLE_LIBS) $(GIO_LIBS)
++
++ifdef LIBSIREN
++ plugin_libs += $(LIBSIREN_LIBS)
++endif
++
++ifdef LIBMSPACK
++ plugin_libs += $(LIBMSPACK_LIBS)
++endif
++
++$(plugin): $(objects)
++$(plugin): CFLAGS := $(CFLAGS) $(PURPLE_CFLAGS) $(GIO_CFLAGS) $(FALLBACK_CFLAGS) -D VERSION='"$(version)"'
++$(plugin): LIBS := $(plugin_libs)
++
++messages.pot: $(sources)
++ $(XGETTEXT) -m -c --keyword --keyword=_ --keyword=N_ -o $@ $^
++
++%.dylib::
++ $(QUIET_LINK)$(CC) $(LDFLAGS) -dynamiclib -o $@ $^ $(LIBS)
++
++%.so %.dll::
++ $(QUIET_LINK)$(CC) $(LDFLAGS) -shared -o $@ $^ $(LIBS)
++
++%.a::
++ $(QUIET_LINK)$(AR) rcs $@ $^
++
++%.o:: %.c
++ $(QUIET_CC)$(CC) $(CFLAGS) -MMD -o $@ -c $<
++
++%.res:: %.rc
++ $(QUIET_WR)$(WINDRES) $< -O coff -o $@
++
++clean:
++ $(QUIET_CLEAN)$(RM) $(plugin) $(objects) $(deps) `find -name '*.mo'`
++
++%.mo:: %.po
++ $(QUIET_MO)$(MSGFMT) -c -o $@ $<
++
++dist: base := msn-pecan-$(version)
++dist:
++ git archive --format=tar --prefix=$(base)/ HEAD > /tmp/$(base).tar
++ mkdir -p $(base)
++ git-changelog > $(base)/ChangeLog
++ chmod 664 $(base)/ChangeLog
++ tar --append -f /tmp/$(base).tar --owner root --group root $(base)/ChangeLog
++ echo $(version) > $(base)/.version
++ chmod 664 $(base)/.version
++ tar --append -f /tmp/$(base).tar --owner root --group root $(base)/.version
++ rm -r $(base)
++ bzip2 /tmp/$(base).tar
++
++install: $(plugin)
++ install -D $(plugin) $(D)/$(PURPLE_PLUGINDIR)/$(plugin)
++ # chcon -t textrel_shlib_t $(D)/$(PURPLE_PLUGINDIR)/$(plugin) # for selinux
++
++uninstall:
++ rm -f $(D)/$(PURPLE_PLUGINDIR)/$(plugin)
++ for x in $(CATALOGS); do \
++ rm -f $(D)/$(PURPLE_DATADIR)/locale/$$x/LC_MESSAGES/libmsn-pecan.mo; \
++ done
++
++update_locales: messages.pot
++ for x in $(CATALOGS); do \
++ msgmerge -N -U --backup=off po/$$x.po $<; \
++ done
++
++locales: $(foreach e,$(CATALOGS),po/$(e).mo)
++
++install_locales: locales
++ for x in $(CATALOGS); do \
++ install -m 644 -D po/$$x.mo $(D)/$(PURPLE_DATADIR)/locale/$$x/LC_MESSAGES/libmsn-pecan.mo; \
++ done
++
++win32-check:
++ test -f win32/libintl.dll.a
++
++win32: D := libmsn-pecan
++win32: win32-check $(plugin) locales
++ mkdir -p $(D)/plugins
++ cp $(plugin) libmsn-pecan-$(version)-dbg.dll
++ $(CROSS_COMPILE)strip $(plugin)
++ cp $(plugin) $(D)/plugins/
++ for x in $(CATALOGS); do \
++ mkdir -p $(D)/locale/$$x/LC_MESSAGES/; \
++ cp po/$$x.mo $(D)/locale/$$x/LC_MESSAGES/libmsn-pecan.mo; \
++ done
++ cp COPYING $(D)
++ tar -cf /tmp/libmsn-pecan.tar $(D)
++
++-include $(deps)
+diff -rupN msn-pecan-0.1.1/pn_log.c msn-pecan-0.1.1-new//pn_log.c
+--- msn-pecan-0.1.1/pn_log.c 2010-08-28 10:48:09.000000000 -0500
++++ msn-pecan-0.1.1-new//pn_log.c 2011-03-27 09:01:42.448553000 -0600
+@@ -159,7 +159,8 @@ pn_base_log_helper (guint level,
+ if (level <= PN_LOG_LEVEL_INFO || level == PN_LOG_LEVEL_TEST) {
+ char *arg_s;
+ arg_s = g_strdup_printf("%s\n", tmp);
+- ops->print(purple_level, "msn-pecan", arg_s);
++// ops->print(purple_level, "msn-pecan", arg_s);
++
+ g_free(arg_s);
+ }
+ #endif
+diff -rupN msn-pecan-0.1.1/pn_log.c~ msn-pecan-0.1.1-new//pn_log.c~
+--- msn-pecan-0.1.1/pn_log.c~ 1969-12-31 18:00:00.000000000 -0600
++++ msn-pecan-0.1.1-new//pn_log.c~ 2011-03-27 09:01:42.480553000 -0600
+@@ -0,0 +1,179 @@
++/**
++ * Copyright (C) 2007-2009 Felipe Contreras
++ *
++ * This program is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU General Public License
++ * as published by the Free Software Foundation; either version 2
++ * of the License, or (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
++ */
++
++#include "pn_log.h"
++#include "pn_printf.h"
++
++#ifdef PN_DEBUG
++
++/* #define PURPLE_DEBUG */
++/* #define PN_DEBUG_FILE */
++
++#include <fcntl.h>
++#include <unistd.h>
++
++#include <glib/gstdio.h>
++
++#ifdef PURPLE_DEBUG
++/* libpurple stuff. */
++#include <debug.h>
++#endif /* PURPLE_DEBUG */
++
++static inline const gchar *
++log_level_to_string (PecanLogLevel level)
++{
++ switch (level)
++ {
++ case PN_LOG_LEVEL_NONE: return "NONE"; break;
++ case PN_LOG_LEVEL_ERROR: return "ERROR"; break;
++ case PN_LOG_LEVEL_WARNING: return "WARNING"; break;
++ case PN_LOG_LEVEL_INFO: return "INFO"; break;
++ case PN_LOG_LEVEL_DEBUG: return "DEBUG"; break;
++ case PN_LOG_LEVEL_LOG: return "LOG"; break;
++ case PN_LOG_LEVEL_TEST: return "TEST"; break;
++ default: return "Unknown"; break;
++ }
++}
++
++#ifdef PN_DUMP_FILE
++void
++pn_dump_file (const gchar *buffer,
++ gsize len)
++{
++ gint fd;
++ static guint c;
++ gchar *basename;
++ gchar *fullname;
++
++ basename = pn_strdup_printf ("pecan-%.6u.bin", c++);
++
++ fullname = g_build_filename (g_get_tmp_dir (), basename, NULL);
++
++ g_free (basename);
++
++ fd = g_open (fullname, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
++
++ if (fd)
++ {
++ write (fd, buffer, len);
++ close (fd);
++ }
++}
++#endif /* PN_DUMP_FILE */
++
++void
++pn_base_log_helper (guint level,
++ const gchar *file,
++ const gchar *function,
++ gint line,
++ const gchar *fmt,
++ ...)
++{
++ gchar *tmp;
++ va_list args;
++ gboolean console_print = FALSE;
++
++ if (level > PECAN_LOG_LEVEL && level != PN_LOG_LEVEL_TEST)
++ return;
++
++ va_start (args, fmt);
++
++#if defined(PECAN_DEVEL)
++ console_print = TRUE;
++#else
++ if (level == PN_LOG_LEVEL_TEST)
++ console_print = TRUE;
++#endif
++
++#if defined(PURPLE_DEBUG)
++ PurpleDebugLevel purple_level;
++ PurpleDebugUiOps *ops;
++
++ switch (level) {
++ case PN_LOG_LEVEL_ERROR:
++ purple_level = PURPLE_DEBUG_ERROR; break;
++ case PN_LOG_LEVEL_WARNING:
++ purple_level = PURPLE_DEBUG_WARNING; break;
++ case PN_LOG_LEVEL_INFO:
++ purple_level = PURPLE_DEBUG_INFO; break;
++ case PN_LOG_LEVEL_DEBUG:
++ case PN_LOG_LEVEL_TEST:
++ purple_level = PURPLE_DEBUG_MISC; break;
++ case PN_LOG_LEVEL_LOG:
++ purple_level = PURPLE_DEBUG_MISC; break;
++ default:
++ purple_level = PURPLE_DEBUG_MISC; break;
++ }
++
++ if (purple_debug_is_enabled())
++ console_print = TRUE;
++
++ ops = purple_debug_get_ui_ops();
++
++ if (!console_print) {
++ if (!ops || !ops->print ||
++ (ops->is_enabled && !ops->is_enabled(purple_level, "msn-pecan")))
++ {
++ return;
++ }
++ }
++#endif
++
++ tmp = pn_strdup_vprintf (fmt, args);
++
++#if defined(PN_DEBUG_FILE)
++ {
++ static FILE *logfile;
++ if (!logfile)
++ {
++ gint fd;
++ fd = g_file_open_tmp ("msn-pecan-XXXXXX", NULL, NULL);
++ if (fd)
++ logfile = fdopen (fd, "w");
++ }
++ if (logfile)
++ {
++ g_fprintf (logfile, "%s\t%s:%d:%s()\t%s\n",
++ log_level_to_string (level),
++ file, line, function,
++ tmp);
++ }
++ }
++#else
++#if defined(PURPLE_DEBUG)
++ if (level <= PN_LOG_LEVEL_INFO || level == PN_LOG_LEVEL_TEST) {
++ char *arg_s;
++ arg_s = g_strdup_printf("%s\n", tmp);
++ ops->print(purple_level, "msn-pecan", arg_s);
++ g_free(arg_s);
++ }
++#endif
++#endif /* PN_DEBUG_FILE */
++
++ if (console_print)
++ g_print ("%s %s:%d:%s() %s\n",
++ log_level_to_string (level),
++ file, line, function,
++ tmp);
++
++ g_free (tmp);
++
++ va_end (args);
++}
++
++#endif /* PN_DEBUG */
diff --git a/3.0/Compiler Source Files/Patches/pidgin-facebookchat-patches b/3.0/Compiler Source Files/Patches/pidgin-facebookchat-patches
new file mode 100644
index 0000000..984f078
--- a/dev/null
+++ b/3.0/Compiler Source Files/Patches/pidgin-facebookchat-patches
@@ -0,0 +1,105 @@
+diff -rupN pidgin-facebookchat//fb_blist.c pidgin-facebookchat-new//fb_blist.c
+--- pidgin-facebookchat//fb_blist.c 2010-11-26 04:07:32.000000000 -0600
++++ pidgin-facebookchat-new//fb_blist.c 2011-03-27 08:55:51.068553000 -0600
+@@ -104,6 +104,10 @@ static GList *get_buddies(FacebookAccoun
+ fbuddy->thumb_url = g_strdup(buddy_icon_url);
+
+ buddy->proto_data = fbuddy;
++// Fix merging contacts on Pre
++ buddy->server_alias = atoll(uid);
++ buddy->name = g_strdup(name);
++
+ }
+ }
+
+@@ -407,6 +411,8 @@ static void got_buddy_list_cb(FacebookAc
+ return;
+
+ JsonParser *parser = fb_get_parser(data, data_len);
++purple_debug_info("facebook", "after json parser\n");
++
+ if (parser == NULL) {
+ if (fba->bad_buddy_list_count++ == 3)
+ {
+diff -rupN pidgin-facebookchat//fb_messages.c pidgin-facebookchat-new//fb_messages.c
+--- pidgin-facebookchat//fb_messages.c 2010-10-09 05:33:13.000000000 -0500
++++ pidgin-facebookchat-new//fb_messages.c 2011-03-27 08:55:51.068553000 -0600
+@@ -398,9 +398,9 @@ static gboolean fb_send_im_fom(FacebookO
+ jstime = g_strdup_printf("%ld%ld", msg->time.tv_sec, (msg->time.tv_usec/1000));
+
+ encoded_message = g_strdup(purple_url_encode(msg->message));
+- postdata = g_strdup_printf("msg_text=%s&msg_id=%d&to=%s&client_time=%s&post_form_id=%s",
++ postdata = g_strdup_printf("msg_text=%s&msg_id=%d&to=%s&client_time=%s&post_form_id=%s&fb_dtsg=%s",
+ encoded_message, msg->msg_id, msg->who, jstime,
+- msg->fba->post_form_id ? msg->fba->post_form_id : "0");
++ msg->fba->post_form_id ? msg->fba->post_form_id : "0",msg->fba->dtsg?msg->fba->dtsg:"(null)");
+ g_free(encoded_message);
+ g_free(jstime);
+
+diff -rupN pidgin-facebookchat//Makefile pidgin-facebookchat-new//Makefile
+--- pidgin-facebookchat//Makefile 2010-10-08 06:35:45.000000000 -0500
++++ pidgin-facebookchat-new//Makefile 2011-03-27 08:55:51.072552999 -0600
+@@ -5,19 +5,22 @@ WIN32_COMPILER = /usr/bin/i586-mingw32-g
+ WIN32_WINDRES = i586-mingw32-windres
+ WIN32_OBJCOPY = i586-mingw32-objcopy
+ #LINUX_ARM_COMPILER = arm-pc-linux-gnu-gcc
+-LINUX_ARM_COMPILER = arm-none-linux-gnueabi-gcc
++LINUX_ARM_COMPILER = gcc
+ LINUX_PPC_COMPILER = powerpc-unknown-linux-gnu-gcc
+ FREEBSD60_COMPILER = i686-pc-freebsd6.0-gcc
+ MACPORT_COMPILER = i686-apple-darwin10-gcc-4.0.1
+
+-LIBPURPLE_CFLAGS = -I/usr/include/libpurple -I/usr/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS -DHAVE_ZLIB
+-GLIB_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -I/usr/local/include -I/usr/include/json-glib-1.0 -ljson-glib-1.0
++LIBPURPLE_CFLAGS = -I/usr/include/libpurple -I/usr/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS -DHAVE_ZLIB -DUSE_JSONC
++#LIBPURPLE_CFLAGS = -I/usr/include/libpurple -I/usr/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS
++GLIB_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -I/usr/local/include -I/usr/local/include/json-glib-1.0 -L/srv/preware/cross-compile/staging/armv7/usr/lib -ljson-glib-1.0 -lglib-2.0 -lz -ljson
+ WIN32_DEV_DIR = /root/pidgin/win32-dev
+ WIN32_PIDGIN_DIR = /root/pidgin/pidgin-2.3.0_win32
+ WIN32_CFLAGS = -I${WIN32_DEV_DIR}/gtk_2_0/include/glib-2.0 -I${WIN32_PIDGIN_DIR}/libpurple/win32 -I${WIN32_DEV_DIR}/gtk_2_0/include -I${WIN32_DEV_DIR}/gtk_2_0/include/glib-2.0 -I${WIN32_DEV_DIR}/gtk_2_0/lib/glib-2.0/include -I/usr/include/json-glib-1.0 -Wno-format
+ WIN32_LIBS = -L${WIN32_DEV_DIR}/gtk_2_0/lib -L${WIN32_PIDGIN_DIR}/libpurple -lglib-2.0 -lgobject-2.0 -lintl -lpurple -lws2_32 -L. -ljson-glib-1.0 -lzlib1
+ MACPORT_CFLAGS = -I/opt/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS -DHAVE_ZLIB -I/opt/local/include/glib-2.0 -I/opt/local/lib/glib-2.0/include -I/opt/local/include -I/opt/local/include/json-glib-1.0 -arch i386 -arch ppc -dynamiclib -L/opt/local/lib -ljson-glib-1.0 -lpurple -lglib-2.0 -lgobject-2.0 -lintl -lz -isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4
+-PIDGIN_DIR = /root/pidgin/pidgin-2.7.0
++PIDGIN_DIR = /srv/preware/cross-compile/packages/x/pidgin/build/src/
++
++CC = gcc
+
+ DEB_PACKAGE_DIR = ./debdir
+
+@@ -54,14 +57,13 @@ FACEBOOK_SOURCES = \
+
+ all: libfacebook.so libfacebook.dll libfacebook64.so libfacebookarm.so libfacebookppc.so installers sourcepackage
+
++linux: libfacebookarm.so
++
+ install:
+- cp libfacebook.so /usr/lib/purple-2/
+- cp libfacebook64.so /usr/lib64/purple-2/
+- cp libfacebookarm.so /usr/lib/pidgin/
+- cp libfacebookppc.so /usr/lib/purple-2/
+- cp facebook16.png /usr/share/pixmaps/pidgin/protocols/16/facebook.png
+- cp facebook22.png /usr/share/pixmaps/pidgin/protocols/22/facebook.png
+- cp facebook48.png /usr/share/pixmaps/pidgin/protocols/48/facebook.png
++ cp libfacebookarm.so /usr/local/lib/purple-2/
++ cp facebook16.png /usr/local/share/pixmaps/pidgin/protocols/16/facebook.png
++ cp facebook22.png /usr/local/share/pixmaps/pidgin/protocols/22/facebook.png
++ cp facebook48.png /usr/local/share/pixmaps/pidgin/protocols/48/facebook.png
+
+ installers: pidgin-facebookchat.exe pidgin-facebookchat.deb pidgin-facebookchat.tar.bz2
+
+@@ -73,13 +75,13 @@ libfacebook.macport.so: ${FACEBOOK_SOURC
+ ${MACPORT_COMPILER} ${MACPORT_CFLAGS} -Wall -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebook.macport.so -shared
+
+ libfacebook.so: ${FACEBOOK_SOURCES}
+- ${LINUX32_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebook.so -shared -fPIC -DPIC
++ ${LINUX32_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebook.so -shared -fPIC -DPIC -static
+
+ libfacebookxmpp.so: libfbxmpp.c ${FACEBOOK_SOURCES}
+ ${LINUX32_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe libfbxmpp.c ${FACEBOOK_SOURCES} -o libfacebookxmpp.so -shared -fPIC -DPIC -I$(PIDGIN_DIR)/libpurple/protocols/jabber/ -lxml2 -I/usr/include/libxml2
+
+ libfacebookarm.so: ${FACEBOOK_SOURCES}
+- ${LINUX_ARM_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebookarm.so -shared -fPIC -DPIC
++ ${LINUX_ARM_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebookarm.so -shared -fPIC -DPIC
+
+ libfacebook64.so: ${FACEBOOK_SOURCES}
+ ${LINUX64_COMPILER} ${LIBPURPLE_CFLAGS} -Wall ${GLIB_CFLAGS} -I. -g -m64 -O2 -pipe ${FACEBOOK_SOURCES} -o libfacebook64.so -shared -fPIC -DPIC
diff --git a/3.0/Compiler Source Files/Patches/pidgin-patches b/3.0/Compiler Source Files/Patches/pidgin-patches
new file mode 100644
index 0000000..484ff22
--- a/dev/null
+++ b/3.0/Compiler Source Files/Patches/pidgin-patches
@@ -0,0 +1,29796 @@
+diff -rupN pidgin-2.7.7/libpurple/blist.c pidgin-2.7.7-new//libpurple/blist.c
+--- pidgin-2.7.7/libpurple/blist.c 2011-03-27 09:05:47.872553000 -0600
++++ pidgin-2.7.7-new//libpurple/blist.c 2011-03-27 09:15:30.072552999 -0600
+@@ -1059,7 +1059,7 @@ void purple_blist_alias_contact(PurpleCo
+
+ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
+ contact, old_alias);
+- g_free(old_alias);
++// g_free(old_alias);
+ }
+
+ void purple_blist_alias_chat(PurpleChat *chat, const char *alias)
+@@ -1137,7 +1137,7 @@ void purple_blist_alias_buddy(PurpleBudd
+
+ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
+ buddy, old_alias);
+- g_free(old_alias);
++// g_free(old_alias);
+ }
+
+ void purple_blist_server_alias_buddy(PurpleBuddy *buddy, const char *alias)
+@@ -1179,7 +1179,7 @@ void purple_blist_server_alias_buddy(Pur
+
+ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
+ buddy, old_alias);
+- g_free(old_alias);
++// g_free(old_alias);
+ }
+
+ /*
+diff -rupN pidgin-2.7.7/libpurple/blist.c~ pidgin-2.7.7-new//libpurple/blist.c~
+--- pidgin-2.7.7/libpurple/blist.c~ 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/blist.c~ 2011-03-27 09:15:30.064552999 -0600
+@@ -0,0 +1,3266 @@
++/*
++ * purple
++ *
++ * Purple is the legal property of its developers, whose names are too numerous
++ * to list here. Please refer to the COPYRIGHT file distributed with this
++ * source distribution.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
++ *
++ */
++#define _PURPLE_BLIST_C_
++
++#include "internal.h"
++#include "blist.h"
++#include "conversation.h"
++#include "dbus-maybe.h"
++#include "debug.h"
++#include "notify.h"
++#include "prefs.h"
++#include "privacy.h"
++#include "prpl.h"
++#include "server.h"
++#include "signals.h"
++#include "util.h"
++#include "value.h"
++#include "xmlnode.h"
++
++static PurpleBlistUiOps *blist_ui_ops = NULL;
++
++static PurpleBuddyList *purplebuddylist = NULL;
++
++/**
++ * A hash table used for efficient lookups of buddies by name.
++ * PurpleAccount* => GHashTable*, with the inner hash table being
++ * struct _purple_hbuddy => PurpleBuddy*
++ */
++static GHashTable *buddies_cache = NULL;
++
++/**
++ * A hash table used for efficient lookups of groups by name.
++ * UTF-8 collate-key => PurpleGroup*.
++ */
++static GHashTable *groups_cache = NULL;
++
++static guint save_timer = 0;
++static gboolean blist_loaded = FALSE;
++
++/*********************************************************************
++ * Private utility functions *
++ *********************************************************************/
++
++static PurpleBlistNode *purple_blist_get_last_sibling(PurpleBlistNode *node)
++{
++ PurpleBlistNode *n = node;
++ if (!n)
++ return NULL;
++ while (n->next)
++ n = n->next;
++ return n;
++}
++
++static PurpleBlistNode *purple_blist_get_last_child(PurpleBlistNode *node)
++{
++ if (!node)
++ return NULL;
++ return purple_blist_get_last_sibling(node->child);
++}
++
++struct _list_account_buddies {
++ GSList *list;
++ PurpleAccount *account;
++};
++
++struct _purple_hbuddy {
++ char *name;
++ PurpleAccount *account;
++ PurpleBlistNode *group;
++};
++
++/* This function must not use purple_normalize */
++static guint _purple_blist_hbuddy_hash(struct _purple_hbuddy *hb)
++{
++ return g_str_hash(hb->name) ^ g_direct_hash(hb->group) ^ g_direct_hash(hb->account);
++}
++
++/* This function must not use purple_normalize */
++static guint _purple_blist_hbuddy_equal(struct _purple_hbuddy *hb1, struct _purple_hbuddy *hb2)
++{
++ return (hb1->group == hb2->group &&
++ hb1->account == hb2->account &&
++ g_str_equal(hb1->name, hb2->name));
++}
++
++static void _purple_blist_hbuddy_free_key(struct _purple_hbuddy *hb)
++{
++ g_free(hb->name);
++ g_free(hb);
++}
++
++static void
++purple_blist_buddies_cache_add_account(PurpleAccount *account)
++{
++ GHashTable *account_buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
++ (GEqualFunc)_purple_blist_hbuddy_equal,
++ (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
++ g_hash_table_insert(buddies_cache, account, account_buddies);
++}
++
++static void
++purple_blist_buddies_cache_remove_account(const PurpleAccount *account)
++{
++ g_hash_table_remove(buddies_cache, account);
++}
++
++
++/*********************************************************************
++ * Writing to disk *
++ *********************************************************************/
++
++static void
++value_to_xmlnode(gpointer key, gpointer hvalue, gpointer user_data)
++{
++ const char *name;
++ PurpleValue *value;
++ xmlnode *node, *child;
++ char buf[21];
++
++ name = (const char *)key;
++ value = (PurpleValue *)hvalue;
++ node = (xmlnode *)user_data;
++
++ g_return_if_fail(value != NULL);
++
++ child = xmlnode_new_child(node, "setting");
++ xmlnode_set_attrib(child, "name", name);
++
++ if (purple_value_get_type(value) == PURPLE_TYPE_INT) {
++ xmlnode_set_attrib(child, "type", "int");
++ g_snprintf(buf, sizeof(buf), "%d", purple_value_get_int(value));
++ xmlnode_insert_data(child, buf, -1);
++ }
++ else if (purple_value_get_type(value) == PURPLE_TYPE_STRING) {
++ xmlnode_set_attrib(child, "type", "string");
++ xmlnode_insert_data(child, purple_value_get_string(value), -1);
++ }
++ else if (purple_value_get_type(value) == PURPLE_TYPE_BOOLEAN) {
++ xmlnode_set_attrib(child, "type", "bool");
++ g_snprintf(buf, sizeof(buf), "%d", purple_value_get_boolean(value));
++ xmlnode_insert_data(child, buf, -1);
++ }
++}
++
++static void
++chat_component_to_xmlnode(gpointer key, gpointer value, gpointer user_data)
++{
++ const char *name;
++ const char *data;
++ xmlnode *node, *child;
++
++ name = (const char *)key;
++ data = (const char *)value;
++ node = (xmlnode *)user_data;
++
++ g_return_if_fail(data != NULL);
++
++ child = xmlnode_new_child(node, "component");
++ xmlnode_set_attrib(child, "name", name);
++ xmlnode_insert_data(child, data, -1);
++}
++
++static xmlnode *
++buddy_to_xmlnode(PurpleBlistNode *bnode)
++{
++ xmlnode *node, *child;
++ PurpleBuddy *buddy;
++
++ buddy = (PurpleBuddy *)bnode;
++
++ node = xmlnode_new("buddy");
++ xmlnode_set_attrib(node, "account", purple_account_get_username(buddy->account));
++ xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(buddy->account));
++
++ child = xmlnode_new_child(node, "name");
++ xmlnode_insert_data(child, buddy->name, -1);
++
++ if (buddy->alias != NULL)
++ {
++ child = xmlnode_new_child(node, "alias");
++ xmlnode_insert_data(child, buddy->alias, -1);
++ }
++
++ /* Write buddy settings */
++ g_hash_table_foreach(buddy->node.settings, value_to_xmlnode, node);
++
++ return node;
++}
++
++static xmlnode *
++contact_to_xmlnode(PurpleBlistNode *cnode)
++{
++ xmlnode *node, *child;
++ PurpleContact *contact;
++ PurpleBlistNode *bnode;
++
++ contact = (PurpleContact *)cnode;
++
++ node = xmlnode_new("contact");
++
++ if (contact->alias != NULL)
++ {
++ xmlnode_set_attrib(node, "alias", contact->alias);
++ }
++
++ /* Write buddies */
++ for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
++ {
++ if (!PURPLE_BLIST_NODE_SHOULD_SAVE(bnode))
++ continue;
++ if (PURPLE_BLIST_NODE_IS_BUDDY(bnode))
++ {
++ child = buddy_to_xmlnode(bnode);
++ xmlnode_insert_child(node, child);
++ }
++ }
++
++ /* Write contact settings */
++ g_hash_table_foreach(cnode->settings, value_to_xmlnode, node);
++
++ return node;
++}
++
++static xmlnode *
++chat_to_xmlnode(PurpleBlistNode *cnode)
++{
++ xmlnode *node, *child;
++ PurpleChat *chat;
++
++ chat = (PurpleChat *)cnode;
++
++ node = xmlnode_new("chat");
++ xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(chat->account));
++ xmlnode_set_attrib(node, "account", purple_account_get_username(chat->account));
++
++ if (chat->alias != NULL)
++ {
++ child = xmlnode_new_child(node, "alias");
++ xmlnode_insert_data(child, chat->alias, -1);
++ }
++
++ /* Write chat components */
++ g_hash_table_foreach(chat->components, chat_component_to_xmlnode, node);
++
++ /* Write chat settings */
++ g_hash_table_foreach(chat->node.settings, value_to_xmlnode, node);
++
++ return node;
++}
++
++static xmlnode *
++group_to_xmlnode(PurpleBlistNode *gnode)
++{
++ xmlnode *node, *child;
++ PurpleGroup *group;
++ PurpleBlistNode *cnode;
++
++ group = (PurpleGroup *)gnode;
++
++ node = xmlnode_new("group");
++ xmlnode_set_attrib(node, "name", group->name);
++
++ /* Write settings */
++ g_hash_table_foreach(group->node.settings, value_to_xmlnode, node);
++
++ /* Write contacts and chats */
++ for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
++ {
++ if (!PURPLE_BLIST_NODE_SHOULD_SAVE(cnode))
++ continue;
++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode))
++ {
++ child = contact_to_xmlnode(cnode);
++ xmlnode_insert_child(node, child);
++ }
++ else if (PURPLE_BLIST_NODE_IS_CHAT(cnode))
++ {
++ child = chat_to_xmlnode(cnode);
++ xmlnode_insert_child(node, child);
++ }
++ }
++
++ return node;
++}
++
++static xmlnode *
++accountprivacy_to_xmlnode(PurpleAccount *account)
++{
++ xmlnode *node, *child;
++ GSList *cur;
++ char buf[10];
++
++ node = xmlnode_new("account");
++ xmlnode_set_attrib(node, "proto", purple_account_get_protocol_id(account));
++ xmlnode_set_attrib(node, "name", purple_account_get_username(account));
++ g_snprintf(buf, sizeof(buf), "%d", account->perm_deny);
++ xmlnode_set_attrib(node, "mode", buf);
++
++ for (cur = account->permit; cur; cur = cur->next)
++ {
++ child = xmlnode_new_child(node, "permit");
++ xmlnode_insert_data(child, cur->data, -1);
++ }
++
++ for (cur = account->deny; cur; cur = cur->next)
++ {
++ child = xmlnode_new_child(node, "block");
++ xmlnode_insert_data(child, cur->data, -1);
++ }
++
++ return node;
++}
++
++static xmlnode *
++blist_to_xmlnode(void)
++{
++ xmlnode *node, *child, *grandchild;
++ PurpleBlistNode *gnode;
++ GList *cur;
++
++ node = xmlnode_new("purple");
++ xmlnode_set_attrib(node, "version", "1.0");
++
++ /* Write groups */
++ child = xmlnode_new_child(node, "blist");
++ for (gnode = purplebuddylist->root; gnode != NULL; gnode = gnode->next)
++ {
++ if (!PURPLE_BLIST_NODE_SHOULD_SAVE(gnode))
++ continue;
++ if (PURPLE_BLIST_NODE_IS_GROUP(gnode))
++ {
++ grandchild = group_to_xmlnode(gnode);
++ xmlnode_insert_child(child, grandchild);
++ }
++ }
++
++ /* Write privacy settings */
++ child = xmlnode_new_child(node, "privacy");
++ for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next)
++ {
++ grandchild = accountprivacy_to_xmlnode(cur->data);
++ xmlnode_insert_child(child, grandchild);
++ }
++
++ return node;
++}
++
++static void
++purple_blist_sync(void)
++{
++ xmlnode *node;
++ char *data;
++
++ if (!blist_loaded)
++ {
++ purple_debug_error("blist", "Attempted to save buddy list before it "
++ "was read!\n");
++ return;
++ }
++
++ node = blist_to_xmlnode();
++ data = xmlnode_to_formatted_str(node, NULL);
++ purple_util_write_data_to_file("blist.xml", data, -1);
++ g_free(data);
++ xmlnode_free(node);
++}
++
++static gboolean
++save_cb(gpointer data)
++{
++ purple_blist_sync();
++ save_timer = 0;
++ return FALSE;
++}
++
++static void
++_purple_blist_schedule_save()
++{
++ if (save_timer == 0)
++ save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
++}
++
++static void
++purple_blist_save_account(PurpleAccount *account)
++{
++#if 1
++ _purple_blist_schedule_save();
++#else
++ if (account != NULL) {
++ /* Save the buddies and privacy data for this account */
++ } else {
++ /* Save all buddies and privacy data */
++ }
++#endif
++}
++
++static void
++purple_blist_save_node(PurpleBlistNode *node)
++{
++ _purple_blist_schedule_save();
++}
++
++void purple_blist_schedule_save()
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++
++ /* Save everything */
++ if (ops && ops->save_account)
++ ops->save_account(NULL);
++}
++
++
++/*********************************************************************
++ * Reading from disk *
++ *********************************************************************/
++
++static void
++parse_setting(PurpleBlistNode *node, xmlnode *setting)
++{
++ const char *name = xmlnode_get_attrib(setting, "name");
++ const char *type = xmlnode_get_attrib(setting, "type");
++ char *value = xmlnode_get_data(setting);
++
++ if (!value)
++ return;
++
++ if (!type || purple_strequal(type, "string"))
++ purple_blist_node_set_string(node, name, value);
++ else if (purple_strequal(type, "bool"))
++ purple_blist_node_set_bool(node, name, atoi(value));
++ else if (purple_strequal(type, "int"))
++ purple_blist_node_set_int(node, name, atoi(value));
++
++ g_free(value);
++}
++
++static void
++parse_buddy(PurpleGroup *group, PurpleContact *contact, xmlnode *bnode)
++{
++ PurpleAccount *account;
++ PurpleBuddy *buddy;
++ char *name = NULL, *alias = NULL;
++ const char *acct_name, *proto, *protocol;
++ xmlnode *x;
++
++ acct_name = xmlnode_get_attrib(bnode, "account");
++ protocol = xmlnode_get_attrib(bnode, "protocol");
++ protocol = _purple_oscar_convert(acct_name, protocol); /* XXX: Remove */
++ proto = xmlnode_get_attrib(bnode, "proto");
++ proto = _purple_oscar_convert(acct_name, proto); /* XXX: Remove */
++
++ if (!acct_name || (!proto && !protocol))
++ return;
++
++ account = purple_accounts_find(acct_name, proto ? proto : protocol);
++
++ if (!account)
++ return;
++
++ if ((x = xmlnode_get_child(bnode, "name")))
++ name = xmlnode_get_data(x);
++
++ if (!name)
++ return;
++
++ if ((x = xmlnode_get_child(bnode, "alias")))
++ alias = xmlnode_get_data(x);
++
++ buddy = purple_buddy_new(account, name, alias);
++ purple_blist_add_buddy(buddy, contact, group,
++ purple_blist_get_last_child((PurpleBlistNode*)contact));
++
++ for (x = xmlnode_get_child(bnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
++ parse_setting((PurpleBlistNode*)buddy, x);
++ }
++
++ g_free(name);
++ g_free(alias);
++}
++
++static void
++parse_contact(PurpleGroup *group, xmlnode *cnode)
++{
++ PurpleContact *contact = purple_contact_new();
++ xmlnode *x;
++ const char *alias;
++
++ purple_blist_add_contact(contact, group,
++ purple_blist_get_last_child((PurpleBlistNode*)group));
++
++ if ((alias = xmlnode_get_attrib(cnode, "alias"))) {
++ purple_blist_alias_contact(contact, alias);
++ }
++
++ for (x = cnode->child; x; x = x->next) {
++ if (x->type != XMLNODE_TYPE_TAG)
++ continue;
++ if (purple_strequal(x->name, "buddy"))
++ parse_buddy(group, contact, x);
++ else if (purple_strequal(x->name, "setting"))
++ parse_setting((PurpleBlistNode*)contact, x);
++ }
++
++ /* if the contact is empty, don't keep it around. it causes problems */
++ if (!((PurpleBlistNode*)contact)->child)
++ purple_blist_remove_contact(contact);
++}
++
++static void
++parse_chat(PurpleGroup *group, xmlnode *cnode)
++{
++ PurpleChat *chat;
++ PurpleAccount *account;
++ const char *acct_name, *proto, *protocol;
++ xmlnode *x;
++ char *alias = NULL;
++ GHashTable *components;
++
++ acct_name = xmlnode_get_attrib(cnode, "account");
++ protocol = xmlnode_get_attrib(cnode, "protocol");
++ proto = xmlnode_get_attrib(cnode, "proto");
++
++ if (!acct_name || (!proto && !protocol))
++ return;
++
++ account = purple_accounts_find(acct_name, proto ? proto : protocol);
++
++ if (!account)
++ return;
++
++ if ((x = xmlnode_get_child(cnode, "alias")))
++ alias = xmlnode_get_data(x);
++
++ components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
++
++ for (x = xmlnode_get_child(cnode, "component"); x; x = xmlnode_get_next_twin(x)) {
++ const char *name;
++ char *value;
++
++ name = xmlnode_get_attrib(x, "name");
++ value = xmlnode_get_data(x);
++ g_hash_table_replace(components, g_strdup(name), value);
++ }
++
++ chat = purple_chat_new(account, alias, components);
++ purple_blist_add_chat(chat, group,
++ purple_blist_get_last_child((PurpleBlistNode*)group));
++
++ for (x = xmlnode_get_child(cnode, "setting"); x; x = xmlnode_get_next_twin(x)) {
++ parse_setting((PurpleBlistNode*)chat, x);
++ }
++
++ g_free(alias);
++}
++
++static void
++parse_group(xmlnode *groupnode)
++{
++ const char *name = xmlnode_get_attrib(groupnode, "name");
++ PurpleGroup *group;
++ xmlnode *cnode;
++
++ if (!name)
++ name = _("Buddies");
++
++ group = purple_group_new(name);
++ purple_blist_add_group(group,
++ purple_blist_get_last_sibling(purplebuddylist->root));
++
++ for (cnode = groupnode->child; cnode; cnode = cnode->next) {
++ if (cnode->type != XMLNODE_TYPE_TAG)
++ continue;
++ if (purple_strequal(cnode->name, "setting"))
++ parse_setting((PurpleBlistNode*)group, cnode);
++ else if (purple_strequal(cnode->name, "contact") ||
++ purple_strequal(cnode->name, "person"))
++ parse_contact(group, cnode);
++ else if (purple_strequal(cnode->name, "chat"))
++ parse_chat(group, cnode);
++ }
++}
++
++/* TODO: Make static and rename to load_blist */
++void
++purple_blist_load()
++{
++ xmlnode *purple, *blist, *privacy;
++
++ blist_loaded = TRUE;
++
++ purple = purple_util_read_xml_from_file("blist.xml", _("buddy list"));
++
++ if (purple == NULL)
++ return;
++
++ blist = xmlnode_get_child(purple, "blist");
++ if (blist) {
++ xmlnode *groupnode;
++ for (groupnode = xmlnode_get_child(blist, "group"); groupnode != NULL;
++ groupnode = xmlnode_get_next_twin(groupnode)) {
++ parse_group(groupnode);
++ }
++ }
++
++ privacy = xmlnode_get_child(purple, "privacy");
++ if (privacy) {
++ xmlnode *anode;
++ for (anode = privacy->child; anode; anode = anode->next) {
++ xmlnode *x;
++ PurpleAccount *account;
++ int imode;
++ const char *acct_name, *proto, *mode, *protocol;
++
++ acct_name = xmlnode_get_attrib(anode, "name");
++ protocol = xmlnode_get_attrib(anode, "protocol");
++ proto = xmlnode_get_attrib(anode, "proto");
++ mode = xmlnode_get_attrib(anode, "mode");
++
++ if (!acct_name || (!proto && !protocol) || !mode)
++ continue;
++
++ account = purple_accounts_find(acct_name, proto ? proto : protocol);
++
++ if (!account)
++ continue;
++
++ imode = atoi(mode);
++ account->perm_deny = (imode != 0 ? imode : PURPLE_PRIVACY_ALLOW_ALL);
++
++ for (x = anode->child; x; x = x->next) {
++ char *name;
++ if (x->type != XMLNODE_TYPE_TAG)
++ continue;
++
++ if (purple_strequal(x->name, "permit")) {
++ name = xmlnode_get_data(x);
++ purple_privacy_permit_add(account, name, TRUE);
++ g_free(name);
++ } else if (purple_strequal(x->name, "block")) {
++ name = xmlnode_get_data(x);
++ purple_privacy_deny_add(account, name, TRUE);
++ g_free(name);
++ }
++ }
++ }
++ }
++
++ xmlnode_free(purple);
++
++ /* This tells the buddy icon code to do its thing. */
++ _purple_buddy_icons_blist_loaded_cb();
++}
++
++
++/*********************************************************************
++ * Stuff *
++ *********************************************************************/
++
++static void
++purple_contact_compute_priority_buddy(PurpleContact *contact)
++{
++ PurpleBlistNode *bnode;
++ PurpleBuddy *new_priority = NULL;
++
++ g_return_if_fail(contact != NULL);
++
++ contact->priority = NULL;
++ for (bnode = ((PurpleBlistNode*)contact)->child;
++ bnode != NULL;
++ bnode = bnode->next)
++ {
++ PurpleBuddy *buddy;
++
++ if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
++ continue;
++
++ buddy = (PurpleBuddy*)bnode;
++ if (new_priority == NULL)
++ {
++ new_priority = buddy;
++ continue;
++ }
++
++ if (purple_account_is_connected(buddy->account))
++ {
++ int cmp = 1;
++ if (purple_account_is_connected(new_priority->account))
++ cmp = purple_presence_compare(purple_buddy_get_presence(new_priority),
++ purple_buddy_get_presence(buddy));
++
++ if (cmp > 0 || (cmp == 0 &&
++ purple_prefs_get_bool("/purple/contact/last_match")))
++ {
++ new_priority = buddy;
++ }
++ }
++ }
++
++ contact->priority = new_priority;
++ contact->priority_valid = TRUE;
++}
++
++
++/*****************************************************************************
++ * Public API functions *
++ *****************************************************************************/
++
++PurpleBuddyList *purple_blist_new()
++{
++ PurpleBlistUiOps *ui_ops;
++ GList *account;
++ PurpleBuddyList *gbl = g_new0(PurpleBuddyList, 1);
++ PURPLE_DBUS_REGISTER_POINTER(gbl, PurpleBuddyList);
++
++ ui_ops = purple_blist_get_ui_ops();
++
++ gbl->buddies = g_hash_table_new_full((GHashFunc)_purple_blist_hbuddy_hash,
++ (GEqualFunc)_purple_blist_hbuddy_equal,
++ (GDestroyNotify)_purple_blist_hbuddy_free_key, NULL);
++
++ buddies_cache = g_hash_table_new_full(g_direct_hash, g_direct_equal,
++ NULL, (GDestroyNotify)g_hash_table_destroy);
++
++ groups_cache = g_hash_table_new_full((GHashFunc)g_str_hash,
++ (GEqualFunc)g_str_equal,
++ (GDestroyNotify)g_free, NULL);
++
++ for (account = purple_accounts_get_all(); account != NULL; account = account->next)
++ {
++ purple_blist_buddies_cache_add_account(account->data);
++ }
++
++ if (ui_ops != NULL && ui_ops->new_list != NULL)
++ ui_ops->new_list(gbl);
++
++ return gbl;
++}
++
++void
++purple_set_blist(PurpleBuddyList *list)
++{
++ purplebuddylist = list;
++}
++
++PurpleBuddyList *
++purple_get_blist()
++{
++ return purplebuddylist;
++}
++
++PurpleBlistNode *
++purple_blist_get_root()
++{
++ return purplebuddylist ? purplebuddylist->root : NULL;
++}
++
++static void
++append_buddy(gpointer key, gpointer value, gpointer user_data)
++{
++ GSList **list = user_data;
++ *list = g_slist_prepend(*list, value);
++}
++
++GSList *
++purple_blist_get_buddies()
++{
++ GSList *buddies = NULL;
++
++ if (!purplebuddylist)
++ return NULL;
++
++ g_hash_table_foreach(purplebuddylist->buddies, append_buddy, &buddies);
++ return buddies;
++}
++
++void *
++purple_blist_get_ui_data()
++{
++ return purplebuddylist->ui_data;
++}
++
++void
++purple_blist_set_ui_data(void *ui_data)
++{
++ purplebuddylist->ui_data = ui_data;
++}
++
++void purple_blist_show()
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++
++ if (ops && ops->show)
++ ops->show(purplebuddylist);
++}
++
++void purple_blist_destroy()
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++
++ purple_debug(PURPLE_DEBUG_INFO, "blist", "Destroying\n");
++
++ if (ops && ops->destroy)
++ ops->destroy(purplebuddylist);
++}
++
++void purple_blist_set_visible(gboolean show)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++
++ if (ops && ops->set_visible)
++ ops->set_visible(purplebuddylist, show);
++}
++
++static PurpleBlistNode *get_next_node(PurpleBlistNode *node, gboolean godeep)
++{
++ if (node == NULL)
++ return NULL;
++
++ if (godeep && node->child)
++ return node->child;
++
++ if (node->next)
++ return node->next;
++
++ return get_next_node(node->parent, FALSE);
++}
++
++PurpleBlistNode *purple_blist_node_next(PurpleBlistNode *node, gboolean offline)
++{
++ PurpleBlistNode *ret = node;
++
++ if (offline)
++ return get_next_node(ret, TRUE);
++ do
++ {
++ ret = get_next_node(ret, TRUE);
++ } while (ret && PURPLE_BLIST_NODE_IS_BUDDY(ret) &&
++ !purple_account_is_connected(purple_buddy_get_account((PurpleBuddy *)ret)));
++
++ return ret;
++}
++
++PurpleBlistNode *purple_blist_node_get_parent(PurpleBlistNode *node)
++{
++ return node ? node->parent : NULL;
++}
++
++PurpleBlistNode *purple_blist_node_get_first_child(PurpleBlistNode *node)
++{
++ return node ? node->child : NULL;
++}
++
++PurpleBlistNode *purple_blist_node_get_sibling_next(PurpleBlistNode *node)
++{
++ return node? node->next : NULL;
++}
++
++PurpleBlistNode *purple_blist_node_get_sibling_prev(PurpleBlistNode *node)
++{
++ return node? node->prev : NULL;
++}
++
++void *
++purple_blist_node_get_ui_data(const PurpleBlistNode *node)
++{
++ g_return_val_if_fail(node, NULL);
++
++ return node->ui_data;
++}
++
++void
++purple_blist_node_set_ui_data(PurpleBlistNode *node, void *ui_data) {
++ g_return_if_fail(node);
++
++ node->ui_data = ui_data;
++}
++
++void
++purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurplePresence *presence;
++ PurpleStatus *status;
++ PurpleBlistNode *cnode;
++
++ g_return_if_fail(buddy != NULL);
++
++ presence = purple_buddy_get_presence(buddy);
++ status = purple_presence_get_active_status(presence);
++
++ purple_debug_info("blist", "Updating buddy status for %s (%s)\n",
++ buddy->name, purple_account_get_protocol_name(buddy->account));
++
++ if (purple_status_is_online(status) &&
++ !purple_status_is_online(old_status)) {
++
++ purple_signal_emit(purple_blist_get_handle(), "buddy-signed-on", buddy);
++
++ cnode = buddy->node.parent;
++ if (++(PURPLE_CONTACT(cnode)->online) == 1)
++ PURPLE_GROUP(cnode->parent)->online++;
++ } else if (!purple_status_is_online(status) &&
++ purple_status_is_online(old_status)) {
++
++ purple_blist_node_set_int(&buddy->node, "last_seen", time(NULL));
++ purple_signal_emit(purple_blist_get_handle(), "buddy-signed-off", buddy);
++
++ cnode = buddy->node.parent;
++ if (--(PURPLE_CONTACT(cnode)->online) == 0)
++ PURPLE_GROUP(cnode->parent)->online--;
++ } else {
++ purple_signal_emit(purple_blist_get_handle(),
++ "buddy-status-changed", buddy, old_status,
++ status);
++ }
++
++ /*
++ * This function used to only call the following two functions if one of
++ * the above signals had been triggered, but that's not good, because
++ * if someone's away message changes and they don't go from away to back
++ * to away then no signal is triggered.
++ *
++ * It's a safe assumption that SOMETHING called this function. PROBABLY
++ * because something, somewhere changed. Calling the stuff below
++ * certainly won't hurt anything. Unless you're on a K6-2 300.
++ */
++ purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
++ if (ops && ops->update)
++ ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
++}
++
++void
++purple_blist_update_node_icon(PurpleBlistNode *node)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++
++ g_return_if_fail(node != NULL);
++
++ if (ops && ops->update)
++ ops->update(purplebuddylist, node);
++}
++
++void
++purple_blist_update_buddy_icon(PurpleBuddy *buddy)
++{
++ purple_blist_update_node_icon((PurpleBlistNode *)buddy);
++}
++
++/*
++ * TODO: Maybe remove the call to this from server.c and call it
++ * from oscar.c and toc.c instead?
++ */
++void purple_blist_rename_buddy(PurpleBuddy *buddy, const char *name)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ struct _purple_hbuddy *hb, *hb2;
++ GHashTable *account_buddies;
++
++ g_return_if_fail(buddy != NULL);
++
++ hb = g_new(struct _purple_hbuddy, 1);
++ hb->name = (gchar *)purple_normalize(buddy->account, buddy->name);
++ hb->account = buddy->account;
++ hb->group = ((PurpleBlistNode *)buddy)->parent->parent;
++ g_hash_table_remove(purplebuddylist->buddies, hb);
++
++ account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
++ g_hash_table_remove(account_buddies, hb);
++
++ hb->name = g_strdup(purple_normalize(buddy->account, name));
++ g_hash_table_replace(purplebuddylist->buddies, hb, buddy);
++
++ hb2 = g_new(struct _purple_hbuddy, 1);
++ hb2->name = g_strdup(hb->name);
++ hb2->account = buddy->account;
++ hb2->group = ((PurpleBlistNode *)buddy)->parent->parent;
++
++ g_hash_table_replace(account_buddies, hb2, buddy);
++
++ g_free(buddy->name);
++ buddy->name = g_strdup(name);
++
++ if (ops && ops->save_node)
++ ops->save_node((PurpleBlistNode *) buddy);
++
++ if (ops && ops->update)
++ ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
++}
++
++static gboolean
++purple_strings_are_different(const char *one, const char *two)
++{
++ return !((one && two && g_utf8_collate(one, two) == 0) ||
++ ((one == NULL || *one == '\0') && (two == NULL || *two == '\0')));
++}
++
++void purple_blist_alias_contact(PurpleContact *contact, const char *alias)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleConversation *conv;
++ PurpleBlistNode *bnode;
++ char *old_alias;
++ char *new_alias = NULL;
++
++ g_return_if_fail(contact != NULL);
++
++ if ((alias != NULL) && (*alias != '\0'))
++ new_alias = purple_utf8_strip_unprintables(alias);
++
++ if (!purple_strings_are_different(contact->alias, new_alias)) {
++ g_free(new_alias);
++ return;
++ }
++
++ old_alias = contact->alias;
++
++ if ((new_alias != NULL) && (*new_alias != '\0'))
++ contact->alias = new_alias;
++ else {
++ contact->alias = NULL;
++ g_free(new_alias); /* could be "\0" */
++ }
++
++ if (ops && ops->save_node)
++ ops->save_node((PurpleBlistNode*) contact);
++
++ if (ops && ops->update)
++ ops->update(purplebuddylist, (PurpleBlistNode *)contact);
++
++ for(bnode = ((PurpleBlistNode *)contact)->child; bnode != NULL; bnode = bnode->next)
++ {
++ PurpleBuddy *buddy = (PurpleBuddy *)bnode;
++
++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name,
++ buddy->account);
++ if (conv)
++ purple_conversation_autoset_title(conv);
++ }
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
++ contact, old_alias);
++// g_free(old_alias);
++}
++
++void purple_blist_alias_chat(PurpleChat *chat, const char *alias)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ char *old_alias;
++ char *new_alias = NULL;
++
++ g_return_if_fail(chat != NULL);
++
++ if ((alias != NULL) && (*alias != '\0'))
++ new_alias = purple_utf8_strip_unprintables(alias);
++
++ if (!purple_strings_are_different(chat->alias, new_alias)) {
++ g_free(new_alias);
++ return;
++ }
++
++ old_alias = chat->alias;
++
++ if ((new_alias != NULL) && (*new_alias != '\0'))
++ chat->alias = new_alias;
++ else {
++ chat->alias = NULL;
++ g_free(new_alias); /* could be "\0" */
++ }
++
++ if (ops && ops->save_node)
++ ops->save_node((PurpleBlistNode*) chat);
++
++ if (ops && ops->update)
++ ops->update(purplebuddylist, (PurpleBlistNode *)chat);
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
++ chat, old_alias);
++ g_free(old_alias);
++}
++
++void purple_blist_alias_buddy(PurpleBuddy *buddy, const char *alias)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleConversation *conv;
++ char *old_alias;
++ char *new_alias = NULL;
++
++ g_return_if_fail(buddy != NULL);
++
++ if ((alias != NULL) && (*alias != '\0'))
++ new_alias = purple_utf8_strip_unprintables(alias);
++
++ if (!purple_strings_are_different(buddy->alias, new_alias)) {
++ g_free(new_alias);
++ return;
++ }
++
++ old_alias = buddy->alias;
++
++ if ((new_alias != NULL) && (*new_alias != '\0'))
++ buddy->alias = new_alias;
++ else {
++ buddy->alias = NULL;
++ g_free(new_alias); /* could be "\0" */
++ }
++
++ if (ops && ops->save_node)
++ ops->save_node((PurpleBlistNode*) buddy);
++
++ if (ops && ops->update)
++ ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
++
++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name,
++ buddy->account);
++ if (conv)
++ purple_conversation_autoset_title(conv);
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
++ buddy, old_alias);
++// g_free(old_alias);
++}
++
++void purple_blist_server_alias_buddy(PurpleBuddy *buddy, const char *alias)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleConversation *conv;
++ char *old_alias;
++ char *new_alias = NULL;
++
++ g_return_if_fail(buddy != NULL);
++
++ if ((alias != NULL) && (*alias != '\0') && g_utf8_validate(alias, -1, NULL))
++ new_alias = purple_utf8_strip_unprintables(alias);
++
++ if (!purple_strings_are_different(buddy->server_alias, new_alias)) {
++ g_free(new_alias);
++ return;
++ }
++
++ old_alias = buddy->server_alias;
++
++ if ((new_alias != NULL) && (*new_alias != '\0'))
++ buddy->server_alias = new_alias;
++ else {
++ buddy->server_alias = NULL;
++ g_free(new_alias); /* could be "\0"; */
++ }
++
++ if (ops && ops->save_node)
++ ops->save_node((PurpleBlistNode*) buddy);
++
++ if (ops && ops->update)
++ ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
++
++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name,
++ buddy->account);
++ if (conv)
++ purple_conversation_autoset_title(conv);
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-aliased",
++ buddy, old_alias);
++// g_free(old_alias);
++}
++
++/*
++ * TODO: If merging, prompt the user if they want to merge.
++ */
++void purple_blist_rename_group(PurpleGroup *source, const char *name)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleGroup *dest;
++ gchar *old_name;
++ gchar *new_name;
++ GList *moved_buddies = NULL;
++ GSList *accts;
++
++ g_return_if_fail(source != NULL);
++ g_return_if_fail(name != NULL);
++
++ new_name = purple_utf8_strip_unprintables(name);
++
++ if (*new_name == '\0' || purple_strequal(new_name, source->name)) {
++ g_free(new_name);
++ return;
++ }
++
++ dest = purple_find_group(new_name);
++ if (dest != NULL && purple_utf8_strcasecmp(source->name, dest->name) != 0) {
++ /* We're merging two groups */
++ PurpleBlistNode *prev, *child, *next;
++
++ prev = purple_blist_get_last_child((PurpleBlistNode*)dest);
++ child = ((PurpleBlistNode*)source)->child;
++
++ /*
++ * TODO: This seems like a dumb way to do this... why not just
++ * append all children from the old group to the end of the new
++ * one? PRPLs might be expecting to receive an add_buddy() for
++ * each moved buddy...
++ */
++ while (child)
++ {
++ next = child->next;
++ if (PURPLE_BLIST_NODE_IS_CONTACT(child)) {
++ PurpleBlistNode *bnode;
++ purple_blist_add_contact((PurpleContact *)child, dest, prev);
++ for (bnode = child->child; bnode != NULL; bnode = bnode->next) {
++ purple_blist_add_buddy((PurpleBuddy *)bnode, (PurpleContact *)child,
++ NULL, bnode->prev);
++ moved_buddies = g_list_append(moved_buddies, bnode);
++ }
++ prev = child;
++ } else if (PURPLE_BLIST_NODE_IS_CHAT(child)) {
++ purple_blist_add_chat((PurpleChat *)child, dest, prev);
++ prev = child;
++ } else {
++ purple_debug(PURPLE_DEBUG_ERROR, "blist",
++ "Unknown child type in group %s\n", source->name);
++ }
++ child = next;
++ }
++
++ /* Make a copy of the old group name and then delete the old group */
++ old_name = g_strdup(source->name);
++ purple_blist_remove_group(source);
++ source = dest;
++ g_free(new_name);
++ } else {
++ /* A simple rename */
++ PurpleBlistNode *cnode, *bnode;
++ gchar* key;
++
++ /* Build a GList of all buddies in this group */
++ for (cnode = ((PurpleBlistNode *)source)->child; cnode != NULL; cnode = cnode->next) {
++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode))
++ for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
++ moved_buddies = g_list_append(moved_buddies, bnode);
++ }
++
++ old_name = source->name;
++ source->name = new_name;
++
++ key = g_utf8_collate_key(old_name, -1);
++ g_hash_table_remove(groups_cache, key);
++ g_free(key);
++
++ key = g_utf8_collate_key(new_name, -1);
++ g_hash_table_insert(groups_cache, key, source);
++ }
++
++ /* Save our changes */
++ if (ops && ops->save_node)
++ ops->save_node((PurpleBlistNode*) source);
++
++ /* Update the UI */
++ if (ops && ops->update)
++ ops->update(purplebuddylist, (PurpleBlistNode*)source);
++
++ /* Notify all PRPLs */
++ /* TODO: Is this condition needed? Seems like it would always be TRUE */
++ if(old_name && !purple_strequal(source->name, old_name)) {
++ for (accts = purple_group_get_accounts(source); accts; accts = g_slist_remove(accts, accts->data)) {
++ PurpleAccount *account = accts->data;
++ PurpleConnection *gc = NULL;
++ PurplePlugin *prpl = NULL;
++ PurplePluginProtocolInfo *prpl_info = NULL;
++ GList *l = NULL, *buddies = NULL;
++
++ gc = purple_account_get_connection(account);
++
++ if(gc)
++ prpl = purple_connection_get_prpl(gc);
++
++ if(gc && prpl)
++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
++
++ if(!prpl_info)
++ continue;
++
++ for(l = moved_buddies; l; l = l->next) {
++ PurpleBuddy *buddy = (PurpleBuddy *)l->data;
++
++ if(buddy && buddy->account == account)
++ buddies = g_list_append(buddies, (PurpleBlistNode *)buddy);
++ }
++
++ if(prpl_info->rename_group) {
++ prpl_info->rename_group(gc, old_name, source, buddies);
++ } else {
++ GList *cur, *groups = NULL;
++
++ /* Make a list of what the groups each buddy is in */
++ for(cur = buddies; cur; cur = cur->next) {
++ PurpleBlistNode *node = (PurpleBlistNode *)cur->data;
++ groups = g_list_prepend(groups, node->parent->parent);
++ }
++
++ purple_account_remove_buddies(account, buddies, groups);
++ g_list_free(groups);
++ purple_account_add_buddies(account, buddies);
++ }
++
++ g_list_free(buddies);
++ }
++ }
++ g_list_free(moved_buddies);
++ g_free(old_name);
++}
++
++static void purple_blist_node_initialize_settings(PurpleBlistNode *node);
++
++PurpleChat *purple_chat_new(PurpleAccount *account, const char *alias, GHashTable *components)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleChat *chat;
++
++ g_return_val_if_fail(account != NULL, NULL);
++ g_return_val_if_fail(components != NULL, NULL);
++
++ chat = g_new0(PurpleChat, 1);
++ chat->account = account;
++ if ((alias != NULL) && (*alias != '\0'))
++ chat->alias = purple_utf8_strip_unprintables(alias);
++ chat->components = components;
++ purple_blist_node_initialize_settings((PurpleBlistNode *)chat);
++ ((PurpleBlistNode *)chat)->type = PURPLE_BLIST_CHAT_NODE;
++
++ if (ops != NULL && ops->new_node != NULL)
++ ops->new_node((PurpleBlistNode *)chat);
++
++ PURPLE_DBUS_REGISTER_POINTER(chat, PurpleChat);
++ return chat;
++}
++
++void
++purple_chat_destroy(PurpleChat *chat)
++{
++ g_hash_table_destroy(chat->components);
++ g_hash_table_destroy(chat->node.settings);
++ g_free(chat->alias);
++ PURPLE_DBUS_UNREGISTER_POINTER(chat);
++ g_free(chat);
++}
++
++PurpleBuddy *purple_buddy_new(PurpleAccount *account, const char *name, const char *alias)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleBuddy *buddy;
++
++ g_return_val_if_fail(account != NULL, NULL);
++ g_return_val_if_fail(name != NULL, NULL);
++
++ buddy = g_new0(PurpleBuddy, 1);
++ buddy->account = account;
++ buddy->name = purple_utf8_strip_unprintables(name);
++ buddy->alias = purple_utf8_strip_unprintables(alias);
++ buddy->presence = purple_presence_new_for_buddy(buddy);
++ ((PurpleBlistNode *)buddy)->type = PURPLE_BLIST_BUDDY_NODE;
++
++ purple_presence_set_status_active(buddy->presence, "offline", TRUE);
++
++ purple_blist_node_initialize_settings((PurpleBlistNode *)buddy);
++
++ if (ops && ops->new_node)
++ ops->new_node((PurpleBlistNode *)buddy);
++
++ PURPLE_DBUS_REGISTER_POINTER(buddy, PurpleBuddy);
++ return buddy;
++}
++
++void
++purple_buddy_destroy(PurpleBuddy *buddy)
++{
++ PurplePlugin *prpl;
++ PurplePluginProtocolInfo *prpl_info;
++
++ /*
++ * Tell the owner PRPL that we're about to free the buddy so it
++ * can free proto_data
++ */
++ prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
++ if (prpl) {
++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
++ if (prpl_info && prpl_info->buddy_free)
++ prpl_info->buddy_free(buddy);
++ }
++
++ /* Delete the node */
++ purple_buddy_icon_unref(buddy->icon);
++ g_hash_table_destroy(buddy->node.settings);
++ purple_presence_destroy(buddy->presence);
++ g_free(buddy->name);
++ g_free(buddy->alias);
++ g_free(buddy->server_alias);
++
++ PURPLE_DBUS_UNREGISTER_POINTER(buddy);
++ g_free(buddy);
++
++ /* FIXME: Once PurpleBuddy is a GObject, timeout callbacks can
++ * g_object_ref() it when connecting the callback and
++ * g_object_unref() it in the handler. That way, it won't
++ * get freed while the timeout is pending and this line can
++ * be removed. */
++ while (g_source_remove_by_user_data((gpointer *)buddy));
++}
++
++void
++purple_buddy_set_icon(PurpleBuddy *buddy, PurpleBuddyIcon *icon)
++{
++ g_return_if_fail(buddy != NULL);
++
++ if (buddy->icon != icon)
++ {
++ purple_buddy_icon_unref(buddy->icon);
++ buddy->icon = (icon != NULL ? purple_buddy_icon_ref(icon) : NULL);
++ }
++
++ purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy);
++
++ purple_blist_update_node_icon((PurpleBlistNode*)buddy);
++}
++
++PurpleAccount *
++purple_buddy_get_account(const PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ return buddy->account;
++}
++
++const char *
++purple_buddy_get_name(const PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ return buddy->name;
++}
++
++PurpleBuddyIcon *
++purple_buddy_get_icon(const PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ return buddy->icon;
++}
++
++gpointer
++purple_buddy_get_protocol_data(const PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ return buddy->proto_data;
++}
++
++void
++purple_buddy_set_protocol_data(PurpleBuddy *buddy, gpointer data)
++{
++ g_return_if_fail(buddy != NULL);
++
++ buddy->proto_data = data;
++}
++
++
++void purple_blist_add_chat(PurpleChat *chat, PurpleGroup *group, PurpleBlistNode *node)
++{
++ PurpleBlistNode *cnode = (PurpleBlistNode*)chat;
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++
++ g_return_if_fail(chat != NULL);
++ g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT((PurpleBlistNode *)chat));
++
++ if (node == NULL) {
++ if (group == NULL)
++ group = purple_group_new(_("Chats"));
++
++ /* Add group to blist if isn't already on it. Fixes #2752. */
++ if (!purple_find_group(group->name)) {
++ purple_blist_add_group(group,
++ purple_blist_get_last_sibling(purplebuddylist->root));
++ }
++ } else {
++ group = (PurpleGroup*)node->parent;
++ }
++
++ /* if we're moving to overtop of ourselves, do nothing */
++ if (cnode == node)
++ return;
++
++ if (cnode->parent) {
++ /* This chat was already in the list and is
++ * being moved.
++ */
++ ((PurpleGroup *)cnode->parent)->totalsize--;
++ if (purple_account_is_connected(chat->account)) {
++ ((PurpleGroup *)cnode->parent)->online--;
++ ((PurpleGroup *)cnode->parent)->currentsize--;
++ }
++ if (cnode->next)
++ cnode->next->prev = cnode->prev;
++ if (cnode->prev)
++ cnode->prev->next = cnode->next;
++ if (cnode->parent->child == cnode)
++ cnode->parent->child = cnode->next;
++
++ if (ops && ops->remove)
++ ops->remove(purplebuddylist, cnode);
++ /* ops->remove() cleaned up the cnode's ui_data, so we need to
++ * reinitialize it */
++ if (ops && ops->new_node)
++ ops->new_node(cnode);
++ }
++
++ if (node != NULL) {
++ if (node->next)
++ node->next->prev = cnode;
++ cnode->next = node->next;
++ cnode->prev = node;
++ cnode->parent = node->parent;
++ node->next = cnode;
++ ((PurpleGroup *)node->parent)->totalsize++;
++ if (purple_account_is_connected(chat->account)) {
++ ((PurpleGroup *)node->parent)->online++;
++ ((PurpleGroup *)node->parent)->currentsize++;
++ }
++ } else {
++ if (((PurpleBlistNode *)group)->child)
++ ((PurpleBlistNode *)group)->child->prev = cnode;
++ cnode->next = ((PurpleBlistNode *)group)->child;
++ cnode->prev = NULL;
++ ((PurpleBlistNode *)group)->child = cnode;
++ cnode->parent = (PurpleBlistNode *)group;
++ group->totalsize++;
++ if (purple_account_is_connected(chat->account)) {
++ group->online++;
++ group->currentsize++;
++ }
++ }
++
++ if (ops && ops->save_node)
++ ops->save_node(cnode);
++
++ if (ops && ops->update)
++ ops->update(purplebuddylist, (PurpleBlistNode *)cnode);
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
++ cnode);
++}
++
++void purple_blist_add_buddy(PurpleBuddy *buddy, PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
++{
++ PurpleBlistNode *cnode, *bnode;
++ PurpleGroup *g;
++ PurpleContact *c;
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ struct _purple_hbuddy *hb, *hb2;
++ GHashTable *account_buddies;
++
++ g_return_if_fail(buddy != NULL);
++ g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY((PurpleBlistNode*)buddy));
++
++ bnode = (PurpleBlistNode *)buddy;
++
++ /* if we're moving to overtop of ourselves, do nothing */
++ if (bnode == node || (!node && bnode->parent &&
++ contact && bnode->parent == (PurpleBlistNode*)contact
++ && bnode == bnode->parent->child))
++ return;
++
++ if (node && PURPLE_BLIST_NODE_IS_BUDDY(node)) {
++ c = (PurpleContact*)node->parent;
++ g = (PurpleGroup*)node->parent->parent;
++ } else if (contact) {
++ c = contact;
++ g = PURPLE_GROUP(PURPLE_BLIST_NODE(c)->parent);
++ } else {
++ g = group;
++ if (g == NULL)
++ g = purple_group_new(_("Buddies"));
++ /* Add group to blist if isn't already on it. Fixes #2752. */
++ if (!purple_find_group(g->name)) {
++ purple_blist_add_group(g,
++ purple_blist_get_last_sibling(purplebuddylist->root));
++ }
++ c = purple_contact_new();
++ purple_blist_add_contact(c, g,
++ purple_blist_get_last_child((PurpleBlistNode*)g));
++ }
++
++ cnode = (PurpleBlistNode *)c;
++
++ if (bnode->parent) {
++ if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
++ ((PurpleContact*)bnode->parent)->online--;
++ if (((PurpleContact*)bnode->parent)->online == 0)
++ ((PurpleGroup*)bnode->parent->parent)->online--;
++ }
++ if (purple_account_is_connected(buddy->account)) {
++ ((PurpleContact*)bnode->parent)->currentsize--;
++ if (((PurpleContact*)bnode->parent)->currentsize == 0)
++ ((PurpleGroup*)bnode->parent->parent)->currentsize--;
++ }
++ ((PurpleContact*)bnode->parent)->totalsize--;
++ /* the group totalsize will be taken care of by remove_contact below */
++
++ if (bnode->parent->parent != (PurpleBlistNode*)g)
++ serv_move_buddy(buddy, (PurpleGroup *)bnode->parent->parent, g);
++
++ if (bnode->next)
++ bnode->next->prev = bnode->prev;
++ if (bnode->prev)
++ bnode->prev->next = bnode->next;
++ if (bnode->parent->child == bnode)
++ bnode->parent->child = bnode->next;
++
++ if (ops && ops->remove)
++ ops->remove(purplebuddylist, bnode);
++
++ if (bnode->parent->parent != (PurpleBlistNode*)g) {
++ struct _purple_hbuddy hb;
++ hb.name = (gchar *)purple_normalize(buddy->account, buddy->name);
++ hb.account = buddy->account;
++ hb.group = bnode->parent->parent;
++ g_hash_table_remove(purplebuddylist->buddies, &hb);
++
++ account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
++ g_hash_table_remove(account_buddies, &hb);
++ }
++
++ if (!bnode->parent->child) {
++ purple_blist_remove_contact((PurpleContact*)bnode->parent);
++ } else {
++ purple_contact_invalidate_priority_buddy((PurpleContact*)bnode->parent);
++ if (ops && ops->update)
++ ops->update(purplebuddylist, bnode->parent);
++ }
++ }
++
++ if (node && PURPLE_BLIST_NODE_IS_BUDDY(node)) {
++ if (node->next)
++ node->next->prev = bnode;
++ bnode->next = node->next;
++ bnode->prev = node;
++ bnode->parent = node->parent;
++ node->next = bnode;
++ } else {
++ if (cnode->child)
++ cnode->child->prev = bnode;
++ bnode->prev = NULL;
++ bnode->next = cnode->child;
++ cnode->child = bnode;
++ bnode->parent = cnode;
++ }
++
++ if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
++ if (++(PURPLE_CONTACT(bnode->parent)->online) == 1)
++ PURPLE_GROUP(bnode->parent->parent)->online++;
++ }
++ if (purple_account_is_connected(buddy->account)) {
++ if (++(PURPLE_CONTACT(bnode->parent)->currentsize) == 1)
++ PURPLE_GROUP(bnode->parent->parent)->currentsize++;
++ }
++ PURPLE_CONTACT(bnode->parent)->totalsize++;
++
++ hb = g_new(struct _purple_hbuddy, 1);
++ hb->name = g_strdup(purple_normalize(buddy->account, buddy->name));
++ hb->account = buddy->account;
++ hb->group = ((PurpleBlistNode*)buddy)->parent->parent;
++
++ g_hash_table_replace(purplebuddylist->buddies, hb, buddy);
++
++ account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
++
++ hb2 = g_new(struct _purple_hbuddy, 1);
++ hb2->name = g_strdup(hb->name);
++ hb2->account = buddy->account;
++ hb2->group = ((PurpleBlistNode*)buddy)->parent->parent;
++
++ g_hash_table_replace(account_buddies, hb2, buddy);
++
++ purple_contact_invalidate_priority_buddy(purple_buddy_get_contact(buddy));
++
++ if (ops && ops->save_node)
++ ops->save_node((PurpleBlistNode*) buddy);
++
++ if (ops && ops->update)
++ ops->update(purplebuddylist, (PurpleBlistNode*)buddy);
++
++ /* Signal that the buddy has been added */
++ purple_signal_emit(purple_blist_get_handle(), "buddy-added", buddy);
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
++ PURPLE_BLIST_NODE(buddy));
++}
++
++PurpleContact *purple_contact_new()
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++
++ PurpleContact *contact = g_new0(PurpleContact, 1);
++ contact->totalsize = 0;
++ contact->currentsize = 0;
++ contact->online = 0;
++ purple_blist_node_initialize_settings((PurpleBlistNode *)contact);
++ ((PurpleBlistNode *)contact)->type = PURPLE_BLIST_CONTACT_NODE;
++
++ if (ops && ops->new_node)
++ ops->new_node((PurpleBlistNode *)contact);
++
++ PURPLE_DBUS_REGISTER_POINTER(contact, PurpleContact);
++ return contact;
++}
++
++void
++purple_contact_destroy(PurpleContact *contact)
++{
++ g_hash_table_destroy(contact->node.settings);
++ g_free(contact->alias);
++ PURPLE_DBUS_UNREGISTER_POINTER(contact);
++ g_free(contact);
++}
++
++PurpleGroup *
++purple_contact_get_group(const PurpleContact *contact)
++{
++ g_return_val_if_fail(contact, NULL);
++
++ return (PurpleGroup *)(((PurpleBlistNode *)contact)->parent);
++}
++
++void purple_contact_set_alias(PurpleContact *contact, const char *alias)
++{
++ purple_blist_alias_contact(contact,alias);
++}
++
++const char *purple_contact_get_alias(PurpleContact* contact)
++{
++ g_return_val_if_fail(contact != NULL, NULL);
++
++ if (contact->alias)
++ return contact->alias;
++
++ return purple_buddy_get_alias(purple_contact_get_priority_buddy(contact));
++}
++
++gboolean purple_contact_on_account(PurpleContact *c, PurpleAccount *account)
++{
++ PurpleBlistNode *bnode, *cnode = (PurpleBlistNode *) c;
++
++ g_return_val_if_fail(c != NULL, FALSE);
++ g_return_val_if_fail(account != NULL, FALSE);
++
++ for (bnode = cnode->child; bnode; bnode = bnode->next) {
++ PurpleBuddy *buddy;
++
++ if (! PURPLE_BLIST_NODE_IS_BUDDY(bnode))
++ continue;
++
++ buddy = (PurpleBuddy *)bnode;
++ if (buddy->account == account)
++ return TRUE;
++ }
++ return FALSE;
++}
++
++void purple_contact_invalidate_priority_buddy(PurpleContact *contact)
++{
++ g_return_if_fail(contact != NULL);
++
++ contact->priority_valid = FALSE;
++}
++
++PurpleGroup *purple_group_new(const char *name)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleGroup *group;
++
++ g_return_val_if_fail(name != NULL, NULL);
++ g_return_val_if_fail(*name != '\0', NULL);
++
++ group = purple_find_group(name);
++ if (group != NULL)
++ return group;
++
++ group = g_new0(PurpleGroup, 1);
++ group->name = purple_utf8_strip_unprintables(name);
++ group->totalsize = 0;
++ group->currentsize = 0;
++ group->online = 0;
++ purple_blist_node_initialize_settings((PurpleBlistNode *)group);
++ ((PurpleBlistNode *)group)->type = PURPLE_BLIST_GROUP_NODE;
++
++ if (ops && ops->new_node)
++ ops->new_node((PurpleBlistNode *)group);
++
++ PURPLE_DBUS_REGISTER_POINTER(group, PurpleGroup);
++ return group;
++}
++
++void
++purple_group_destroy(PurpleGroup *group)
++{
++ g_hash_table_destroy(group->node.settings);
++ g_free(group->name);
++ PURPLE_DBUS_UNREGISTER_POINTER(group);
++ g_free(group);
++}
++
++void purple_blist_add_contact(PurpleContact *contact, PurpleGroup *group, PurpleBlistNode *node)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleGroup *g;
++ PurpleBlistNode *gnode, *cnode, *bnode;
++
++ g_return_if_fail(contact != NULL);
++ g_return_if_fail(PURPLE_BLIST_NODE_IS_CONTACT((PurpleBlistNode*)contact));
++
++ if (PURPLE_BLIST_NODE(contact) == node)
++ return;
++
++ if (node && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
++ PURPLE_BLIST_NODE_IS_CHAT(node)))
++ g = (PurpleGroup*)node->parent;
++ else if (group)
++ g = group;
++ else {
++ g = purple_find_group(_("Buddies"));
++ if (g == NULL) {
++ g = purple_group_new(_("Buddies"));
++ purple_blist_add_group(g,
++ purple_blist_get_last_sibling(purplebuddylist->root));
++ }
++ }
++
++ gnode = (PurpleBlistNode*)g;
++ cnode = (PurpleBlistNode*)contact;
++
++ if (cnode->parent) {
++ if (cnode->parent->child == cnode)
++ cnode->parent->child = cnode->next;
++ if (cnode->prev)
++ cnode->prev->next = cnode->next;
++ if (cnode->next)
++ cnode->next->prev = cnode->prev;
++
++ if (cnode->parent != gnode) {
++ bnode = cnode->child;
++ while (bnode) {
++ PurpleBlistNode *next_bnode = bnode->next;
++ PurpleBuddy *b = (PurpleBuddy*)bnode;
++ GHashTable *account_buddies;
++
++ struct _purple_hbuddy *hb, *hb2;
++
++ hb = g_new(struct _purple_hbuddy, 1);
++ hb->name = g_strdup(purple_normalize(b->account, b->name));
++ hb->account = b->account;
++ hb->group = cnode->parent;
++
++ g_hash_table_remove(purplebuddylist->buddies, hb);
++
++ account_buddies = g_hash_table_lookup(buddies_cache, b->account);
++ g_hash_table_remove(account_buddies, hb);
++
++ if (!purple_find_buddy_in_group(b->account, b->name, g)) {
++ hb->group = gnode;
++ g_hash_table_replace(purplebuddylist->buddies, hb, b);
++
++ hb2 = g_new(struct _purple_hbuddy, 1);
++ hb2->name = g_strdup(hb->name);
++ hb2->account = b->account;
++ hb2->group = gnode;
++
++ g_hash_table_replace(account_buddies, hb2, b);
++
++ if (purple_account_get_connection(b->account))
++ serv_move_buddy(b, (PurpleGroup *)cnode->parent, g);
++ } else {
++ gboolean empty_contact = FALSE;
++
++ /* this buddy already exists in the group, so we're
++ * gonna delete it instead */
++ g_free(hb->name);
++ g_free(hb);
++ if (purple_account_get_connection(b->account))
++ purple_account_remove_buddy(b->account, b, (PurpleGroup *)cnode->parent);
++
++ if (!cnode->child->next)
++ empty_contact = TRUE;
++ purple_blist_remove_buddy(b);
++
++ /** in purple_blist_remove_buddy(), if the last buddy in a
++ * contact is removed, the contact is cleaned up and
++ * g_free'd, so we mustn't try to reference bnode->next */
++ if (empty_contact)
++ return;
++ }
++ bnode = next_bnode;
++ }
++ }
++
++ if (contact->online > 0)
++ ((PurpleGroup*)cnode->parent)->online--;
++ if (contact->currentsize > 0)
++ ((PurpleGroup*)cnode->parent)->currentsize--;
++ ((PurpleGroup*)cnode->parent)->totalsize--;
++
++ if (ops && ops->remove)
++ ops->remove(purplebuddylist, cnode);
++
++ if (ops && ops->remove_node)
++ ops->remove_node(cnode);
++ }
++
++ if (node && (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
++ PURPLE_BLIST_NODE_IS_CHAT(node))) {
++ if (node->next)
++ node->next->prev = cnode;
++ cnode->next = node->next;
++ cnode->prev = node;
++ cnode->parent = node->parent;
++ node->next = cnode;
++ } else {
++ if (gnode->child)
++ gnode->child->prev = cnode;
++ cnode->prev = NULL;
++ cnode->next = gnode->child;
++ gnode->child = cnode;
++ cnode->parent = gnode;
++ }
++
++ if (contact->online > 0)
++ g->online++;
++ if (contact->currentsize > 0)
++ g->currentsize++;
++ g->totalsize++;
++
++ if (ops && ops->save_node)
++ {
++ if (cnode->child)
++ ops->save_node(cnode);
++ for (bnode = cnode->child; bnode; bnode = bnode->next)
++ ops->save_node(bnode);
++ }
++
++ if (ops && ops->update)
++ {
++ if (cnode->child)
++ ops->update(purplebuddylist, cnode);
++
++ for (bnode = cnode->child; bnode; bnode = bnode->next)
++ ops->update(purplebuddylist, bnode);
++ }
++}
++
++void purple_blist_merge_contact(PurpleContact *source, PurpleBlistNode *node)
++{
++ PurpleBlistNode *sourcenode = (PurpleBlistNode*)source;
++ PurpleBlistNode *prev, *cur, *next;
++ PurpleContact *target;
++
++ g_return_if_fail(source != NULL);
++ g_return_if_fail(node != NULL);
++
++ if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
++ target = (PurpleContact *)node;
++ prev = purple_blist_get_last_child(node);
++ } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
++ target = (PurpleContact *)node->parent;
++ prev = node;
++ } else {
++ return;
++ }
++
++ if (source == target || !target)
++ return;
++
++ next = sourcenode->child;
++
++ while (next) {
++ cur = next;
++ next = cur->next;
++ if (PURPLE_BLIST_NODE_IS_BUDDY(cur)) {
++ purple_blist_add_buddy((PurpleBuddy *)cur, target, NULL, prev);
++ prev = cur;
++ }
++ }
++}
++
++void purple_blist_add_group(PurpleGroup *group, PurpleBlistNode *node)
++{
++ PurpleBlistUiOps *ops;
++ PurpleBlistNode *gnode = (PurpleBlistNode*)group;
++ gchar* key;
++
++ g_return_if_fail(group != NULL);
++ g_return_if_fail(PURPLE_BLIST_NODE_IS_GROUP((PurpleBlistNode *)group));
++
++ ops = purple_blist_get_ui_ops();
++
++ /* if we're moving to overtop of ourselves, do nothing */
++ if (gnode == node) {
++ if (!purplebuddylist->root)
++ node = NULL;
++ else
++ return;
++ }
++
++ if (purple_find_group(group->name)) {
++ /* This is just being moved */
++
++ if (ops && ops->remove)
++ ops->remove(purplebuddylist, (PurpleBlistNode *)group);
++
++ if (gnode == purplebuddylist->root)
++ purplebuddylist->root = gnode->next;
++ if (gnode->prev)
++ gnode->prev->next = gnode->next;
++ if (gnode->next)
++ gnode->next->prev = gnode->prev;
++ } else {
++ key = g_utf8_collate_key(group->name, -1);
++ g_hash_table_insert(groups_cache, key, group);
++ }
++
++ if (node && PURPLE_BLIST_NODE_IS_GROUP(node)) {
++ gnode->next = node->next;
++ gnode->prev = node;
++ if (node->next)
++ node->next->prev = gnode;
++ node->next = gnode;
++ } else {
++ if (purplebuddylist->root)
++ purplebuddylist->root->prev = gnode;
++ gnode->next = purplebuddylist->root;
++ gnode->prev = NULL;
++ purplebuddylist->root = gnode;
++ }
++
++ if (ops && ops->save_node) {
++ ops->save_node(gnode);
++ for (node = gnode->child; node; node = node->next)
++ ops->save_node(node);
++ }
++
++ if (ops && ops->update) {
++ ops->update(purplebuddylist, gnode);
++ for (node = gnode->child; node; node = node->next)
++ ops->update(purplebuddylist, node);
++ }
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-added",
++ gnode);
++}
++
++void purple_blist_remove_contact(PurpleContact *contact)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleBlistNode *node, *gnode;
++
++ g_return_if_fail(contact != NULL);
++
++ node = (PurpleBlistNode *)contact;
++ gnode = node->parent;
++
++ if (node->child) {
++ /*
++ * If this contact has children then remove them. When the last
++ * buddy is removed from the contact, the contact is automatically
++ * deleted.
++ */
++ while (node->child->next) {
++ purple_blist_remove_buddy((PurpleBuddy*)node->child);
++ }
++ /*
++ * Remove the last buddy and trigger the deletion of the contact.
++ * It would probably be cleaner if contact-deletion was done after
++ * a timeout? Or if it had to be done manually, like below?
++ */
++ purple_blist_remove_buddy((PurpleBuddy*)node->child);
++ } else {
++ /* Remove the node from its parent */
++ if (gnode->child == node)
++ gnode->child = node->next;
++ if (node->prev)
++ node->prev->next = node->next;
++ if (node->next)
++ node->next->prev = node->prev;
++
++ /* Update the UI */
++ if (ops && ops->remove)
++ ops->remove(purplebuddylist, node);
++
++ if (ops && ops->remove_node)
++ ops->remove_node(node);
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
++ PURPLE_BLIST_NODE(contact));
++
++ /* Delete the node */
++ purple_contact_destroy(contact);
++ }
++}
++
++void purple_blist_remove_buddy(PurpleBuddy *buddy)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleBlistNode *node, *cnode, *gnode;
++ PurpleContact *contact;
++ PurpleGroup *group;
++ struct _purple_hbuddy hb;
++ GHashTable *account_buddies;
++
++ g_return_if_fail(buddy != NULL);
++
++ node = (PurpleBlistNode *)buddy;
++ cnode = node->parent;
++ gnode = (cnode != NULL) ? cnode->parent : NULL;
++ contact = (PurpleContact *)cnode;
++ group = (PurpleGroup *)gnode;
++
++ /* Remove the node from its parent */
++ if (node->prev)
++ node->prev->next = node->next;
++ if (node->next)
++ node->next->prev = node->prev;
++ if ((cnode != NULL) && (cnode->child == node))
++ cnode->child = node->next;
++
++ /* Adjust size counts */
++ if (contact != NULL) {
++ if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
++ contact->online--;
++ if (contact->online == 0)
++ group->online--;
++ }
++ if (purple_account_is_connected(buddy->account)) {
++ contact->currentsize--;
++ if (contact->currentsize == 0)
++ group->currentsize--;
++ }
++ contact->totalsize--;
++
++ /* Re-sort the contact */
++ if (cnode->child && contact->priority == buddy) {
++ purple_contact_invalidate_priority_buddy(contact);
++ if (ops && ops->update)
++ ops->update(purplebuddylist, cnode);
++ }
++ }
++
++ /* Remove this buddy from the buddies hash table */
++ hb.name = (gchar *)purple_normalize(buddy->account, buddy->name);
++ hb.account = buddy->account;
++ hb.group = gnode;
++ g_hash_table_remove(purplebuddylist->buddies, &hb);
++
++ account_buddies = g_hash_table_lookup(buddies_cache, buddy->account);
++ g_hash_table_remove(account_buddies, &hb);
++
++ /* Update the UI */
++ if (ops && ops->remove)
++ ops->remove(purplebuddylist, node);
++
++ if (ops && ops->remove_node)
++ ops->remove_node(node);
++
++ /* Signal that the buddy has been removed before freeing the memory for it */
++ purple_signal_emit(purple_blist_get_handle(), "buddy-removed", buddy);
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
++ PURPLE_BLIST_NODE(buddy));
++
++ purple_buddy_destroy(buddy);
++
++ /* If the contact is empty then remove it */
++ if ((contact != NULL) && !cnode->child)
++ purple_blist_remove_contact(contact);
++}
++
++void purple_blist_remove_chat(PurpleChat *chat)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleBlistNode *node, *gnode;
++ PurpleGroup *group;
++
++ g_return_if_fail(chat != NULL);
++
++ node = (PurpleBlistNode *)chat;
++ gnode = node->parent;
++ group = (PurpleGroup *)gnode;
++
++ if (gnode != NULL)
++ {
++ /* Remove the node from its parent */
++ if (gnode->child == node)
++ gnode->child = node->next;
++ if (node->prev)
++ node->prev->next = node->next;
++ if (node->next)
++ node->next->prev = node->prev;
++
++ /* Adjust size counts */
++ if (purple_account_is_connected(chat->account)) {
++ group->online--;
++ group->currentsize--;
++ }
++ group->totalsize--;
++
++ }
++
++ /* Update the UI */
++ if (ops && ops->remove)
++ ops->remove(purplebuddylist, node);
++
++ if (ops && ops->remove_node)
++ ops->remove_node(node);
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
++ PURPLE_BLIST_NODE(chat));
++
++ /* Delete the node */
++ purple_chat_destroy(chat);
++}
++
++void purple_blist_remove_group(PurpleGroup *group)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleBlistNode *node;
++ GList *l;
++ gchar* key;
++
++ g_return_if_fail(group != NULL);
++
++ node = (PurpleBlistNode *)group;
++
++ /* Make sure the group is empty */
++ if (node->child)
++ return;
++
++ /* Remove the node from its parent */
++ if (purplebuddylist->root == node)
++ purplebuddylist->root = node->next;
++ if (node->prev)
++ node->prev->next = node->next;
++ if (node->next)
++ node->next->prev = node->prev;
++
++ key = g_utf8_collate_key(group->name, -1);
++ g_hash_table_remove(groups_cache, key);
++ g_free(key);
++
++ /* Update the UI */
++ if (ops && ops->remove)
++ ops->remove(purplebuddylist, node);
++
++ if (ops && ops->remove_node)
++ ops->remove_node(node);
++
++ purple_signal_emit(purple_blist_get_handle(), "blist-node-removed",
++ PURPLE_BLIST_NODE(group));
++
++ /* Remove the group from all accounts that are online */
++ for (l = purple_connections_get_all(); l != NULL; l = l->next)
++ {
++ PurpleConnection *gc = (PurpleConnection *)l->data;
++
++ if (purple_connection_get_state(gc) == PURPLE_CONNECTED)
++ purple_account_remove_group(purple_connection_get_account(gc), group);
++ }
++
++ /* Delete the node */
++ purple_group_destroy(group);
++}
++
++PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact)
++{
++ g_return_val_if_fail(contact != NULL, NULL);
++
++ if (!contact->priority_valid)
++ purple_contact_compute_priority_buddy(contact);
++
++ return contact->priority;
++}
++
++const char *purple_buddy_get_alias_only(PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ if ((buddy->alias != NULL) && (*buddy->alias != '\0')) {
++ return buddy->alias;
++ } else if ((buddy->server_alias != NULL) &&
++ (*buddy->server_alias != '\0')) {
++
++ return buddy->server_alias;
++ }
++
++ return NULL;
++}
++
++
++const char *purple_buddy_get_contact_alias(PurpleBuddy *buddy)
++{
++ PurpleContact *c;
++
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ /* Search for an alias for the buddy. In order of precedence: */
++ /* The buddy alias */
++ if (buddy->alias != NULL)
++ return buddy->alias;
++
++ /* The contact alias */
++ c = purple_buddy_get_contact(buddy);
++ if ((c != NULL) && (c->alias != NULL))
++ return c->alias;
++
++ /* The server alias */
++ if ((buddy->server_alias) && (*buddy->server_alias))
++ return buddy->server_alias;
++
++ /* The buddy's user name (i.e. no alias) */
++ return buddy->name;
++}
++
++
++const char *purple_buddy_get_alias(PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ /* Search for an alias for the buddy. In order of precedence: */
++ /* The buddy alias */
++ if (buddy->alias != NULL)
++ return buddy->alias;
++
++ /* The server alias */
++ if ((buddy->server_alias) && (*buddy->server_alias))
++ return buddy->server_alias;
++
++ /* The buddy's user name (i.e. no alias) */
++ return buddy->name;
++}
++
++const char *purple_buddy_get_local_buddy_alias(PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy, NULL);
++ return buddy->alias;
++}
++
++const char *purple_buddy_get_server_alias(PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ if ((buddy->server_alias) && (*buddy->server_alias))
++ return buddy->server_alias;
++
++ return NULL;
++}
++
++const char *purple_buddy_get_local_alias(PurpleBuddy *buddy)
++{
++ PurpleContact *c;
++
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ /* Search for an alias for the buddy. In order of precedence: */
++ /* The buddy alias */
++ if (buddy->alias != NULL)
++ return buddy->alias;
++
++ /* The contact alias */
++ c = purple_buddy_get_contact(buddy);
++ if ((c != NULL) && (c->alias != NULL))
++ return c->alias;
++
++ /* The buddy's user name (i.e. no alias) */
++ return buddy->name;
++}
++
++const char *purple_chat_get_name(PurpleChat *chat)
++{
++ char *ret = NULL;
++ PurplePlugin *prpl;
++ PurplePluginProtocolInfo *prpl_info = NULL;
++
++ g_return_val_if_fail(chat != NULL, NULL);
++
++ if ((chat->alias != NULL) && (*chat->alias != '\0'))
++ return chat->alias;
++
++ prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
++
++ if (prpl_info->chat_info) {
++ struct proto_chat_entry *pce;
++ GList *parts = prpl_info->chat_info(purple_account_get_connection(chat->account));
++ pce = parts->data;
++ ret = g_hash_table_lookup(chat->components, pce->identifier);
++ g_list_foreach(parts, (GFunc)g_free, NULL);
++ g_list_free(parts);
++ }
++
++ return ret;
++}
++
++PurpleBuddy *purple_find_buddy(PurpleAccount *account, const char *name)
++{
++ PurpleBuddy *buddy;
++ struct _purple_hbuddy hb;
++ PurpleBlistNode *group;
++
++ g_return_val_if_fail(purplebuddylist != NULL, NULL);
++ g_return_val_if_fail(account != NULL, NULL);
++ g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
++
++ hb.account = account;
++ hb.name = (gchar *)purple_normalize(account, name);
++
++ for (group = purplebuddylist->root; group; group = group->next) {
++ if (!group->child)
++ continue;
++
++ hb.group = group;
++ if ((buddy = g_hash_table_lookup(purplebuddylist->buddies, &hb))) {
++ return buddy;
++ }
++ }
++
++ return NULL;
++}
++
++PurpleBuddy *purple_find_buddy_in_group(PurpleAccount *account, const char *name,
++ PurpleGroup *group)
++{
++ struct _purple_hbuddy hb;
++
++ g_return_val_if_fail(purplebuddylist != NULL, NULL);
++ g_return_val_if_fail(account != NULL, NULL);
++ g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
++
++ hb.name = (gchar *)purple_normalize(account, name);
++ hb.account = account;
++ hb.group = (PurpleBlistNode*)group;
++
++ return g_hash_table_lookup(purplebuddylist->buddies, &hb);
++}
++
++static void find_acct_buddies(gpointer key, gpointer value, gpointer data)
++{
++ PurpleBuddy *buddy = value;
++ GSList **list = data;
++
++ *list = g_slist_prepend(*list, buddy);
++}
++
++GSList *purple_find_buddies(PurpleAccount *account, const char *name)
++{
++ PurpleBuddy *buddy;
++ PurpleBlistNode *node;
++ GSList *ret = NULL;
++
++ g_return_val_if_fail(purplebuddylist != NULL, NULL);
++ g_return_val_if_fail(account != NULL, NULL);
++
++ if ((name != NULL) && (*name != '\0')) {
++ struct _purple_hbuddy hb;
++
++ hb.name = (gchar *)purple_normalize(account, name);
++ hb.account = account;
++
++ for (node = purplebuddylist->root; node != NULL; node = node->next) {
++ if (!node->child)
++ continue;
++
++ hb.group = node;
++ if ((buddy = g_hash_table_lookup(purplebuddylist->buddies, &hb)) != NULL)
++ ret = g_slist_prepend(ret, buddy);
++ }
++ } else {
++ GSList *list = NULL;
++ GHashTable *buddies = g_hash_table_lookup(buddies_cache, account);
++ g_hash_table_foreach(buddies, find_acct_buddies, &list);
++ ret = list;
++ }
++
++ return ret;
++}
++
++PurpleGroup *purple_find_group(const char *name)
++{
++ gchar* key;
++ PurpleGroup *group;
++
++ g_return_val_if_fail(purplebuddylist != NULL, NULL);
++ g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
++
++ key = g_utf8_collate_key(name, -1);
++ group = g_hash_table_lookup(groups_cache, key);
++ g_free(key);
++
++ return group;
++}
++
++PurpleChat *
++purple_blist_find_chat(PurpleAccount *account, const char *name)
++{
++ char *chat_name;
++ PurpleChat *chat;
++ PurplePlugin *prpl;
++ PurplePluginProtocolInfo *prpl_info = NULL;
++ struct proto_chat_entry *pce;
++ PurpleBlistNode *node, *group;
++ GList *parts;
++ char *normname;
++
++ g_return_val_if_fail(purplebuddylist != NULL, NULL);
++ g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
++
++ if (!purple_account_is_connected(account))
++ return NULL;
++
++ prpl = purple_find_prpl(purple_account_get_protocol_id(account));
++ prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
++
++ if (prpl_info->find_blist_chat != NULL)
++ return prpl_info->find_blist_chat(account, name);
++
++ normname = g_strdup(purple_normalize(account, name));
++ for (group = purplebuddylist->root; group != NULL; group = group->next) {
++ for (node = group->child; node != NULL; node = node->next) {
++ if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
++
++ chat = (PurpleChat*)node;
++
++ if (account != chat->account)
++ continue;
++
++ parts = prpl_info->chat_info(
++ purple_account_get_connection(chat->account));
++
++ pce = parts->data;
++ chat_name = g_hash_table_lookup(chat->components,
++ pce->identifier);
++ g_list_foreach(parts, (GFunc)g_free, NULL);
++ g_list_free(parts);
++
++ if (chat->account == account && chat_name != NULL &&
++ normname != NULL && !strcmp(purple_normalize(account, chat_name), normname)) {
++ g_free(normname);
++ return chat;
++ }
++ }
++ }
++ }
++
++ g_free(normname);
++ return NULL;
++}
++
++PurpleGroup *
++purple_chat_get_group(PurpleChat *chat)
++{
++ g_return_val_if_fail(chat != NULL, NULL);
++
++ return (PurpleGroup *)(((PurpleBlistNode *)chat)->parent);
++}
++
++PurpleAccount *
++purple_chat_get_account(PurpleChat *chat)
++{
++ g_return_val_if_fail(chat != NULL, NULL);
++
++ return chat->account;
++}
++
++GHashTable *
++purple_chat_get_components(PurpleChat *chat)
++{
++ g_return_val_if_fail(chat != NULL, NULL);
++
++ return chat->components;
++}
++
++PurpleContact *purple_buddy_get_contact(PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ return PURPLE_CONTACT(PURPLE_BLIST_NODE(buddy)->parent);
++}
++
++PurplePresence *purple_buddy_get_presence(const PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++ return buddy->presence;
++}
++
++PurpleMediaCaps purple_buddy_get_media_caps(const PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, 0);
++ return buddy->media_caps;
++}
++
++void purple_buddy_set_media_caps(PurpleBuddy *buddy, PurpleMediaCaps media_caps)
++{
++ g_return_if_fail(buddy != NULL);
++ buddy->media_caps = media_caps;
++}
++
++PurpleGroup *purple_buddy_get_group(PurpleBuddy *buddy)
++{
++ g_return_val_if_fail(buddy != NULL, NULL);
++
++ if (((PurpleBlistNode *)buddy)->parent == NULL)
++ return NULL;
++
++ return (PurpleGroup *)(((PurpleBlistNode*)buddy)->parent->parent);
++}
++
++GSList *purple_group_get_accounts(PurpleGroup *group)
++{
++ GSList *l = NULL;
++ PurpleBlistNode *gnode, *cnode, *bnode;
++
++ gnode = (PurpleBlistNode *)group;
++
++ for (cnode = gnode->child; cnode; cnode = cnode->next) {
++ if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
++ if (!g_slist_find(l, ((PurpleChat *)cnode)->account))
++ l = g_slist_append(l, ((PurpleChat *)cnode)->account);
++ } else if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
++ for (bnode = cnode->child; bnode; bnode = bnode->next) {
++ if (PURPLE_BLIST_NODE_IS_BUDDY(bnode)) {
++ if (!g_slist_find(l, ((PurpleBuddy *)bnode)->account))
++ l = g_slist_append(l, ((PurpleBuddy *)bnode)->account);
++ }
++ }
++ }
++ }
++
++ return l;
++}
++
++void purple_blist_add_account(PurpleAccount *account)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleBlistNode *gnode, *cnode, *bnode;
++
++ g_return_if_fail(purplebuddylist != NULL);
++
++ if (!ops || !ops->update)
++ return;
++
++ for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) {
++ if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
++ continue;
++ for (cnode = gnode->child; cnode; cnode = cnode->next) {
++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
++ gboolean recompute = FALSE;
++ for (bnode = cnode->child; bnode; bnode = bnode->next) {
++ if (PURPLE_BLIST_NODE_IS_BUDDY(bnode) &&
++ ((PurpleBuddy*)bnode)->account == account) {
++ recompute = TRUE;
++ ((PurpleContact*)cnode)->currentsize++;
++ if (((PurpleContact*)cnode)->currentsize == 1)
++ ((PurpleGroup*)gnode)->currentsize++;
++ ops->update(purplebuddylist, bnode);
++ }
++ }
++ if (recompute ||
++ purple_blist_node_get_bool(cnode, "show_offline")) {
++ purple_contact_invalidate_priority_buddy((PurpleContact*)cnode);
++ ops->update(purplebuddylist, cnode);
++ }
++ } else if (PURPLE_BLIST_NODE_IS_CHAT(cnode) &&
++ ((PurpleChat*)cnode)->account == account) {
++ ((PurpleGroup *)gnode)->online++;
++ ((PurpleGroup *)gnode)->currentsize++;
++ ops->update(purplebuddylist, cnode);
++ }
++ }
++ ops->update(purplebuddylist, gnode);
++ }
++}
++
++void purple_blist_remove_account(PurpleAccount *account)
++{
++ PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
++ PurpleBlistNode *gnode, *cnode, *bnode;
++ PurpleBuddy *buddy;
++ PurpleChat *chat;
++ PurpleContact *contact;
++ PurpleGroup *group;
++ GList *list = NULL, *iter = NULL;
++
++ g_return_if_fail(purplebuddylist != NULL);
++
++ for (gnode = purplebuddylist->root; gnode; gnode = gnode->next) {
++ if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
++ continue;
++
++ group = (PurpleGroup *)gnode;
++
++ for (cnode = gnode->child; cnode; cnode = cnode->next) {
++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
++ gboolean recompute = FALSE;
++ contact = (PurpleContact *)cnode;
++
++ for (bnode = cnode->child; bnode; bnode = bnode->next) {
++ if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
++ continue;
++
++ buddy = (PurpleBuddy *)bnode;
++ if (account == buddy->account) {
++ PurplePresence *presence;
++
++ presence = purple_buddy_get_presence(buddy);
++
++ if(purple_presence_is_online(presence)) {
++ contact->online--;
++ if (contact->online == 0)
++ group->online--;
++
++ purple_blist_node_set_int(&buddy->node,
++ "last_seen", time(NULL));
++ }
++
++ contact->currentsize--;
++ if (contact->currentsize == 0)
++ group->currentsize--;
++
++ if (!g_list_find(list, presence))
++ list = g_list_prepend(list, presence);
++
++ if (contact->priority == buddy)
++ purple_contact_invalidate_priority_buddy(contact);
++ else
++ recompute = TRUE;
++
++ if (ops && ops->remove) {
++ ops->remove(purplebuddylist, bnode);
++ }
++ }
++ }
++ if (recompute) {
++ purple_contact_invalidate_priority_buddy(contact);
++ if (ops && ops->update)
++ ops->update(purplebuddylist, cnode);
++ }
++ } else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
++ chat = (PurpleChat *)cnode;
++
++ if(chat->account == account) {
++ group->currentsize--;
++ group->online--;
++
++ if (ops && ops->remove)
++ ops->remove(purplebuddylist, cnode);
++ }
++ }
++ }
++ }
++
++ for (iter = list; iter; iter = iter->next)
++ {
++ purple_presence_set_status_active(iter->data, "offline", TRUE);
++ }
++ g_list_free(list);
++}
++
++gboolean purple_group_on_account(PurpleGroup *g, PurpleAccount *account)
++{
++ PurpleBlistNode *cnode;
++ for (cnode = ((PurpleBlistNode *)g)->child; cnode; cnode = cnode->next) {
++ if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
++ if(purple_contact_on_account((PurpleContact *) cnode, account))
++ return TRUE;
++ } else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
++ PurpleChat *chat = (PurpleChat *)cnode;
++ if ((!account && purple_account_is_connected(chat->account))
++ || chat->account == account)
++ return TRUE;
++ }
++ }
++ return FALSE;
++}
++
++const char *purple_group_get_name(PurpleGroup *group)
++{
++ g_return_val_if_fail(group != NULL, NULL);
++
++ return group->name;
++}
++
++void
++purple_blist_request_add_buddy(PurpleAccount *account, const char *username,
++ const char *group, const char *alias)
++{
++ PurpleBlistUiOps *ui_ops;
++
++ ui_ops = purple_blist_get_ui_ops();
++
++ if (ui_ops != NULL && ui_ops->request_add_buddy != NULL)
++ ui_ops->request_add_buddy(account, username, group, alias);
++}
++
++void
++purple_blist_request_add_chat(PurpleAccount *account, PurpleGroup *group,
++ const char *alias, const char *name)
++{
++ PurpleBlistUiOps *ui_ops;
++
++ ui_ops = purple_blist_get_ui_ops();
++
++ if (ui_ops != NULL && ui_ops->request_add_chat != NULL)
++ ui_ops->request_add_chat(account, group, alias, name);
++}
++
++void
++purple_blist_request_add_group(void)
++{
++ PurpleBlistUiOps *ui_ops;
++
++ ui_ops = purple_blist_get_ui_ops();
++
++ if (ui_ops != NULL && ui_ops->request_add_group != NULL)
++ ui_ops->request_add_group();
++}
++
++static void
++purple_blist_node_destroy(PurpleBlistNode *node)
++{
++ PurpleBlistUiOps *ui_ops;
++ PurpleBlistNode *child, *next_child;
++
++ ui_ops = purple_blist_get_ui_ops();
++ child = node->child;
++ while (child) {
++ next_child = child->next;
++ purple_blist_node_destroy(child);
++ child = next_child;
++ }
++
++ /* Allow the UI to free data */
++ node->parent = NULL;
++ node->child = NULL;
++ node->next = NULL;
++ node->prev = NULL;
++ if (ui_ops && ui_ops->remove)
++ ui_ops->remove(purplebuddylist, node);
++
++ if (PURPLE_BLIST_NODE_IS_BUDDY(node))
++ purple_buddy_destroy((PurpleBuddy*)node);
++ else if (PURPLE_BLIST_NODE_IS_CHAT(node))
++ purple_chat_destroy((PurpleChat*)node);
++ else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
++ purple_contact_destroy((PurpleContact*)node);
++ else if (PURPLE_BLIST_NODE_IS_GROUP(node))
++ purple_group_destroy((PurpleGroup*)node);
++}
++
++static void
++purple_blist_node_setting_free(gpointer data)
++{
++ PurpleValue *value;
++
++ value = (PurpleValue *)data;
++
++ purple_value_destroy(value);
++}
++
++static void purple_blist_node_initialize_settings(PurpleBlistNode *node)
++{
++ if (node->settings)
++ return;
++
++ node->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
++ (GDestroyNotify)purple_blist_node_setting_free);
++}
++
++void purple_blist_node_remove_setting(PurpleBlistNode *node, const char *key)
++{
++ PurpleBlistUiOps *ops;
++ g_return_if_fail(node != NULL);
++ g_return_if_fail(node->settings != NULL);
++ g_return_if_fail(key != NULL);
++
++ g_hash_table_remove(node->settings, key);
++
++ ops = purple_blist_get_ui_ops();
++ if (ops && ops->save_node)
++ ops->save_node(node);
++}
++
++void
++purple_blist_node_set_flags(PurpleBlistNode *node, PurpleBlistNodeFlags flags)
++{
++ g_return_if_fail(node != NULL);
++
++ node->flags = flags;
++}
++
++PurpleBlistNodeFlags
++purple_blist_node_get_flags(PurpleBlistNode *node)
++{
++ g_return_val_if_fail(node != NULL, 0);
++
++ return node->flags;
++}
++
++PurpleBlistNodeType
++purple_blist_node_get_type(PurpleBlistNode *node)
++{
++ g_return_val_if_fail(node != NULL, PURPLE_BLIST_OTHER_NODE);
++ return node->type;
++}
++
++void
++purple_blist_node_set_bool(PurpleBlistNode* node, const char *key, gboolean data)
++{
++ PurpleValue *value;
++ PurpleBlistUiOps *ops;
++
++ g_return_if_fail(node != NULL);
++ g_return_if_fail(node->settings != NULL);
++ g_return_if_fail(key != NULL);
++
++ value = purple_value_new(PURPLE_TYPE_BOOLEAN);
++ purple_value_set_boolean(value, data);
++
++ g_hash_table_replace(node->settings, g_strdup(key), value);
++
++ ops = purple_blist_get_ui_ops();
++ if (ops && ops->save_node)
++ ops->save_node(node);
++}
++
++gboolean
++purple_blist_node_get_bool(PurpleBlistNode* node, const char *key)
++{
++ PurpleValue *value;
++
++ g_return_val_if_fail(node != NULL, FALSE);
++ g_return_val_if_fail(node->settings != NULL, FALSE);
++ g_return_val_if_fail(key != NULL, FALSE);
++
++ value = g_hash_table_lookup(node->settings, key);
++
++ if (value == NULL)
++ return FALSE;
++
++ g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_BOOLEAN, FALSE);
++
++ return purple_value_get_boolean(value);
++}
++
++void
++purple_blist_node_set_int(PurpleBlistNode* node, const char *key, int data)
++{
++ PurpleValue *value;
++ PurpleBlistUiOps *ops;
++
++ g_return_if_fail(node != NULL);
++ g_return_if_fail(node->settings != NULL);
++ g_return_if_fail(key != NULL);
++
++ value = purple_value_new(PURPLE_TYPE_INT);
++ purple_value_set_int(value, data);
++
++ g_hash_table_replace(node->settings, g_strdup(key), value);
++
++ ops = purple_blist_get_ui_ops();
++ if (ops && ops->save_node)
++ ops->save_node(node);
++}
++
++int
++purple_blist_node_get_int(PurpleBlistNode* node, const char *key)
++{
++ PurpleValue *value;
++
++ g_return_val_if_fail(node != NULL, 0);
++ g_return_val_if_fail(node->settings != NULL, 0);
++ g_return_val_if_fail(key != NULL, 0);
++
++ value = g_hash_table_lookup(node->settings, key);
++
++ if (value == NULL)
++ return 0;
++
++ g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_INT, 0);
++
++ return purple_value_get_int(value);
++}
++
++void
++purple_blist_node_set_string(PurpleBlistNode* node, const char *key, const char *data)
++{
++ PurpleValue *value;
++ PurpleBlistUiOps *ops;
++
++ g_return_if_fail(node != NULL);
++ g_return_if_fail(node->settings != NULL);
++ g_return_if_fail(key != NULL);
++
++ value = purple_value_new(PURPLE_TYPE_STRING);
++ purple_value_set_string(value, data);
++
++ g_hash_table_replace(node->settings, g_strdup(key), value);
++
++ ops = purple_blist_get_ui_ops();
++ if (ops && ops->save_node)
++ ops->save_node(node);
++}
++
++const char *
++purple_blist_node_get_string(PurpleBlistNode* node, const char *key)
++{
++ PurpleValue *value;
++
++ g_return_val_if_fail(node != NULL, NULL);
++ g_return_val_if_fail(node->settings != NULL, NULL);
++ g_return_val_if_fail(key != NULL, NULL);
++
++ value = g_hash_table_lookup(node->settings, key);
++
++ if (value == NULL)
++ return NULL;
++
++ g_return_val_if_fail(purple_value_get_type(value) == PURPLE_TYPE_STRING, NULL);
++
++ return purple_value_get_string(value);
++}
++
++GList *
++purple_blist_node_get_extended_menu(PurpleBlistNode *n)
++{
++ GList *menu = NULL;
++
++ g_return_val_if_fail(n != NULL, NULL);
++
++ purple_signal_emit(purple_blist_get_handle(),
++ "blist-node-extended-menu",
++ n, &menu);
++ return menu;
++}
++
++int purple_blist_get_group_size(PurpleGroup *group, gboolean offline)
++{
++ if (!group)
++ return 0;
++
++ return offline ? group->totalsize : group->currentsize;
++}
++
++int purple_blist_get_group_online_count(PurpleGroup *group)
++{
++ if (!group)
++ return 0;
++
++ return group->online;
++}
++
++void
++purple_blist_set_ui_ops(PurpleBlistUiOps *ops)
++{
++ gboolean overrode = FALSE;
++ blist_ui_ops = ops;
++
++ if (!ops)
++ return;
++
++ if (!ops->save_node) {
++ ops->save_node = purple_blist_save_node;
++ overrode = TRUE;
++ }
++ if (!ops->remove_node) {
++ ops->remove_node = purple_blist_save_node;
++ overrode = TRUE;
++ }
++ if (!ops->save_account) {
++ ops->save_account = purple_blist_save_account;
++ overrode = TRUE;
++ }
++
++ if (overrode && (ops->save_node != purple_blist_save_node ||
++ ops->remove_node != purple_blist_save_node ||
++ ops->save_account != purple_blist_save_account)) {
++ purple_debug_warning("blist", "Only some of the blist saving UI ops "
++ "were overridden. This probably is not what you want!\n");
++ }
++}
++
++PurpleBlistUiOps *
++purple_blist_get_ui_ops(void)
++{
++ return blist_ui_ops;
++}
++
++
++void *
++purple_blist_get_handle(void)
++{
++ static int handle;
++
++ return &handle;
++}
++
++void
++purple_blist_init(void)
++{
++ void *handle = purple_blist_get_handle();
++
++ purple_signal_register(handle, "buddy-status-changed",
++ purple_marshal_VOID__POINTER_POINTER_POINTER, NULL,
++ 3,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY),
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_STATUS),
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_STATUS));
++ purple_signal_register(handle, "buddy-privacy-changed",
++ purple_marshal_VOID__POINTER, NULL,
++ 1,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY));
++
++ purple_signal_register(handle, "buddy-idle-changed",
++ purple_marshal_VOID__POINTER_INT_INT, NULL,
++ 3,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY),
++ purple_value_new(PURPLE_TYPE_INT),
++ purple_value_new(PURPLE_TYPE_INT));
++
++
++ purple_signal_register(handle, "buddy-signed-on",
++ purple_marshal_VOID__POINTER, NULL, 1,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY));
++
++ purple_signal_register(handle, "buddy-signed-off",
++ purple_marshal_VOID__POINTER, NULL, 1,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY));
++
++ purple_signal_register(handle, "buddy-got-login-time",
++ purple_marshal_VOID__POINTER, NULL, 1,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY));
++
++ purple_signal_register(handle, "blist-node-added",
++ purple_marshal_VOID__POINTER, NULL, 1,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_NODE));
++
++ purple_signal_register(handle, "blist-node-removed",
++ purple_marshal_VOID__POINTER, NULL, 1,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_NODE));
++
++ purple_signal_register(handle, "buddy-added",
++ purple_marshal_VOID__POINTER, NULL, 1,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY));
++
++ purple_signal_register(handle, "buddy-removed",
++ purple_marshal_VOID__POINTER, NULL, 1,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY));
++
++ purple_signal_register(handle, "buddy-icon-changed",
++ purple_marshal_VOID__POINTER, NULL, 1,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY));
++
++ purple_signal_register(handle, "update-idle", purple_marshal_VOID, NULL, 0);
++
++ purple_signal_register(handle, "blist-node-extended-menu",
++ purple_marshal_VOID__POINTER_POINTER, NULL, 2,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_NODE),
++ purple_value_new(PURPLE_TYPE_BOXED, "GList **"));
++
++ purple_signal_register(handle, "blist-node-aliased",
++ purple_marshal_VOID__POINTER_POINTER, NULL, 2,
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_NODE),
++ purple_value_new(PURPLE_TYPE_STRING));
++
++ purple_signal_register(handle, "buddy-caps-changed",
++ purple_marshal_VOID__POINTER_INT_INT, NULL,
++ 3, purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_BLIST_BUDDY),
++ purple_value_new(PURPLE_TYPE_INT),
++ purple_value_new(PURPLE_TYPE_INT));
++
++ purple_signal_connect(purple_accounts_get_handle(), "account-created",
++ handle,
++ PURPLE_CALLBACK(purple_blist_buddies_cache_add_account),
++ NULL);
++
++ purple_signal_connect(purple_accounts_get_handle(), "account-destroying",
++ handle,
++ PURPLE_CALLBACK(purple_blist_buddies_cache_remove_account),
++ NULL);
++}
++
++void
++purple_blist_uninit(void)
++{
++ PurpleBlistNode *node, *next_node;
++
++ /* This happens if we quit before purple_set_blist is called. */
++ if (purplebuddylist == NULL)
++ return;
++
++ if (save_timer != 0) {
++ purple_timeout_remove(save_timer);
++ save_timer = 0;
++ purple_blist_sync();
++ }
++
++ purple_blist_destroy();
++
++ node = purple_blist_get_root();
++ while (node) {
++ next_node = node->next;
++ purple_blist_node_destroy(node);
++ node = next_node;
++ }
++ purplebuddylist->root = NULL;
++
++ g_hash_table_destroy(purplebuddylist->buddies);
++ g_hash_table_destroy(buddies_cache);
++ g_hash_table_destroy(groups_cache);
++
++ buddies_cache = NULL;
++ groups_cache = NULL;
++
++ PURPLE_DBUS_UNREGISTER_POINTER(purplebuddylist);
++ g_free(purplebuddylist);
++ purplebuddylist = NULL;
++
++ purple_signals_disconnect_by_handle(purple_blist_get_handle());
++ purple_signals_unregister_by_instance(purple_blist_get_handle());
++}
+diff -rupN pidgin-2.7.7/libpurple/certificate.c~ pidgin-2.7.7-new//libpurple/certificate.c~
+--- pidgin-2.7.7/libpurple/certificate.c~ 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/certificate.c~ 2011-03-27 09:15:31.012553000 -0600
+@@ -0,0 +1,2235 @@
++/**
++ * @file certificate.c Public-Key Certificate API
++ * @ingroup core
++ */
++
++/*
++ *
++ * purple
++ *
++ * Purple is the legal property of its developers, whose names are too numerous
++ * to list here. Please refer to the COPYRIGHT file distributed with this
++ * source distribution.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
++ */
++
++#include "internal.h"
++#include "certificate.h"
++#include "dbus-maybe.h"
++#include "debug.h"
++#include "request.h"
++#include "signals.h"
++#include "util.h"
++
++/** List holding pointers to all registered certificate schemes */
++static GList *cert_schemes = NULL;
++/** List of registered Verifiers */
++static GList *cert_verifiers = NULL;
++/** List of registered Pools */
++static GList *cert_pools = NULL;
++
++/*
++ * TODO: Merge this with PurpleCertificateVerificationStatus for 3.0.0 */
++typedef enum {
++ PURPLE_CERTIFICATE_UNKNOWN_ERROR = -1,
++
++ /* Not an error */
++ PURPLE_CERTIFICATE_NO_PROBLEMS = 0,
++
++ /* Non-fatal */
++ PURPLE_CERTIFICATE_NON_FATALS_MASK = 0x0000FFFF,
++
++ /* The certificate is self-signed. */
++ PURPLE_CERTIFICATE_SELF_SIGNED = 0x01,
++
++ /* The CA is not in libpurple's pool of certificates. */
++ PURPLE_CERTIFICATE_CA_UNKNOWN = 0x02,
++
++ /* The current time is before the certificate's specified
++ * activation time.
++ */
++ PURPLE_CERTIFICATE_NOT_ACTIVATED = 0x04,
++
++ /* The current time is after the certificate's specified expiration time */
++ PURPLE_CERTIFICATE_EXPIRED = 0x08,
++
++ /* The certificate's subject name doesn't match the expected */
++ PURPLE_CERTIFICATE_NAME_MISMATCH = 0x10,
++
++ /* No CA pool was found. This shouldn't happen... */
++ PURPLE_CERTIFICATE_NO_CA_POOL = 0x20,
++
++ /* Fatal */
++ PURPLE_CERTIFICATE_FATALS_MASK = 0xFFFF0000,
++
++ /* The signature chain could not be validated. Due to limitations in the
++ * the current API, this also indicates one of the CA certificates in the
++ * chain is expired (or not yet activated). FIXME 3.0.0 */
++ PURPLE_CERTIFICATE_INVALID_CHAIN = 0x10000,
++
++ /* The signature has been revoked. */
++ PURPLE_CERTIFICATE_REVOKED = 0x20000,
++
++ PURPLE_CERTIFICATE_LAST = 0x40000,
++} PurpleCertificateInvalidityFlags;
++
++static const gchar *
++invalidity_reason_to_string(PurpleCertificateInvalidityFlags flag)
++{
++ switch (flag) {
++ case PURPLE_CERTIFICATE_SELF_SIGNED:
++ return _("The certificate is self-signed and cannot be "
++ "automatically checked.");
++ break;
++ case PURPLE_CERTIFICATE_CA_UNKNOWN:
++ return _("The certificate is not trusted because no certificate "
++ "that can verify it is currently trusted.");
++ break;
++ case PURPLE_CERTIFICATE_NOT_ACTIVATED:
++ return _("The certificate is not valid yet. Check that your "
++ "computer's date and time are accurate.");
++ break;
++ case PURPLE_CERTIFICATE_EXPIRED:
++ return _("The certificate has expired and should not be "
++ "considered valid. Check that your computer's date "
++ "and time are accurate.");
++ break;
++ case PURPLE_CERTIFICATE_NAME_MISMATCH:
++ /* Translators: "domain" refers to a DNS domain (e.g. talk.google.com) */
++ return _("The certificate presented is not issued to this domain.");
++ break;
++ case PURPLE_CERTIFICATE_NO_CA_POOL:
++ return _("You have no database of root certificates, so "
++ "this certificate cannot be validated.");
++ break;
++ case PURPLE_CERTIFICATE_INVALID_CHAIN:
++ return _("The certificate chain presented is invalid.");
++ break;
++ case PURPLE_CERTIFICATE_REVOKED:
++ return _("The certificate has been revoked.");
++ break;
++ case PURPLE_CERTIFICATE_UNKNOWN_ERROR:
++ default:
++ return _("An unknown certificate error occurred.");
++ break;
++ }
++}
++
++void
++purple_certificate_verify (PurpleCertificateVerifier *verifier,
++ const gchar *subject_name, GList *cert_chain,
++ PurpleCertificateVerifiedCallback cb,
++ gpointer cb_data)
++{
++ PurpleCertificateVerificationRequest *vrq;
++ PurpleCertificateScheme *scheme;
++
++ g_return_if_fail(subject_name != NULL);
++ /* If you don't have a cert to check, why are you requesting that it
++ be verified? */
++ g_return_if_fail(cert_chain != NULL);
++ g_return_if_fail(cb != NULL);
++
++ /* Look up the CertificateScheme */
++ scheme = purple_certificate_find_scheme(verifier->scheme_name);
++ g_return_if_fail(scheme);
++
++ /* Check that at least the first cert in the chain matches the
++ Verifier scheme */
++ g_return_if_fail(scheme ==
++ ((PurpleCertificate *) (cert_chain->data))->scheme);
++
++ /* Construct and fill in the request fields */
++ vrq = g_new0(PurpleCertificateVerificationRequest, 1);
++ vrq->verifier = verifier;
++ vrq->scheme = scheme;
++ vrq->subject_name = g_strdup(subject_name);
++ vrq->cert_chain = purple_certificate_copy_list(cert_chain);
++ vrq->cb = cb;
++ vrq->cb_data = cb_data;
++
++ /* Initiate verification */
++ (verifier->start_verification)(vrq);
++}
++
++void
++purple_certificate_verify_complete(PurpleCertificateVerificationRequest *vrq,
++ PurpleCertificateVerificationStatus st)
++{
++ PurpleCertificateVerifier *vr;
++
++ g_return_if_fail(vrq);
++
++ const char *acceptbadcert;
++ acceptbadcert = purple_prefs_get_string("/purple/acceptbadcert");
++
++ if (acceptbadcert != NULL)
++ {
++ purple_debug_warning("certificate", "Certificate Error. Accepting as requested!\n");
++ purple_prefs_remove("/purple/acceptbadcert");
++
++
++ const char *acceptbadcert;
++ acceptbadcert = purple_prefs_get_string("/purple/acceptbadcert");
++
++ if (acceptbadcert != NULL)
++ {
++ purple_debug_warning("certificate", "Certificate Error. Accepting as requested!\n");
++ purple_prefs_remove("/purple/acceptbadcert");
++
++ //Dodgy Cert Acceptance
++ st = PURPLE_CERTIFICATE_VALID;
++ }
++ //Dodgy Cert Acceptance
++ st = PURPLE_CERTIFICATE_VALID;
++ }
++
++ if (st == PURPLE_CERTIFICATE_VALID) {
++ purple_debug_info("certificate",
++ "Successfully verified certificate for %s\n",
++ vrq->subject_name);
++ } else {
++ purple_debug_error("certificate",
++ "Failed to verify certificate for %s\n",
++ vrq->subject_name);
++ }
++
++ /* Pass the results on to the request's callback */
++ (vrq->cb)(st, vrq->cb_data);
++
++ /* And now to eliminate the request */
++ /* Fetch the Verifier responsible... */
++ vr = vrq->verifier;
++ /* ...and order it to KILL */
++ (vr->destroy_request)(vrq);
++
++ /* Now the internals have been cleaned up, so clean up the libpurple-
++ created elements */
++ g_free(vrq->subject_name);
++ purple_certificate_destroy_list(vrq->cert_chain);
++
++ /* A structure born
++ * to much ado
++ * and with so much within.
++ * It reaches now
++ * its quiet end. */
++ g_free(vrq);
++}
++
++
++PurpleCertificate *
++purple_certificate_copy(PurpleCertificate *crt)
++{
++ g_return_val_if_fail(crt, NULL);
++ g_return_val_if_fail(crt->scheme, NULL);
++ g_return_val_if_fail(crt->scheme->copy_certificate, NULL);
++
++ return (crt->scheme->copy_certificate)(crt);
++}
++
++GList *
++purple_certificate_copy_list(GList *crt_list)
++{
++ GList *new_l, *l;
++
++ /* First, make a shallow copy of the list */
++ new_l = g_list_copy(crt_list);
++
++ /* Now go through and actually duplicate each certificate */
++ for (l = new_l; l; l = l->next) {
++ l->data = purple_certificate_copy(l->data);
++ }
++
++ return new_l;
++}
++
++void
++purple_certificate_destroy (PurpleCertificate *crt)
++{
++ PurpleCertificateScheme *scheme;
++
++ if (NULL == crt) return;
++
++ scheme = crt->scheme;
++
++ (scheme->destroy_certificate)(crt);
++}
++
++void
++purple_certificate_destroy_list (GList * crt_list)
++{
++ PurpleCertificate *crt;
++ GList *l;
++
++ for (l=crt_list; l; l = l->next) {
++ crt = (PurpleCertificate *) l->data;
++ purple_certificate_destroy(crt);
++ }
++
++ g_list_free(crt_list);
++}
++
++gboolean
++purple_certificate_signed_by(PurpleCertificate *crt, PurpleCertificate *issuer)
++{
++ PurpleCertificateScheme *scheme;
++
++ g_return_val_if_fail(crt, FALSE);
++ g_return_val_if_fail(issuer, FALSE);
++
++ scheme = crt->scheme;
++ g_return_val_if_fail(scheme, FALSE);
++ /* We can't compare two certs of unrelated schemes, obviously */
++ g_return_val_if_fail(issuer->scheme == scheme, FALSE);
++
++ return (scheme->signed_by)(crt, issuer);
++}
++
++gboolean
++purple_certificate_check_signature_chain_with_failing(GList *chain,
++ PurpleCertificate **failing)
++{
++ GList *cur;
++ PurpleCertificate *crt, *issuer;
++ gchar *uid;
++ time_t now, activation, expiration;
++ gboolean ret;
++
++ g_return_val_if_fail(chain, FALSE);
++
++ if (failing)
++ *failing = NULL;
++
++ uid = purple_certificate_get_unique_id((PurpleCertificate *) chain->data);
++ purple_debug_info("certificate",
++ "Checking signature chain for uid=%s\n",
++ uid);
++ g_free(uid);
++
++ /* If this is a single-certificate chain, say that it is valid */
++ if (chain->next == NULL) {
++ purple_debug_info("certificate",
++ "...Singleton. We'll say it's valid.\n");
++ return TRUE;
++ }
++
++ now = time(NULL);
++
++ /* Load crt with the first certificate */
++ crt = (PurpleCertificate *)(chain->data);
++ /* And start with the second certificate in the chain */
++ for ( cur = chain->next; cur; cur = cur->next ) {
++
++ issuer = (PurpleCertificate *)(cur->data);
++
++ uid = purple_certificate_get_unique_id(issuer);
++
++ ret = purple_certificate_get_times(issuer, &activation, &expiration);
++ if (!ret || now < activation || now > expiration) {
++ if (!ret)
++ purple_debug_error("certificate",
++ "...Failed to get validity times for certificate %s\n"
++ "Chain is INVALID\n", uid);
++ else if (now > expiration)
++ purple_debug_error("certificate",
++ "...Issuer %s expired at %s\nChain is INVALID\n",
++ uid, ctime(&expiration));
++ else
++ purple_debug_error("certificate",
++ "...Not-yet-activated issuer %s will be valid at %s\n"
++ "Chain is INVALID\n", uid, ctime(&activation));
++
++ if (failing)
++ *failing = crt;
++
++ g_free(uid);
++ return FALSE;
++ }
++
++ /* Check the signature for this link */
++ if (! purple_certificate_signed_by(crt, issuer) ) {
++ purple_debug_error("certificate",
++ "...Bad or missing signature by %s\nChain is INVALID\n",
++ uid);
++ g_free(uid);
++
++ if (failing)
++ *failing = crt;
++
++ return FALSE;
++ }
++
++ purple_debug_info("certificate",
++ "...Good signature by %s\n",
++ uid);
++ g_free(uid);
++
++ /* The issuer is now the next crt whose signature is to be
++ checked */
++ crt = issuer;
++ }
++
++ /* If control reaches this point, the chain is valid */
++ purple_debug_info("certificate", "Chain is VALID\n");
++ return TRUE;
++}
++
++gboolean
++purple_certificate_check_signature_chain(GList *chain)
++{
++ return purple_certificate_check_signature_chain_with_failing(chain, NULL);
++}
++
++PurpleCertificate *
++purple_certificate_import(PurpleCertificateScheme *scheme, const gchar *filename)
++{
++ g_return_val_if_fail(scheme, NULL);
++ g_return_val_if_fail(scheme->import_certificate, NULL);
++ g_return_val_if_fail(filename, NULL);
++
++ return (scheme->import_certificate)(filename);
++}
++
++GSList *
++purple_certificates_import(PurpleCertificateScheme *scheme, const gchar *filename)
++{
++ g_return_val_if_fail(scheme, NULL);
++ g_return_val_if_fail(scheme->import_certificates, NULL);
++ g_return_val_if_fail(filename, NULL);
++
++ return (scheme->import_certificates)(filename);
++}
++
++gboolean
++purple_certificate_export(const gchar *filename, PurpleCertificate *crt)
++{
++ PurpleCertificateScheme *scheme;
++
++ g_return_val_if_fail(filename, FALSE);
++ g_return_val_if_fail(crt, FALSE);
++ g_return_val_if_fail(crt->scheme, FALSE);
++
++ scheme = crt->scheme;
++ g_return_val_if_fail(scheme->export_certificate, FALSE);
++
++ return (scheme->export_certificate)(filename, crt);
++}
++
++static gboolean
++byte_arrays_equal(const GByteArray *array1, const GByteArray *array2)
++{
++ g_return_val_if_fail(array1 != NULL, FALSE);
++ g_return_val_if_fail(array2 != NULL, FALSE);
++
++ return (array1->len == array2->len) &&
++ (0 == memcmp(array1->data, array2->data, array1->len));
++}
++
++GByteArray *
++purple_certificate_get_fingerprint_sha1(PurpleCertificate *crt)
++{
++ PurpleCertificateScheme *scheme;
++ GByteArray *fpr;
++
++ g_return_val_if_fail(crt, NULL);
++ g_return_val_if_fail(crt->scheme, NULL);
++
++ scheme = crt->scheme;
++
++ g_return_val_if_fail(scheme->get_fingerprint_sha1, NULL);
++
++ fpr = (scheme->get_fingerprint_sha1)(crt);
++
++ return fpr;
++}
++
++gchar *
++purple_certificate_get_unique_id(PurpleCertificate *crt)
++{
++ g_return_val_if_fail(crt, NULL);
++ g_return_val_if_fail(crt->scheme, NULL);
++ g_return_val_if_fail(crt->scheme->get_unique_id, NULL);
++
++ return (crt->scheme->get_unique_id)(crt);
++}
++
++gchar *
++purple_certificate_get_issuer_unique_id(PurpleCertificate *crt)
++{
++ g_return_val_if_fail(crt, NULL);
++ g_return_val_if_fail(crt->scheme, NULL);
++ g_return_val_if_fail(crt->scheme->get_issuer_unique_id, NULL);
++
++ return (crt->scheme->get_issuer_unique_id)(crt);
++}
++
++gchar *
++purple_certificate_get_subject_name(PurpleCertificate *crt)
++{
++ PurpleCertificateScheme *scheme;
++ gchar *subject_name;
++
++ g_return_val_if_fail(crt, NULL);
++ g_return_val_if_fail(crt->scheme, NULL);
++
++ scheme = crt->scheme;
++
++ g_return_val_if_fail(scheme->get_subject_name, NULL);
++
++ subject_name = (scheme->get_subject_name)(crt);
++
++ return subject_name;
++}
++
++gboolean
++purple_certificate_check_subject_name(PurpleCertificate *crt, const gchar *name)
++{
++ PurpleCertificateScheme *scheme;
++
++ g_return_val_if_fail(crt, FALSE);
++ g_return_val_if_fail(crt->scheme, FALSE);
++ g_return_val_if_fail(name, FALSE);
++
++ scheme = crt->scheme;
++
++ g_return_val_if_fail(scheme->check_subject_name, FALSE);
++
++ return (scheme->check_subject_name)(crt, name);
++}
++
++gboolean
++purple_certificate_get_times(PurpleCertificate *crt, time_t *activation, time_t *expiration)
++{
++ PurpleCertificateScheme *scheme;
++
++ g_return_val_if_fail(crt, FALSE);
++
++ scheme = crt->scheme;
++
++ g_return_val_if_fail(scheme, FALSE);
++
++ /* If both provided references are NULL, what are you doing calling
++ this? */
++ g_return_val_if_fail( (activation != NULL) || (expiration != NULL), FALSE);
++
++ /* Throw the request on down to the certscheme */
++ return (scheme->get_times)(crt, activation, expiration);
++}
++
++gchar *
++purple_certificate_pool_mkpath(PurpleCertificatePool *pool, const gchar *id)
++{
++ gchar *path;
++ gchar *esc_scheme_name, *esc_name, *esc_id;
++
++ g_return_val_if_fail(pool, NULL);
++ g_return_val_if_fail(pool->scheme_name, NULL);
++ g_return_val_if_fail(pool->name, NULL);
++
++ /* Escape all the elements for filesystem-friendliness */
++ esc_scheme_name = pool ? g_strdup(purple_escape_filename(pool->scheme_name)) : NULL;
++ esc_name = pool ? g_strdup(purple_escape_filename(pool->name)) : NULL;
++ esc_id = id ? g_strdup(purple_escape_filename(id)) : NULL;
++
++ path = g_build_filename(purple_user_dir(),
++ "certificates", /* TODO: constantize this? */
++ esc_scheme_name,
++ esc_name,
++ esc_id,
++ NULL);
++
++ g_free(esc_scheme_name);
++ g_free(esc_name);
++ g_free(esc_id);
++ return path;
++}
++
++gboolean
++purple_certificate_pool_usable(PurpleCertificatePool *pool)
++{
++ g_return_val_if_fail(pool, FALSE);
++ g_return_val_if_fail(pool->scheme_name, FALSE);
++
++ /* Check that the pool's scheme is loaded */
++ if (purple_certificate_find_scheme(pool->scheme_name) == NULL) {
++ return FALSE;
++ }
++
++ return TRUE;
++}
++
++PurpleCertificateScheme *
++purple_certificate_pool_get_scheme(PurpleCertificatePool *pool)
++{
++ g_return_val_if_fail(pool, NULL);
++ g_return_val_if_fail(pool->scheme_name, NULL);
++
++ return purple_certificate_find_scheme(pool->scheme_name);
++}
++
++gboolean
++purple_certificate_pool_contains(PurpleCertificatePool *pool, const gchar *id)
++{
++ g_return_val_if_fail(pool, FALSE);
++ g_return_val_if_fail(id, FALSE);
++ g_return_val_if_fail(pool->cert_in_pool, FALSE);
++
++ return (pool->cert_in_pool)(id);
++}
++
++PurpleCertificate *
++purple_certificate_pool_retrieve(PurpleCertificatePool *pool, const gchar *id)
++{
++ g_return_val_if_fail(pool, NULL);
++ g_return_val_if_fail(id, NULL);
++ g_return_val_if_fail(pool->get_cert, NULL);
++
++ return (pool->get_cert)(id);
++}
++
++gboolean
++purple_certificate_pool_store(PurpleCertificatePool *pool, const gchar *id, PurpleCertificate *crt)
++{
++ gboolean ret = FALSE;
++
++ g_return_val_if_fail(pool, FALSE);
++ g_return_val_if_fail(id, FALSE);
++ g_return_val_if_fail(pool->put_cert, FALSE);
++
++ /* Whether crt->scheme matches find_scheme(pool->scheme_name) is not
++ relevant... I think... */
++ g_return_val_if_fail(
++ g_ascii_strcasecmp(pool->scheme_name, crt->scheme->name) == 0,
++ FALSE);
++
++ ret = (pool->put_cert)(id, crt);
++
++ /* Signal that the certificate was stored if success*/
++ if (ret) {
++ purple_signal_emit(pool, "certificate-stored",
++ pool, id);
++ }
++
++ return ret;
++}
++
++gboolean
++purple_certificate_pool_delete(PurpleCertificatePool *pool, const gchar *id)
++{
++ gboolean ret = FALSE;
++
++ g_return_val_if_fail(pool, FALSE);
++ g_return_val_if_fail(id, FALSE);
++ g_return_val_if_fail(pool->delete_cert, FALSE);
++
++ ret = (pool->delete_cert)(id);
++
++ /* Signal that the certificate was deleted if success */
++ if (ret) {
++ purple_signal_emit(pool, "certificate-deleted",
++ pool, id);
++ }
++
++ return ret;
++}
++
++GList *
++purple_certificate_pool_get_idlist(PurpleCertificatePool *pool)
++{
++ g_return_val_if_fail(pool, NULL);
++ g_return_val_if_fail(pool->get_idlist, NULL);
++
++ return (pool->get_idlist)();
++}
++
++void
++purple_certificate_pool_destroy_idlist(GList *idlist)
++{
++ GList *l;
++
++ /* Iterate through and free them strings */
++ for ( l = idlist; l; l = l->next ) {
++ g_free(l->data);
++ }
++
++ g_list_free(idlist);
++}
++
++
++/****************************************************************************/
++/* Builtin Verifiers, Pools, etc. */
++/****************************************************************************/
++
++static void
++x509_singleuse_verify_cb (PurpleCertificateVerificationRequest *vrq, gint id)
++{
++ g_return_if_fail(vrq);
++
++ purple_debug_info("certificate/x509_singleuse",
++ "VRQ on cert from %s gave %d\n",
++ vrq->subject_name, id);
++
++ /* Signal what happened back to the caller */
++ if (1 == id) {
++ /* Accepted! */
++ purple_certificate_verify_complete(vrq,
++ PURPLE_CERTIFICATE_VALID);
++ } else {
++ /* Not accepted */
++ purple_certificate_verify_complete(vrq,
++ PURPLE_CERTIFICATE_INVALID);
++
++ }
++}
++
++static void
++x509_singleuse_start_verify (PurpleCertificateVerificationRequest *vrq)
++{
++ gchar *sha_asc;
++ GByteArray *sha_bin;
++ gchar *cn;
++ const gchar *cn_match;
++ gchar *primary, *secondary;
++ PurpleCertificate *crt = (PurpleCertificate *) vrq->cert_chain->data;
++
++ /* Pull out the SHA1 checksum */
++ sha_bin = purple_certificate_get_fingerprint_sha1(crt);
++ /* Now decode it for display */
++ sha_asc = purple_base16_encode_chunked(sha_bin->data,
++ sha_bin->len);
++
++ /* Get the cert Common Name */
++ cn = purple_certificate_get_subject_name(crt);
++
++ /* Determine whether the name matches */
++ if (purple_certificate_check_subject_name(crt, vrq->subject_name)) {
++ cn_match = "";
++ } else {
++ cn_match = _("(DOES NOT MATCH)");
++ }
++
++ /* Make messages */
++ primary = g_strdup_printf(_("%s has presented the following certificate for just-this-once use:"), vrq->subject_name);
++ secondary = g_strdup_printf(_("Common name: %s %s\nFingerprint (SHA1): %s"), cn, cn_match, sha_asc);
++
++ /* Make a semi-pretty display */
++ purple_request_accept_cancel(
++ vrq->cb_data, /* TODO: Find what the handle ought to be */
++ _("Single-use Certificate Verification"),
++ primary,
++ secondary,
++ 0, /* Accept by default */
++ NULL, /* No account */
++ NULL, /* No other user */
++ NULL, /* No associated conversation */
++ vrq,
++ x509_singleuse_verify_cb,
++ x509_singleuse_verify_cb );
++
++ /* Cleanup */
++ g_free(cn);
++ g_free(primary);
++ g_free(secondary);
++ g_free(sha_asc);
++ g_byte_array_free(sha_bin, TRUE);
++}
++
++static void
++x509_singleuse_destroy_request (PurpleCertificateVerificationRequest *vrq)
++{
++ /* I don't do anything! */
++}
++
++static PurpleCertificateVerifier x509_singleuse = {
++ "x509", /* Scheme name */
++ "singleuse", /* Verifier name */
++ x509_singleuse_start_verify, /* start_verification function */
++ x509_singleuse_destroy_request, /* Request cleanup operation */
++
++ NULL,
++ NULL,
++ NULL,
++ NULL
++};
++
++
++
++/***** X.509 Certificate Authority pool, keyed by Distinguished Name *****/
++/* This is implemented in what may be the most inefficient and bugprone way
++ possible; however, future optimizations should not be difficult. */
++
++static PurpleCertificatePool x509_ca;
++
++/** Holds a key-value pair for quickish certificate lookup */
++typedef struct {
++ gchar *dn;
++ PurpleCertificate *crt;
++} x509_ca_element;
++
++static void
++x509_ca_element_free(x509_ca_element *el)
++{
++ if (NULL == el) return;
++
++ g_free(el->dn);
++ purple_certificate_destroy(el->crt);
++ g_free(el);
++}
++
++/** System directory to probe for CA certificates */
++/* This is set in the lazy_init function */
++static GList *x509_ca_paths = NULL;
++
++/** A list of loaded CAs, populated from the above path whenever the lazy_init
++ happens. Contains pointers to x509_ca_elements */
++static GList *x509_ca_certs = NULL;
++
++/** Used for lazy initialization purposes. */
++static gboolean x509_ca_initialized = FALSE;
++
++/** Adds a certificate to the in-memory cache, doing nothing else */
++static gboolean
++x509_ca_quiet_put_cert(PurpleCertificate *crt)
++{
++ x509_ca_element *el;
++
++ /* lazy_init calls this function, so calling lazy_init here is a
++ Bad Thing */
++
++ g_return_val_if_fail(crt, FALSE);
++ g_return_val_if_fail(crt->scheme, FALSE);
++ /* Make sure that this is some kind of X.509 certificate */
++ /* TODO: Perhaps just check crt->scheme->name instead? */
++ g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_ca.scheme_name), FALSE);
++
++ el = g_new0(x509_ca_element, 1);
++ el->dn = purple_certificate_get_unique_id(crt);
++ el->crt = purple_certificate_copy(crt);
++ x509_ca_certs = g_list_prepend(x509_ca_certs, el);
++
++ return TRUE;
++}
++
++/* Since the libpurple CertificatePools get registered before plugins are
++ loaded, an X.509 Scheme is generally not available when x509_ca_init is
++ called, but x509_ca requires X.509 operations in order to properly load.
++
++ To solve this, I present the lazy_init function. It attempts to finish
++ initialization of the Pool, but it usually fails when it is called from
++ x509_ca_init. However, this is OK; initialization is then simply deferred
++ until someone tries to use functions from the pool. */
++static gboolean
++x509_ca_lazy_init(void)
++{
++ PurpleCertificateScheme *x509;
++ GDir *certdir;
++ const gchar *entry;
++ GPatternSpec *pempat, *crtpat;
++ GList *iter = NULL;
++ GSList *crts = NULL;
++
++ if (x509_ca_initialized) return TRUE;
++
++ /* Check that X.509 is registered */
++ x509 = purple_certificate_find_scheme(x509_ca.scheme_name);
++ if ( !x509 ) {
++ purple_debug_warning("certificate/x509/ca",
++ "Lazy init failed because an X.509 Scheme "
++ "is not yet registered. Maybe it will be "
++ "better later.\n");
++ return FALSE;
++ }
++
++ /* Use a glob to only read .pem files */
++ pempat = g_pattern_spec_new("*.pem");
++ crtpat = g_pattern_spec_new("*.crt");
++
++ /* Populate the certificates pool from the search path(s) */
++ for (iter = x509_ca_paths; iter; iter = iter->next) {
++ certdir = g_dir_open(iter->data, 0, NULL);
++ if (!certdir) {
++ purple_debug_error("certificate/x509/ca", "Couldn't open location '%s'\n", (const char *)iter->data);
++ continue;
++ }
++
++ while ( (entry = g_dir_read_name(certdir)) ) {
++ gchar *fullpath;
++ PurpleCertificate *crt;
++
++ if (!g_pattern_match_string(pempat, entry) && !g_pattern_match_string(crtpat, entry)) {
++ continue;
++ }
++
++ fullpath = g_build_filename(iter->data, entry, NULL);
++
++ /* TODO: Respond to a failure in the following? */
++ crts = purple_certificates_import(x509, fullpath);
++
++ while (crts && crts->data) {
++ crt = crts->data;
++ if (x509_ca_quiet_put_cert(crt)) {
++ gchar *name;
++ name = purple_certificate_get_subject_name(crt);
++ purple_debug_info("certificate/x509/ca",
++ "Loaded %s from %s\n",
++ name ? name : "(unknown)", fullpath);
++ g_free(name);
++ } else {
++ purple_debug_error("certificate/x509/ca",
++ "Failed to load certificate from %s\n",
++ fullpath);
++ }
++ purple_certificate_destroy(crt);
++ crts = g_slist_delete_link(crts, crts);
++ }
++
++ g_free(fullpath);
++ }
++ g_dir_close(certdir);
++ }
++
++ g_pattern_spec_free(pempat);
++ g_pattern_spec_free(crtpat);
++
++ purple_debug_info("certificate/x509/ca",
++ "Lazy init completed.\n");
++ x509_ca_initialized = TRUE;
++ return TRUE;
++}
++
++static gboolean
++x509_ca_init(void)
++{
++ /* Attempt to point at the appropriate system path */
++ if (NULL == x509_ca_paths) {
++#ifdef _WIN32
++ x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR,
++ "ca-certs", NULL));
++#else
++# ifdef SSL_CERTIFICATES_DIR
++ x509_ca_paths = g_list_append(NULL, g_strdup(SSL_CERTIFICATES_DIR));
++# endif
++ x509_ca_paths = g_list_append(x509_ca_paths,
++ g_build_filename("/media/internal", "Messaging_Certificates", NULL));
++#endif
++ }
++
++ /* Attempt to initialize now, but if it doesn't work, that's OK;
++ it will get done later */
++ if ( ! x509_ca_lazy_init()) {
++ purple_debug_info("certificate/x509/ca",
++ "Init failed, probably because a "
++ "dependency is not yet registered. "
++ "It has been deferred to later.\n");
++ }
++
++ return TRUE;
++}
++
++static void
++x509_ca_uninit(void)
++{
++ GList *l;
++
++ for (l = x509_ca_certs; l; l = l->next) {
++ x509_ca_element *el = l->data;
++ x509_ca_element_free(el);
++ }
++ g_list_free(x509_ca_certs);
++ x509_ca_certs = NULL;
++ x509_ca_initialized = FALSE;
++ g_list_foreach(x509_ca_paths, (GFunc)g_free, NULL);
++ g_list_free(x509_ca_paths);
++ x509_ca_paths = NULL;
++}
++
++/** Look up a ca_element by dn */
++static x509_ca_element *
++x509_ca_locate_cert(GList *lst, const gchar *dn)
++{
++ GList *cur;
++
++ for (cur = lst; cur; cur = cur->next) {
++ x509_ca_element *el = cur->data;
++ if (purple_strequal(dn, el->dn)) {
++ return el;
++ }
++ }
++ return NULL;
++}
++
++static GSList *
++x509_ca_locate_certs(GList *lst, const gchar *dn)
++{
++ GList *cur;
++ GSList *crts = NULL;
++
++ for (cur = lst; cur; cur = cur->next) {
++ x509_ca_element *el = cur->data;
++ if (purple_strequal(dn, el->dn)) {
++ crts = g_slist_prepend(crts, el);
++ }
++ }
++ return crts;
++}
++
++
++static gboolean
++x509_ca_cert_in_pool(const gchar *id)
++{
++ g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
++ g_return_val_if_fail(id, FALSE);
++
++ if (x509_ca_locate_cert(x509_ca_certs, id) != NULL) {
++ return TRUE;
++ } else {
++ return FALSE;
++ }
++
++ return FALSE;
++}
++
++static PurpleCertificate *
++x509_ca_get_cert(const gchar *id)
++{
++ PurpleCertificate *crt = NULL;
++ x509_ca_element *el;
++
++ g_return_val_if_fail(x509_ca_lazy_init(), NULL);
++ g_return_val_if_fail(id, NULL);
++
++ /* Search the memory-cached pool */
++ el = x509_ca_locate_cert(x509_ca_certs, id);
++
++ if (el != NULL) {
++ /* Make a copy of the memcached one for the function caller
++ to play with */
++ crt = purple_certificate_copy(el->crt);
++ } else {
++ crt = NULL;
++ }
++
++ return crt;
++}
++
++static GSList *
++x509_ca_get_certs(const gchar *id)
++{
++ GSList *crts = NULL, *els = NULL;
++
++ g_return_val_if_fail(x509_ca_lazy_init(), NULL);
++ g_return_val_if_fail(id, NULL);
++
++ /* Search the memory-cached pool */
++ els = x509_ca_locate_certs(x509_ca_certs, id);
++
++ if (els != NULL) {
++ GSList *cur;
++ /* Make a copy of the memcached ones for the function caller
++ to play with */
++ for (cur = els; cur; cur = cur->next) {
++ x509_ca_element *el = cur->data;
++ crts = g_slist_prepend(crts, purple_certificate_copy(el->crt));
++ }
++ g_slist_free(els);
++ }
++
++ return crts;
++}
++
++static gboolean
++x509_ca_put_cert(const gchar *id, PurpleCertificate *crt)
++{
++ gboolean ret = FALSE;
++
++ g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
++
++ /* TODO: This is a quick way of doing this. At some point the change
++ ought to be flushed to disk somehow. */
++ ret = x509_ca_quiet_put_cert(crt);
++
++ return ret;
++}
++
++static gboolean
++x509_ca_delete_cert(const gchar *id)
++{
++ x509_ca_element *el;
++
++ g_return_val_if_fail(x509_ca_lazy_init(), FALSE);
++ g_return_val_if_fail(id, FALSE);
++
++ /* Is the id even in the pool? */
++ el = x509_ca_locate_cert(x509_ca_certs, id);
++ if ( el == NULL ) {
++ purple_debug_warning("certificate/x509/ca",
++ "Id %s wasn't in the pool\n",
++ id);
++ return FALSE;
++ }
++
++ /* Unlink it from the memory cache and destroy it */
++ x509_ca_certs = g_list_remove(x509_ca_certs, el);
++ x509_ca_element_free(el);
++
++ return TRUE;
++}
++
++static GList *
++x509_ca_get_idlist(void)
++{
++ GList *l, *idlist;
++
++ g_return_val_if_fail(x509_ca_lazy_init(), NULL);
++
++ idlist = NULL;
++ for (l = x509_ca_certs; l; l = l->next) {
++ x509_ca_element *el = l->data;
++ idlist = g_list_prepend(idlist, g_strdup(el->dn));
++ }
++
++ return idlist;
++}
++
++
++static PurpleCertificatePool x509_ca = {
++ "x509", /* Scheme name */
++ "ca", /* Pool name */
++ N_("Certificate Authorities"),/* User-friendly name */
++ NULL, /* Internal data */
++ x509_ca_init, /* init */
++ x509_ca_uninit, /* uninit */
++ x509_ca_cert_in_pool, /* Certificate exists? */
++ x509_ca_get_cert, /* Cert retriever */
++ x509_ca_put_cert, /* Cert writer */
++ x509_ca_delete_cert, /* Cert remover */
++ x509_ca_get_idlist, /* idlist retriever */
++
++ NULL,
++ NULL,
++ NULL,
++ NULL
++
++};
++
++
++
++/***** Cache of certificates given by TLS/SSL peers *****/
++static PurpleCertificatePool x509_tls_peers;
++
++static gboolean
++x509_tls_peers_init(void)
++{
++ gchar *poolpath;
++ int ret;
++
++ /* Set up key cache here if it isn't already done */
++ poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
++ ret = purple_build_dir(poolpath, 0700); /* Make it this user only */
++
++ if (ret != 0)
++ purple_debug_info("certificate/tls_peers",
++ "Could not create %s. Certificates will not be cached.\n",
++ poolpath);
++
++ g_free(poolpath);
++
++ return TRUE;
++}
++
++static gboolean
++x509_tls_peers_cert_in_pool(const gchar *id)
++{
++ gchar *keypath;
++ gboolean ret = FALSE;
++
++ g_return_val_if_fail(id, FALSE);
++
++ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
++
++ ret = g_file_test(keypath, G_FILE_TEST_IS_REGULAR);
++
++ g_free(keypath);
++ return ret;
++}
++
++static PurpleCertificate *
++x509_tls_peers_get_cert(const gchar *id)
++{
++ PurpleCertificateScheme *x509;
++ PurpleCertificate *crt;
++ gchar *keypath;
++
++ g_return_val_if_fail(id, NULL);
++
++ /* Is it in the pool? */
++ if ( !x509_tls_peers_cert_in_pool(id) ) {
++ return NULL;
++ }
++
++ /* Look up the X.509 scheme */
++ x509 = purple_certificate_find_scheme("x509");
++ g_return_val_if_fail(x509, NULL);
++
++ /* Okay, now find and load that key */
++ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
++ crt = purple_certificate_import(x509, keypath);
++
++ g_free(keypath);
++
++ return crt;
++}
++
++static gboolean
++x509_tls_peers_put_cert(const gchar *id, PurpleCertificate *crt)
++{
++ gboolean ret = FALSE;
++ gchar *keypath;
++
++ g_return_val_if_fail(crt, FALSE);
++ g_return_val_if_fail(crt->scheme, FALSE);
++ /* Make sure that this is some kind of X.509 certificate */
++ /* TODO: Perhaps just check crt->scheme->name instead? */
++ g_return_val_if_fail(crt->scheme == purple_certificate_find_scheme(x509_tls_peers.scheme_name), FALSE);
++
++ /* Work out the filename and export */
++ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
++ ret = purple_certificate_export(keypath, crt);
++
++ g_free(keypath);
++ return ret;
++}
++
++static gboolean
++x509_tls_peers_delete_cert(const gchar *id)
++{
++ gboolean ret = FALSE;
++ gchar *keypath;
++
++ g_return_val_if_fail(id, FALSE);
++
++ /* Is the id even in the pool? */
++ if (!x509_tls_peers_cert_in_pool(id)) {
++ purple_debug_warning("certificate/tls_peers",
++ "Id %s wasn't in the pool\n",
++ id);
++ return FALSE;
++ }
++
++ /* OK, so work out the keypath and delete the thing */
++ keypath = purple_certificate_pool_mkpath(&x509_tls_peers, id);
++ if ( unlink(keypath) != 0 ) {
++ purple_debug_error("certificate/tls_peers",
++ "Unlink of %s failed!\n",
++ keypath);
++ ret = FALSE;
++ } else {
++ ret = TRUE;
++ }
++
++ g_free(keypath);
++ return ret;
++}
++
++static GList *
++x509_tls_peers_get_idlist(void)
++{
++ GList *idlist = NULL;
++ GDir *dir;
++ const gchar *entry;
++ gchar *poolpath;
++
++ /* Get a handle on the pool directory */
++ poolpath = purple_certificate_pool_mkpath(&x509_tls_peers, NULL);
++ dir = g_dir_open(poolpath,
++ 0, /* No flags */
++ NULL); /* Not interested in what the error is */
++ g_free(poolpath);
++
++ g_return_val_if_fail(dir, NULL);
++
++ /* Traverse the directory listing and create an idlist */
++ while ( (entry = g_dir_read_name(dir)) != NULL ) {
++ /* Unescape the filename */
++ const char *unescaped = purple_unescape_filename(entry);
++
++ /* Copy the entry name into our list (GLib owns the original
++ string) */
++ idlist = g_list_prepend(idlist, g_strdup(unescaped));
++ }
++
++ /* Release the directory */
++ g_dir_close(dir);
++
++ return idlist;
++}
++
++static PurpleCertificatePool x509_tls_peers = {
++ "x509", /* Scheme name */
++ "tls_peers", /* Pool name */
++ N_("SSL Peers Cache"), /* User-friendly name */
++ NULL, /* Internal data */
++ x509_tls_peers_init, /* init */
++ NULL, /* uninit not required */
++ x509_tls_peers_cert_in_pool, /* Certificate exists? */
++ x509_tls_peers_get_cert, /* Cert retriever */
++ x509_tls_peers_put_cert, /* Cert writer */
++ x509_tls_peers_delete_cert, /* Cert remover */
++ x509_tls_peers_get_idlist, /* idlist retriever */
++
++ NULL,
++ NULL,
++ NULL,
++ NULL
++};
++
++
++/***** A Verifier that uses the tls_peers cache and the CA pool to validate certificates *****/
++static PurpleCertificateVerifier x509_tls_cached;
++
++
++/* The following is several hacks piled together and needs to be fixed.
++ * It exists because show_cert (see its comments) needs the original reason
++ * given to user_auth in order to rebuild the dialog.
++ */
++/* TODO: This will cause a ua_ctx to become memleaked if the request(s) get
++ closed by handle or otherwise abnormally. */
++typedef struct {
++ PurpleCertificateVerificationRequest *vrq;
++ gchar *reason;
++} x509_tls_cached_ua_ctx;
++
++static x509_tls_cached_ua_ctx *
++x509_tls_cached_ua_ctx_new(PurpleCertificateVerificationRequest *vrq,
++ const gchar *reason)
++{
++ x509_tls_cached_ua_ctx *c;
++
++ c = g_new0(x509_tls_cached_ua_ctx, 1);
++ c->vrq = vrq;
++ c->reason = g_strdup(reason);
++
++ return c;
++}
++
++
++static void
++x509_tls_cached_ua_ctx_free(x509_tls_cached_ua_ctx *c)
++{
++ g_return_if_fail(c);
++ g_free(c->reason);
++ g_free(c);
++}
++
++static void
++x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
++ const gchar *reason);
++
++static void
++x509_tls_cached_show_cert(x509_tls_cached_ua_ctx *c, gint id)
++{
++ PurpleCertificate *disp_crt = c->vrq->cert_chain->data;
++
++ /* Since clicking a button closes the request, show it again */
++ x509_tls_cached_user_auth(c->vrq, c->reason);
++
++ /* Show the certificate AFTER re-opening the dialog so that this
++ appears above the other */
++ purple_certificate_display_x509(disp_crt);
++
++ x509_tls_cached_ua_ctx_free(c);
++}
++
++static void
++x509_tls_cached_user_auth_cb (x509_tls_cached_ua_ctx *c, gint id)
++{
++ PurpleCertificateVerificationRequest *vrq;
++ PurpleCertificatePool *tls_peers;
++
++ g_return_if_fail(c);
++ g_return_if_fail(c->vrq);
++
++ vrq = c->vrq;
++
++ x509_tls_cached_ua_ctx_free(c);
++
++ tls_peers = purple_certificate_find_pool("x509","tls_peers");
++
++ if (2 == id) {
++ gchar *cache_id = vrq->subject_name;
++ purple_debug_info("certificate/x509/tls_cached",
++ "User ACCEPTED cert\nCaching first in chain for future use as %s...\n",
++ cache_id);
++
++ purple_certificate_pool_store(tls_peers, cache_id,
++ vrq->cert_chain->data);
++
++ purple_certificate_verify_complete(vrq,
++ PURPLE_CERTIFICATE_VALID);
++ } else {
++ purple_debug_warning("certificate/x509/tls_cached",
++ "User REJECTED cert\n");
++ purple_certificate_verify_complete(vrq,
++ PURPLE_CERTIFICATE_INVALID);
++ }
++}
++
++static void
++x509_tls_cached_user_auth_accept_cb(x509_tls_cached_ua_ctx *c, gint ignore)
++{
++ x509_tls_cached_user_auth_cb(c, 2);
++}
++
++static void
++x509_tls_cached_user_auth_reject_cb(x509_tls_cached_ua_ctx *c, gint ignore)
++{
++ x509_tls_cached_user_auth_cb(c, 1);
++}
++
++/** Validates a certificate by asking the user
++ * @param reason String to explain why the user needs to accept/refuse the
++ * certificate.
++ * @todo Needs a handle argument
++ */
++static void
++x509_tls_cached_user_auth(PurpleCertificateVerificationRequest *vrq,
++ const gchar *reason)
++{
++ const char *acceptbadcert;
++ acceptbadcert = purple_prefs_get_string("/purple/acceptbadcert");
++
++ if (acceptbadcert != NULL)
++ {
++ purple_debug_warning("certificate/x509/tls_cached", "Certificate Error. Accepting as requested!\n");
++ purple_prefs_remove("/purple/acceptbadcert");
++
++ //Dodgy Cert Acceptance
++ x509_tls_cached_user_auth_accept_cb(x509_tls_cached_ua_ctx_new(vrq, reason),2);
++ }
++ else
++ {
++ purple_debug_warning("certificate/x509/tls_cached", "Certificate Error. Rejecting as requested!\n");
++
++ //Reject Bad Certificate
++ x509_tls_cached_user_auth_reject_cb(x509_tls_cached_ua_ctx_new(vrq, reason),1);
++ }
++}
++
++static void
++x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
++ PurpleCertificateInvalidityFlags flags);
++
++static void
++x509_tls_cached_complete(PurpleCertificateVerificationRequest *vrq,
++ PurpleCertificateInvalidityFlags flags)
++{
++ PurpleCertificatePool *tls_peers;
++ PurpleCertificate *peer_crt = vrq->cert_chain->data;
++
++ if (flags & PURPLE_CERTIFICATE_FATALS_MASK) {
++ /* TODO: Also print any other warnings? */
++ const gchar *error;
++ gchar *tmp, *secondary;
++
++ if (flags & PURPLE_CERTIFICATE_INVALID_CHAIN)
++ error = invalidity_reason_to_string(PURPLE_CERTIFICATE_INVALID_CHAIN);
++ else if (flags & PURPLE_CERTIFICATE_REVOKED)
++ error = invalidity_reason_to_string(PURPLE_CERTIFICATE_REVOKED);
++ else
++ error = invalidity_reason_to_string(PURPLE_CERTIFICATE_UNKNOWN_ERROR);
++
++ tmp = g_strdup_printf(_("The certificate for %s could not be validated."),
++ vrq->subject_name);
++ secondary = g_strconcat(tmp, " ", error, NULL);
++ g_free(tmp);
++
++ purple_notify_error(NULL, /* TODO: Probably wrong. */
++ _("SSL Certificate Error"),
++ _("Unable to validate certificate"),
++ secondary);
++ g_free(secondary);
++
++ purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_INVALID);
++ return;
++ } else if (flags & PURPLE_CERTIFICATE_NON_FATALS_MASK) {
++ /* Non-fatal error. Prompt the user. */
++ gchar *tmp;
++ GString *errors;
++ guint32 i = 1;
++
++ tmp = g_strdup_printf(_("The certificate for %s could not be validated."),
++ vrq->subject_name);
++ errors = g_string_new(tmp);
++ g_free(tmp);
++
++ errors = g_string_append_c(errors, '\n');
++
++ /* Special case a name mismatch because we want to display the two names... */
++ if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) {
++ gchar *sn = purple_certificate_get_subject_name(peer_crt);
++
++ if (sn) {
++ g_string_append_printf(errors, _("The certificate claims to be "
++ "from \"%s\" instead. This could mean that you are "
++ "not connecting to the service you believe you are."),
++ sn);
++ g_free(sn);
++
++ flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
++ }
++ }
++
++ while (i != PURPLE_CERTIFICATE_LAST) {
++ if (flags & i) {
++ errors = g_string_append_c(errors, '\n');
++ g_string_append(errors, invalidity_reason_to_string(i));
++ }
++
++ i <<= 1;
++ }
++
++ x509_tls_cached_user_auth(vrq, errors->str);
++ g_string_free(errors, TRUE);
++ return;
++ }
++
++ /* If we reach this point, the certificate is good. */
++
++ /* Look up the local cache and store it there for future use */
++ tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,
++ "tls_peers");
++ if (tls_peers) {
++ if (!purple_certificate_pool_store(tls_peers,vrq->subject_name,
++ peer_crt)) {
++ purple_debug_error("certificate/x509/tls_cached",
++ "FAILED to cache peer certificate\n");
++ }
++ } else {
++ purple_debug_error("certificate/x509/tls_cached",
++ "Unable to locate tls_peers certificate cache.\n");
++ }
++
++ purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID);
++}
++
++static void
++x509_tls_cached_cert_in_cache(PurpleCertificateVerificationRequest *vrq,
++ PurpleCertificateInvalidityFlags flags)
++{
++ /* TODO: Looking this up by name over and over is expensive.
++ Fix, please! */
++ PurpleCertificatePool *tls_peers =
++ purple_certificate_find_pool(x509_tls_cached.scheme_name,
++ "tls_peers");
++
++ /* The peer's certificate should be the first in the list */
++ PurpleCertificate *peer_crt =
++ (PurpleCertificate *) vrq->cert_chain->data;
++
++ PurpleCertificate *cached_crt;
++ GByteArray *peer_fpr, *cached_fpr;
++
++ /* Load up the cached certificate */
++ cached_crt = purple_certificate_pool_retrieve(
++ tls_peers, vrq->subject_name);
++ if ( !cached_crt ) {
++ purple_debug_warning("certificate/x509/tls_cached",
++ "Lookup failed on cached certificate!\n"
++ "Falling back to full verification.\n");
++ /* vrq now becomes the problem of unknown_peer */
++ x509_tls_cached_unknown_peer(vrq, flags);
++ return;
++ }
++
++ /* Now get SHA1 sums for both and compare them */
++ /* TODO: This is not an elegant way to compare certs */
++ peer_fpr = purple_certificate_get_fingerprint_sha1(peer_crt);
++ cached_fpr = purple_certificate_get_fingerprint_sha1(cached_crt);
++ if (!memcmp(peer_fpr->data, cached_fpr->data, peer_fpr->len)) {
++ purple_debug_info("certificate/x509/tls_cached",
++ "Peer cert matched cached\n");
++ x509_tls_cached_complete(vrq, flags);
++ } else {
++ purple_debug_error("certificate/x509/tls_cached",
++ "Peer cert did NOT match cached\n");
++ /* vrq now becomes the problem of the user */
++ x509_tls_cached_unknown_peer(vrq, flags);
++ }
++
++ purple_certificate_destroy(cached_crt);
++ g_byte_array_free(peer_fpr, TRUE);
++ g_byte_array_free(cached_fpr, TRUE);
++}
++
++/*
++ * This is called from two points in x509_tls_cached_unknown_peer below
++ * once we've verified the signature chain is valid. Now we need to verify
++ * the subject name of the certificate.
++ */
++static void
++x509_tls_cached_check_subject_name(PurpleCertificateVerificationRequest *vrq,
++ PurpleCertificateInvalidityFlags flags)
++{
++ PurpleCertificate *peer_crt;
++ GList *chain = vrq->cert_chain;
++
++ peer_crt = (PurpleCertificate *) chain->data;
++
++ /* Last, check that the hostname matches */
++ if ( ! purple_certificate_check_subject_name(peer_crt,
++ vrq->subject_name) ) {
++ gchar *sn = purple_certificate_get_subject_name(peer_crt);
++
++ flags |= PURPLE_CERTIFICATE_NAME_MISMATCH;
++ purple_debug_error("certificate/x509/tls_cached",
++ "Name mismatch: Certificate given for %s "
++ "has a name of %s\n",
++ vrq->subject_name, sn);
++ g_free(sn);
++ }
++
++ x509_tls_cached_complete(vrq, flags);
++}
++
++/* For when we've never communicated with this party before */
++/* TODO: Need ways to specify possibly multiple problems with a cert, or at
++ least reprioritize them.
++ */
++static void
++x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq,
++ PurpleCertificateInvalidityFlags flags)
++{
++ PurpleCertificatePool *ca;
++ PurpleCertificate *peer_crt;
++ PurpleCertificate *ca_crt, *end_crt;
++ PurpleCertificate *failing_crt;
++ GList *chain = vrq->cert_chain;
++ GSList *ca_crts, *cur;
++ GByteArray *last_fpr, *ca_fpr;
++ gboolean valid = FALSE;
++ gchar *ca_id;
++
++ peer_crt = (PurpleCertificate *) chain->data;
++
++ /* TODO: Figure out a way to check for a bad signature, as opposed to
++ "not self-signed" */
++ if ( purple_certificate_signed_by(peer_crt, peer_crt) ) {
++ flags |= PURPLE_CERTIFICATE_SELF_SIGNED;
++
++ purple_debug_info("certificate/x509/tls_cached",
++ "Certificate for %s is self-signed.\n",
++ vrq->subject_name);
++
++ x509_tls_cached_check_subject_name(vrq, flags);
++ return;
++ } /* if (self signed) */
++
++ /* Next, attempt to verify the last certificate against a CA */
++ ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca");
++
++ /* Next, check that the certificate chain is valid */
++ if (!purple_certificate_check_signature_chain_with_failing(chain,
++ &failing_crt))
++ {
++ gboolean chain_validated = FALSE;
++ /*
++ * Check if the failing certificate is in the CA store. If it is, then
++ * consider this fully validated. This works around issues with some
++ * prominent intermediate CAs whose signature is md5WithRSAEncryption.
++ * I'm looking at CACert Class 3 here. See #4458 for details.
++ */
++ if (ca) {
++ gchar *uid = purple_certificate_get_unique_id(failing_crt);
++ PurpleCertificate *ca_crt = purple_certificate_pool_retrieve(ca, uid);
++ if (ca_crt != NULL) {
++ GByteArray *failing_fpr;
++ GByteArray *ca_fpr;
++ failing_fpr = purple_certificate_get_fingerprint_sha1(failing_crt);
++ ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt);
++ if (byte_arrays_equal(failing_fpr, ca_fpr)) {
++ purple_debug_info("certificate/x509/tls_cached",
++ "Full chain verification failed (probably a bad "
++ "signature algorithm), but found the last "
++ "certificate %s in the CA pool.\n", uid);
++ chain_validated = TRUE;
++ }
++
++ g_byte_array_free(failing_fpr, TRUE);
++ g_byte_array_free(ca_fpr, TRUE);
++ }
++
++ purple_certificate_destroy(ca_crt);
++ g_free(uid);
++ }
++
++ /*
++ * If we get here, either the cert matched the stuff right above
++ * or it didn't, in which case we give up and complain to the user.
++ */
++ if (!chain_validated)
++ /* TODO: Tell the user where the chain broke? */
++ flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
++
++ x509_tls_cached_check_subject_name(vrq, flags);
++ return;
++ } /* if (signature chain not good) */
++
++ /* If, for whatever reason, there is no Certificate Authority pool
++ loaded, we'll verify the subject name and then warn about thsi. */
++ if ( !ca ) {
++ purple_debug_error("certificate/x509/tls_cached",
++ "No X.509 Certificate Authority pool "
++ "could be found!\n");
++
++ flags |= PURPLE_CERTIFICATE_NO_CA_POOL;
++
++ x509_tls_cached_check_subject_name(vrq, flags);
++ return;
++ }
++
++ end_crt = g_list_last(chain)->data;
++
++ /* Attempt to look up the last certificate's issuer */
++ ca_id = purple_certificate_get_issuer_unique_id(end_crt);
++ purple_debug_info("certificate/x509/tls_cached",
++ "Checking for a CA with DN=%s\n",
++ ca_id);
++ ca_crts = x509_ca_get_certs(ca_id);
++ if ( NULL == ca_crts ) {
++ flags |= PURPLE_CERTIFICATE_CA_UNKNOWN;
++
++ purple_debug_warning("certificate/x509/tls_cached",
++ "Certificate Authority with DN='%s' not "
++ "found. I'll prompt the user, I guess.\n",
++ ca_id);
++ g_free(ca_id);
++
++ x509_tls_cached_check_subject_name(vrq, flags);
++ return;
++ }
++
++ g_free(ca_id);
++
++ /*
++ * Check the fingerprints; if they match, then this certificate *is* one
++ * of the designated "trusted roots", and we don't need to verify the
++ * signature. This is good because some of the older roots are self-signed
++ * with bad hash algorithms that we don't want to allow in any other
++ * circumstances (one of Verisign's root CAs is self-signed with MD2).
++ *
++ * If the fingerprints don't match, we'll fall back to checking the
++ * signature.
++ *
++ * GnuTLS doesn't seem to include the final root in the verification
++ * list, so this check will never succeed. NSS *does* include it in
++ * the list, so here we are.
++ */
++ last_fpr = purple_certificate_get_fingerprint_sha1(end_crt);
++ for (cur = ca_crts; cur; cur = cur->next) {
++ ca_crt = cur->data;
++ ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt);
++
++ if ( byte_arrays_equal(last_fpr, ca_fpr) ||
++ purple_certificate_signed_by(end_crt, ca_crt) )
++ {
++ /* TODO: If signed_by ever returns a reason, maybe mention
++ that, too. */
++ /* TODO: Also mention the CA involved. While I could do this
++ now, a full DN is a little much with which to assault the
++ user's poor, leaky eyes. */
++ valid = TRUE;
++ g_byte_array_free(ca_fpr, TRUE);
++ break;
++ }
++
++ g_byte_array_free(ca_fpr, TRUE);
++ }
++
++ if (valid == FALSE)
++ flags |= PURPLE_CERTIFICATE_INVALID_CHAIN;
++
++ g_slist_foreach(ca_crts, (GFunc)purple_certificate_destroy, NULL);
++ g_slist_free(ca_crts);
++ g_byte_array_free(last_fpr, TRUE);
++
++ x509_tls_cached_check_subject_name(vrq, flags);
++}
++
++static void
++x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq)
++{
++ const gchar *tls_peers_name = "tls_peers"; /* Name of local cache */
++ PurpleCertificatePool *tls_peers;
++ time_t now, activation, expiration;
++ PurpleCertificateInvalidityFlags flags = PURPLE_CERTIFICATE_NO_PROBLEMS;
++ gboolean ret;
++
++ g_return_if_fail(vrq);
++
++ purple_debug_info("certificate/x509/tls_cached",
++ "Starting verify for %s\n",
++ vrq->subject_name);
++
++ /*
++ * Verify the first certificate (the main one) has been activated and
++ * isn't expired, i.e. activation < now < expiration.
++ */
++ now = time(NULL);
++ ret = purple_certificate_get_times(vrq->cert_chain->data, &activation,
++ &expiration);
++ if (!ret) {
++ flags |= PURPLE_CERTIFICATE_EXPIRED | PURPLE_CERTIFICATE_NOT_ACTIVATED;
++ purple_debug_error("certificate/x509/tls_cached",
++ "Failed to get validity times for certificate %s\n",
++ vrq->subject_name);
++ } else if (now > expiration) {
++ flags |= PURPLE_CERTIFICATE_EXPIRED;
++ purple_debug_error("certificate/x509/tls_cached",
++ "Certificate %s expired at %s\n",
++ vrq->subject_name, ctime(&expiration));
++ } else if (now < activation) {
++ flags |= PURPLE_CERTIFICATE_NOT_ACTIVATED;
++ purple_debug_error("certificate/x509/tls_cached",
++ "Certificate %s is not yet valid, will be at %s\n",
++ vrq->subject_name, ctime(&activation));
++ }
++
++ tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name,tls_peers_name);
++
++ if (!tls_peers) {
++ purple_debug_error("certificate/x509/tls_cached",
++ "Couldn't find local peers cache %s\n",
++ tls_peers_name);
++
++ /* vrq now becomes the problem of unknown_peer */
++ x509_tls_cached_unknown_peer(vrq, flags);
++ return;
++ }
++
++ /* Check if the peer has a certificate cached already */
++ purple_debug_info("certificate/x509/tls_cached",
++ "Checking for cached cert...\n");
++ if (purple_certificate_pool_contains(tls_peers, vrq->subject_name)) {
++ purple_debug_info("certificate/x509/tls_cached",
++ "...Found cached cert\n");
++ /* vrq is now the responsibility of cert_in_cache */
++ x509_tls_cached_cert_in_cache(vrq, flags);
++ } else {
++ purple_debug_warning("certificate/x509/tls_cached",
++ "...Not in cache\n");
++ /* vrq now becomes the problem of unknown_peer */
++ x509_tls_cached_unknown_peer(vrq, flags);
++ }
++}
++
++static void
++x509_tls_cached_destroy_request(PurpleCertificateVerificationRequest *vrq)
++{
++ g_return_if_fail(vrq);
++}
++
++static PurpleCertificateVerifier x509_tls_cached = {
++ "x509", /* Scheme name */
++ "tls_cached", /* Verifier name */
++ x509_tls_cached_start_verify, /* Verification begin */
++ x509_tls_cached_destroy_request,/* Request cleanup */
++
++ NULL,
++ NULL,
++ NULL,
++ NULL
++
++};
++
++/****************************************************************************/
++/* Subsystem */
++/****************************************************************************/
++void
++purple_certificate_init(void)
++{
++ /* Register builtins */
++ purple_certificate_register_verifier(&x509_singleuse);
++ purple_certificate_register_pool(&x509_ca);
++ purple_certificate_register_pool(&x509_tls_peers);
++ purple_certificate_register_verifier(&x509_tls_cached);
++}
++
++void
++purple_certificate_uninit(void)
++{
++ /* Unregister all Verifiers */
++ g_list_foreach(cert_verifiers, (GFunc)purple_certificate_unregister_verifier, NULL);
++
++ /* Unregister all Pools */
++ g_list_foreach(cert_pools, (GFunc)purple_certificate_unregister_pool, NULL);
++}
++
++gpointer
++purple_certificate_get_handle(void)
++{
++ static gint handle;
++ return &handle;
++}
++
++PurpleCertificateScheme *
++purple_certificate_find_scheme(const gchar *name)
++{
++ PurpleCertificateScheme *scheme = NULL;
++ GList *l;
++
++ g_return_val_if_fail(name, NULL);
++
++ /* Traverse the list of registered schemes and locate the
++ one whose name matches */
++ for(l = cert_schemes; l; l = l->next) {
++ scheme = (PurpleCertificateScheme *)(l->data);
++
++ /* Name matches? that's our man */
++ if(!g_ascii_strcasecmp(scheme->name, name))
++ return scheme;
++ }
++
++ purple_debug_warning("certificate",
++ "CertificateScheme %s requested but not found.\n",
++ name);
++
++ /* TODO: Signalling and such? */
++
++ return NULL;
++}
++
++GList *
++purple_certificate_get_schemes(void)
++{
++ return cert_schemes;
++}
++
++gboolean
++purple_certificate_register_scheme(PurpleCertificateScheme *scheme)
++{
++ g_return_val_if_fail(scheme != NULL, FALSE);
++
++ /* Make sure no scheme is registered with the same name */
++ if (purple_certificate_find_scheme(scheme->name) != NULL) {
++ return FALSE;
++ }
++
++ /* Okay, we're golden. Register it. */
++ cert_schemes = g_list_prepend(cert_schemes, scheme);
++
++ /* TODO: Signalling and such? */
++
++ purple_debug_info("certificate",
++ "CertificateScheme %s registered\n",
++ scheme->name);
++
++ return TRUE;
++}
++
++gboolean
++purple_certificate_unregister_scheme(PurpleCertificateScheme *scheme)
++{
++ if (NULL == scheme) {
++ purple_debug_warning("certificate",
++ "Attempting to unregister NULL scheme\n");
++ return FALSE;
++ }
++
++ /* TODO: signalling? */
++
++ /* TODO: unregister all CertificateVerifiers for this scheme?*/
++ /* TODO: unregister all CertificatePools for this scheme? */
++ /* Neither of the above should be necessary, though */
++ cert_schemes = g_list_remove(cert_schemes, scheme);
++
++ purple_debug_info("certificate",
++ "CertificateScheme %s unregistered\n",
++ scheme->name);
++
++
++ return TRUE;
++}
++
++PurpleCertificateVerifier *
++purple_certificate_find_verifier(const gchar *scheme_name, const gchar *ver_name)
++{
++ PurpleCertificateVerifier *vr = NULL;
++ GList *l;
++
++ g_return_val_if_fail(scheme_name, NULL);
++ g_return_val_if_fail(ver_name, NULL);
++
++ /* Traverse the list of registered verifiers and locate the
++ one whose name matches */
++ for(l = cert_verifiers; l; l = l->next) {
++ vr = (PurpleCertificateVerifier *)(l->data);
++
++ /* Scheme and name match? */
++ if(!g_ascii_strcasecmp(vr->scheme_name, scheme_name) &&
++ !g_ascii_strcasecmp(vr->name, ver_name))
++ return vr;
++ }
++
++ purple_debug_warning("certificate",
++ "CertificateVerifier %s, %s requested but not found.\n",
++ scheme_name, ver_name);
++
++ /* TODO: Signalling and such? */
++
++ return NULL;
++}
++
++
++GList *
++purple_certificate_get_verifiers(void)
++{
++ return cert_verifiers;
++}
++
++gboolean
++purple_certificate_register_verifier(PurpleCertificateVerifier *vr)
++{
++ g_return_val_if_fail(vr != NULL, FALSE);
++
++ /* Make sure no verifier is registered with the same scheme/name */
++ if (purple_certificate_find_verifier(vr->scheme_name, vr->name) != NULL) {
++ return FALSE;
++ }
++
++ /* Okay, we're golden. Register it. */
++ cert_verifiers = g_list_prepend(cert_verifiers, vr);
++
++ /* TODO: Signalling and such? */
++
++ purple_debug_info("certificate",
++ "CertificateVerifier %s registered\n",
++ vr->name);
++ return TRUE;
++}
++
++gboolean
++purple_certificate_unregister_verifier(PurpleCertificateVerifier *vr)
++{
++ if (NULL == vr) {
++ purple_debug_warning("certificate",
++ "Attempting to unregister NULL verifier\n");
++ return FALSE;
++ }
++
++ /* TODO: signalling? */
++
++ cert_verifiers = g_list_remove(cert_verifiers, vr);
++
++
++ purple_debug_info("certificate",
++ "CertificateVerifier %s unregistered\n",
++ vr->name);
++
++ return TRUE;
++}
++
++PurpleCertificatePool *
++purple_certificate_find_pool(const gchar *scheme_name, const gchar *pool_name)
++{
++ PurpleCertificatePool *pool = NULL;
++ GList *l;
++
++ g_return_val_if_fail(scheme_name, NULL);
++ g_return_val_if_fail(pool_name, NULL);
++
++ /* Traverse the list of registered pools and locate the
++ one whose name matches */
++ for(l = cert_pools; l; l = l->next) {
++ pool = (PurpleCertificatePool *)(l->data);
++
++ /* Scheme and name match? */
++ if(!g_ascii_strcasecmp(pool->scheme_name, scheme_name) &&
++ !g_ascii_strcasecmp(pool->name, pool_name))
++ return pool;
++ }
++
++ purple_debug_warning("certificate",
++ "CertificatePool %s, %s requested but not found.\n",
++ scheme_name, pool_name);
++
++ /* TODO: Signalling and such? */
++
++ return NULL;
++
++}
++
++GList *
++purple_certificate_get_pools(void)
++{
++ return cert_pools;
++}
++
++gboolean
++purple_certificate_register_pool(PurpleCertificatePool *pool)
++{
++ g_return_val_if_fail(pool, FALSE);
++ g_return_val_if_fail(pool->scheme_name, FALSE);
++ g_return_val_if_fail(pool->name, FALSE);
++ g_return_val_if_fail(pool->fullname, FALSE);
++
++ /* Make sure no pools are registered under this name */
++ if (purple_certificate_find_pool(pool->scheme_name, pool->name)) {
++ return FALSE;
++ }
++
++ /* Initialize the pool if needed */
++ if (pool->init) {
++ gboolean success;
++
++ success = pool->init();
++ if (!success)
++ return FALSE;
++ }
++
++ /* Register the Pool */
++ cert_pools = g_list_prepend(cert_pools, pool);
++
++ /* TODO: Emit a signal that the pool got registered */
++
++ PURPLE_DBUS_REGISTER_POINTER(pool, PurpleCertificatePool);
++ purple_signal_register(pool, /* Signals emitted from pool */
++ "certificate-stored",
++ purple_marshal_VOID__POINTER_POINTER,
++ NULL, /* No callback return value */
++ 2, /* Two non-data arguments */
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_CERTIFICATEPOOL),
++ purple_value_new(PURPLE_TYPE_STRING));
++
++ purple_signal_register(pool, /* Signals emitted from pool */
++ "certificate-deleted",
++ purple_marshal_VOID__POINTER_POINTER,
++ NULL, /* No callback return value */
++ 2, /* Two non-data arguments */
++ purple_value_new(PURPLE_TYPE_SUBTYPE,
++ PURPLE_SUBTYPE_CERTIFICATEPOOL),
++ purple_value_new(PURPLE_TYPE_STRING));
++
++ purple_debug_info("certificate",
++ "CertificatePool %s registered\n",
++ pool->name);
++
++ return TRUE;
++}
++
++gboolean
++purple_certificate_unregister_pool(PurpleCertificatePool *pool)
++{
++ if (NULL == pool) {
++ purple_debug_warning("certificate",
++ "Attempting to unregister NULL pool\n");
++ return FALSE;
++ }
++
++ /* Check that the pool is registered */
++ if (!g_list_find(cert_pools, pool)) {
++ purple_debug_warning("certificate",
++ "Pool to unregister isn't registered!\n");
++
++ return FALSE;
++ }
++
++ /* Uninit the pool if needed */
++ PURPLE_DBUS_UNREGISTER_POINTER(pool);
++ if (pool->uninit) {
++ pool->uninit();
++ }
++
++ cert_pools = g_list_remove(cert_pools, pool);
++
++ /* TODO: Signalling? */
++ purple_signal_unregister(pool, "certificate-stored");
++ purple_signal_unregister(pool, "certificate-deleted");
++
++ purple_debug_info("certificate",
++ "CertificatePool %s unregistered\n",
++ pool->name);
++ return TRUE;
++}
++
++/****************************************************************************/
++/* Scheme-specific functions */
++/****************************************************************************/
++
++void
++purple_certificate_display_x509(PurpleCertificate *crt)
++{
++ gchar *sha_asc;
++ GByteArray *sha_bin;
++ gchar *cn;
++ time_t activation, expiration;
++ gchar *activ_str, *expir_str;
++ gchar *secondary;
++
++ /* Pull out the SHA1 checksum */
++ sha_bin = purple_certificate_get_fingerprint_sha1(crt);
++ /* Now decode it for display */
++ sha_asc = purple_base16_encode_chunked(sha_bin->data,
++ sha_bin->len);
++
++ /* Get the cert Common Name */
++ /* TODO: Will break on CA certs */
++ cn = purple_certificate_get_subject_name(crt);
++
++ /* Get the certificate times */
++ /* TODO: Check the times against localtime */
++ /* TODO: errorcheck? */
++ if (!purple_certificate_get_times(crt, &activation, &expiration)) {
++ purple_debug_error("certificate",
++ "Failed to get certificate times!\n");
++ activation = expiration = 0;
++ }
++ activ_str = g_strdup(ctime(&activation));
++ expir_str = g_strdup(ctime(&expiration));
++
++ /* Make messages */
++ secondary = g_strdup_printf(_("Common name: %s\n\n"
++ "Fingerprint (SHA1): %s\n\n"
++ "Activation date: %s\n"
++ "Expiration date: %s\n"),
++ cn ? cn : "(null)",
++ sha_asc ? sha_asc : "(null)",
++ activ_str ? activ_str : "(null)",
++ expir_str ? expir_str : "(null)");
++
++ /* Make a semi-pretty display */
++ purple_notify_info(
++ NULL, /* TODO: Find what the handle ought to be */
++ _("Certificate Information"),
++ "",
++ secondary);
++
++ /* Cleanup */
++ g_free(cn);
++ g_free(secondary);
++ g_free(sha_asc);
++ g_free(activ_str);
++ g_free(expir_str);
++ g_byte_array_free(sha_bin, TRUE);
++}
++
++void purple_certificate_add_ca_search_path(const char *path)
++{
++ if (g_list_find_custom(x509_ca_paths, path, (GCompareFunc)strcmp))
++ return;
++ x509_ca_paths = g_list_append(x509_ca_paths, g_strdup(path));
++}
++
+diff -rupN pidgin-2.7.7/libpurple/plugins/ssl/ssl-gnutls.c pidgin-2.7.7-new//libpurple/plugins/ssl/ssl-gnutls.c
+--- pidgin-2.7.7/libpurple/plugins/ssl/ssl-gnutls.c 2011-03-27 09:05:47.652552999 -0600
++++ pidgin-2.7.7-new//libpurple/plugins/ssl/ssl-gnutls.c 2011-03-27 09:15:29.968552999 -0600
+@@ -856,7 +856,10 @@ x509_destroy_certificate(PurpleCertifica
+ static gboolean
+ x509_certificate_signed_by(PurpleCertificate * crt,
+ PurpleCertificate * issuer)
++
+ {
++// Don't care if cert is signed with MD2 hash
++ gnutls_certificate_set_verify_flags(issuer, GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD2);
+ gnutls_x509_crt crt_dat;
+ gnutls_x509_crt issuer_dat;
+ unsigned int verify; /* used to store result from GnuTLS verifier */
+Binary files pidgin-2.7.7/libpurple/protocols/facebook/arm/libjson-glib-1.0.so and pidgin-2.7.7-new//libpurple/protocols/facebook/arm/libjson-glib-1.0.so differ
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/COPYING pidgin-2.7.7-new//libpurple/protocols/facebook/COPYING
+--- pidgin-2.7.7/libpurple/protocols/facebook/COPYING 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/COPYING 2011-03-27 09:15:30.752552999 -0600
+@@ -0,0 +1,674 @@
++ GNU GENERAL PUBLIC LICENSE
++ Version 3, 29 June 2007
++
++ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
++ Everyone is permitted to copy and distribute verbatim copies
++ of this license document, but changing it is not allowed.
++
++ Preamble
++
++ The GNU General Public License is a free, copyleft license for
++software and other kinds of works.
++
++ The licenses for most software and other practical works are designed
++to take away your freedom to share and change the works. By contrast,
++the GNU General Public License is intended to guarantee your freedom to
++share and change all versions of a program--to make sure it remains free
++software for all its users. We, the Free Software Foundation, use the
++GNU General Public License for most of our software; it applies also to
++any other work released this way by its authors. You can apply it to
++your programs, too.
++
++ When we speak of free software, we are referring to freedom, not
++price. Our General Public Licenses are designed to make sure that you
++have the freedom to distribute copies of free software (and charge for
++them if you wish), that you receive source code or can get it if you
++want it, that you can change the software or use pieces of it in new
++free programs, and that you know you can do these things.
++
++ To protect your rights, we need to prevent others from denying you
++these rights or asking you to surrender the rights. Therefore, you have
++certain responsibilities if you distribute copies of the software, or if
++you modify it: responsibilities to respect the freedom of others.
++
++ For example, if you distribute copies of such a program, whether
++gratis or for a fee, you must pass on to the recipients the same
++freedoms that you received. You must make sure that they, too, receive
++or can get the source code. And you must show them these terms so they
++know their rights.
++
++ Developers that use the GNU GPL protect your rights with two steps:
++(1) assert copyright on the software, and (2) offer you this License
++giving you legal permission to copy, distribute and/or modify it.
++
++ For the developers' and authors' protection, the GPL clearly explains
++that there is no warranty for this free software. For both users' and
++authors' sake, the GPL requires that modified versions be marked as
++changed, so that their problems will not be attributed erroneously to
++authors of previous versions.
++
++ Some devices are designed to deny users access to install or run
++modified versions of the software inside them, although the manufacturer
++can do so. This is fundamentally incompatible with the aim of
++protecting users' freedom to change the software. The systematic
++pattern of such abuse occurs in the area of products for individuals to
++use, which is precisely where it is most unacceptable. Therefore, we
++have designed this version of the GPL to prohibit the practice for those
++products. If such problems arise substantially in other domains, we
++stand ready to extend this provision to those domains in future versions
++of the GPL, as needed to protect the freedom of users.
++
++ Finally, every program is threatened constantly by software patents.
++States should not allow patents to restrict development and use of
++software on general-purpose computers, but in those that do, we wish to
++avoid the special danger that patents applied to a free program could
++make it effectively proprietary. To prevent this, the GPL assures that
++patents cannot be used to render the program non-free.
++
++ The precise terms and conditions for copying, distribution and
++modification follow.
++
++ TERMS AND CONDITIONS
++
++ 0. Definitions.
++
++ "This License" refers to version 3 of the GNU General Public License.
++
++ "Copyright" also means copyright-like laws that apply to other kinds of
++works, such as semiconductor masks.
++
++ "The Program" refers to any copyrightable work licensed under this
++License. Each licensee is addressed as "you". "Licensees" and
++"recipients" may be individuals or organizations.
++
++ To "modify" a work means to copy from or adapt all or part of the work
++in a fashion requiring copyright permission, other than the making of an
++exact copy. The resulting work is called a "modified version" of the
++earlier work or a work "based on" the earlier work.
++
++ A "covered work" means either the unmodified Program or a work based
++on the Program.
++
++ To "propagate" a work means to do anything with it that, without
++permission, would make you directly or secondarily liable for
++infringement under applicable copyright law, except executing it on a
++computer or modifying a private copy. Propagation includes copying,
++distribution (with or without modification), making available to the
++public, and in some countries other activities as well.
++
++ To "convey" a work means any kind of propagation that enables other
++parties to make or receive copies. Mere interaction with a user through
++a computer network, with no transfer of a copy, is not conveying.
++
++ An interactive user interface displays "Appropriate Legal Notices"
++to the extent that it includes a convenient and prominently visible
++feature that (1) displays an appropriate copyright notice, and (2)
++tells the user that there is no warranty for the work (except to the
++extent that warranties are provided), that licensees may convey the
++work under this License, and how to view a copy of this License. If
++the interface presents a list of user commands or options, such as a
++menu, a prominent item in the list meets this criterion.
++
++ 1. Source Code.
++
++ The "source code" for a work means the preferred form of the work
++for making modifications to it. "Object code" means any non-source
++form of a work.
++
++ A "Standard Interface" means an interface that either is an official
++standard defined by a recognized standards body, or, in the case of
++interfaces specified for a particular programming language, one that
++is widely used among developers working in that language.
++
++ The "System Libraries" of an executable work include anything, other
++than the work as a whole, that (a) is included in the normal form of
++packaging a Major Component, but which is not part of that Major
++Component, and (b) serves only to enable use of the work with that
++Major Component, or to implement a Standard Interface for which an
++implementation is available to the public in source code form. A
++"Major Component", in this context, means a major essential component
++(kernel, window system, and so on) of the specific operating system
++(if any) on which the executable work runs, or a compiler used to
++produce the work, or an object code interpreter used to run it.
++
++ The "Corresponding Source" for a work in object code form means all
++the source code needed to generate, install, and (for an executable
++work) run the object code and to modify the work, including scripts to
++control those activities. However, it does not include the work's
++System Libraries, or general-purpose tools or generally available free
++programs which are used unmodified in performing those activities but
++which are not part of the work. For example, Corresponding Source
++includes interface definition files associated with source files for
++the work, and the source code for shared libraries and dynamically
++linked subprograms that the work is specifically designed to require,
++such as by intimate data communication or control flow between those
++subprograms and other parts of the work.
++
++ The Corresponding Source need not include anything that users
++can regenerate automatically from other parts of the Corresponding
++Source.
++
++ The Corresponding Source for a work in source code form is that
++same work.
++
++ 2. Basic Permissions.
++
++ All rights granted under this License are granted for the term of
++copyright on the Program, and are irrevocable provided the stated
++conditions are met. This License explicitly affirms your unlimited
++permission to run the unmodified Program. The output from running a
++covered work is covered by this License only if the output, given its
++content, constitutes a covered work. This License acknowledges your
++rights of fair use or other equivalent, as provided by copyright law.
++
++ You may make, run and propagate covered works that you do not
++convey, without conditions so long as your license otherwise remains
++in force. You may convey covered works to others for the sole purpose
++of having them make modifications exclusively for you, or provide you
++with facilities for running those works, provided that you comply with
++the terms of this License in conveying all material for which you do
++not control copyright. Those thus making or running the covered works
++for you must do so exclusively on your behalf, under your direction
++and control, on terms that prohibit them from making any copies of
++your copyrighted material outside their relationship with you.
++
++ Conveying under any other circumstances is permitted solely under
++the conditions stated below. Sublicensing is not allowed; section 10
++makes it unnecessary.
++
++ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
++
++ No covered work shall be deemed part of an effective technological
++measure under any applicable law fulfilling obligations under article
++11 of the WIPO copyright treaty adopted on 20 December 1996, or
++similar laws prohibiting or restricting circumvention of such
++measures.
++
++ When you convey a covered work, you waive any legal power to forbid
++circumvention of technological measures to the extent such circumvention
++is effected by exercising rights under this License with respect to
++the covered work, and you disclaim any intention to limit operation or
++modification of the work as a means of enforcing, against the work's
++users, your or third parties' legal rights to forbid circumvention of
++technological measures.
++
++ 4. Conveying Verbatim Copies.
++
++ You may convey verbatim copies of the Program's source code as you
++receive it, in any medium, provided that you conspicuously and
++appropriately publish on each copy an appropriate copyright notice;
++keep intact all notices stating that this License and any
++non-permissive terms added in accord with section 7 apply to the code;
++keep intact all notices of the absence of any warranty; and give all
++recipients a copy of this License along with the Program.
++
++ You may charge any price or no price for each copy that you convey,
++and you may offer support or warranty protection for a fee.
++
++ 5. Conveying Modified Source Versions.
++
++ You may convey a work based on the Program, or the modifications to
++produce it from the Program, in the form of source code under the
++terms of section 4, provided that you also meet all of these conditions:
++
++ a) The work must carry prominent notices stating that you modified
++ it, and giving a relevant date.
++
++ b) The work must carry prominent notices stating that it is
++ released under this License and any conditions added under section
++ 7. This requirement modifies the requirement in section 4 to
++ "keep intact all notices".
++
++ c) You must license the entire work, as a whole, under this
++ License to anyone who comes into possession of a copy. This
++ License will therefore apply, along with any applicable section 7
++ additional terms, to the whole of the work, and all its parts,
++ regardless of how they are packaged. This License gives no
++ permission to license the work in any other way, but it does not
++ invalidate such permission if you have separately received it.
++
++ d) If the work has interactive user interfaces, each must display
++ Appropriate Legal Notices; however, if the Program has interactive
++ interfaces that do not display Appropriate Legal Notices, your
++ work need not make them do so.
++
++ A compilation of a covered work with other separate and independent
++works, which are not by their nature extensions of the covered work,
++and which are not combined with it such as to form a larger program,
++in or on a volume of a storage or distribution medium, is called an
++"aggregate" if the compilation and its resulting copyright are not
++used to limit the access or legal rights of the compilation's users
++beyond what the individual works permit. Inclusion of a covered work
++in an aggregate does not cause this License to apply to the other
++parts of the aggregate.
++
++ 6. Conveying Non-Source Forms.
++
++ You may convey a covered work in object code form under the terms
++of sections 4 and 5, provided that you also convey the
++machine-readable Corresponding Source under the terms of this License,
++in one of these ways:
++
++ a) Convey the object code in, or embodied in, a physical product
++ (including a physical distribution medium), accompanied by the
++ Corresponding Source fixed on a durable physical medium
++ customarily used for software interchange.
++
++ b) Convey the object code in, or embodied in, a physical product
++ (including a physical distribution medium), accompanied by a
++ written offer, valid for at least three years and valid for as
++ long as you offer spare parts or customer support for that product
++ model, to give anyone who possesses the object code either (1) a
++ copy of the Corresponding Source for all the software in the
++ product that is covered by this License, on a durable physical
++ medium customarily used for software interchange, for a price no
++ more than your reasonable cost of physically performing this
++ conveying of source, or (2) access to copy the
++ Corresponding Source from a network server at no charge.
++
++ c) Convey individual copies of the object code with a copy of the
++ written offer to provide the Corresponding Source. This
++ alternative is allowed only occasionally and noncommercially, and
++ only if you received the object code with such an offer, in accord
++ with subsection 6b.
++
++ d) Convey the object code by offering access from a designated
++ place (gratis or for a charge), and offer equivalent access to the
++ Corresponding Source in the same way through the same place at no
++ further charge. You need not require recipients to copy the
++ Corresponding Source along with the object code. If the place to
++ copy the object code is a network server, the Corresponding Source
++ may be on a different server (operated by you or a third party)
++ that supports equivalent copying facilities, provided you maintain
++ clear directions next to the object code saying where to find the
++ Corresponding Source. Regardless of what server hosts the
++ Corresponding Source, you remain obligated to ensure that it is
++ available for as long as needed to satisfy these requirements.
++
++ e) Convey the object code using peer-to-peer transmission, provided
++ you inform other peers where the object code and Corresponding
++ Source of the work are being offered to the general public at no
++ charge under subsection 6d.
++
++ A separable portion of the object code, whose source code is excluded
++from the Corresponding Source as a System Library, need not be
++included in conveying the object code work.
++
++ A "User Product" is either (1) a "consumer product", which means any
++tangible personal property which is normally used for personal, family,
++or household purposes, or (2) anything designed or sold for incorporation
++into a dwelling. In determining whether a product is a consumer product,
++doubtful cases shall be resolved in favor of coverage. For a particular
++product received by a particular user, "normally used" refers to a
++typical or common use of that class of product, regardless of the status
++of the particular user or of the way in which the particular user
++actually uses, or expects or is expected to use, the product. A product
++is a consumer product regardless of whether the product has substantial
++commercial, industrial or non-consumer uses, unless such uses represent
++the only significant mode of use of the product.
++
++ "Installation Information" for a User Product means any methods,
++procedures, authorization keys, or other information required to install
++and execute modified versions of a covered work in that User Product from
++a modified version of its Corresponding Source. The information must
++suffice to ensure that the continued functioning of the modified object
++code is in no case prevented or interfered with solely because
++modification has been made.
++
++ If you convey an object code work under this section in, or with, or
++specifically for use in, a User Product, and the conveying occurs as
++part of a transaction in which the right of possession and use of the
++User Product is transferred to the recipient in perpetuity or for a
++fixed term (regardless of how the transaction is characterized), the
++Corresponding Source conveyed under this section must be accompanied
++by the Installation Information. But this requirement does not apply
++if neither you nor any third party retains the ability to install
++modified object code on the User Product (for example, the work has
++been installed in ROM).
++
++ The requirement to provide Installation Information does not include a
++requirement to continue to provide support service, warranty, or updates
++for a work that has been modified or installed by the recipient, or for
++the User Product in which it has been modified or installed. Access to a
++network may be denied when the modification itself materially and
++adversely affects the operation of the network or violates the rules and
++protocols for communication across the network.
++
++ Corresponding Source conveyed, and Installation Information provided,
++in accord with this section must be in a format that is publicly
++documented (and with an implementation available to the public in
++source code form), and must require no special password or key for
++unpacking, reading or copying.
++
++ 7. Additional Terms.
++
++ "Additional permissions" are terms that supplement the terms of this
++License by making exceptions from one or more of its conditions.
++Additional permissions that are applicable to the entire Program shall
++be treated as though they were included in this License, to the extent
++that they are valid under applicable law. If additional permissions
++apply only to part of the Program, that part may be used separately
++under those permissions, but the entire Program remains governed by
++this License without regard to the additional permissions.
++
++ When you convey a copy of a covered work, you may at your option
++remove any additional permissions from that copy, or from any part of
++it. (Additional permissions may be written to require their own
++removal in certain cases when you modify the work.) You may place
++additional permissions on material, added by you to a covered work,
++for which you have or can give appropriate copyright permission.
++
++ Notwithstanding any other provision of this License, for material you
++add to a covered work, you may (if authorized by the copyright holders of
++that material) supplement the terms of this License with terms:
++
++ a) Disclaiming warranty or limiting liability differently from the
++ terms of sections 15 and 16 of this License; or
++
++ b) Requiring preservation of specified reasonable legal notices or
++ author attributions in that material or in the Appropriate Legal
++ Notices displayed by works containing it; or
++
++ c) Prohibiting misrepresentation of the origin of that material, or
++ requiring that modified versions of such material be marked in
++ reasonable ways as different from the original version; or
++
++ d) Limiting the use for publicity purposes of names of licensors or
++ authors of the material; or
++
++ e) Declining to grant rights under trademark law for use of some
++ trade names, trademarks, or service marks; or
++
++ f) Requiring indemnification of licensors and authors of that
++ material by anyone who conveys the material (or modified versions of
++ it) with contractual assumptions of liability to the recipient, for
++ any liability that these contractual assumptions directly impose on
++ those licensors and authors.
++
++ All other non-permissive additional terms are considered "further
++restrictions" within the meaning of section 10. If the Program as you
++received it, or any part of it, contains a notice stating that it is
++governed by this License along with a term that is a further
++restriction, you may remove that term. If a license document contains
++a further restriction but permits relicensing or conveying under this
++License, you may add to a covered work material governed by the terms
++of that license document, provided that the further restriction does
++not survive such relicensing or conveying.
++
++ If you add terms to a covered work in accord with this section, you
++must place, in the relevant source files, a statement of the
++additional terms that apply to those files, or a notice indicating
++where to find the applicable terms.
++
++ Additional terms, permissive or non-permissive, may be stated in the
++form of a separately written license, or stated as exceptions;
++the above requirements apply either way.
++
++ 8. Termination.
++
++ You may not propagate or modify a covered work except as expressly
++provided under this License. Any attempt otherwise to propagate or
++modify it is void, and will automatically terminate your rights under
++this License (including any patent licenses granted under the third
++paragraph of section 11).
++
++ However, if you cease all violation of this License, then your
++license from a particular copyright holder is reinstated (a)
++provisionally, unless and until the copyright holder explicitly and
++finally terminates your license, and (b) permanently, if the copyright
++holder fails to notify you of the violation by some reasonable means
++prior to 60 days after the cessation.
++
++ Moreover, your license from a particular copyright holder is
++reinstated permanently if the copyright holder notifies you of the
++violation by some reasonable means, this is the first time you have
++received notice of violation of this License (for any work) from that
++copyright holder, and you cure the violation prior to 30 days after
++your receipt of the notice.
++
++ Termination of your rights under this section does not terminate the
++licenses of parties who have received copies or rights from you under
++this License. If your rights have been terminated and not permanently
++reinstated, you do not qualify to receive new licenses for the same
++material under section 10.
++
++ 9. Acceptance Not Required for Having Copies.
++
++ You are not required to accept this License in order to receive or
++run a copy of the Program. Ancillary propagation of a covered work
++occurring solely as a consequence of using peer-to-peer transmission
++to receive a copy likewise does not require acceptance. However,
++nothing other than this License grants you permission to propagate or
++modify any covered work. These actions infringe copyright if you do
++not accept this License. Therefore, by modifying or propagating a
++covered work, you indicate your acceptance of this License to do so.
++
++ 10. Automatic Licensing of Downstream Recipients.
++
++ Each time you convey a covered work, the recipient automatically
++receives a license from the original licensors, to run, modify and
++propagate that work, subject to this License. You are not responsible
++for enforcing compliance by third parties with this License.
++
++ An "entity transaction" is a transaction transferring control of an
++organization, or substantially all assets of one, or subdividing an
++organization, or merging organizations. If propagation of a covered
++work results from an entity transaction, each party to that
++transaction who receives a copy of the work also receives whatever
++licenses to the work the party's predecessor in interest had or could
++give under the previous paragraph, plus a right to possession of the
++Corresponding Source of the work from the predecessor in interest, if
++the predecessor has it or can get it with reasonable efforts.
++
++ You may not impose any further restrictions on the exercise of the
++rights granted or affirmed under this License. For example, you may
++not impose a license fee, royalty, or other charge for exercise of
++rights granted under this License, and you may not initiate litigation
++(including a cross-claim or counterclaim in a lawsuit) alleging that
++any patent claim is infringed by making, using, selling, offering for
++sale, or importing the Program or any portion of it.
++
++ 11. Patents.
++
++ A "contributor" is a copyright holder who authorizes use under this
++License of the Program or a work on which the Program is based. The
++work thus licensed is called the contributor's "contributor version".
++
++ A contributor's "essential patent claims" are all patent claims
++owned or controlled by the contributor, whether already acquired or
++hereafter acquired, that would be infringed by some manner, permitted
++by this License, of making, using, or selling its contributor version,
++but do not include claims that would be infringed only as a
++consequence of further modification of the contributor version. For
++purposes of this definition, "control" includes the right to grant
++patent sublicenses in a manner consistent with the requirements of
++this License.
++
++ Each contributor grants you a non-exclusive, worldwide, royalty-free
++patent license under the contributor's essential patent claims, to
++make, use, sell, offer for sale, import and otherwise run, modify and
++propagate the contents of its contributor version.
++
++ In the following three paragraphs, a "patent license" is any express
++agreement or commitment, however denominated, not to enforce a patent
++(such as an express permission to practice a patent or covenant not to
++sue for patent infringement). To "grant" such a patent license to a
++party means to make such an agreement or commitment not to enforce a
++patent against the party.
++
++ If you convey a covered work, knowingly relying on a patent license,
++and the Corresponding Source of the work is not available for anyone
++to copy, free of charge and under the terms of this License, through a
++publicly available network server or other readily accessible means,
++then you must either (1) cause the Corresponding Source to be so
++available, or (2) arrange to deprive yourself of the benefit of the
++patent license for this particular work, or (3) arrange, in a manner
++consistent with the requirements of this License, to extend the patent
++license to downstream recipients. "Knowingly relying" means you have
++actual knowledge that, but for the patent license, your conveying the
++covered work in a country, or your recipient's use of the covered work
++in a country, would infringe one or more identifiable patents in that
++country that you have reason to believe are valid.
++
++ If, pursuant to or in connection with a single transaction or
++arrangement, you convey, or propagate by procuring conveyance of, a
++covered work, and grant a patent license to some of the parties
++receiving the covered work authorizing them to use, propagate, modify
++or convey a specific copy of the covered work, then the patent license
++you grant is automatically extended to all recipients of the covered
++work and works based on it.
++
++ A patent license is "discriminatory" if it does not include within
++the scope of its coverage, prohibits the exercise of, or is
++conditioned on the non-exercise of one or more of the rights that are
++specifically granted under this License. You may not convey a covered
++work if you are a party to an arrangement with a third party that is
++in the business of distributing software, under which you make payment
++to the third party based on the extent of your activity of conveying
++the work, and under which the third party grants, to any of the
++parties who would receive the covered work from you, a discriminatory
++patent license (a) in connection with copies of the covered work
++conveyed by you (or copies made from those copies), or (b) primarily
++for and in connection with specific products or compilations that
++contain the covered work, unless you entered into that arrangement,
++or that patent license was granted, prior to 28 March 2007.
++
++ Nothing in this License shall be construed as excluding or limiting
++any implied license or other defenses to infringement that may
++otherwise be available to you under applicable patent law.
++
++ 12. No Surrender of Others' Freedom.
++
++ If conditions are imposed on you (whether by court order, agreement or
++otherwise) that contradict the conditions of this License, they do not
++excuse you from the conditions of this License. If you cannot convey a
++covered work so as to satisfy simultaneously your obligations under this
++License and any other pertinent obligations, then as a consequence you may
++not convey it at all. For example, if you agree to terms that obligate you
++to collect a royalty for further conveying from those to whom you convey
++the Program, the only way you could satisfy both those terms and this
++License would be to refrain entirely from conveying the Program.
++
++ 13. Use with the GNU Affero General Public License.
++
++ Notwithstanding any other provision of this License, you have
++permission to link or combine any covered work with a work licensed
++under version 3 of the GNU Affero General Public License into a single
++combined work, and to convey the resulting work. The terms of this
++License will continue to apply to the part which is the covered work,
++but the special requirements of the GNU Affero General Public License,
++section 13, concerning interaction through a network will apply to the
++combination as such.
++
++ 14. Revised Versions of this License.
++
++ The Free Software Foundation may publish revised and/or new versions of
++the GNU General Public License from time to time. Such new versions will
++be similar in spirit to the present version, but may differ in detail to
++address new problems or concerns.
++
++ Each version is given a distinguishing version number. If the
++Program specifies that a certain numbered version of the GNU General
++Public License "or any later version" applies to it, you have the
++option of following the terms and conditions either of that numbered
++version or of any later version published by the Free Software
++Foundation. If the Program does not specify a version number of the
++GNU General Public License, you may choose any version ever published
++by the Free Software Foundation.
++
++ If the Program specifies that a proxy can decide which future
++versions of the GNU General Public License can be used, that proxy's
++public statement of acceptance of a version permanently authorizes you
++to choose that version for the Program.
++
++ Later license versions may give you additional or different
++permissions. However, no additional obligations are imposed on any
++author or copyright holder as a result of your choosing to follow a
++later version.
++
++ 15. Disclaimer of Warranty.
++
++ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
++APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
++HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
++OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
++THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
++PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
++IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
++ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
++
++ 16. Limitation of Liability.
++
++ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
++WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
++THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
++GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
++USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
++DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
++PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
++EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
++SUCH DAMAGES.
++
++ 17. Interpretation of Sections 15 and 16.
++
++ If the disclaimer of warranty and limitation of liability provided
++above cannot be given local legal effect according to their terms,
++reviewing courts shall apply local law that most closely approximates
++an absolute waiver of all civil liability in connection with the
++Program, unless a warranty or assumption of liability accompanies a
++copy of the Program in return for a fee.
++
++ END OF TERMS AND CONDITIONS
++
++ How to Apply These Terms to Your New Programs
++
++ If you develop a new program, and you want it to be of the greatest
++possible use to the public, the best way to achieve this is to make it
++free software which everyone can redistribute and change under these terms.
++
++ To do so, attach the following notices to the program. It is safest
++to attach them to the start of each source file to most effectively
++state the exclusion of warranty; and each file should have at least
++the "copyright" line and a pointer to where the full notice is found.
++
++ <one line to give the program's name and a brief idea of what it does.>
++ Copyright (C) <year> <name of author>
++
++ This program is free software: you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation, either version 3 of the License, or
++ (at your option) any later version.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program. If not, see <http://www.gnu.org/licenses/>.
++
++Also add information on how to contact you by electronic and paper mail.
++
++ If the program does terminal interaction, make it output a short
++notice like this when it starts in an interactive mode:
++
++ <program> Copyright (C) <year> <name of author>
++ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
++ This is free software, and you are welcome to redistribute it
++ under certain conditions; type `show c' for details.
++
++The hypothetical commands `show w' and `show c' should show the appropriate
++parts of the General Public License. Of course, your program's commands
++might be different; for a GUI interface, you would use an "about box".
++
++ You should also get your employer (if you work as a programmer) or school,
++if any, to sign a "copyright disclaimer" for the program, if necessary.
++For more information on this, and how to apply and follow the GNU GPL, see
++<http://www.gnu.org/licenses/>.
++
++ The GNU General Public License does not permit incorporating your program
++into proprietary programs. If your program is a subroutine library, you
++may consider it more useful to permit linking proprietary applications with
++the library. If this is what you want to do, use the GNU Lesser General
++Public License instead of this License. But first, please read
++<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/COPYRIGHT pidgin-2.7.7-new//libpurple/protocols/facebook/COPYRIGHT
+--- pidgin-2.7.7/libpurple/protocols/facebook/COPYRIGHT 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/COPYRIGHT 2011-03-27 09:15:30.848552999 -0600
+@@ -0,0 +1,5 @@
++This protocol plugin is copyright (C) 2008 by the following:
++
++Mark Doliner
++Casey Ho
++Eion Robb
+Binary files pidgin-2.7.7/libpurple/protocols/facebook/facebook16.png and pidgin-2.7.7-new//libpurple/protocols/facebook/facebook16.png differ
+Binary files pidgin-2.7.7/libpurple/protocols/facebook/facebook22.png and pidgin-2.7.7-new//libpurple/protocols/facebook/facebook22.png differ
+Binary files pidgin-2.7.7/libpurple/protocols/facebook/facebook48.png and pidgin-2.7.7-new//libpurple/protocols/facebook/facebook48.png differ
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/facebook.nsi pidgin-2.7.7-new//libpurple/protocols/facebook/facebook.nsi
+--- pidgin-2.7.7/libpurple/protocols/facebook/facebook.nsi 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/facebook.nsi 2011-03-27 09:15:30.820553000 -0600
+@@ -0,0 +1,99 @@
++; Script based on the Skype4Pidgin and Off-the-Record Messaging NSI files
++
++
++SetCompress off
++
++; todo: SetBrandingImage
++; HM NIS Edit Wizard helper defines
++!define PRODUCT_NAME "pidgin-facebookchat"
++!define PRODUCT_VERSION "1.69"
++!define PRODUCT_PUBLISHER "Eion Robb"
++!define PRODUCT_WEB_SITE "http://pidgin-facebookchat.googlecode.com/"
++!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
++!define PRODUCT_UNINST_ROOT_KEY "HKLM"
++
++; MUI 1.67 compatible ------
++!include "MUI.nsh"
++
++; MUI Settings
++!define MUI_ABORTWARNING
++!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
++!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
++
++; Welcome page
++!insertmacro MUI_PAGE_WELCOME
++; License page
++!insertmacro MUI_PAGE_LICENSE "COPYING"
++; Instfiles page
++!insertmacro MUI_PAGE_INSTFILES
++!define MUI_FINISHPAGE_RUN
++!define MUI_FINISHPAGE_RUN_TEXT "Run Pidgin"
++!define MUI_FINISHPAGE_RUN_FUNCTION "RunPidgin"
++!insertmacro MUI_PAGE_FINISH
++
++; Uninstaller pages
++;!insertmacro MUI_UNPAGE_INSTFILES
++
++; Language files
++!insertmacro MUI_LANGUAGE "English"
++
++; MUI end ------
++
++Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
++OutFile "${PRODUCT_NAME}.exe"
++
++Var "PidginDir"
++
++ShowInstDetails show
++ShowUnInstDetails show
++
++Section "MainSection" SEC01
++ ;Check for pidgin installation
++ Call GetPidginInstPath
++
++ SetOverwrite try
++
++ SetOutPath "$PidginDir\pixmaps\pidgin"
++ File "/oname=protocols\16\facebook.png" "facebook16.png"
++ File "/oname=protocols\22\facebook.png" "facebook22.png"
++ File "/oname=protocols\48\facebook.png" "facebook48.png"
++
++ SetOutPath "$PidginDir\ca-certs"
++ File "login.facebook.com.pem"
++
++ SetOverwrite try
++ copy:
++ ClearErrors
++ Delete "$PidginDir\plugins\libfacebook.dll"
++ IfErrors dllbusy
++ SetOutPath "$PidginDir\plugins"
++ File "libfacebook.dll"
++ Goto after_copy
++ dllbusy:
++ MessageBox MB_RETRYCANCEL "libfacebook.dll is busy. Please close Pidgin (including tray icon) and try again" IDCANCEL cancel
++ Goto copy
++ cancel:
++ Abort "Installation of pidgin-facebookchat aborted"
++ after_copy:
++
++ SetOutPath "$PidginDir"
++ File "libjson-glib-1.0.dll"
++
++SectionEnd
++
++Function GetPidginInstPath
++ Push $0
++ ReadRegStr $0 HKLM "Software\pidgin" ""
++ IfFileExists "$0\pidgin.exe" cont
++ ReadRegStr $0 HKCU "Software\pidgin" ""
++ IfFileExists "$0\pidgin.exe" cont
++ MessageBox MB_OK|MB_ICONINFORMATION "Failed to find Pidgin installation."
++ Abort "Failed to find Pidgin installation. Please install Pidgin first."
++ cont:
++ StrCpy $PidginDir $0
++FunctionEnd
++
++Function RunPidgin
++ ExecShell "" "$PidginDir\pidgin.exe"
++FunctionEnd
++
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/facebook.svg pidgin-2.7.7-new//libpurple/protocols/facebook/facebook.svg
+--- pidgin-2.7.7/libpurple/protocols/facebook/facebook.svg 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/facebook.svg 2011-03-27 09:15:30.744552999 -0600
+@@ -0,0 +1,108 @@
++<?xml version="1.0" encoding="UTF-8" standalone="no"?>
++<!-- Created with Inkscape (http://www.inkscape.org/) -->
++
++<svg
++ xmlns:svg="http://www.w3.org/2000/svg"
++ xmlns="http://www.w3.org/2000/svg"
++ xmlns:xlink="http://www.w3.org/1999/xlink"
++ version="1.1"
++ width="48"
++ height="48"
++ id="svg2">
++ <defs
++ id="defs6">
++ <linearGradient
++ id="linearGradient3650">
++ <stop
++ id="stop3652"
++ style="stop-color:#6a6a6a;stop-opacity:1"
++ offset="0" />
++ <stop
++ id="stop3658"
++ style="stop-color:#779cf4;stop-opacity:1"
++ offset="0.88979578" />
++ <stop
++ id="stop3654"
++ style="stop-color:#545eff;stop-opacity:1"
++ offset="1" />
++ </linearGradient>
++ <linearGradient
++ id="linearGradient3634">
++ <stop
++ id="stop3636"
++ style="stop-color:#4072e6;stop-opacity:1"
++ offset="0" />
++ <stop
++ id="stop3638"
++ style="stop-color:#334e89;stop-opacity:1"
++ offset="1" />
++ </linearGradient>
++ <radialGradient
++ cx="13.327877"
++ cy="6.1316919"
++ r="17.96468"
++ fx="13.327877"
++ fy="6.1316919"
++ id="radialGradient3640"
++ xlink:href="#linearGradient3634"
++ gradientUnits="userSpaceOnUse"
++ gradientTransform="matrix(1.8070156,1.4532131,-1.0519575,1.3080694,-2.6667852,-23.653083)" />
++ <radialGradient
++ cx="24.023664"
++ cy="21.943848"
++ r="19.95002"
++ fx="24.023664"
++ fy="21.943848"
++ id="radialGradient3656"
++ xlink:href="#linearGradient3650"
++ gradientUnits="userSpaceOnUse"
++ gradientTransform="matrix(1.3070175,1.2268147e-7,-1.2100323e-7,1.289138,-7.3756861,-6.3448031)" />
++ <filter
++ x="-0.070605978"
++ y="-0.54366601"
++ width="1.1412119"
++ height="2.087332"
++ color-interpolation-filters="sRGB"
++ id="filter3678">
++ <feGaussianBlur
++ id="feGaussianBlur3680"
++ stdDeviation="1.2013434" />
++ </filter>
++ </defs>
++ <rect
++ width="40.835415"
++ height="5.3033009"
++ ry="1.7350562"
++ x="3.5355339"
++ y="38.277283"
++ transform="matrix(1,0,0,0.94255278,0.61871843,3.785993)"
++ id="rect3660"
++ style="opacity:0.53252037;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;filter:url(#filter3678)" />
++ <g
++ id="g3618">
++ <rect
++ width="38.910927"
++ height="38.947548"
++ ry="3.0047133"
++ x="4.5682015"
++ y="2.4700744"
++ id="rect2818"
++ style="fill:url(#radialGradient3656);fill-opacity:1;fill-rule:nonzero;stroke:#24469d;stroke-width:0.98911101;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" />
++ <rect
++ width="35.929359"
++ height="36.256153"
++ ry="1.7350562"
++ x="6.1269403"
++ y="3.735842"
++ id="rect3592"
++ style="fill:url(#radialGradient3640);fill-opacity:1;fill-rule:nonzero;stroke:none" />
++ <path
++ d="m 7,31.978036 -7.938e-4,5.031486 C 6.9992062,38.10436 7.9191305,39 8.9741214,39 L 38.95642,39 c 1.054991,0 1.971131,-0.904795 1.971131,-1.999633 l 0.0092,-4.994865 L 7,31.978036 z"
++ id="rect3598"
++ style="opacity:0.46341463;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
++ </g>
++ <path
++ d="m 31.5,6.96875 c -3.441955,0 -6.239955,2.6781558 -6.46875,6.0625 L 25,16 l -3,0 0,5 3,0 0,18 7,0 0,-18 5.5,0 0,-5 -5.46875,0 0,-1.875 c 0,-1.178295 0.915762,-2.125 2.09375,-2.125 1.828085,-0.153263 3.458457,-0.138113 4.885084,0.321843 l 0,-4.9999996 C 36.391262,6.8079395 33.910076,6.9137911 31.5,6.96875 z"
++ id="rect3609"
++ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" />
++</svg>
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_blist.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_blist.c
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_blist.c 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_blist.c 2011-03-27 09:15:30.752552999 -0600
+@@ -0,0 +1,719 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "libfacebook.h"
++#include "fb_connection.h"
++#include "fb_blist.h"
++#include "fb_util.h"
++#include "fb_friendlist.h"
++#include "blist.h"
++
++static void set_buddies_offline(PurpleBuddy *buddy,
++ GHashTable *online_buddies_list)
++{
++ if (PURPLE_BUDDY_IS_ONLINE(buddy) &&
++ g_hash_table_lookup(online_buddies_list, buddy->name) == NULL)
++ {
++ purple_prpl_got_user_status(buddy->account, buddy->name,
++ purple_primitive_get_id_from_type(
++ PURPLE_STATUS_OFFLINE),
++ NULL);
++ }
++}
++
++static void buddy_icon_cb(FacebookAccount *fba, const gchar *data, gsize data_len,
++ gpointer user_data)
++{
++ gchar *buddyname;
++ PurpleBuddy *buddy;
++ FacebookBuddy *fbuddy;
++ gpointer buddy_icon_data;
++
++ buddyname = user_data;
++
++ purple_debug_info("facebook",
++ "buddy icon for buddy %s %" G_GSIZE_FORMAT "\n",
++ buddyname, data_len);
++
++ buddy = purple_find_buddy(fba->account, buddyname);
++ g_free(buddyname);
++
++ g_return_if_fail(buddy != NULL);
++
++ fbuddy = buddy->proto_data;
++
++ g_return_if_fail(fbuddy != NULL);
++
++ buddy_icon_data = g_memdup(data, data_len);
++
++ purple_buddy_icons_set_for_user(fba->account, buddy->name,
++ buddy_icon_data, data_len, fbuddy->thumb_url);
++}
++
++/**
++ * Find buddy names
++ */
++static GList *get_buddies(FacebookAccount *fba, const gchar *uid,
++ const gchar *name, JsonArray *friend_list_ids)
++{
++ GList *buddies;
++ GList *cur;
++
++ buddies = fb_get_buddies_friend_list(fba, uid, friend_list_ids);
++
++ // Initialize proto data for each buddy.
++ for (cur = buddies; cur != NULL; cur = cur->next)
++ {
++ PurpleBuddy *buddy;
++
++ buddy = (PurpleBuddy *) cur->data;
++
++ /* Set the FacebookBuddy structure */
++ if (buddy->proto_data == NULL)
++ {
++ FacebookBuddy *fbuddy;
++ gchar *buddy_icon_url;
++
++ fbuddy = g_new0(FacebookBuddy, 1);
++ fbuddy->buddy = buddy;
++ fbuddy->fba = fba;
++ fbuddy->uid = atoll(uid);
++ fbuddy->name = g_strdup(name);
++
++ // load the old buddy icon url from the icon 'checksum'
++ buddy_icon_url = (char *)
++ purple_buddy_icons_get_checksum_for_user(buddy);
++ if (buddy_icon_url != NULL)
++ fbuddy->thumb_url = g_strdup(buddy_icon_url);
++
++ buddy->proto_data = fbuddy;
++ }
++ }
++
++ return buddies;
++}
++
++static void process_buddy_icon(FacebookAccount *fba, FacebookBuddy *fbuddy,
++ const gchar *buddy_icon_url)
++{
++ PurpleBuddy *buddy;
++ gchar *icon_host;
++ gchar *icon_path, *real_path;
++ gchar *search_tmp;
++
++ buddy = fbuddy->buddy;
++
++ /* Seperate the URL into pieces */
++ purple_url_parse(buddy_icon_url, &icon_host, NULL, &icon_path, NULL, NULL);
++
++ if (icon_path != NULL && icon_path[0] != '/')
++ {
++ /* Slap a / at the front of that badboy */
++ real_path = g_strconcat("/", icon_path, NULL);
++ g_free(icon_path);
++ icon_path = real_path;
++ }
++
++ if (fbuddy->thumb_url == NULL ||
++ !g_str_equal(fbuddy->thumb_url, icon_path))
++ {
++ g_free(fbuddy->thumb_url);
++ if (g_str_equal(icon_path, "/pics/q_silhouette.gif"))
++ {
++ fbuddy->thumb_url = NULL;
++ /* User has no icon */
++ purple_buddy_icons_set_for_user(fba->account,
++ purple_buddy_get_name(buddy), NULL, 0, NULL);
++ }
++ else
++ {
++ fbuddy->thumb_url = g_strdup(icon_path);
++
++ /* small icon at /profile6/1845/74/q800753867_2878.jpg */
++ /* bigger icon at /profile6/1845/74/n800753867_2878.jpg */
++ search_tmp = strstr(icon_path, "/q");
++ if (search_tmp)
++ *(search_tmp + 1) = 'n';
++ else
++ {
++ search_tmp = strstr(icon_path, "_q.jpg");
++ if (search_tmp)
++ *(search_tmp + 1) = 'n';
++ }
++ purple_debug_info("facebook", "buddy %s has a new buddy icon at http://%s%s\n", buddy->name, icon_host, icon_path);
++ /* Fetch their icon */
++ fb_post_or_get(fba, FB_METHOD_GET, icon_host,
++ icon_path, NULL,
++ buddy_icon_cb, g_strdup(purple_buddy_get_name(buddy)), FALSE);
++ }
++ }
++ g_free(icon_host);
++ g_free(icon_path);
++}
++
++static void process_buddies(FacebookAccount *fba, GHashTable *online_buddies_list,
++ JsonObject *nowAvailableList, gchar *uid, JsonObject *userInfo)
++{
++ const gchar *name;
++ gboolean idle;
++ GList *buddies, *cur;
++ gboolean current_buddy_online;
++
++ JsonArray *friend_list_ids;
++
++ friend_list_ids = NULL;
++ name = json_node_get_string(json_object_get_member(userInfo, "name"));
++
++ /* look for "uid":{"i":_____} */
++ if (json_object_has_member(nowAvailableList, uid))
++ {
++ JsonObject *userBlistInfo;
++ userBlistInfo = json_node_get_object(
++ json_object_get_member(nowAvailableList, uid));
++ idle = json_node_get_boolean(
++ json_object_get_member(userBlistInfo, "i"));
++ if (json_object_has_member(userBlistInfo, "fl")) {
++ friend_list_ids = json_node_get_array(
++ json_object_get_member(userBlistInfo, "fl"));
++ }
++
++ current_buddy_online = TRUE;
++ } else {
++ /* if we're here, the buddy's info has been sent,
++ * but they're not actually online */
++ current_buddy_online = FALSE;
++ idle = FALSE;
++ }
++
++ /* is this us? */
++ if (atoll(uid) == fba->uid)
++ {
++ purple_connection_set_display_name(fba->pc, name);
++
++ /* check that we don't want to show ourselves */
++ current_buddy_online = !purple_account_get_bool(
++ fba->account, "facebook_hide_self", TRUE);
++ }
++
++ buddies = get_buddies(fba, uid, name, friend_list_ids);
++ for (cur = buddies; cur != NULL; cur = cur->next)
++ {
++ PurpleBuddy *buddy;
++ FacebookBuddy *fbuddy;
++
++ buddy = (PurpleBuddy *)cur->data;
++ fbuddy = buddy->proto_data;
++
++ process_buddy_icon(fba, fbuddy, json_node_get_string(
++ json_object_get_member(userInfo, "thumbSrc")));
++
++ purple_presence_set_idle(purple_buddy_get_presence(buddy),
++ idle, 0);
++
++ if (current_buddy_online)
++ {
++ /* Add buddy to the list of online buddies */
++ g_hash_table_insert(online_buddies_list, buddy->name, buddy);
++
++ // Set buddy as online in buddy list. We check for several
++ // conditions before doing this, because if we set it always
++ // Pidgin has a bug where the logs go nuts with "x is online".
++ if (!PURPLE_BUDDY_IS_ONLINE(buddy) ||
++ idle != purple_presence_is_idle(
++ purple_buddy_get_presence(buddy)))
++ {
++ purple_prpl_got_user_status(fba->account, buddy->name,
++ purple_primitive_get_id_from_type(
++ idle ? PURPLE_STATUS_AWAY :
++ PURPLE_STATUS_AVAILABLE), NULL);
++ }
++ }
++ }
++
++ /* update the blist if we have no previous alias */
++ fb_blist_set_alias(fba, uid, name);
++}
++
++static void process_notifications(FacebookAccount *fba,
++ JsonObject *notifications)
++{
++ if (notifications != NULL &&
++ purple_account_get_check_mail(fba->account))
++ {
++ JsonNode *inboxCount_node = json_object_get_member(
++ notifications, "inboxCount");
++ if (inboxCount_node) {
++ gint inbox_count = json_node_get_int(inboxCount_node);
++ if (inbox_count &&
++ inbox_count != fba->last_inbox_count) {
++ fba->last_inbox_count = inbox_count;
++ gchar *url = g_strdup("http://www.facebook.com/inbox/");
++ purple_notify_emails(
++ fba->pc, inbox_count,
++ FALSE, NULL, NULL,
++ (const char**) &(fba->account->username),
++ (const char**) &(url), NULL, NULL);
++ g_free(url);
++ }
++ }
++ }
++}
++
++static void got_status_stream_cb(FacebookAccount *fba, const gchar *data,
++ gsize data_len, gpointer userdata)
++{
++ gchar *error = NULL;
++ JsonParser *parser;
++ JsonObject *objnode;
++ gint new_latest;
++ const gchar *html;
++ gchar **messages;
++ gchar *message;
++ gint i;
++ gchar *uid_string;
++ gchar *message_string;
++ gsize uid_length;
++ FacebookBuddy *fbuddy;
++ PurpleBuddy *buddy;
++ GHashTable *processed_buddies;
++
++ purple_debug_info("facebook", "parsing status message stream\n");
++
++ if (fba == NULL)
++ return;
++
++ parser = fb_get_parser(data, data_len);
++ if (parser == NULL) {
++ purple_debug_info("facebook", "could not parse\n");
++ return;
++ }
++
++ //purple_debug_misc("facebook", "status message stream\n%s\n", data);
++
++ objnode = fb_get_json_object(parser, &error);
++
++ if (error || !json_object_has_member(objnode, "payload")) {
++ purple_debug_info("facebook", "no payload\n");
++ json_parser_free(parser);
++ return;
++ }
++
++ objnode = json_node_get_object(json_object_get_member(
++ objnode, "payload"));
++
++ html = json_node_get_string(json_object_get_member(
++ objnode, "html"));
++ //purple_debug_misc("facebook", "html data\n%s\n", html);
++
++ processed_buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
++
++ messages = g_strsplit(html, "/h6>", -1);
++ for(i = 0; messages[i]; i++)
++ {
++ message = messages[i];
++ uid_length = 0;
++
++ //find uid:
++ //start with aid_ ... "
++ uid_string = strstr(message, "aid_");
++ if (!uid_string)
++ continue;
++ uid_string += 4;
++ while (uid_string[uid_length] >= '0' &&
++ uid_string[uid_length] <= '9')
++ {
++ uid_length++;
++ }
++ uid_string = g_strndup(uid_string, uid_length);
++ purple_debug_info("facebook", "uid: %s\n", uid_string);
++
++ //find message:
++ // last index of
++ // /a> ... <
++ message_string = g_strrstr(message, "/a>");
++ if (!message_string)
++ {
++ g_free(uid_string);
++ continue;
++ }
++ message_string = strchr(message_string, '>');
++ if (!message_string)
++ {
++ g_free(uid_string);
++ continue;
++ }
++ message_string += 1;
++ message_string = g_strndup(message_string, g_strrstr(message_string, "<")-message_string);
++ purple_debug_info("facebook", "message: %s\n", message_string);
++
++ if (g_hash_table_lookup(processed_buddies, uid_string))
++ {
++ // Already processed a status message for this buddy
++ g_free(uid_string);
++ g_free(message_string);
++ continue;
++ }
++ g_hash_table_insert(processed_buddies, uid_string, uid_string);
++
++ buddy = purple_find_buddy(fba->account, uid_string);
++ if (buddy && buddy->proto_data)
++ {
++ fbuddy = buddy->proto_data;
++ g_free(fbuddy->status);
++
++ fbuddy->status = purple_strreplace(message_string, "&hearts;", "♥");
++ g_free(message_string); message_string = fbuddy->status;
++ fbuddy->status = purple_markup_strip_html(message_string);
++
++ purple_prpl_got_user_status(fba->account, buddy->name,
++ purple_status_get_id(purple_presence_get_active_status(
++ purple_buddy_get_presence(buddy))), "message", fbuddy->status, NULL);
++ }
++
++ g_free(message_string);
++ }
++ g_strfreev(messages);
++ g_hash_table_destroy(processed_buddies);
++
++ new_latest = json_node_get_int(json_object_get_member(
++ objnode, "newestStoryTime"));
++ if (!new_latest)
++ {
++ purple_debug_info("facebook", "no newestStoryTime\n");
++ } else {
++ fba->last_status_timestamp = new_latest;
++ }
++
++ json_parser_free(parser);
++}
++
++static void got_buddy_list_cb(FacebookAccount *fba, const gchar *data,
++ gsize data_len, gpointer userdata)
++{
++ GSList *buddies_list;
++ GHashTable *online_buddies_list = g_hash_table_new(
++ g_str_hash, g_str_equal);
++ gchar *uid;
++
++ purple_debug_info("facebook", "parsing buddy list\n");
++
++ if (fba == NULL)
++ return;
++
++ JsonParser *parser = fb_get_parser(data, data_len);
++ if (parser == NULL) {
++ if (fba->bad_buddy_list_count++ == 3)
++ {
++ purple_connection_error_reason(fba->pc,
++ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
++ _("Could not retrieve buddy list"));
++ }
++ return;
++ }
++
++ purple_debug_misc("facebook", "buddy list\n%s\n", data);
++
++ gchar *error = NULL;
++ JsonObject *objnode = fb_get_json_object(parser, &error);
++ if (error) {
++ purple_debug_info("facebook", "eion test\n");
++ if (json_node_get_int(json_object_get_member(objnode, "error")) == 1356007)
++ {
++ //Chat is down for maintenence
++ purple_connection_error_reason(fba->pc,
++ PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
++ error);
++ fba->pc->wants_to_die = TRUE;
++ }
++ if (fba->bad_buddy_list_count++ == 3)
++ {
++ purple_connection_error_reason(
++ fba->pc,
++ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
++ error);
++ }
++ g_free(error);
++ json_parser_free(parser);
++
++ return;
++ }
++
++ /* look for "userInfos":{ ... }, */
++ if (!json_object_has_member(objnode, "payload"))
++ {
++ json_parser_free(parser);
++ return;
++ }
++ objnode = json_node_get_object(json_object_get_member(
++ objnode, "payload"));
++ if (!json_object_has_member(objnode, "buddy_list"))
++ {
++ json_parser_free(parser);
++ return;
++ }
++ JsonObject *buddy_list = json_node_get_object(json_object_get_member(
++ objnode, "buddy_list"));
++ if (!json_object_has_member(buddy_list, "userInfos"))
++ {
++ json_parser_free(parser);
++ return;
++ }
++
++ //Reset invalid buddy list counter
++ fba->bad_buddy_list_count = 0;
++
++ if (purple_account_get_bool(fba->account, "facebook_use_groups", TRUE))
++ {
++ //Only process if we have the setting
++ fb_process_friend_lists(fba, buddy_list);
++ }
++
++ // Iterate through the list of buddy infos sent to us.
++ JsonObject *userInfos;
++ JsonObject *nowAvailableList;
++ userInfos = json_node_get_object(json_object_get_member(
++ buddy_list, "userInfos"));
++ nowAvailableList = json_node_get_object(json_object_get_member(
++ buddy_list, "nowAvailableList"));
++ GList *userIds;
++ userIds = json_object_get_members(userInfos);
++ GList *currentUserNode;
++ for( currentUserNode = userIds;
++ currentUserNode;
++ currentUserNode = g_list_next(currentUserNode))
++ {
++ uid = currentUserNode->data;
++
++ JsonObject *userInfo;
++ userInfo = json_node_get_object(json_object_get_member(
++ userInfos, uid));
++ // Process the user, which generally consists of updating
++ // state info such as name, idle item, status message,etc.
++ process_buddies(fba, online_buddies_list, nowAvailableList,
++ uid, userInfo);
++ }
++ g_list_free(userIds);
++
++ // Set users offline. We do this in a seperate function because FB
++ // only sends us a list of users who are online. We find the users
++ // that are not in the union of of buddy list users + online, and
++ // mark them as offline.
++ buddies_list = purple_find_buddies(fba->account, NULL);
++ if (buddies_list != NULL)
++ {
++ g_slist_foreach(
++ buddies_list,
++ (GFunc)set_buddies_offline, online_buddies_list);
++ g_slist_free(buddies_list);
++ }
++ g_hash_table_destroy(online_buddies_list);
++
++ // The buddy list also contains notifications data. Process and
++ // display is appropriate.
++ process_notifications(fba, json_node_get_object(
++ json_object_get_member(objnode, "notifications")));
++
++ json_parser_free(parser);
++}
++
++gboolean fb_get_buddy_list(gpointer data)
++{
++ FacebookAccount *fba;
++ gchar *postdata;
++
++ fba = data;
++
++ postdata = g_strdup_printf(
++ "user=%" G_GINT64_FORMAT "&popped_out=true&force_render=true&buddy_list=1&__a=1&post_form_id_source=AsyncRequest&post_form_id=%s&fb_dtsg=%s&notifications=1",
++ fba->uid, fba->post_form_id?fba->post_form_id:"(null)", fba->dtsg?fba->dtsg:"(null)");
++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/chat/buddy_list.php",
++ postdata, got_buddy_list_cb, NULL, FALSE);
++ g_free(postdata);
++
++ postdata = g_strdup_printf("/ajax/intent.php?filter=app_2915120374&request_type=1&__a=1&newest=%d&ignore_self=true",
++ fba->last_status_timestamp);
++ fb_post_or_get(fba, FB_METHOD_GET, NULL, postdata,
++ NULL, got_status_stream_cb, NULL, FALSE);
++ g_free(postdata);
++
++ return TRUE;
++}
++
++static void got_full_buddy_list(FacebookAccount *fba, const gchar *data,
++ gsize data_len, gpointer userdata)
++{
++ int i;
++ PurpleGroup *fb_group;
++ JsonParser *parser;
++ gchar *error = NULL;
++ JsonObject *objnode, *node;
++ JsonArray *entries;
++
++ purple_debug_info("facebook", "parsing full buddy list\n");
++
++ if (fba == NULL)
++ return;
++
++ parser = fb_get_parser(data, data_len);
++ if (parser == NULL)
++ return;
++
++ purple_debug_misc("facebook", "full buddy list\n%s\n", data);
++
++ objnode = fb_get_json_object(parser, &error);
++ if (!json_object_has_member(objnode, "payload"))
++ {
++ json_parser_free(parser);
++ return;
++ }
++ objnode = json_node_get_object(json_object_get_member(
++ objnode, "payload"));
++ if (!json_object_has_member(objnode, "entries"))
++ {
++ json_parser_free(parser);
++ return;
++ }
++ entries = json_node_get_array(json_object_get_member(
++ objnode, "entries"));
++
++ fb_group = purple_find_group(DEFAULT_GROUP_NAME);
++ if (fb_group == NULL)
++ {
++ fb_group = purple_group_new(DEFAULT_GROUP_NAME);
++ purple_blist_add_group(fb_group, NULL);
++ }
++ for(i = 0; i < json_array_get_length(entries); i++)
++ {
++ node = json_node_get_object(json_array_get_element(entries, i));
++ const gchar *type = json_node_get_string(json_object_get_member(node, "ty"));
++ if (type[0] != 'u' && type[0] != 'g')
++ continue;
++ const gchar *uid = json_node_get_string(json_object_get_member(node, "i"));
++ const gchar *name = json_node_get_string(json_object_get_member(node, "t"));
++
++ if (type[0] == 'g')
++ {
++ PurpleChat *chat = purple_blist_find_chat(fba->account, uid);
++ purple_blist_alias_chat(chat, name);
++ continue;
++ }
++
++ if (purple_find_buddy(fba->account, uid))
++ continue;
++
++ PurpleBuddy *buddy = purple_buddy_new(fba->account, uid, name);
++ purple_blist_add_buddy(buddy, NULL, fb_group, NULL);
++ FacebookBuddy *fbuddy = g_new0(FacebookBuddy, 1);
++ fbuddy->buddy = buddy;
++ fbuddy->fba = fba;
++ fbuddy->uid = atoll(uid);
++ fbuddy->name = g_strdup(name);
++ buddy->proto_data = fbuddy;
++
++ const gchar *thumb_url = json_node_get_string(json_object_get_member(node, "it"));
++ process_buddy_icon(fba, fbuddy, thumb_url);
++ }
++
++ json_parser_free(parser);
++}
++
++void fb_get_full_buddy_list(FacebookAccount *fba)
++{
++ gchar *url;
++
++ url = g_strdup_printf("/ajax/typeahead_search.php?u=%" G_GINT64_FORMAT "&__a=1", fba->uid);
++ fb_post_or_get(fba, FB_METHOD_GET, NULL, url, NULL, got_full_buddy_list, NULL, FALSE);
++ g_free(url);
++}
++
++void fb_blist_poke_buddy(PurpleBlistNode *node, gpointer data)
++{
++ PurpleBuddy *buddy;
++ FacebookBuddy *fbuddy;
++ FacebookAccount *fba;
++ gchar *postdata;
++
++ if(!PURPLE_BLIST_NODE_IS_BUDDY(node))
++ return;
++ buddy = (PurpleBuddy *) node;
++ if (!buddy)
++ return;
++ fbuddy = buddy->proto_data;
++ if (!fbuddy)
++ return;
++ fba = fbuddy->fba;
++ if (!fba)
++ return;
++
++ postdata = g_strdup_printf("uid=%" G_GINT64_FORMAT "&pokeback=0&post_form_id=%s", fbuddy->uid, fba->post_form_id);
++
++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/poke.php",
++ postdata, NULL, NULL, FALSE);
++
++ g_free(postdata);
++}
++
++void fb_blist_set_alias(FacebookAccount *fba, const gchar *id,
++ const gchar *name)
++{
++ const char *current_alias;
++ PurpleBuddy *buddy;
++
++ buddy = purple_find_buddy(fba->account, id);
++ if (!buddy) {
++ return;
++ }
++
++ /* Set an alias if no user-defined alias is set yet. This provides
++ * a basic name alias for each user which is more useful than a
++ * number. A small corner case bug here- aliases will not change
++ * in accordance with people changing their names on Facebook.
++ */
++ current_alias = purple_buddy_get_alias_only(buddy);
++ if (!current_alias) {
++ purple_debug_info("facebook", "aliasing %s to %s\n", id, name);
++ purple_blist_alias_buddy(buddy, name);
++ }
++
++ /* In case user removes an alias, we have the server as fallback */
++ serv_got_alias(fba->pc, id, name);
++}
++
++void fb_blist_init(FacebookAccount *fba)
++{
++ fb_friendlist_init(fba);
++
++ fb_get_buddy_list(fba);
++
++ /* periodically check for updates to your buddy list */
++ fba->buddy_list_timer = purple_timeout_add_seconds(60,
++ fb_get_buddy_list, fba);
++
++ fb_get_full_buddy_list(fba);
++}
++
++void fb_blist_destroy(FacebookAccount *fba)
++{
++ if (fba->buddy_list_timer) {
++ purple_timeout_remove(fba->buddy_list_timer);
++ }
++
++ fb_friendlist_destroy(fba);
++}
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_blist.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_blist.h
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_blist.h 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_blist.h 2011-03-27 09:15:30.736552999 -0600
+@@ -0,0 +1,37 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef FACEBOOK_BLIST_H
++#define FACEBOOK_BLIST_H
++
++#include "libfacebook.h"
++
++gboolean fb_get_buddy_list(gpointer data);
++void fb_blist_poke_buddy(PurpleBlistNode *node, gpointer data);
++
++void fb_blist_set_alias(FacebookAccount *fba, const char *id,
++ const char *name);
++
++void fb_blist_init(FacebookAccount *fba);
++void fb_blist_destroy(FacebookAccount *fba);
++
++void fb_get_full_buddy_list(FacebookAccount *fba);
++
++#endif /* FACEBOOK_BLIST_H */
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_chat.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_chat.c
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_chat.c 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_chat.c 2011-03-27 09:15:30.888552999 -0600
+@@ -0,0 +1,273 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "fb_chat.h"
++#include "fb_blist.h"
++#include "fb_util.h"
++#include "fb_connection.h"
++#include "fb_friendlist.h"
++#include "fb_messages.h"
++#include "fb_conversation.h"
++
++#include "conversation.h"
++
++void
++fb_got_facepile(FacebookAccount *fba, const gchar *data, gsize data_len, gpointer user_data)
++{
++ gchar *group = user_data;
++ JsonParser *parser;
++ JsonObject *object, *payload, *user_obj;
++ JsonArray *facepile;
++ PurpleConversation *conv;
++ PurpleConvChat *chat;
++ gchar *uid;
++ guint i;
++ PurpleGroup *pgroup;
++
++ purple_debug_info("facebook", "got facepile %s\n", data?data:"(null)");
++
++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group, fba->account);
++ chat = PURPLE_CONV_CHAT(conv);
++
++ parser = fb_get_parser(data, data_len);
++
++ if (!parser)
++ {
++ purple_debug_warning("facebook",
++ "could not fetch facepile for group %s\n", group);
++ g_free(group);
++ return;
++ }
++
++ object = fb_get_json_object(parser, NULL);
++ payload = json_node_get_object(
++ json_object_get_member(object, "payload"));
++ facepile = json_node_get_array(
++ json_object_get_member(payload, "facepile_click_info"));
++
++ pgroup = purple_find_group(DEFAULT_GROUP_NAME);
++ if (!pgroup)
++ {
++ pgroup = purple_group_new(DEFAULT_GROUP_NAME);
++ purple_blist_add_group(pgroup, NULL);
++ }
++
++ purple_conv_chat_clear_users(chat);
++ uid = g_strdup_printf("%" G_GINT64_FORMAT, fba->uid);
++ purple_conv_chat_add_user(chat, uid, NULL, PURPLE_CBFLAGS_NONE, FALSE);
++ if (!purple_find_buddy(fba->account, uid))
++ {
++ PurpleBuddy *buddy = purple_buddy_new(fba->account, uid, "You");
++ purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE);
++ purple_blist_add_buddy(buddy, NULL, pgroup, NULL);
++ }
++ g_free(uid);
++
++ for (i = 0; i < json_array_get_length(facepile); i++)
++ {
++ user_obj = json_node_get_object(
++ json_array_get_element(facepile, i));
++ uid = g_strdup_printf("%" G_GINT64_FORMAT, (gint64)json_node_get_int(json_object_get_member(user_obj, "uid")));
++
++ purple_conv_chat_add_user(PURPLE_CONV_CHAT(conv), uid, NULL, PURPLE_CBFLAGS_NONE, FALSE);
++
++ if (!purple_find_buddy(fba->account, uid))
++ {
++ const char *alias = json_node_get_string(json_object_get_member(user_obj, "name"));
++ PurpleBuddy *buddy = purple_buddy_new(fba->account, uid, alias);
++ purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE);
++ purple_blist_add_buddy(buddy, NULL, pgroup, NULL);
++ }
++
++ g_free(uid);
++ }
++
++ g_free(group);
++}
++
++PurpleConversation *
++fb_find_chat(FacebookAccount *fba, const gchar *group)
++{
++ PurpleConversation *conv;
++ gchar *postdata;
++
++ conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group, fba->account);
++
++ if (conv == NULL)
++ {
++ conv = serv_got_joined_chat(fba->pc, atoi(group), group);
++
++ postdata = g_strdup_printf("gid=%s&post_form_id=%s&fb_dtsg=%s&lsd=", group,
++ fba->post_form_id, fba->dtsg);
++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/groups/chat/update_facepiles.php?__a=1",
++ postdata, fb_got_facepile, g_strdup(group), FALSE);
++ g_free(postdata);
++ }
++
++ return conv;
++}
++
++void
++fb_got_groups(FacebookAccount *fba, const gchar *data, gsize data_len, gpointer user_data)
++{
++ // look for /home.php?sk=group_ ...
++ gchar **splits;
++ gint i;
++ PurpleGroup *group;
++
++ splits = g_strsplit(data, "<a href=\\\"\\/home.php?sk=group_", 0);
++
++ if (!splits || !splits[0])
++ {
++ g_strfreev(splits);
++ return;
++ }
++
++ group = purple_find_group(DEFAULT_GROUP_NAME);
++ if (!group)
++ {
++ group = purple_group_new(DEFAULT_GROUP_NAME);
++ purple_blist_add_group(group, NULL);
++ }
++
++ for(i = 1; splits[i]; i++)
++ {
++ gchar *eos;
++ eos = strchr(splits[i], '\\');
++ if (eos != NULL)
++ {
++ *eos = '\0';
++ purple_debug_info("facebook", "searching for %s\n", splits[i]);
++ if (!purple_blist_find_chat(fba->account, splits[i]))
++ {
++ gchar *alias = NULL;
++ if (eos[1] == '"' && eos[2] == '>')
++ {
++ purple_debug_info("facebook", "searching for alias\n");
++ gchar *eoa = strchr(&eos[3], '<');
++ if (eoa)
++ {
++ *eoa = '\0';
++ alias = &eos[3];
++ purple_debug_info("facebook", "found chat alias %s\n", alias);
++ }
++ }
++
++ purple_debug_info("facebook", "adding chat %s to buddy list...\n", splits[i]);
++ // Add the group chat to the buddy list
++ GHashTable *components = fb_chat_info_defaults(fba->pc, splits[i]);
++ PurpleChat *chat = purple_chat_new(fba->account, alias, components);
++ purple_blist_add_chat(chat, group, NULL);
++ purple_debug_info("facebook", "done\n");
++ }
++ }
++ }
++
++ g_strfreev(splits);
++}
++
++void
++fb_get_groups(FacebookAccount *fba)
++{
++ fb_post_or_get(fba, FB_METHOD_GET, NULL, "/ajax/home/groups.php?__a=1", NULL, fb_got_groups, NULL, FALSE);
++}
++
++int
++fb_chat_send(PurpleConnection *pc, int id, const char *message, PurpleMessageFlags flags)
++{
++ PurpleConversation *conv;
++ const char *group;
++
++ conv = purple_find_chat(pc, id);
++ if (conv != NULL)
++ {
++ group = purple_conversation_get_name(conv);
++
++ return fb_send_im(pc, group, message, flags);
++ }
++
++ return -1;
++}
++
++void
++fb_chat_fake_leave(PurpleConnection *pc, int id)
++{
++ PurpleConversation *conv;
++ const char *group;
++
++ conv = purple_find_chat(pc, id);
++ if (conv != NULL)
++ {
++ group = purple_conversation_get_name(conv);
++ fb_conversation_closed(pc, group);
++ }
++}
++
++GHashTable *
++fb_chat_info_defaults(PurpleConnection *pc, const char *chat_name)
++{
++ GHashTable *table;
++
++ table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
++
++ if (chat_name != NULL)
++ {
++ g_hash_table_insert(table, "group", g_strdup(chat_name));
++ }
++
++ return table;
++}
++
++gchar *
++fb_get_chat_name(GHashTable *components)
++{
++ gchar *group;
++
++ group = (gchar *) g_hash_table_lookup(components, "group");
++
++ return g_strdup(group);
++}
++
++GList *
++fb_chat_info(PurpleConnection *pc)
++{
++ GList *m = NULL;
++ struct proto_chat_entry *pce;
++
++ pce = g_new0(struct proto_chat_entry, 1);
++ pce->label = _("Group ID");
++ pce->identifier = "group";
++ pce->required = TRUE;
++ m = g_list_append(m, pce);
++
++ return m;
++}
++
++void
++fb_fake_join_chat(PurpleConnection *pc, GHashTable *components)
++{
++ FacebookAccount *fba = pc->proto_data;
++ gchar *group = (gchar *) g_hash_table_lookup(components, "group");
++
++ if (group != NULL)
++ {
++ fb_find_chat(fba, group);
++ }
++}
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_chat.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_chat.h
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_chat.h 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_chat.h 2011-03-27 09:15:30.876553000 -0600
+@@ -0,0 +1,35 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef FACEBOOK_CHAT_H
++#define FACEBOOK_CHAT_H
++
++#include "libfacebook.h"
++
++PurpleConversation *fb_find_chat(FacebookAccount *fba, const gchar *group);
++void fb_get_groups(FacebookAccount *fba);
++int fb_chat_send(PurpleConnection *, int id, const char *message, PurpleMessageFlags flags);
++void fb_chat_fake_leave(PurpleConnection *, int id);
++GHashTable *fb_chat_info_defaults(PurpleConnection *, const char *chat_name);
++gchar *fb_get_chat_name(GHashTable *components);
++GList *fb_chat_info(PurpleConnection *);
++void fb_fake_join_chat(PurpleConnection *, GHashTable *components);
++
++#endif /* FACEBOOK_CHAT_H */
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_connection.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_connection.c
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_connection.c 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_connection.c 2011-03-27 09:15:30.744552999 -0600
+@@ -0,0 +1,685 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "fb_connection.h"
++
++#if !GLIB_CHECK_VERSION (2, 22, 0)
++#define g_hostname_is_ip_address(hostname) (g_ascii_isdigit(hostname[0]) && g_strstr_len(hostname, 4, "."))
++#endif
++
++static void fb_attempt_connection(FacebookConnection *);
++static void fb_next_connection(FacebookAccount *fba);
++
++#ifdef HAVE_ZLIB
++#include <zlib.h>
++
++static gchar *fb_gunzip(const guchar *gzip_data, ssize_t *len_ptr)
++{
++ gsize gzip_data_len = *len_ptr;
++ z_stream zstr;
++ int gzip_err = 0;
++ gchar *data_buffer;
++ gulong gzip_len = G_MAXUINT16;
++ GString *output_string = NULL;
++
++ data_buffer = g_new0(gchar, gzip_len);
++
++ zstr.next_in = NULL;
++ zstr.avail_in = 0;
++ zstr.zalloc = Z_NULL;
++ zstr.zfree = Z_NULL;
++ zstr.opaque = 0;
++ gzip_err = inflateInit2(&zstr, MAX_WBITS+32);
++ if (gzip_err != Z_OK)
++ {
++ g_free(data_buffer);
++ purple_debug_error("facebook", "no built-in gzip support in zlib\n");
++ return NULL;
++ }
++
++ zstr.next_in = (Bytef *)gzip_data;
++ zstr.avail_in = gzip_data_len;
++
++ zstr.next_out = (Bytef *)data_buffer;
++ zstr.avail_out = gzip_len;
++
++ gzip_err = inflate(&zstr, Z_SYNC_FLUSH);
++
++ if (gzip_err == Z_DATA_ERROR)
++ {
++ inflateEnd(&zstr);
++ inflateInit2(&zstr, -MAX_WBITS);
++ if (gzip_err != Z_OK)
++ {
++ g_free(data_buffer);
++ purple_debug_error("facebook", "Cannot decode gzip header\n");
++ return NULL;
++ }
++ zstr.next_in = (Bytef *)gzip_data;
++ zstr.avail_in = gzip_data_len;
++ zstr.next_out = (Bytef *)data_buffer;
++ zstr.avail_out = gzip_len;
++ gzip_err = inflate(&zstr, Z_SYNC_FLUSH);
++ }
++ output_string = g_string_new("");
++ while (gzip_err == Z_OK)
++ {
++ //append data to buffer
++ output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out);
++ //reset buffer pointer
++ zstr.next_out = (Bytef *)data_buffer;
++ zstr.avail_out = gzip_len;
++ gzip_err = inflate(&zstr, Z_SYNC_FLUSH);
++ }
++ if (gzip_err == Z_STREAM_END)
++ {
++ output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out);
++ } else {
++ purple_debug_error("facebook", "gzip inflate error\n");
++ }
++ inflateEnd(&zstr);
++
++ g_free(data_buffer);
++
++ if (len_ptr)
++ *len_ptr = output_string->len;
++
++ return g_string_free(output_string, FALSE);
++}
++#else /* !HAVE_ZLIB */
++#warning You really want to compile with -DHAVE_ZLIB
++#endif
++
++void fb_connection_destroy(FacebookConnection *fbconn)
++{
++ fbconn->fba->conns = g_slist_remove(fbconn->fba->conns, fbconn);
++
++ if (fbconn->request != NULL)
++ g_string_free(fbconn->request, TRUE);
++
++ g_free(fbconn->rx_buf);
++
++ if (fbconn->connect_data != NULL)
++ purple_proxy_connect_cancel(fbconn->connect_data);
++
++ if (fbconn->ssl_conn != NULL)
++ purple_ssl_close(fbconn->ssl_conn);
++
++ if (fbconn->fd >= 0) {
++ close(fbconn->fd);
++ }
++
++ if (fbconn->input_watcher > 0)
++ purple_input_remove(fbconn->input_watcher);
++
++ g_free(fbconn->url);
++ g_free(fbconn->hostname);
++ g_free(fbconn);
++}
++
++static void fb_update_cookies(FacebookAccount *fba, const gchar *headers)
++{
++ const gchar *cookie_start;
++ const gchar *cookie_end;
++ gchar *cookie_name;
++ gchar *cookie_value;
++ int header_len;
++
++ g_return_if_fail(headers != NULL);
++
++ header_len = strlen(headers);
++
++ /* look for the next "Set-Cookie: " */
++ /* grab the data up until ';' */
++ cookie_start = headers;
++ while ((cookie_start = strstr(cookie_start, "\r\nSet-Cookie: ")) &&
++ (cookie_start - headers) < header_len)
++ {
++ cookie_start += 14;
++ cookie_end = strchr(cookie_start, '=');
++ cookie_name = g_strndup(cookie_start, cookie_end-cookie_start);
++ cookie_start = cookie_end + 1;
++ cookie_end = strchr(cookie_start, ';');
++ cookie_value= g_strndup(cookie_start, cookie_end-cookie_start);
++ cookie_start = cookie_end;
++
++ g_hash_table_replace(fba->cookie_table, cookie_name,
++ cookie_value);
++ }
++}
++
++static void fb_connection_process_data(FacebookConnection *fbconn)
++{
++ ssize_t len;
++ gchar *tmp;
++
++ len = fbconn->rx_len;
++ tmp = g_strstr_len(fbconn->rx_buf, len, "\r\n\r\n");
++ if (tmp == NULL) {
++ /* This is a corner case that occurs when the connection is
++ * prematurely closed either on the client or the server.
++ * This can either be no data at all or a partial set of
++ * headers. We pass along the data to be good, but don't
++ * do any fancy massaging. In all likelihood the result will
++ * be tossed by the connection callback func anyways
++ */
++ tmp = g_strndup(fbconn->rx_buf, len);
++ } else {
++ tmp += 4;
++ len -= g_strstr_len(fbconn->rx_buf, len, "\r\n\r\n") -
++ fbconn->rx_buf + 4;
++ tmp = g_memdup(tmp, len + 1);
++ tmp[len] = '\0';
++ fbconn->rx_buf[fbconn->rx_len - len] = '\0';
++ fb_update_cookies(fbconn->fba, fbconn->rx_buf);
++
++#ifdef HAVE_ZLIB
++ if (strstr(fbconn->rx_buf, "Content-Encoding: gzip"))
++ {
++ /* we've received compressed gzip data, decompress */
++ gchar *gunzipped;
++ gunzipped = fb_gunzip((const guchar *)tmp, &len);
++ g_free(tmp);
++ tmp = gunzipped;
++ }
++#endif
++ }
++
++ g_free(fbconn->rx_buf);
++ fbconn->rx_buf = NULL;
++
++ if (fbconn->callback != NULL) {
++ purple_debug_info("facebook", "executing callback for %s\n", fbconn->url);
++ fbconn->callback(fbconn->fba, tmp, len, fbconn->user_data);
++ }
++
++ g_free(tmp);
++}
++
++static void fb_fatal_connection_cb(FacebookConnection *fbconn)
++{
++ PurpleConnection *pc = fbconn->fba->pc;
++
++ purple_debug_error("facebook", "fatal connection error\n");
++
++ fb_connection_destroy(fbconn);
++
++ /* We died. Do not pass Go. Do not collect $200 */
++ /* In all seriousness, don't attempt to call the normal callback here.
++ * That may lead to the wrong error message being displayed */
++ purple_connection_error_reason(pc,
++ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
++ _("Server closed the connection."));
++
++}
++
++static void fb_post_or_get_readdata_cb(gpointer data, gint source,
++ PurpleInputCondition cond)
++{
++ FacebookConnection *fbconn;
++ FacebookAccount *fba;
++ gchar buf[4096];
++ ssize_t len;
++
++ fbconn = data;
++ fba = fbconn->fba;
++
++ if (fbconn->method & FB_METHOD_SSL) {
++ len = purple_ssl_read(fbconn->ssl_conn,
++ buf, sizeof(buf) - 1);
++ } else {
++ len = recv(fbconn->fd, buf, sizeof(buf) - 1, 0);
++ }
++
++ if (len < 0)
++ {
++ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
++ /* Try again later */
++ return;
++ }
++
++ if (fbconn->method & FB_METHOD_SSL && fbconn->rx_len > 0) {
++ /*
++ * This is a slightly hacky workaround for a bug in either
++ * GNU TLS or in the SSL implementation on Facebook's web
++ * servers. The sequence of events is:
++ * 1. We attempt to read the first time and successfully read
++ * the server's response.
++ * 2. We attempt to read a second time and libpurple's call
++ * to gnutls_record_recv() returns the error
++ * GNUTLS_E_UNEXPECTED_PACKET_LENGTH, or
++ * "A TLS packet with unexpected length was received."
++ *
++ * Normally the server would have closed the connection
++ * cleanly and this second read() request would have returned
++ * 0. Or maybe it's normal for SSL connections to be severed
++ * in this manner? In any case, this differs from the behavior
++ * of the standard recv() system call.
++ */
++ purple_debug_warning("facebook",
++ "ssl error, but data received. attempting to continue\n");
++ } else {
++ /* TODO: Is this a regular occurrence? If so then maybe resend the request? */
++ fb_fatal_connection_cb(fbconn);
++ return;
++ }
++ }
++
++ if (len > 0)
++ {
++ buf[len] = '\0';
++
++ fbconn->rx_buf = g_realloc(fbconn->rx_buf,
++ fbconn->rx_len + len + 1);
++ memcpy(fbconn->rx_buf + fbconn->rx_len, buf, len + 1);
++ fbconn->rx_len += len;
++
++ /* Wait for more data before processing */
++ return;
++ }
++
++ /* The server closed the connection, let's parse the data */
++ fb_connection_process_data(fbconn);
++
++ fb_connection_destroy(fbconn);
++
++ fb_next_connection(fba);
++}
++
++static void fb_post_or_get_ssl_readdata_cb (gpointer data,
++ PurpleSslConnection *ssl, PurpleInputCondition cond)
++{
++ fb_post_or_get_readdata_cb(data, -1, cond);
++}
++
++static void fb_post_or_get_connect_cb(gpointer data, gint source,
++ const gchar *error_message)
++{
++ FacebookConnection *fbconn;
++ ssize_t len;
++
++ fbconn = data;
++ fbconn->connect_data = NULL;
++
++ if (error_message)
++ {
++ purple_debug_error("facebook", "post_or_get_connect failure to %s\n", fbconn->url);
++ purple_debug_error("facebook", "post_or_get_connect_cb %s\n",
++ error_message);
++ fb_fatal_connection_cb(fbconn);
++ return;
++ }
++
++ fbconn->fd = source;
++
++ /* TODO: Check the return value of write() */
++ len = write(fbconn->fd, fbconn->request->str,
++ fbconn->request->len);
++ fbconn->input_watcher = purple_input_add(fbconn->fd,
++ PURPLE_INPUT_READ,
++ fb_post_or_get_readdata_cb, fbconn);
++}
++
++static void fb_post_or_get_ssl_connect_cb(gpointer data,
++ PurpleSslConnection *ssl, PurpleInputCondition cond)
++{
++ FacebookConnection *fbconn;
++ ssize_t len;
++
++ fbconn = data;
++
++ purple_debug_info("facebook", "post_or_get_ssl_connect_cb\n");
++
++ /* TODO: Check the return value of write() */
++ len = purple_ssl_write(fbconn->ssl_conn,
++ fbconn->request->str, fbconn->request->len);
++ purple_ssl_input_add(fbconn->ssl_conn,
++ fb_post_or_get_ssl_readdata_cb, fbconn);
++}
++
++static void fb_host_lookup_cb(GSList *hosts, gpointer data,
++ const char *error_message)
++{
++ GSList *host_lookup_list;
++ struct sockaddr_in *addr;
++ gchar *hostname;
++ gchar *ip_address;
++ FacebookAccount *fba;
++ PurpleDnsQueryData *query;
++
++ /* Extract variables */
++ host_lookup_list = data;
++
++ fba = host_lookup_list->data;
++ host_lookup_list =
++ g_slist_delete_link(host_lookup_list, host_lookup_list);
++ hostname = host_lookup_list->data;
++ host_lookup_list =
++ g_slist_delete_link(host_lookup_list, host_lookup_list);
++ query = host_lookup_list->data;
++ host_lookup_list =
++ g_slist_delete_link(host_lookup_list, host_lookup_list);
++
++ /* The callback has executed, so we no longer need to keep track of
++ * the original query. This always needs to run when the cb is
++ * executed. */
++ fba->dns_queries = g_slist_remove(fba->dns_queries, query);
++
++ /* Any problems, capt'n? */
++ if (error_message != NULL)
++ {
++ purple_debug_warning("facebook",
++ "Error doing host lookup: %s\n", error_message);
++ return;
++ }
++
++ if (hosts == NULL)
++ {
++ purple_debug_warning("facebook",
++ "Could not resolve host name\n");
++ return;
++ }
++
++ /* Discard the length... */
++ hosts = g_slist_delete_link(hosts, hosts);
++ /* Copy the address then free it... */
++ addr = hosts->data;
++ ip_address = g_strdup(inet_ntoa(addr->sin_addr));
++ g_free(addr);
++ hosts = g_slist_delete_link(hosts, hosts);
++
++ /*
++ * DNS lookups can return a list of IP addresses, but we only cache
++ * the first one. So free the rest.
++ */
++ while (hosts != NULL)
++ {
++ /* Discard the length... */
++ hosts = g_slist_delete_link(hosts, hosts);
++ /* Free the address... */
++ g_free(hosts->data);
++ hosts = g_slist_delete_link(hosts, hosts);
++ }
++
++ g_hash_table_insert(fba->hostname_ip_cache, hostname, ip_address);
++}
++
++static void fb_cookie_foreach_cb(gchar *cookie_name,
++ gchar *cookie_value, GString *str)
++{
++ /* TODO: Need to escape name and value? */
++ g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value);
++}
++
++/**
++ * Serialize the fba->cookie_table hash table to a string.
++ */
++gchar *fb_cookies_to_string(FacebookAccount *fba)
++{
++ GString *str;
++
++ str = g_string_new(NULL);
++
++ g_hash_table_foreach(fba->cookie_table,
++ (GHFunc)fb_cookie_foreach_cb, str);
++
++ return g_string_free(str, FALSE);
++}
++
++static void fb_ssl_connection_error(PurpleSslConnection *ssl,
++ PurpleSslErrorType errortype, gpointer data)
++{
++ FacebookConnection *fbconn = data;
++ PurpleConnection *pc = fbconn->fba->pc;
++
++ fbconn->ssl_conn = NULL;
++ fb_connection_destroy(fbconn);
++ purple_connection_ssl_error(pc, errortype);
++}
++
++void fb_post_or_get(FacebookAccount *fba, FacebookMethod method,
++ const gchar *host, const gchar *url, const gchar *postdata,
++ FacebookProxyCallbackFunc callback_func, gpointer user_data,
++ gboolean keepalive)
++{
++ GString *request;
++ gchar *cookies;
++ FacebookConnection *fbconn;
++ gchar *real_url;
++ gboolean is_proxy = FALSE;
++ const gchar *user_agent;
++ const gchar* const *languages;
++ gchar *language_names;
++ PurpleProxyInfo *proxy_info = NULL;
++ gchar *proxy_auth;
++ gchar *proxy_auth_base64;
++
++ /* TODO: Fix keepalive and use it as much as possible */
++ keepalive = FALSE;
++
++ if (host == NULL)
++ host = "www.facebook.com";
++
++ if (fba && fba->account)
++ {
++ if (purple_account_get_bool(fba->account, "use-https", FALSE))
++ method |= FB_METHOD_SSL;
++ }
++
++ if (fba && fba->account && !(method & FB_METHOD_SSL))
++ {
++ proxy_info = purple_proxy_get_setup(fba->account);
++ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL)
++ proxy_info = purple_global_proxy_get_info();
++ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP)
++ {
++ is_proxy = TRUE;
++ }
++ }
++ if (is_proxy == TRUE)
++ {
++ real_url = g_strdup_printf("http://%s%s", host, url);
++ } else {
++ real_url = g_strdup(url);
++ }
++
++ cookies = fb_cookies_to_string(fba);
++ user_agent = purple_account_get_string(fba->account, "user-agent", "Opera/9.50 (Windows NT 5.1; U; en-GB)");
++
++ if (method & FB_METHOD_POST && !postdata)
++ postdata = "";
++
++ /* Build the request */
++ request = g_string_new(NULL);
++ g_string_append_printf(request, "%s %s HTTP/1.0\r\n",
++ (method & FB_METHOD_POST) ? "POST" : "GET",
++ real_url);
++ if (is_proxy == FALSE)
++ g_string_append_printf(request, "Host: %s\r\n", host);
++ g_string_append_printf(request, "Connection: %s\r\n",
++ (keepalive ? "Keep-Alive" : "close"));
++ g_string_append_printf(request, "User-Agent: %s\r\n", user_agent);
++ if (method & FB_METHOD_POST) {
++ g_string_append_printf(request,
++ "Content-Type: application/x-www-form-urlencoded\r\n");
++ g_string_append_printf(request,
++ "Content-length: %zu\r\n", strlen(postdata));
++ }
++ g_string_append_printf(request, "Accept: */*\r\n");
++ g_string_append_printf(request, "Cookie: isfbe=false;%s\r\n", cookies);
++#ifdef HAVE_ZLIB
++ g_string_append_printf(request, "Accept-Encoding: gzip\r\n");
++#endif
++ if (is_proxy == TRUE)
++ {
++ if (purple_proxy_info_get_username(proxy_info) &&
++ purple_proxy_info_get_password(proxy_info))
++ {
++ proxy_auth = g_strdup_printf("%s:%s", purple_proxy_info_get_username(proxy_info), purple_proxy_info_get_password(proxy_info));
++ proxy_auth_base64 = purple_base64_encode((guchar *)proxy_auth, strlen(proxy_auth));
++ g_string_append_printf(request, "Proxy-Authorization: Basic %s\r\n", proxy_auth_base64);
++ g_free(proxy_auth_base64);
++ g_free(proxy_auth);
++ }
++ }
++
++ /* Tell the server what language we accept, so that we get error messages in our language (rather than our IP's) */
++ languages = g_get_language_names();
++ language_names = g_strjoinv(", ", (gchar **)languages);
++ purple_util_chrreplace(language_names, '_', '-');
++ g_string_append_printf(request, "Accept-Language: %s\r\n", language_names);
++ g_free(language_names);
++
++ purple_debug_info("facebook", "getting url %s\n", url);
++
++ g_string_append_printf(request, "\r\n");
++ if (method & FB_METHOD_POST)
++ g_string_append_printf(request, "%s", postdata);
++
++ /* If it needs to go over a SSL connection, we probably shouldn't print
++ * it in the debug log. Without this condition a user's password is
++ * printed in the debug log */
++ if (method == FB_METHOD_POST)
++ purple_debug_info("facebook", "sending request data:\n%s\n",
++ postdata);
++
++ g_free(cookies);
++
++ fbconn = g_new0(FacebookConnection, 1);
++ fbconn->fba = fba;
++ fbconn->url = real_url;
++ fbconn->method = method;
++ fbconn->hostname = g_strdup(host);
++ fbconn->request = request;
++ fbconn->callback = callback_func;
++ fbconn->user_data = user_data;
++ fbconn->fd = -1;
++ fbconn->connection_keepalive = keepalive;
++ fbconn->request_time = time(NULL);
++
++ g_queue_push_head(fba->waiting_conns, fbconn);
++ fb_next_connection(fba);
++}
++
++static void fb_next_connection(FacebookAccount *fba)
++{
++ FacebookConnection *fbconn;
++
++ g_return_if_fail(fba != NULL);
++
++ if (!g_queue_is_empty(fba->waiting_conns))
++ {
++ if(g_slist_length(fba->conns) < FB_MAX_CONNECTIONS)
++ {
++ fbconn = g_queue_pop_tail(fba->waiting_conns);
++ fb_attempt_connection(fbconn);
++ }
++ }
++}
++
++static void fb_attempt_connection(FacebookConnection *fbconn)
++{
++ gboolean is_proxy = FALSE;
++ FacebookAccount *fba = fbconn->fba;
++ PurpleProxyInfo *proxy_info = NULL;
++
++ if (fba && fba->account && !(fbconn->method & FB_METHOD_SSL))
++ {
++ proxy_info = purple_proxy_get_setup(fba->account);
++ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL)
++ proxy_info = purple_global_proxy_get_info();
++ if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP)
++ {
++ is_proxy = TRUE;
++ }
++ }
++
++#if 0
++ /* Connection to attempt retries. This code doesn't work perfectly, but
++ * remains here for future reference if needed */
++ if (time(NULL) - fbconn->request_time > 5) {
++ /* We've continuously tried to remake this connection for a
++ * bit now. It isn't happening, sadly. Time to die. */
++ purple_debug_error("facebook", "could not connect after retries\n");
++ fb_fatal_connection_cb(fbconn);
++ return;
++ }
++
++ purple_debug_info("facebook", "making connection attempt\n");
++
++ /* TODO: If we're retrying the connection, consider clearing the cached
++ * DNS value. This will require some juggling with the hostname param */
++ /* TODO/FIXME: This retries almost instantenously, which in some cases
++ * runs at blinding speed. Slow it down. */
++ /* TODO/FIXME: this doesn't retry properly on non-ssl connections */
++#endif
++
++ fba->conns = g_slist_prepend(fba->conns, fbconn);
++
++ /*
++ * Do a separate DNS lookup for the given host name and cache it
++ * for next time.
++ *
++ * TODO: It would be better if we did this before we call
++ * purple_proxy_connect(), so we could re-use the result.
++ * Or even better: Use persistent HTTP connections for servers
++ * that we access continually.
++ *
++ * TODO: This cache of the hostname<-->IP address does not respect
++ * the TTL returned by the DNS server. We should expire things
++ * from the cache after some amount of time.
++ */
++ if (!is_proxy && !(fbconn->method & FB_METHOD_SSL) && !g_hostname_is_ip_address(fbconn->hostname))
++ {
++ /* Don't do this for proxy connections, since proxies do the DNS lookup */
++ gchar *host_ip;
++
++ host_ip = g_hash_table_lookup(fba->hostname_ip_cache, fbconn->hostname);
++ if (host_ip != NULL) {
++ g_free(fbconn->hostname);
++ fbconn->hostname = g_strdup(host_ip);
++ } else if (fba->account && !fba->account->disconnecting) {
++ GSList *host_lookup_list = NULL;
++ PurpleDnsQueryData *query;
++
++ host_lookup_list = g_slist_prepend(
++ host_lookup_list, g_strdup(fbconn->hostname));
++ host_lookup_list = g_slist_prepend(
++ host_lookup_list, fba);
++
++ query = purple_dnsquery_a(fbconn->hostname, 80,
++ fb_host_lookup_cb, host_lookup_list);
++ fba->dns_queries = g_slist_prepend(fba->dns_queries, query);
++ host_lookup_list = g_slist_append(host_lookup_list, query);
++ }
++ }
++
++ if (fbconn->method & FB_METHOD_SSL) {
++ fbconn->ssl_conn = purple_ssl_connect(fba->account, fbconn->hostname,
++ 443, fb_post_or_get_ssl_connect_cb,
++ fb_ssl_connection_error, fbconn);
++ } else {
++ fbconn->connect_data = purple_proxy_connect(NULL, fba->account,
++ fbconn->hostname, 80, fb_post_or_get_connect_cb, fbconn);
++ }
++
++ return;
++}
++
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_connection.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_connection.h
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_connection.h 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_connection.h 2011-03-27 09:15:30.712552999 -0600
+@@ -0,0 +1,62 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef FACEBOOK_CONNECTION_H
++#define FACEBOOK_CONNECTION_H
++
++#include "libfacebook.h"
++
++/*
++ * This is a bitmask.
++ */
++typedef enum
++{
++ FB_METHOD_GET = 0x0001,
++ FB_METHOD_POST = 0x0002,
++ FB_METHOD_SSL = 0x0004
++} FacebookMethod;
++
++typedef struct _FacebookConnection FacebookConnection;
++struct _FacebookConnection {
++ FacebookAccount *fba;
++ FacebookMethod method;
++ gchar *hostname;
++ gchar *url;
++ GString *request;
++ FacebookProxyCallbackFunc callback;
++ gpointer user_data;
++ char *rx_buf;
++ size_t rx_len;
++ PurpleProxyConnectData *connect_data;
++ PurpleSslConnection *ssl_conn;
++ int fd;
++ guint input_watcher;
++ gboolean connection_keepalive;
++ time_t request_time;
++};
++
++void fb_connection_destroy(FacebookConnection *fbconn);
++void fb_post_or_get(FacebookAccount *fba, FacebookMethod method,
++ const gchar *host, const gchar *url, const gchar *postdata,
++ FacebookProxyCallbackFunc callback_func, gpointer user_data,
++ gboolean keepalive);
++gchar *fb_cookies_to_string(FacebookAccount *fba);
++
++#endif /* FACEBOOK_CONNECTION_H */
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_conversation.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_conversation.c
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_conversation.c 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_conversation.c 2011-03-27 09:15:30.860553000 -0600
+@@ -0,0 +1,307 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "fb_conversation.h"
++#include "fb_connection.h"
++#include "fb_util.h"
++#include "fb_json.h"
++
++#include "conversation.h"
++#include "signals.h"
++
++/*****************************************************************************
++ * MESSAGE PROCESSING *
++ *****************************************************************************/
++void fb_conversation_handle_message(FacebookAccount *fba, const char *from,
++ const char *to, gint64 message_time, const gchar *message_orig,
++ gboolean log)
++{
++ gchar *tmp, *message_text;
++
++ if (!log) {
++ purple_debug_info("facebook", "message with no logging\n");
++ }
++
++ // Process message.
++ message_text = fb_strdup_withhtml(message_orig);
++ tmp = message_text;
++ message_text = fb_replace_styled_text(message_text);
++ g_free(tmp);
++
++ if (fba->uid != atoll(from) || fba->uid == atoll(to)) {
++ purple_debug_info("facebook",
++ "displaying received message %lld: %s\n",
++ (long long int) message_time, message_text);
++ // TODO/FIXME: cheat here by changing formatting colors.
++ // Or add an option to just disable history on conv open. TBD.
++ serv_got_im(fba->pc, from, message_text,
++ log?
++ PURPLE_MESSAGE_RECV :
++ PURPLE_MESSAGE_RECV,
++ message_time / 1000);
++ if (message_time > fba->last_message_time) {
++ fba->last_message_time = message_time;
++ } else {
++ purple_debug_warning("facebook",
++ "displaying message out of sync\n");
++ }
++ } else if (!g_hash_table_remove(
++ fba->sent_messages_hash, message_orig))
++ {
++ purple_debug_info("facebook",
++ "displaying sent message %lld: %s\n",
++ (long long int) message_time, message_text);
++
++ serv_got_im(fba->pc, to, message_text,
++ log?
++ PURPLE_MESSAGE_SEND :
++ PURPLE_MESSAGE_SEND,
++ message_time / 1000);
++ if (message_time > fba->last_message_time) {
++ fba->last_message_time = message_time;
++ } else {
++ purple_debug_warning("facebook",
++ "displaying message out of sync\n");
++ }
++ }
++
++ // Cleanup.
++ g_free(message_text);
++}
++
++void fb_conversation_handle_chat(FacebookAccount *fba, const char *from,
++ const char *group, gint64 message_time, const gchar *message_orig,
++ gboolean log)
++{
++ gchar *tmp, *message_text;
++
++ if (!log) {
++ purple_debug_info("facebook", "message with no logging\n");
++ }
++
++ // Process message.
++ message_text = fb_strdup_withhtml(message_orig);
++ tmp = message_text;
++ message_text = fb_replace_styled_text(message_text);
++ g_free(tmp);
++
++ purple_debug_info("facebook",
++ "displaying group message %lld: %s\n",
++ (long long int) message_time, message_text);
++
++ if (!purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, group, fba->account))
++ serv_got_joined_chat(fba->pc, atoi(group), group);
++
++ serv_got_chat_in(fba->pc, atoi(group), from, PURPLE_MESSAGE_RECV, message_text, message_time / 1000);
++
++ if (message_time > fba->last_message_time)
++ {
++ fba->last_message_time = message_time;
++ } else {
++ purple_debug_warning("facebook",
++ "displaying message out of sync\n");
++ }
++
++ // Cleanup.
++ g_free(message_text);
++}
++
++
++/*****************************************************************************
++ * HISTORY CODE *
++ *****************************************************************************/
++
++static void fb_history_fetch_cb(FacebookAccount *fba, const gchar *data,
++ gsize data_len, gpointer userdata)
++{
++ JsonParser *parser;
++ JsonObject *object, *payload;
++ JsonArray *history;
++ guint i;
++ gint64 min_time;
++
++ parser = fb_get_parser(data, data_len);
++
++ if (!parser) {
++ // We didn't get data, but this isn't necessarily fatal.
++ purple_debug_warning("facebook",
++ "bad data while fetching history\n");
++ return;
++ }
++
++ min_time = atoll((char *) userdata);
++ g_free(userdata);
++ purple_debug_info("facebook", "history fetch with min time of %lld\n",
++ (long long int) min_time);
++
++ object = fb_get_json_object(parser, NULL);
++ payload = json_node_get_object(
++ json_object_get_member(object, "payload"));
++ history = json_node_get_array(
++ json_object_get_member(payload, "history"));
++
++ purple_debug_info("facebook",
++ "found %d history items to possibly render\n",
++ json_array_get_length(history));
++
++ for (i = 0; i < json_array_get_length(history); i++) {
++ const gchar *type;
++ JsonObject *message_obj;
++
++ message_obj = json_node_get_object(
++ json_array_get_element(history, i));
++ type = json_node_get_string(json_object_get_member(
++ message_obj, "type"));
++
++ if (g_str_equal(type, "msg")) {
++ gint64 message_time;
++ const gchar *message;
++ gchar *from;
++ gchar *to;
++ JsonObject *text_obj;
++
++ from = g_strdup_printf("%" G_GINT64_FORMAT, (gint64)json_node_get_int(
++ json_object_get_member(message_obj, "from")));
++ to = g_strdup_printf("%" G_GINT64_FORMAT, (gint64)json_node_get_int(
++ json_object_get_member(message_obj, "to")));
++
++ text_obj = json_node_get_object(
++ json_object_get_member(message_obj, "msg"));
++ message = json_node_get_string(
++ json_object_get_member(text_obj, "text"));
++
++ message_time = fb_time_kludge(json_node_get_int(
++ json_object_get_member(message_obj, "time")));
++
++ if (message_time > min_time) {
++ purple_debug_info("facebook",
++ "displaying history message %lld\n",
++ (long long int) message_time);
++
++ //check to see that this isn't for a multi-user chat
++ if (purple_blist_find_chat(fba->account, to) ||
++ purple_find_conversation_with_account(
++ PURPLE_CONV_TYPE_CHAT, to, fba->account))
++ {
++ fb_conversation_handle_chat(
++ fba, from, to, message_time, message,
++ min_time != 0);
++ } else {
++ fb_conversation_handle_message(
++ fba, from, to, message_time, message,
++ min_time != 0);
++ }
++ }
++
++ g_free(from);
++ g_free(to);
++ }
++ }
++
++ json_parser_free(parser);
++}
++
++void fb_history_fetch(FacebookAccount *fba, const char *who,
++ gboolean display_all)
++{
++ g_return_if_fail(fba != NULL);
++
++ purple_debug_info("facebook", "fetching history with %s\n", who);
++
++ gint64 min_time = fba->last_message_time;
++ if (display_all) {
++ min_time = 0;
++ }
++
++ gchar *url = g_strdup_printf("/ajax/chat/history.php?id=%s&__a=1", who);
++ fb_post_or_get(
++ fba, FB_METHOD_GET, NULL, url, NULL, fb_history_fetch_cb,
++ g_strdup_printf("%lld", (long long int) min_time), FALSE);
++ g_free(url);
++}
++
++/*****************************************************************************
++ * GENERAL EVENTS CODE *
++ *****************************************************************************/
++
++void fb_conversation_closed(PurpleConnection *gc, const char *who)
++{
++ FacebookAccount *fba = gc->proto_data;
++ gchar *postdata;
++
++ g_return_if_fail(fba->post_form_id != NULL);
++
++ /* notify server that we closed the chat window */
++ /* close_chat=589039771&window_id=3168919846&
++ * post_form_id=c258fe42460c7e8b61e242a37ef05afc */
++ postdata = g_strdup_printf("close_chat=%s&post_form_id=%s&fb_dtsg=%s&"
++ "post_form_id_source=AsyncRequest&__a=1", who,
++ fba->post_form_id, fba->dtsg);
++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/chat/settings.php",
++ postdata, NULL, NULL, FALSE);
++ g_free(postdata);
++}
++
++static void fb_conversation_created(PurpleConversation *conv)
++{
++ PurpleAccount *account = purple_conversation_get_account(conv);
++
++ if (!fb_conversation_is_fb(conv)) {
++ return;
++ }
++
++ purple_debug_info("facebook", "conversation created with %s\n",
++ conv->name);
++
++ if (purple_account_get_bool(account, "facebook_show_history", TRUE))
++ {
++ fb_history_fetch(account->gc->proto_data, conv->name, TRUE);
++ }
++}
++
++gboolean fb_conversation_is_fb(PurpleConversation *conv)
++{
++ PurpleAccount *account = purple_conversation_get_account(conv);
++ const gchar *prpl = purple_account_get_protocol_id(account);
++ return g_str_equal(prpl, FACEBOOK_PLUGIN_ID);
++}
++
++void fb_conversation_init(FacebookAccount *fba)
++{
++ fba->last_message_time = 0;
++
++ purple_signal_connect(
++ purple_conversations_get_handle(),
++ "conversation-created",
++ fba,
++ PURPLE_CALLBACK(fb_conversation_created),
++ NULL);
++}
++
++void fb_conversation_destroy(FacebookAccount *fba)
++{
++ purple_signal_disconnect(
++ purple_conversations_get_handle(),
++ "conversation-created",
++ fba,
++ PURPLE_CALLBACK(fb_conversation_created));
++}
++
++
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_conversation.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_conversation.h
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_conversation.h 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_conversation.h 2011-03-27 09:15:30.852553000 -0600
+@@ -0,0 +1,41 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef FACEBOOK_CONVERSATION_H
++#define FACEBOOK_CONVERSATION_H
++
++#include "libfacebook.h"
++
++void fb_conversation_init(FacebookAccount *fba);
++void fb_conversation_destroy(FacebookAccount *fba);
++
++void fb_conversation_closed(PurpleConnection *gc, const char *who);
++gboolean fb_conversation_is_fb(PurpleConversation *conv);
++
++void fb_history_fetch(FacebookAccount *fba, const char *who,
++ gboolean display_all);
++void fb_conversation_handle_message(FacebookAccount *fba, const char *from,
++ const char *to, gint64 message_time, const gchar *message_orig,
++ gboolean log);
++void fb_conversation_handle_chat(FacebookAccount *fba, const char *from,
++ const char *group, gint64 message_time, const gchar *message_orig,
++ gboolean log);
++
++#endif /* FACEBOOK_CONVERSATION_H */
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_friendlist.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_friendlist.c
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_friendlist.c 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_friendlist.c 2011-03-27 09:15:30.856552999 -0600
+@@ -0,0 +1,520 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "fb_friendlist.h"
++#include "fb_connection.h"
++#include "libfacebook.h"
++#include "fb_util.h"
++
++typedef struct _MoveRequest MoveRequest;
++struct _MoveRequest {
++ char *old_group;
++ char *new_group;
++ char *who;
++};
++
++/******************************************************************************/
++/* Friend list modification methods */
++/******************************************************************************/
++static void handle_move_request(FacebookAccount *fba, MoveRequest *request)
++{
++ const gchar *old_list_id;
++ const gchar *new_list_id;
++ gchar *postdata;
++ gboolean remove_flist, no_original_list;
++ const gchar *command;
++
++ purple_debug_info("facebook",
++ "handling movement of %s from %s to %s\n",
++ request->who, request->old_group, request->new_group);
++
++ old_list_id = fb_get_list_id(fba, request->old_group);
++ new_list_id = fb_get_list_id(fba, request->new_group);
++
++ remove_flist = !new_list_id || g_str_equal(new_list_id, "-1");
++ no_original_list = !old_list_id || g_str_equal(old_list_id, "-1");
++
++ if (remove_flist) {
++ command = "&remove_fl=true";
++ } else if (no_original_list) {
++ command = "&add_fl=true";
++ } else {
++ command = "&move_fl=true";
++ }
++
++ postdata = g_strdup_printf(
++ "post_form_id=%s&drag_uid=%s&user=%" G_GINT64_FORMAT
++ "&new_flid=%s&old_flid=%s%s",
++ fba->post_form_id,
++ request->who,
++ fba->uid,
++ remove_flist ? "" : new_list_id,
++ no_original_list ? "" : old_list_id,
++ command);
++
++ fb_post_or_get(fba, FB_METHOD_POST, NULL,
++ "/ajax/chat/buddy_list_settings.php",
++ postdata, NULL, NULL, FALSE);
++
++ g_free(postdata);
++
++ g_free(request->who);
++ g_free(request->old_group);
++ g_free(request->new_group);
++ g_free(request);
++}
++
++static void create_list_cb(FacebookAccount *fba, const gchar *data,
++ gsize data_len, gpointer userdata)
++{
++ // NOTE: this method can also be used for movements between
++ // friend lists if necessary.
++
++ JsonParser *parser;
++ JsonObject *objnode;
++ MoveRequest *request;
++
++ // Parse out old data.
++ parser = fb_get_parser(data, data_len);
++ if (parser == NULL) {
++ return;
++ }
++
++ objnode = fb_get_json_object(parser, NULL);
++ if (!objnode ||
++ !json_object_has_member(objnode, "payload"))
++ {
++ json_parser_free(parser);
++ return;
++ }
++
++ objnode = json_node_get_object(json_object_get_member(
++ objnode, "payload"));
++ fb_process_friend_lists(fba, objnode);
++
++ json_parser_free(parser);
++
++ // Move Friend
++ request = (MoveRequest *) userdata;
++ if (request) {
++ handle_move_request(fba, request);
++ }
++}
++
++static void create_friend_list(FacebookAccount *fba, const gchar *new_group,
++ MoveRequest *request)
++{
++ gchar *postdata;
++ gchar *new_group_escaped;
++
++ purple_debug_info("facebook", "creating friend list %s\n", new_group);
++
++ new_group_escaped = fb_strdup_withhtml(new_group);
++
++ postdata = g_strdup_printf(
++ "post_form_id=%s&create=%s&user=%" G_GINT64_FORMAT,
++ fba->post_form_id,
++ new_group_escaped,
++ fba->uid);
++
++ fb_post_or_get(fba, FB_METHOD_POST, NULL,
++ "/ajax/chat/buddy_list_settings.php",
++ postdata, create_list_cb, request, FALSE);
++
++ g_free(postdata);
++ g_free(new_group_escaped);
++}
++
++void fb_group_buddy_move(PurpleConnection *pc, const char *who,
++ const char *old_group, const char *new_group)
++{
++ FacebookAccount *fba;
++ MoveRequest *request;
++ const gchar *new_list_id;
++
++ if (!purple_account_get_bool(pc->account, "facebook_use_groups", TRUE))
++ {
++ //Dont do anything if we're ignoring groups
++ return;
++ }
++
++ fba = pc->proto_data;
++
++ purple_debug_info("facebook", "handling move of %s from %s to %s\n",
++ who, old_group, new_group);
++
++ // Don't do anything if groups are not actually changing.
++ if (!purple_utf8_strcasecmp(old_group, new_group)) {
++ purple_debug_info("facebook", "groups are same, not moving\n");
++ return;
++ }
++
++ // Facebook doesn't support moving yourself because you can't be in a
++ // friend list. Let buddy list be updated as appropriate.
++ if (atoll(who) == fba->uid) {
++ purple_debug_info("facebook",
++ "moving self, do not update server\n");
++ return;
++ }
++
++ request = g_new0(MoveRequest, 1);
++ request->old_group = g_utf8_strdown(old_group, -1);
++ request->new_group = g_utf8_strdown(new_group, -1);
++ request->who = g_strdup(who);
++
++ new_list_id = fb_get_list_id(fba, request->new_group);
++ if (new_list_id) {
++ handle_move_request(fba, request);
++ } else {
++ create_friend_list(fba, new_group, request);
++ }
++}
++
++void fb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy,
++ PurpleGroup *group)
++{
++ // This method should only remove a buddy from a friend list.
++ // Nothing more. It should not defriend a user ever. See issue
++ // #185 for a good explaination of why this is a bad idea.
++ //
++ // Moreover, defriending is such a rare operation that we should
++ // never make it easy. Facebook intentionally hides such a action
++ // behind multiple layers of links and dialogs.
++ //
++ // If the plugin is ever to perform an actual defriending, it needs
++ // to provide a dialog and user prompt at the absolute bare minimum.
++ FacebookAccount *fba;
++
++ if (!purple_account_get_bool(pc->account, "facebook_use_groups", TRUE))
++ {
++ //Dont do anything if we're ignoring groups
++ return;
++ }
++
++ purple_debug_info("facebook", "handing removal of buddy %s\n",
++ buddy->name);
++
++ fba = pc->proto_data;
++
++ fb_group_buddy_move(pc, buddy->name, purple_group_get_name(group),
++ DEFAULT_GROUP_NAME);
++}
++
++void fb_group_rename(PurpleConnection *pc, const char *old_name,
++ PurpleGroup *group, GList *moved_buddies)
++{
++
++ if (!purple_account_get_bool(pc->account, "facebook_use_groups", TRUE))
++ {
++ //Dont do anything if we're ignoring groups
++ return;
++ }
++
++ purple_debug_info("facebook",
++ "handling group rename of %s to %s\n",
++ old_name, purple_group_get_name(group));
++
++ // We don't do anything here. Facebook's AJAX API for renaming groups
++ // is horribly, horribly overcomplicated. There is no simple rename
++ // call, instead you must also pass in all the current data about the
++ // friend list and port it over. While it is possible to implement
++ // this, it is risky and could potentially destroy a friend list if
++ // the API changes. That's a Bad Thing(tm). Given the risk involved
++ // with this operation and how rare it is, it's not worth it.
++ //
++ // The problem is compounded by the fact that renaming groups triggers
++ // all sorts of weird behaviors in Pidgin. Renaming to a new name is
++ // simple. Renaming to an existing group name (hence a merge)
++ // triggers completely different behavior with calls to group_buddy
++ // before the call to rename. This completely defeats the purpose of
++ // having a rename function because group_buddy is called instead.
++ //
++ // Thus, the final decision is to use the buddy_move call.
++
++ // TODO: warn users that renaming has no effect here.
++}
++
++void fb_group_remove(PurpleConnection *pc, PurpleGroup *group)
++{
++ if (!purple_account_get_bool(pc->account, "facebook_use_groups", TRUE))
++ {
++ //Dont do anything if we're ignoring groups
++ return;
++ }
++
++ purple_debug_info("facebook", "got group removal of %s\n",
++ purple_group_get_name(group));
++
++ // We don't do anything here. This is because a group rename also
++ // fires a group removal event. This assumes that the new group is
++ // equivalent to the old group, but Facebook friend lists are much more
++ // than simple groups- they are privacy control lists too. There is
++ // no easy way to port the settings between groups. Better off not
++ // deleting, and the user can do the cleanup with their browser.
++}
++
++/******************************************************************************/
++/* Friend list fetch methods */
++/******************************************************************************/
++
++const gchar *fb_get_list_id(FacebookAccount *fba, const gchar *list_name)
++{
++ if (!purple_utf8_strcasecmp(list_name, DEFAULT_GROUP_NAME)) {
++ return "-1";
++ }
++
++ return g_hash_table_lookup(fba->friend_lists_reverse, purple_normalize_nocase(NULL, list_name));
++}
++
++gboolean fb_process_friend_lists(FacebookAccount *fba,
++ JsonObject *buddy_list)
++{
++ JsonObject *fl_obj;
++ GList *friend_list_ids, *cur;
++
++ purple_debug_info("facebook", "processing friend list data\n");
++
++ if (!json_object_has_member(buddy_list, "flData"))
++ {
++ purple_debug_info("facebook", "no friend list data\n");
++ return FALSE;
++ }
++
++
++ fl_obj = json_node_get_object(json_object_get_member(
++ buddy_list, "flData"));
++ friend_list_ids = json_object_get_members(fl_obj);
++ for (cur = friend_list_ids; cur != NULL; cur = cur->next)
++ {
++ gchar *id;
++ const gchar *name;
++ JsonObject *data;
++
++ id = (gchar *) cur->data;
++ data = json_node_get_object(json_object_get_member(
++ fl_obj, id));
++ name = json_node_get_string(json_object_get_member(
++ data, "n"));
++ if (name) {
++ // Either -1 isnt a valid JSON string or JSON-glib does
++ // this wrong. I'm too tired to tell the difference.
++ if (g_str_equal(id, "_1")) {
++ id = "-1";
++ }
++ purple_debug_info("facebook",
++ "got friend list %s with id %s\n",
++ name, id);
++ g_hash_table_insert(fba->friend_lists,
++ g_strdup(id), g_strdup(name));
++ g_hash_table_insert(fba->friend_lists_reverse,
++ g_utf8_strdown(name, -1), g_strdup(id));
++ }
++ }
++
++ g_list_free(friend_list_ids);
++
++ return TRUE;
++}
++
++static void destroy_buddy(gpointer key, gpointer value, gpointer data)
++{
++ PurpleBuddy *buddy;
++ gchar *group_name;
++ FacebookAccount *fba;
++
++ buddy = (PurpleBuddy *) value;
++ group_name = (gchar *) key;
++ fba = (FacebookAccount *) data;
++
++ purple_debug_info("facebook", "removing %s from group %s\n",
++ buddy->name, group_name);
++ if (atoll(buddy->name) == fba->uid) {
++ purple_debug_info("facebook", "not removing self from %s\n",
++ group_name);
++ return;
++ }
++
++ purple_blist_remove_buddy(buddy);
++}
++
++static PurpleBuddy *add_buddy(FacebookAccount *fba,
++ const gchar *friend_list_id, const gchar *uid, GHashTable *cur_groups)
++{
++ const gchar *group_name;
++ PurpleGroup *fb_group;
++ PurpleBuddy *buddy;
++
++ group_name = g_hash_table_lookup(fba->friend_lists, purple_normalize_nocase(NULL, friend_list_id));
++ if (!group_name || group_name[0] == '\0') {
++ purple_debug_info("facebook",
++ "did not find name of list %s\n",
++ friend_list_id);
++ group_name = DEFAULT_GROUP_NAME;
++ }
++
++ // Initialize group as necessary.
++ fb_group = purple_find_group(group_name);
++ if (fb_group == NULL)
++ {
++ purple_debug_info("facebook", "adding friend list %s\n",
++ group_name);
++ fb_group = purple_group_new(group_name);
++ purple_blist_add_group(fb_group, NULL);
++ }
++
++ buddy = (PurpleBuddy *)g_hash_table_lookup(cur_groups, purple_normalize_nocase(NULL, group_name));
++ if (!buddy) {
++ purple_debug_info("facebook", "adding %s to %s\n",
++ uid, group_name);
++ buddy = purple_buddy_new(fba->account, uid, NULL);
++ purple_blist_add_buddy(buddy, NULL, fb_group, NULL);
++ g_hash_table_remove(cur_groups, purple_normalize_nocase(NULL, group_name));
++ }
++
++ return buddy;
++}
++
++
++GList *fb_get_buddies_friend_list (FacebookAccount *fba,
++ const gchar *uid, JsonArray *friend_list_ids)
++{
++ GSList *buddies;
++ GSList *cur;
++ GHashTable *cur_groups;
++ int i;
++ GList *final_buddies, *cur_buddy;
++ PurpleGroup *fb_group;
++ PurpleBuddy *buddy;
++
++ final_buddies = NULL;
++ buddies = purple_find_buddies(fba->account, uid);
++
++ // If we're already in the buddy list, stop. Ignore FB info because
++ // it will be incorrect.
++ if (atoll(uid) == fba->uid && buddies != NULL) {
++ purple_debug_info("facebook",
++ "already have buddies for self, not adding\n");
++ for (cur = buddies; cur != NULL; cur = cur->next)
++ {
++ final_buddies = g_list_append(
++ final_buddies, cur->data);
++ }
++ g_slist_free(buddies);
++ return final_buddies;
++ }
++
++ //Do we want to ignore groups?
++ if (!purple_account_get_bool(fba->account, "facebook_use_groups", TRUE))
++ {
++ if (buddies != NULL) {
++ //Copy the slist into the list
++ for (cur = buddies; cur != NULL; cur = cur->next)
++ {
++ final_buddies = g_list_append(
++ final_buddies, cur->data);
++ }
++ g_slist_free(buddies);
++ return final_buddies;
++ } else {
++ buddy = purple_buddy_new(fba->account, uid, NULL);
++ fb_group = purple_find_group(DEFAULT_GROUP_NAME);
++ if (fb_group == NULL)
++ {
++ fb_group = purple_group_new(DEFAULT_GROUP_NAME);
++ purple_blist_add_group(fb_group, NULL);
++ }
++ purple_blist_add_buddy(buddy, NULL, fb_group, NULL);
++ final_buddies = g_list_append(final_buddies, buddy);
++ return final_buddies;
++ }
++ }
++
++ // Determine what buddies exist and what groups they are in.
++ cur_groups = g_hash_table_new_full(g_str_hash, g_str_equal,
++ g_free, NULL);
++ for (cur = buddies; cur != NULL; cur = cur->next)
++ {
++ const gchar *group_name;
++
++ group_name = purple_group_get_name(purple_buddy_get_group(
++ (PurpleBuddy *)cur->data));
++ group_name = purple_normalize_nocase(NULL, group_name);
++
++ g_hash_table_insert(cur_groups, g_strdup(group_name), cur->data);
++ }
++ g_slist_free(buddies);
++
++ // Create/insert necessary buddies
++ if (friend_list_ids) {
++ for (i = 0; i < json_array_get_length(friend_list_ids); i++)
++ {
++ const gchar *friend_list_id;
++
++ friend_list_id = json_node_get_string(
++ json_array_get_element(friend_list_ids, i));
++
++ buddy = add_buddy(fba, friend_list_id, uid, cur_groups);
++
++ final_buddies = g_list_append(final_buddies, buddy);
++ }
++ } else {
++ // No friend list data, so we use the default group.
++ final_buddies = g_list_append(final_buddies,
++ add_buddy(fba, "-1", uid, cur_groups));
++ }
++
++ // Figure out which groups/buddies are not represented.
++ for (cur_buddy = final_buddies; cur_buddy != NULL;
++ cur_buddy = cur_buddy->next)
++ {
++ const gchar *group_name = purple_group_get_name(purple_buddy_get_group(
++ (PurpleBuddy *)cur_buddy->data));
++ g_hash_table_remove(cur_groups, purple_normalize_nocase(NULL, group_name));
++ }
++
++ // Delete remaining buddies to maintain sync state with server.
++ g_hash_table_foreach(cur_groups, destroy_buddy, fba);
++
++ // Cleanup!
++ g_hash_table_destroy(cur_groups);
++
++ return final_buddies;
++}
++
++void fb_friendlist_init(FacebookAccount *fba)
++{
++ /* data structure mapping friend list id to name. libpurple only
++ * recognizes name, does not have group aliases */
++ fba->friend_lists = g_hash_table_new_full(g_str_hash, g_str_equal,
++ g_free, g_free);
++ /* structure mapping names to list id for speed. */
++ fba->friend_lists_reverse = g_hash_table_new_full(g_str_hash,
++ g_str_equal, g_free, g_free);
++}
++
++void fb_friendlist_destroy(FacebookAccount *fba)
++{
++ if (fba->friend_lists) {
++ g_hash_table_destroy(fba->friend_lists);
++ }
++ if (fba->friend_lists_reverse) {
++ g_hash_table_destroy(fba->friend_lists_reverse);
++ }
++}
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_friendlist.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_friendlist.h
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_friendlist.h 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_friendlist.h 2011-03-27 09:15:30.688552999 -0600
+@@ -0,0 +1,47 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef FACEBOOK_FRIENDLIST_H
++#define FACEBOOK_FRIENDLIST_H
++
++#include "libfacebook.h"
++#include "fb_json.h"
++
++#define DEFAULT_GROUP_NAME "Facebook"
++
++/* Friend list modification methods */
++void fb_group_buddy_move(PurpleConnection *pc, const char *who,
++ const char *old_group, const char *new_group);
++void fb_group_rename(PurpleConnection *pc, const char *old_name,
++ PurpleGroup *group, GList *moved_buddies);
++void fb_group_remove(PurpleConnection *pc, PurpleGroup *group);
++void fb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy,
++ PurpleGroup *group);
++
++/* Friend list fetch methods */
++const gchar *fb_get_list_id(FacebookAccount *fba, const gchar *list_name);
++gboolean fb_process_friend_lists(FacebookAccount *fba, JsonObject *buddy_list);
++GList *fb_get_buddies_friend_list (FacebookAccount *fba,
++ const gchar *uid, JsonArray *friend_list_ids);
++
++void fb_friendlist_init(FacebookAccount *fba);
++void fb_friendlist_destroy(FacebookAccount *fba);
++
++#endif /* FACEBOOK_FRIENDLIST_H */
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_info.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_info.c
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_info.c 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_info.c 2011-03-27 09:15:30.912552999 -0600
+@@ -0,0 +1,237 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "fb_connection.h"
++#include "fb_info.h"
++#include "fb_blist.h"
++
++/*
++ * TODO: Do we really want to do this? Maybe we could just set a
++ * flag that says that this protocol supports HTML?
++ */
++static gchar *fb_remove_useless_stripped_links(const gchar *input)
++{
++ /* removes stripped links like "(/s.php? ... )" from user info */
++ /* as an artifact of purple_markup_strip_html */
++
++ gchar *output = g_strdup(input);
++ gchar *i = output;
++ gchar *end;
++
++ while ((i = strstr(i, " (/")))
++ {
++ end = strchr(i, ')');
++ if (end)
++ {
++ end += 1;
++ /* overwrite everything after the brackets to before it */
++ g_stpcpy(i, end);
++ }
++ }
++
++ return output;
++}
++
++static void fb_get_info_cb(FacebookAccount *fba, const gchar *data, gsize data_len, gpointer user_data)
++{
++ PurpleNotifyUserInfo *user_info;
++ PurpleBuddyIcon *buddy_icon;
++ size_t image_size;
++ gconstpointer image_pointer;
++ int icon_id = -1;
++ gchar *uid = user_data;
++ gchar *label_tmp;
++ gchar *value_tmp;
++ gchar *value_tmp2;
++ gchar *search_start;
++ gchar *search_end;
++ PurpleBuddy *buddy = NULL;
++ FacebookBuddy *fbuddy = NULL;
++
++ purple_debug_info("facebook", "get_info_cb\n");
++ purple_debug_misc("facebook", "%s\n", data);
++
++ buddy = purple_find_buddy(fba->account, uid);
++ if (buddy)
++ {
++ fbuddy = buddy->proto_data;
++ }
++
++ /* look from <div id="info_tab" class="info_tab"> */
++ /* until </div></div></div></div> */
++ search_start = g_strstr_len(data, data_len, "<div id=\"info_tab\" class=\"info_tab\">");
++ if (search_start == NULL)
++ {
++ search_start = g_strstr_len(data, data_len, "window.location.replace(\"http:\\/\\/www.facebook.com\\");
++ if (search_start)
++ {
++ search_start += strlen("window.location.replace(\"http:\\/\\/www.facebook.com\\");
++ search_end = strchr(search_start, '"');
++ value_tmp = g_strndup(search_start, search_end - search_start);
++ if (value_tmp) {
++ purple_debug_info("facebook", "info url: %s\n", value_tmp);
++ fb_post_or_get(fba, FB_METHOD_GET, NULL, value_tmp, NULL, fb_get_info_cb, uid, FALSE);
++ g_free(value_tmp);
++ return;
++ }
++ }
++ purple_debug_warning("facebook",
++ "could not find user info, showing default");
++ user_info = purple_notify_user_info_new();
++ value_tmp = g_strdup_printf("<a href=\"http://www.facebook.com/profile.php?id=%s\">%s</a>",
++ uid, _("View web profile"));
++ purple_notify_user_info_add_pair(user_info, NULL, value_tmp);
++ purple_notify_user_info_add_section_break(user_info);
++ g_free(value_tmp);
++ purple_notify_userinfo(fba->pc, uid, user_info, NULL, NULL);
++ purple_notify_user_info_destroy(user_info);
++ g_free(uid);
++ return;
++ }
++ search_end = strstr(search_start, "</div></div></div></div>");
++
++ user_info = purple_notify_user_info_new();
++
++ /* Insert link to profile at top */
++ value_tmp = g_strdup_printf("<a href=\"http://www.facebook.com/profile.php?id=%s\">%s</a>",
++ uid, _("View web profile"));
++ purple_notify_user_info_add_pair(user_info, NULL, value_tmp);
++ purple_notify_user_info_add_section_break(user_info);
++ g_free(value_tmp);
++
++ value_tmp = g_strstr_len(data, data_len, "<title>Facebook | ");
++ if (value_tmp)
++ {
++ value_tmp = strchr(value_tmp, '|')+2;
++ value_tmp2 = g_strndup(value_tmp, strstr(value_tmp, "</title>")-value_tmp);
++ value_tmp = g_strchomp(purple_markup_strip_html(value_tmp2));
++ purple_notify_user_info_add_pair(user_info, _("Name"), value_tmp);
++ fb_blist_set_alias(fba, uid, value_tmp);
++ g_free(value_tmp);
++ g_free(value_tmp2);
++ }
++
++ value_tmp = g_strstr_len(data, data_len, "<span id=\"profile_status\"");
++ if (value_tmp)
++ {
++ value_tmp2 = strstr(value_tmp, "</span>");
++ if (value_tmp2)
++ {
++ value_tmp = strchr(value_tmp, '>')+1;
++ value_tmp2 = g_strndup(value_tmp, strchr(value_tmp, '<')-value_tmp);
++ purple_debug_info("facebook", "status: %s\n", value_tmp2);
++ value_tmp = g_strchomp(purple_markup_strip_html(value_tmp2));
++ if (*value_tmp == '\0')
++ {
++ //For some reason their status message disappeared
++ //Try using their status message from the buddy list
++ if (fbuddy)
++ {
++ g_free(value_tmp);
++ value_tmp = g_strdup(fbuddy->status);
++ }
++ }
++ purple_notify_user_info_add_pair(user_info, _("Status"), value_tmp);
++ g_free(value_tmp);
++ g_free(value_tmp2);
++ }
++ }
++
++ buddy_icon = purple_buddy_icons_find(fba->account, uid);
++ if (buddy_icon)
++ {
++ image_pointer = purple_buddy_icon_get_data(buddy_icon, &image_size);
++ icon_id = purple_imgstore_add_with_id(g_memdup(image_pointer, image_size), image_size, NULL);
++ value_tmp = g_strdup_printf("<img id='%d'>", icon_id);
++ purple_debug_info("facebook", "user info pic: '%s'\n", value_tmp);
++ purple_notify_user_info_add_pair(user_info, NULL, value_tmp);
++ g_free(value_tmp);
++ }
++
++ while ((search_start = strstr(search_start, "<dt>")) && search_start < search_end)
++ {
++ search_start += 4;
++ if (search_start[0] == '<' && search_start[1] == '/' && search_start[2] == 'd' && search_start[3] == 't')
++ {
++ /* the tag closes as soon as it opens (bad xhtml) */
++ continue;
++ }
++
++ label_tmp = g_strndup(search_start, strchr(search_start, ':')-search_start);
++ if (!*label_tmp)
++ {
++ g_free(label_tmp);
++ continue;
++ }
++
++ search_start = strstr(search_start, "<dd>");
++ if (!search_start)
++ {
++ g_free(label_tmp);
++ break;
++ }
++
++ search_start += 4;
++ value_tmp = g_strndup(search_start, strstr(search_start, "</dd>")-search_start);
++ if (!*value_tmp)
++ {
++ g_free(label_tmp);
++ g_free(value_tmp);
++ continue;
++ }
++
++ /* turn html to plaintext */
++ if (strcmp(label_tmp, "AIM")) {
++ value_tmp2 = g_strchomp(purple_markup_strip_html(value_tmp));
++ g_free(value_tmp);
++ value_tmp = value_tmp2;
++
++ /* remove the silly links */
++ value_tmp2 = fb_remove_useless_stripped_links(value_tmp);
++ g_free(value_tmp);
++ value_tmp = value_tmp2;
++ }
++
++ purple_debug_info("facebook", "label: %s\n", label_tmp);
++ purple_debug_info("facebook", "value: %s\n", value_tmp);
++ purple_notify_user_info_add_pair(user_info, label_tmp, value_tmp);
++ g_free(label_tmp);
++ g_free(value_tmp);
++ }
++
++ purple_notify_userinfo(fba->pc, uid, user_info, NULL, NULL);
++ purple_notify_user_info_destroy(user_info);
++
++ if (icon_id >= 0)
++ purple_imgstore_unref_by_id(icon_id);
++
++ g_free(uid);
++}
++
++void fb_get_info(PurpleConnection *pc, const gchar *uid)
++{
++ gchar *profile_url;
++
++ profile_url = g_strdup_printf("/profile.php?id=%s&v=info", uid);
++
++ fb_post_or_get(pc->proto_data, FB_METHOD_GET, NULL, profile_url, NULL, fb_get_info_cb, g_strdup(uid), FALSE);
++
++ g_free(profile_url);
++}
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_info.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_info.h
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_info.h 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_info.h 2011-03-27 09:15:30.876553000 -0600
+@@ -0,0 +1,28 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef FACEBOOK_INFO_H
++#define FACEBOOK_INFO_H
++
++#include "libfacebook.h"
++
++void fb_get_info(PurpleConnection *pc, const gchar *uid);
++
++#endif /* FACEBOOK_INFO_H */
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_json.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_json.c
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_json.c 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_json.c 2011-03-27 09:15:30.892553000 -0600
+@@ -0,0 +1,87 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "fb_json.h"
++
++#ifdef USE_JSONC
++
++JsonParser *
++json_parser_new(void)
++{
++ JsonParser *parser;
++
++ parser = g_new0(JsonParser, 1);
++ parser->tok = json_tokener_new();
++
++ return parser;
++}
++
++gboolean
++json_parser_load_from_data(JsonParser *parser, const gchar *data,
++ gssize length, GError **error)
++{
++ if (parser->tok == NULL)
++ return FALSE;
++
++ parser->root = json_tokener_parse_ex(parser->tok, (char*)data, (int)length);
++
++ if (parser->tok->err != json_tokener_success)
++ {
++ json_object_put(parser->root);
++ parser->root = NULL;
++ return FALSE;
++ }
++
++ return TRUE;
++}
++
++void
++json_parser_free(JsonParser *parser)
++{
++ json_tokener_free(parser->tok);
++ json_object_put(parser->root);
++ g_free(parser);
++}
++
++JsonNode *
++json_parser_get_root(JsonParser *parser)
++{
++ return parser->root;
++}
++
++GList *
++json_object_get_members(JsonObject *obj)
++{
++ GList *keys = NULL;
++ struct lh_entry *entry;
++
++ for (entry = json_object_get_object(obj)->head;
++ entry;
++ entry = entry->next)
++ {
++ keys = g_list_prepend(keys, entry->k);
++ }
++
++ keys = g_list_reverse(keys);
++
++ return keys;
++}
++
++#endif
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_json.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_json.h
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_json.h 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_json.h 2011-03-27 09:15:30.744552999 -0600
+@@ -0,0 +1,64 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef FACEBOOK_JSON_H
++#define FACEBOOK_JSON_H
++
++#ifndef USE_JSONC
++# include <json-glib/json-glib.h>
++#define json_parser_free(parser) g_object_unref(parser)
++#else /* USE_JSONC */
++# include <glib.h>
++# include <json/json.h>
++typedef struct json_object JsonNode;
++typedef struct json_object JsonObject;
++typedef struct json_object JsonArray;
++typedef struct {
++ struct json_tokener *tok;
++ struct json_object *root;
++} JsonParser;
++
++gboolean json_parser_load_from_data(JsonParser *parser,
++ const gchar *data,
++ gssize length,
++ GError **error);
++
++JsonNode* json_parser_get_root(JsonParser *parser);
++JsonParser* json_parser_new(void);
++void json_parser_free(JsonParser *parser);
++
++#define json_object_has_member(obj, key) ((gboolean)json_object_object_get(obj, key))
++#define json_object_get_member(obj, key) json_object_object_get(obj, key)
++GList* json_object_get_members(JsonObject *object);
++
++#define json_node_get_array(node) (node)
++#define json_node_get_object(node) (node)
++#define json_node_get_boolean(node) json_object_get_boolean(node)
++#define json_node_get_double(node) json_object_get_double(node)
++#define json_node_get_int(node) json_object_get_int(node)
++#define json_node_get_string(node) json_object_get_string(node)
++
++#define json_array_get_element(array, index) json_object_array_get_idx(array, index)
++#define json_array_get_length(array) json_object_array_length(array)
++
++#endif /* USE_JSONC */
++
++#endif /* FACEBOOK_JSON_H */
++
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_managefriends.c pidgin-2.7.7-new//libpurple/protocols/facebook/fb_managefriends.c
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_managefriends.c 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_managefriends.c 2011-03-27 09:15:30.868553000 -0600
+@@ -0,0 +1,233 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#include "fb_managefriends.h"
++#include "fb_connection.h"
++
++static void fb_auth_accept_cb(gpointer data)
++{
++ FacebookBuddy *fbuddy = data;
++ FacebookAccount *fba = fbuddy->fba;
++ gchar *buddy_uid;
++ gchar *postdata;
++
++ g_return_if_fail(fba != NULL);
++ g_return_if_fail(fba->post_form_id != NULL);
++ g_return_if_fail(fbuddy->uid != 0);
++
++ buddy_uid = g_strdup_printf("%" G_GINT64_FORMAT, fbuddy->uid);
++
++ postdata = g_strdup_printf(
++ "type=friend_connect&id=%s&actions[accept]=Confirm&"
++ "post_form_id=%s&fb_dtsg=%s&confirm=%s&"
++ "post_form_id_source=AsyncRequest&__a=1",
++ buddy_uid, fba->post_form_id, fba->dtsg, buddy_uid);
++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/reqs.php",
++ postdata, NULL, NULL, FALSE);
++
++ g_hash_table_remove(fba->auth_buddies, buddy_uid);
++
++ g_free(postdata);
++ g_free(fbuddy);
++ g_free(buddy_uid);
++}
++
++static void fb_auth_reject_cb(gpointer data)
++{
++ FacebookBuddy *fbuddy = data;
++ FacebookAccount *fba = fbuddy->fba;
++ gchar *buddy_uid;
++ gchar *postdata;
++
++ g_return_if_fail(fba != NULL);
++ g_return_if_fail(fba->post_form_id != NULL);
++ g_return_if_fail(fbuddy->uid != 0);
++
++ buddy_uid = g_strdup_printf("%" G_GINT64_FORMAT, fbuddy->uid);
++
++ postdata = g_strdup_printf(
++ "type=friend_connect&id=%s&action=reject&"
++ "post_form_id=%s&fb_dtsg=%s&"
++ "post_form_id_source=AsyncRequest&__a=1",
++ buddy_uid, fba->post_form_id, fba->dtsg);
++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/reqs.php",
++ postdata, NULL, NULL, FALSE);
++
++ g_hash_table_remove(fba->auth_buddies, buddy_uid);
++
++ g_free(postdata);
++ g_free(fbuddy);
++ g_free(buddy_uid);
++}
++
++static void fb_check_friend_request_cb(FacebookAccount *fba, const gchar *data,
++ gsize data_len, gpointer user_data)
++{
++ const char *uid_pre_text = "class=\"confirm\" id=\"friend_connect_";
++ const char *name_pre_text = "<td class=\"info\"><a ";
++ const char *msg_pre_text = "<div class=\"personal_msg\"><span>";
++ gchar *uid;
++ gint64 uid_int;
++ gchar *name;
++ gchar *msg;
++ gchar *msg_plain;
++ FacebookBuddy *buddy;
++ const gchar *search_start = data;
++
++ g_return_if_fail(data_len > 0);
++ g_return_if_fail(data != NULL);
++
++ /* loop through the data and look for confirm_friend_add_([0-9]*)" */
++ while ((search_start = strstr(search_start, uid_pre_text)))
++ {
++ search_start += strlen(uid_pre_text);
++ uid = g_strndup(search_start,
++ strchr(search_start, '"') - search_start);
++ purple_debug_info("facebook", "uid: %s\n", uid);
++
++ uid_int = atoll(uid);
++
++ if (g_hash_table_lookup_extended(fba->auth_buddies,
++ uid, NULL, NULL))
++ {
++ /* we've already notified the user of this friend request */
++ g_free(uid);
++ continue;
++ }
++
++ name = strstr(search_start, name_pre_text);
++ if (name != NULL)
++ {
++ name += strlen(name_pre_text);
++ name = strchr(name, '>') + 1;
++ name = g_strndup(name, strchr(name, '<') - name);
++ purple_debug_info("facebook", "name: %s\n", name);
++ } else {
++ name = NULL;
++ }
++
++ msg = strstr(search_start, msg_pre_text);
++ if (msg != NULL)
++ {
++ msg += strlen(msg_pre_text);
++ msg = g_strndup(msg, strstr(msg, "</span></div>") - msg);
++ msg_plain = purple_markup_strip_html(msg);
++ g_free(msg);
++ purple_debug_info("facebook", "msg: %s\n", msg_plain);
++ } else {
++ msg_plain = NULL;
++ }
++
++ buddy = g_new0(FacebookBuddy, 1);
++ buddy->fba = fba;
++ buddy->uid = uid_int;
++ purple_account_request_authorization(
++ fba->account, uid, NULL,
++ name, msg_plain, TRUE,
++ fb_auth_accept_cb, fb_auth_reject_cb, buddy);
++
++ /* Don't display an auth request for this buddy again */
++ g_hash_table_insert(fba->auth_buddies, uid, NULL);
++
++ g_free(name);
++ g_free(msg_plain);
++ }
++}
++
++gboolean fb_check_friend_requests(gpointer data)
++{
++ FacebookAccount *fba;
++
++ fba = data;
++
++ if (purple_account_get_bool(
++ fba->account, "facebook_manage_friends", FALSE)) {
++ fb_post_or_get(fba, FB_METHOD_GET, NULL, "/reqs.php", NULL,
++ fb_check_friend_request_cb, NULL, FALSE);
++ }
++
++ return TRUE;
++}
++
++void fb_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
++{
++ gchar *postdata;
++ FacebookAccount *fba = pc->proto_data;
++ gchar *buddy_tmp;
++
++ if (!purple_account_get_bool(
++ fba->account, "facebook_manage_friends", FALSE)) {
++ /*
++ * We used to pop up dialogs here but if a user renamed a group,
++ * this would spawn message for each person in the buddy list. Bad!
++ purple_notify_info(fba->pc, _("Friend not added"),
++ _("Adding Facebook friends via Pidgin is disabled"),
++ _("Either add a friend via Facebook.com or edit your account preferences"));
++ */
++ purple_debug_warning("facebook", "attempted to add %s but was blocked\n", buddy->name);
++ return;
++ }
++
++ if (atoll(buddy->name) == fba->uid)
++ {
++ purple_account_set_bool(fba->account,
++ "facebook_hide_self", FALSE);
++ return;
++ }
++
++ buddy_tmp = g_strdup(purple_url_encode(buddy->name));
++ postdata = g_strdup_printf(
++ "user=%" G_GINT64_FORMAT "&profile_id=%s&message=&"
++ "source=&submit=1&post_form_id=%s&fb_dtsg=%s&"
++ "post_form_id_source=AsyncRequest&__a=1",
++ fba->uid, buddy_tmp, fba->post_form_id, fba->dtsg);
++ g_free(buddy_tmp);
++
++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/profile/connect.php",
++ postdata, NULL, NULL, FALSE);
++
++ g_free(postdata);
++}
++
++void fb_buddy_delete(PurpleConnection *pc, PurpleBuddy *buddy,
++ PurpleGroup *group)
++{
++ FacebookAccount *fba = pc->proto_data;
++ gchar *buddy_tmp, *postdata;
++
++ //This function removes a buddy from our friends list on facebook
++ //and shouldn't really be used
++ if (!purple_account_get_bool(fba->account, "facebook_manage_friends", FALSE)) {
++ purple_debug_warning("facebook", "attempted to add %s but was blocked\n", buddy->name);
++ return;
++ }
++
++ buddy_tmp = g_strdup(purple_url_encode(buddy->name));
++ postdata = g_strdup_printf(
++ "uid=%s&post_form_id=%s&fb_dtsg=%s&"
++ "post_form_id_source=AsyncRequest&__a=1",
++ buddy_tmp, fba->post_form_id, fba->dtsg);
++ g_free(buddy_tmp);
++
++ fb_post_or_get(fba, FB_METHOD_POST, NULL, "/ajax/profile/removefriend.php",
++ postdata, NULL, NULL, FALSE);
++
++ g_free(postdata);
++}
+diff -rupN pidgin-2.7.7/libpurple/protocols/facebook/fb_managefriends.h pidgin-2.7.7-new//libpurple/protocols/facebook/fb_managefriends.h
+--- pidgin-2.7.7/libpurple/protocols/facebook/fb_managefriends.h 1969-12-31 18:00:00.000000000 -0600
++++ pidgin-2.7.7-new//libpurple/protocols/facebook/fb_managefriends.h 2011-03-27 09:15:30.868553000 -0600
+@@ -0,0 +1,30 @@
++/*
++ * libfacebook
++ *
++ * libfacebook is the property of its developers. See the COPYRIGHT file
++ * for more details.
++ *
++ * This program is free software: you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation, either version 3 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program. If not, see <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef FACEBOOK_MANAGEFRIENDS_H
++#define FACEBOOK_MANAGEFRIENDS_H
++
+