/* * Copyright (C) 2014, Lanedo * * 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 "config.h" #include #include #include #include #include #include #include #include #include #include "tracker-reset.h" #include "tracker-daemon.h" #include "tracker-process.h" #include "tracker-config.h" #include "tracker-color.h" static gboolean hard_reset; static gboolean soft_reset; static gboolean remove_config; static gchar *filename = NULL; #define RESET_OPTIONS_ENABLED() \ (hard_reset || \ soft_reset || \ remove_config || \ filename) static GOptionEntry entries[] = { { "hard", 'r', 0, G_OPTION_ARG_NONE, &hard_reset, N_("Kill all Tracker processes and remove all databases"), NULL }, { "soft", 'e', 0, G_OPTION_ARG_NONE, &soft_reset, N_("Same as --hard but the backup & journal are restored after restart"), NULL }, { "config", 'c', 0, G_OPTION_ARG_NONE, &remove_config, N_("Remove all configuration files so they are re-generated on next start"), NULL }, { "file", 'f', 0, G_OPTION_ARG_FILENAME, &filename, N_("Erase indexed information about a file, works recursively for directories"), N_("FILE") }, { NULL } }; static void log_handler (const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data) { switch (log_level) { case G_LOG_LEVEL_WARNING: case G_LOG_LEVEL_CRITICAL: case G_LOG_LEVEL_ERROR: case G_LOG_FLAG_RECURSION: case G_LOG_FLAG_FATAL: g_fprintf (stderr, "%s\n", message); fflush (stderr); break; case G_LOG_LEVEL_MESSAGE: case G_LOG_LEVEL_INFO: case G_LOG_LEVEL_DEBUG: case G_LOG_LEVEL_MASK: default: g_fprintf (stdout, "%s\n", message); fflush (stdout); break; } } static void delete_file (GFile *file, gpointer user_data) { if (g_file_delete (file, NULL, NULL)) { gchar *path; path = g_file_get_path (file); g_print (" %s\n", path); g_free (path); } } static void directory_foreach (GFile *file, gchar *suffix, GFunc func, gpointer user_data) { GFileEnumerator *enumerator; GFileInfo *info; GFile *child; enumerator = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL); while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) { if (!suffix || g_str_has_suffix (g_file_info_get_name (info), suffix)) { child = g_file_enumerator_get_child (enumerator, info); (func) (child, user_data); g_object_unref (child); } g_object_unref (info); } g_object_unref (enumerator); } static int delete_info_recursively (GFile *file) { TrackerSparqlConnection *connection; TrackerMinerManager *miner_manager; TrackerSparqlCursor *cursor; gchar *query, *uri; GError *error = NULL; connection = tracker_sparql_connection_get (NULL, &error); if (error) goto error; uri = g_file_get_uri (file); /* First, query whether the item exists */ query = g_strdup_printf ("SELECT ?u { ?u nie:url '%s' }", uri); cursor = tracker_sparql_connection_query (connection, query, NULL, &error); /* If the item doesn't exist, bail out. */ if (!cursor || !tracker_sparql_cursor_next (cursor, NULL, &error)) { g_clear_object (&cursor); if (error) goto error; return EXIT_SUCCESS; } g_object_unref (cursor); /* Now, delete the element recursively */ g_print ("%s\n", _("Deleting…")); query = g_strdup_printf ("DELETE { " " ?f a rdfs:Resource . " " ?ie a rdfs:Resource " "} WHERE {" " ?f nie:url ?url . " " ?ie nie:isStoredAs ?f . " " FILTER (?url = '%s' ||" " STRSTARTS (?url, '%s/'))" "}", uri, uri); g_free (uri); tracker_sparql_connection_update (connection, query, G_PRIORITY_DEFAULT, NULL, &error); g_free (query); if (error) goto error; g_object_unref (connection); g_print ("%s\n", _("The indexed data for this file has been deleted " "and will be reindexed again.")); /* Request reindexing of this data, it was previously in the store. */ miner_manager = tracker_miner_manager_new_full (FALSE, NULL); tracker_miner_manager_index_file (miner_manager, file, NULL, &error); g_object_unref (miner_manager); if (error) goto error; return EXIT_SUCCESS; error: g_warning ("%s", error->message); g_error_free (error); return EXIT_FAILURE; } static gint reset_run (void) { GError *error = NULL; if (hard_reset && soft_reset) { g_printerr ("%s\n", /* TRANSLATORS: --hard and --soft are commandline arguments */ _("You can not use the --hard and --soft arguments together")); return EXIT_FAILURE; } if (hard_reset || soft_reset) { gchar response[100] = { 0 }; g_print (CRIT_BEGIN "%s" CRIT_END "\n%s\n\n%s %s: ", _("CAUTION: This process may irreversibly delete data."), _("Although most content indexed by Tracker can " "be safely reindexed, it can’t be assured that " "this is the case for all data. Be aware that " "you may be incurring in a data loss situation, " "proceed at your own risk."), _("Are you sure you want to proceed?"), /* TRANSLATORS: This is to be displayed on command line output */ _("[y|N]")); fgets (response, 100, stdin); response[strlen (response) - 1] = '\0'; /* TRANSLATORS: this is our test for a [y|N] question in the command line. * A partial or full match will be considered an affirmative answer, * it is intentionally lowercase, so please keep it like this. */ if (!response[0] || !g_str_has_prefix (_("yes"), response)) { return EXIT_FAILURE; } } if (filename) { GFile *file; gint retval; file = g_file_new_for_commandline_arg (filename); retval = delete_info_recursively (file); g_object_unref (file); return retval; } /* KILL processes first... */ if (hard_reset || soft_reset) { tracker_process_stop (TRACKER_PROCESS_TYPE_NONE, TRACKER_PROCESS_TYPE_ALL); } if (hard_reset || soft_reset) { guint log_handler_id; GFile *cache_location, *data_location; gchar *dir; TrackerDBManager *db_manager; #ifndef DISABLE_JOURNAL gchar *rotate_to; TrackerDBConfig *db_config; gsize chunk_size; gint chunk_size_mb; TrackerDBJournal *journal_writer; #endif /* DISABLE_JOURNAL */ dir = g_build_filename (g_get_user_cache_dir (), "tracker", NULL); cache_location = g_file_new_for_path (dir); g_free (dir); dir = g_build_filename (g_get_user_data_dir (), "tracker", "data", NULL); data_location = g_file_new_for_path (dir); g_free (dir); /* Set log handler for library messages */ log_handler_id = g_log_set_handler (NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL, log_handler, NULL); g_log_set_default_handler (log_handler, NULL); #ifndef DISABLE_JOURNAL db_config = tracker_db_config_new (); chunk_size_mb = tracker_db_config_get_journal_chunk_size (db_config); chunk_size = (gsize) ((gsize) chunk_size_mb * (gsize) 1024 * (gsize) 1024); rotate_to = tracker_db_config_get_journal_rotate_destination (db_config); /* This call is needed to set the journal's filename */ tracker_db_journal_set_rotating ((chunk_size_mb != -1), chunk_size, rotate_to); g_free (rotate_to); g_object_unref (db_config); #endif /* DISABLE_JOURNAL */ /* Clean up (select_cache_size and update_cache_size don't matter here) */ db_manager = tracker_db_manager_new (TRACKER_DB_MANAGER_REMOVE_ALL, cache_location, data_location, NULL, FALSE, FALSE, 100, 100, NULL, NULL, NULL, NULL, &error); if (!db_manager) { g_message ("Error initializing database: %s", error->message); g_free (error); return EXIT_FAILURE; } tracker_db_manager_remove_all (db_manager); #ifndef DISABLE_JOURNAL journal_writer = tracker_db_journal_new (data_location, FALSE, NULL); tracker_db_journal_remove (journal_writer); #endif /* DISABLE_JOURNAL */ tracker_db_manager_remove_version_file (db_manager); tracker_db_manager_free (db_manager); /* Unset log handler */ g_log_remove_handler (NULL, log_handler_id); if (!remove_config) { return EXIT_SUCCESS; } } if (remove_config) { GFile *file; const gchar *home_conf_dir; gchar *path; GSList *all, *l; /* Check the default XDG_DATA_HOME location */ home_conf_dir = g_getenv ("XDG_CONFIG_HOME"); if (home_conf_dir && tracker_path_has_write_access_or_was_created (home_conf_dir)) { path = g_build_path (G_DIR_SEPARATOR_S, home_conf_dir, "tracker", NULL); } else { home_conf_dir = g_getenv ("HOME"); if (!home_conf_dir || !tracker_path_has_write_access_or_was_created (home_conf_dir)) { home_conf_dir = g_get_home_dir (); } path = g_build_path (G_DIR_SEPARATOR_S, home_conf_dir, ".config", "tracker", NULL); } file = g_file_new_for_path (path); g_free (path); g_print ("%s\n", _("Removing configuration files…")); directory_foreach (file, ".cfg", (GFunc) delete_file, NULL); g_object_unref (file); g_print ("%s\n", _("Resetting existing configuration…")); all = tracker_gsettings_get_all (NULL); if (!all) { return EXIT_FAILURE; } for (l = all; l; l = l->next) { ComponentGSettings *c = l->data; gchar **keys, **p; if (!c) { continue; } g_print (" %s\n", c->name); keys = g_settings_schema_list_keys (c->schema); for (p = keys; p && *p; p++) { g_print (" %s\n", *p); g_settings_reset (c->settings, *p); } if (keys) { g_strfreev (keys); } g_settings_apply (c->settings); } g_settings_sync (); tracker_gsettings_free (all); return EXIT_SUCCESS; } /* All known options have their own exit points */ g_warn_if_reached (); return EXIT_FAILURE; } static int reset_run_default (void) { GOptionContext *context; gchar *help; context = g_option_context_new (NULL); g_option_context_add_main_entries (context, entries, NULL); help = g_option_context_get_help (context, FALSE, NULL); g_option_context_free (context); g_printerr ("%s\n", help); g_free (help); return EXIT_FAILURE; } static gboolean reset_options_enabled (void) { return RESET_OPTIONS_ENABLED (); } int tracker_reset (int argc, const char **argv) { GOptionContext *context; GError *error = NULL; context = g_option_context_new (NULL); g_option_context_add_main_entries (context, entries, NULL); argv[0] = "tracker reset"; if (!g_option_context_parse (context, &argc, (char***) &argv, &error)) { g_printerr ("%s, %s\n", _("Unrecognized options"), error->message); g_error_free (error); g_option_context_free (context); return EXIT_FAILURE; } g_option_context_free (context); if (reset_options_enabled ()) { return reset_run (); } return reset_run_default (); }