2 * @file core.c Purple Core API
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 #include "certificate.h"
30 #include "connection.h"
31 #include "conversation.h"
45 #include "savedstatuses.h"
49 #include "sound-theme-loader.h"
53 #include "theme-manager.h"
57 # ifndef DBUS_API_SUBJECT_TO_CHANGE
58 # define DBUS_API_SUBJECT_TO_CHANGE
60 # include <dbus/dbus.h>
61 # include "dbus-purple.h"
62 # include "dbus-server.h"
63 # include "dbus-bindings.h"
73 static PurpleCoreUiOps
*_ops
= NULL
;
74 static PurpleCore
*_core
= NULL
;
79 purple_core_init(const char *ui
)
84 g_return_val_if_fail(ui
!= NULL
, FALSE
);
85 g_return_val_if_fail(purple_get_core() == NULL
, FALSE
);
88 bindtextdomain(PACKAGE
, LOCALEDIR
);
96 _core
= core
= g_new0(PurpleCore
, 1);
97 core
->ui
= g_strdup(ui
);
98 core
->reserved
= NULL
;
100 ops
= purple_core_get_ui_ops();
102 /* The signals subsystem is important and should be first. */
103 purple_signals_init();
107 purple_signal_register(core
, "uri-handler",
108 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER
,
109 purple_value_new(PURPLE_TYPE_BOOLEAN
), 3,
110 purple_value_new(PURPLE_TYPE_STRING
), /* Protocol */
111 purple_value_new(PURPLE_TYPE_STRING
), /* Command */
112 purple_value_new(PURPLE_TYPE_BOXED
, "GHashTable *")); /* Parameters */
114 purple_signal_register(core
, "quitting", purple_marshal_VOID
, NULL
, 0);
116 /* The prefs subsystem needs to be initialized before static protocols
117 * for protocol prefs to work. */
124 if (ops
->ui_prefs_init
!= NULL
)
125 ops
->ui_prefs_init();
127 if (ops
->debug_ui_init
!= NULL
)
128 ops
->debug_ui_init();
135 purple_ciphers_init();
138 /* Since plugins get probed so early we should probably initialize their
139 * subsystem right away too.
141 purple_plugins_init();
143 /* Initialize all static protocols. */
146 purple_plugins_probe(G_MODULE_SUFFIX
);
148 purple_theme_manager_init();
150 /* The buddy icon code uses the imgstore, so init it early. */
151 purple_imgstore_init();
153 /* Accounts use status, buddy icons and connection signals, so
154 * initialize these before accounts
156 purple_status_init();
157 purple_buddy_icons_init();
158 purple_connections_init();
160 purple_accounts_init();
161 purple_savedstatuses_init();
162 purple_notify_init();
163 purple_certificate_init();
164 purple_conversations_init();
167 purple_network_init();
168 purple_privacy_init();
169 purple_pounces_init();
171 purple_dnsquery_init();
177 purple_smileys_init();
179 * Call this early on to try to auto-detect our IP address and
180 * hopefully save some time later.
182 purple_network_get_my_ip(-1);
184 if (ops
!= NULL
&& ops
->ui_init
!= NULL
)
187 /* The UI may have registered some theme types, so refresh them */
188 purple_theme_manager_refresh();
194 purple_core_quit(void)
196 PurpleCoreUiOps
*ops
;
197 PurpleCore
*core
= purple_get_core();
199 g_return_if_fail(core
!= NULL
);
201 /* The self destruct sequence has been initiated */
202 purple_signal_emit(purple_get_core(), "quitting");
204 /* Transmission ends */
205 purple_connections_disconnect_all();
208 * Certificates must be destroyed before the SSL plugins, because
209 * PurpleCertificates contain pointers to PurpleCertificateSchemes,
210 * and the PurpleCertificateSchemes will be unregistered when the
211 * SSL plugin is uninit.
213 purple_certificate_uninit();
215 /* The SSL plugins must be uninit before they're unloaded */
218 /* Unload all non-loader, non-prpl plugins before shutting down
220 purple_debug_info("main", "Unloading normal plugins\n");
221 purple_plugins_unload(PURPLE_PLUGIN_STANDARD
);
223 /* Save .xml files, remove signals, etc. */
224 purple_smileys_uninit();
225 purple_idle_uninit();
226 purple_pounces_uninit();
227 purple_blist_uninit();
228 purple_ciphers_uninit();
229 purple_notify_uninit();
230 purple_conversations_uninit();
231 purple_connections_uninit();
232 purple_buddy_icons_uninit();
233 purple_savedstatuses_uninit();
234 purple_status_uninit();
235 purple_accounts_uninit();
236 purple_sound_uninit();
237 purple_theme_manager_uninit();
238 purple_xfers_uninit();
239 purple_proxy_uninit();
240 purple_dnsquery_uninit();
241 purple_imgstore_uninit();
242 purple_network_uninit();
244 /* Everything after unloading all plugins must not fail if prpls aren't
246 purple_debug_info("main", "Unloading all plugins\n");
247 purple_plugins_destroy_all();
249 ops
= purple_core_get_ui_ops();
250 if (ops
!= NULL
&& ops
->quit
!= NULL
)
253 /* Everything after prefs_uninit must not try to read any prefs */
254 purple_prefs_uninit();
255 purple_plugins_uninit();
257 purple_dbus_uninit();
260 purple_cmds_uninit();
261 /* Everything after util_uninit cannot try to write things to the confdir */
262 purple_util_uninit();
265 purple_signals_uninit();
278 purple_core_quit_cb(gpointer unused
)
286 purple_core_get_version(void)
292 purple_core_get_ui(void)
294 PurpleCore
*core
= purple_get_core();
296 g_return_val_if_fail(core
!= NULL
, NULL
);
302 purple_get_core(void)
308 purple_core_set_ui_ops(PurpleCoreUiOps
*ops
)
314 purple_core_get_ui_ops(void)
320 static char *purple_dbus_owner_user_dir(void)
322 DBusMessage
*msg
= NULL
, *reply
= NULL
;
323 DBusConnection
*dbus_connection
= NULL
;
324 DBusError dbus_error
;
325 char *remote_user_dir
= NULL
;
327 if ((dbus_connection
= purple_dbus_get_connection()) == NULL
)
330 if ((msg
= dbus_message_new_method_call(DBUS_SERVICE_PURPLE
, DBUS_PATH_PURPLE
, DBUS_INTERFACE_PURPLE
, "PurpleUserDir")) == NULL
)
333 dbus_error_init(&dbus_error
);
334 reply
= dbus_connection_send_with_reply_and_block(dbus_connection
, msg
, 5000, &dbus_error
);
335 dbus_message_unref(msg
);
336 dbus_error_free(&dbus_error
);
340 dbus_error_init(&dbus_error
);
341 dbus_message_get_args(reply
, &dbus_error
, DBUS_TYPE_STRING
, &remote_user_dir
, DBUS_TYPE_INVALID
);
342 remote_user_dir
= g_strdup(remote_user_dir
);
343 dbus_error_free(&dbus_error
);
344 dbus_message_unref(reply
);
347 return remote_user_dir
;
350 #endif /* HAVE_DBUS */
353 purple_core_ensure_single_instance()
355 gboolean is_single_instance
= TRUE
;
357 /* in the future, other mechanisms might have already set this to FALSE */
358 if (is_single_instance
)
360 if (!purple_dbus_is_owner())
362 const char *user_dir
= purple_user_dir();
363 char *dbus_owner_user_dir
= purple_dbus_owner_user_dir();
365 is_single_instance
= !purple_strequal(dbus_owner_user_dir
, user_dir
);
366 g_free(dbus_owner_user_dir
);
369 #endif /* HAVE_DBUS */
371 return is_single_instance
;
375 move_and_symlink_dir(const char *path
, const char *basename
, const char *old_base
, const char *new_base
, const char *relative
)
377 char *new_name
= g_build_filename(new_base
, basename
, NULL
);
381 if (g_rename(path
, new_name
))
383 purple_debug_error("core", "Error renaming %s to %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
384 path
, new_name
, g_strerror(errno
));
391 /* NOTE: This new_name is relative. */
392 new_name
= g_build_filename(relative
, basename
, NULL
);
393 old_name
= g_build_filename(old_base
, basename
, NULL
);
394 if (symlink(new_name
, old_name
))
396 purple_debug_warning("core", "Error symlinking %s to %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
397 old_name
, new_name
, g_strerror(errno
));
407 purple_core_migrate(void)
409 const char *user_dir
= purple_user_dir();
410 char *old_user_dir
= g_strconcat(purple_home_dir(),
411 G_DIR_SEPARATOR_S
".gaim", NULL
);
422 if (!g_file_test(old_user_dir
, G_FILE_TEST_EXISTS
))
424 /* ~/.gaim doesn't exist, so there's nothing to migrate. */
425 g_free(old_user_dir
);
429 status_file
= g_strconcat(user_dir
, G_DIR_SEPARATOR_S
"migrating", NULL
);
431 if (g_file_test(user_dir
, G_FILE_TEST_EXISTS
))
433 /* If we're here, we have both ~/.gaim and .purple. */
435 if (!g_file_test(status_file
, G_FILE_TEST_EXISTS
))
437 /* There's no "migrating" status file,
438 * so ~/.purple is all up to date. */
440 g_free(old_user_dir
);
445 /* If we're here, it's time to migrate from ~/.gaim to ~/.purple. */
447 /* Ensure the user directory exists */
448 if (!g_file_test(user_dir
, G_FILE_TEST_IS_DIR
))
450 if (g_mkdir(user_dir
, S_IRUSR
| S_IWUSR
| S_IXUSR
) == -1)
452 purple_debug_error("core", "Error creating directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
453 user_dir
, g_strerror(errno
));
455 g_free(old_user_dir
);
460 /* This writes ~/.purple/migrating, which allows us to detect
461 * incomplete migrations and properly retry. */
462 if (!(fp
= g_fopen(status_file
, "w")))
464 purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
465 status_file
, g_strerror(errno
));
467 g_free(old_user_dir
);
472 /* Open ~/.gaim so we can loop over its contents. */
474 if (!(dir
= g_dir_open(old_user_dir
, 0, &err
)))
476 purple_debug_error("core", "Error opening directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
478 (err
? err
->message
: "Unknown error"));
482 g_free(old_user_dir
);
486 /* Loop over the contents of ~/.gaim */
487 while ((entry
= g_dir_read_name(dir
)))
489 char *name
= g_build_filename(old_user_dir
, entry
, NULL
);
492 /* Deal with symlinks... */
493 if (g_file_test(name
, G_FILE_TEST_IS_SYMLINK
))
495 /* We're only going to duplicate a logs symlink. */
496 if (purple_strequal(entry
, "logs"))
501 if ((link
= g_file_read_link(name
, &err
)) == NULL
)
503 char *name_utf8
= g_filename_to_utf8(name
, -1, NULL
, NULL
, NULL
);
504 purple_debug_error("core", "Error reading symlink %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
505 name_utf8
? name_utf8
: name
, err
->message
);
511 g_free(old_user_dir
);
515 logs_dir
= g_build_filename(user_dir
, "logs", NULL
);
517 if (purple_strequal(link
, "../.purple/logs") ||
518 purple_strequal(link
, logs_dir
))
520 /* If the symlink points to the new directory, we're
521 * likely just trying again after a failed migration,
522 * so there's no need to fail here. */
528 /* In case we are trying again after a failed migration, we need
529 * to unlink any existing symlink. If it's a directory, this
530 * will fail, and so will the symlink below, which is good
531 * because the user should sort things out. */
534 /* Relative links will most likely still be
535 * valid from ~/.purple, though it's not
536 * guaranteed. Oh well. */
537 if (symlink(link
, logs_dir
))
539 purple_debug_error("core", "Error symlinking %s to %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
540 logs_dir
, link
, g_strerror(errno
));
546 g_free(old_user_dir
);
555 /* Ignore all other symlinks. */
560 /* Deal with directories... */
561 if (g_file_test(name
, G_FILE_TEST_IS_DIR
))
563 if (purple_strequal(entry
, "icons"))
565 /* This is a special case for the Album plugin, which
566 * stores data in the icons folder. We're not copying
567 * the icons directory over because previous bugs
568 * meant that it filled up with junk for many users.
569 * This is a great time to purge it. */
573 const char *icons_entry
;
576 if (!(icons_dir
= g_dir_open(name
, 0, &err
)))
578 purple_debug_error("core", "Error opening directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
580 (err
? err
->message
: "Unknown error"));
586 g_free(old_user_dir
);
590 new_icons_dir
= g_build_filename(user_dir
, "icons", NULL
);
591 /* Ensure the new icon directory exists */
592 if (!g_file_test(new_icons_dir
, G_FILE_TEST_IS_DIR
))
594 if (g_mkdir(new_icons_dir
, S_IRUSR
| S_IWUSR
| S_IXUSR
) == -1)
596 purple_debug_error("core", "Error creating directory %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
597 new_icons_dir
, g_strerror(errno
));
598 g_free(new_icons_dir
);
599 g_dir_close(icons_dir
);
603 g_free(old_user_dir
);
608 while ((icons_entry
= g_dir_read_name(icons_dir
)))
610 char *icons_name
= g_build_filename(name
, icons_entry
, NULL
);
612 if (g_file_test(icons_name
, G_FILE_TEST_IS_DIR
))
614 if (!move_and_symlink_dir(icons_name
, icons_entry
,
615 name
, new_icons_dir
, "../../.purple/icons"))
618 g_free(new_icons_dir
);
619 g_dir_close(icons_dir
);
623 g_free(old_user_dir
);
630 g_dir_close(icons_dir
);
632 else if (purple_strequal(entry
, "plugins"))
634 /* Do nothing, because we broke plugin compatibility.
635 * This means that the plugins directory gets left behind. */
639 /* All other directories are moved and symlinked. */
640 if (!move_and_symlink_dir(name
, entry
, old_user_dir
, user_dir
, "../.purple"))
645 g_free(old_user_dir
);
650 else if (g_file_test(name
, G_FILE_TEST_IS_REGULAR
))
652 /* Regular files are copied. */
657 if (!(fp
= g_fopen(name
, "rb")))
659 purple_debug_error("core", "Error opening file %s for reading: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
660 name
, g_strerror(errno
));
664 g_free(old_user_dir
);
668 new_name
= g_build_filename(user_dir
, entry
, NULL
);
669 if (!(new_file
= g_fopen(new_name
, "wb")))
671 purple_debug_error("core", "Error opening file %s for writing: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
672 new_name
, g_strerror(errno
));
678 g_free(old_user_dir
);
684 unsigned char buf
[256];
687 size
= fread(buf
, 1, sizeof(buf
), fp
);
688 if (size
!= sizeof(buf
) && !feof(fp
))
690 purple_debug_error("core", "Error reading %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
691 name
, g_strerror(errno
));
698 g_free(old_user_dir
);
702 if (!fwrite(buf
, size
, 1, new_file
) && ferror(new_file
) != 0)
704 purple_debug_error("core", "Error writing %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
705 new_name
, g_strerror(errno
));
712 g_free(old_user_dir
);
717 if (fclose(new_file
))
719 purple_debug_error("core", "Error writing: %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
720 new_name
, g_strerror(errno
));
724 purple_debug_warning("core", "Error closing %s: %s\n",
725 name
, g_strerror(errno
));
730 purple_debug_warning("core", "Not a regular file or directory: %s\n", name
);
735 /* The migration was successful, so delete the status file. */
736 if (g_unlink(status_file
))
738 purple_debug_error("core", "Error unlinking file %s: %s. Please report this at " PURPLE_DEVEL_WEBSITE
"\n",
739 status_file
, g_strerror(errno
));
744 old_icons_dir
= g_build_filename(old_user_dir
, "icons", NULL
);
745 _purple_buddy_icon_set_old_icons_dir(old_icons_dir
);
746 g_free(old_icons_dir
);
748 g_free(old_user_dir
);
754 GHashTable
* purple_core_get_ui_info() {
755 PurpleCoreUiOps
*ops
= purple_core_get_ui_ops();
757 if(NULL
== ops
|| NULL
== ops
->get_ui_info
)
760 return ops
->get_ui_info();