/*
* libopensync - A synchronization framework
* Copyright (C) 2004-2005 Armin Bauer <armin.bauer@opensync.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <opensync.h>
#include "opensync_internals.h"
/**
* @defgroup OSyncHashtablePrivateAPI OpenSync Hashtable Internals
* @ingroup OSyncPrivate
* @brief The private API of the Hashtables
*
* This gives you an insight in the private API of the Hashtables
*
*/
/*@{*/
static void osync_hashtable_assert_loaded(OSyncHashTable *table)
{
osync_assert_msg(table, "You have to pass a valid hashtable to the call!");
osync_assert_msg(table->dbhandle, "Hashtable not loaded yet. You have to load the hashtable first using osync_hashtable_load!");
}
/*@}*/
/**
* @defgroup OSyncHashtableAPI OpenSync Hashtables
* @ingroup OSyncPublic
* @brief A Hashtable can be used to detect changes
*
* Hashtables can be used to detect changes since the last invokation. They do this
* by keeping track of all reported uids and the hashes of the objects.
*
* Hashes are strings that change when the objects is updated or when the content of
* the object changes. So hashes can either be a real hash like an MD5 or something
* like a timestamp. The only important thing is that the hash changes once the item
* gets updated.
*
* The hashtable works like this:
* - You first malloc it with osync_hashtable_new()
* - Then you load the saved hashtable from disk with osync_hashtable_load()
*
* Now you can query and alter the table. You can ask if a item has changed by doing:
* - osync_hashtable_get_changetype() to get the changetype of a certain uid and hash
* - or the convience function osync_hashtable_detect_change which calls
* osync_hashtable_get_changetype() and sets this changetype on the change object and then
* automatically calls osync_hashtable_report()
* After you reported all objects you can query the table for the deleted objects using
* osync_hashtable_get_deleted() or osync_hashtable_report_deleted()
*
* After you are done call:
* - osync_hashtable_close()
* - osync_hashtable_free()
*
* The hashtable works like this:
*
* First the items are reported with a certain uid or hash. If the uid does not yet
* exist in the database it is reported as ADDED. if the uid exists and the hash is different
* it is reported as MODIFIED. if the uid exists but the hash is the same it means that the
* object is UNMODIFIED.
*
* To be able to report deleted objects the hashtables keeps track of the uids you reported.
* After you are done with asking the hashtable for changes you can ask it for deleted objects.
* All items that are in the hashtable but where not reported by you have to be DELETED.
*
*/
/*@{*/
/*! @brief Creates a new hashtable
*
* Hashtables can be used to detect what has been changed since
* the last sync
*
* @returns A new hashtable
*
*/
OSyncHashTable *osync_hashtable_new(void)
{
OSyncHashTable *table = g_malloc0(sizeof(OSyncHashTable));
g_assert(table);
table->used_entries = g_hash_table_new(g_str_hash, g_str_equal);
return table;
}
/*! @brief Frees a hashtable
*
*
* @param table The hashtable to free
*
*/
void osync_hashtable_free(OSyncHashTable *table)
{
g_hash_table_destroy(table->used_entries);
g_free(table);
}
/*! @brief Makes a hashtable forget
*
* You can ask the hashtable to detect the changes. In the end you can
* ask the hashtable for all items that have been deleted since the last sync.
* For this the hashtable maintains a internal table of items you already reported and
* reports the items it didnt see yet as deleted.
* This function resets the internal table so it start to report deleted items again
*
* @param table The hashtable
*
*/
void osync_hashtable_forget(OSyncHashTable *table)
{
g_hash_table_destroy(table->used_entries);
table->used_entries = g_hash_table_new(g_str_hash, g_str_equal);
}
/*! @brief Loads a hashtable from disk
*
* @param table The hashtable
* @param member The member for which to load the table
* @param error An error struct
* @returns TRUE if successfull, FALSE otherwise
*
*/
osync_bool osync_hashtable_load(OSyncHashTable *table, OSyncMember *member, OSyncError **error)
{
return osync_db_open_hashtable(table, member, error);
}
/*! @brief Closes a previously loaded table
*
* This function also makes the hashtable "forget"
*
* @param table The hashtable
*
*/
void osync_hashtable_close(OSyncHashTable *table)
{
osync_hashtable_assert_loaded(table);
osync_hashtable_forget(table);
osync_db_close(table->dbhandle);
}
/*! @brief Returns the number of entries in this hashtable
*
* @param table The hashtable
* @returns The number of entries
*
*/
int osync_hashtable_num_entries(OSyncHashTable *table)
{
osync_hashtable_assert_loaded(table);
return osync_db_count(table->dbhandle, "SELECT count(*) FROM tbl_hash");
}
/*! @brief Gets the nth entry from the table
*
* This is mainly usefull for debugging or special purposes
*
* @param table The hashtable
* @param i The number of the entry to return
* @param uid A pointer to a char * that will hold the uid. The caller is responible for freeing
* @param hash A pointer to a char * that will hold the hash. The caller is responible for freeing
* @returns TRUE if successfull, FALSE otherwise
*
*/
osync_bool osync_hashtable_nth_entry(OSyncHashTable *table, int i, char **uid, char **hash)
{
osync_hashtable_assert_loaded(table);
sqlite3 *sdb = table->dbhandle->db;
sqlite3_stmt *ppStmt = NULL;
char *query = g_strdup_printf("SELECT uid, hash FROM tbl_hash LIMIT 1 OFFSET %i", i);
sqlite3_prepare(sdb, query, -1, &ppStmt, NULL);
sqlite3_step(ppStmt);
*uid = g_strdup((gchar*)sqlite3_column_text(ppStmt, 0));
*hash = g_strdup((gchar*)sqlite3_column_text(ppStmt, 1));
sqlite3_finalize(ppStmt);
g_free(query);
return TRUE;
}
/*! @brief Update the hash for a entry
*
* Updates the hash for a entry in the hashtable. Do this after you see that a hash
* has changed, for example after reading it during get_changes or after you
* wrote it
*
* @param table The hashtable
* @param change The change with the new hash information
*/
void osync_hashtable_update_hash(OSyncHashTable *table, OSyncChange *change)
{
osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, table, change);
osync_hashtable_assert_loaded(table);
osync_assert_msg(change, "Change was NULL. Bug in a plugin");
osync_assert_msg(change->uid, "No uid was set on change. Bug in a plugin");
osync_trace(TRACE_INTERNAL, "Updating hashtable with hash \"%s\" and changetype %i",
change->hash, osync_change_get_changetype(change));
switch (osync_change_get_changetype(change)) {
case CHANGE_MODIFIED:
case CHANGE_ADDED:
osync_db_save_hash(table, change->uid, change->hash,
osync_change_get_objtype(change) ? osync_change_get_objtype(change)->name : NULL);
break;
case CHANGE_DELETED:
osync_db_delete_hash(table, change->uid);
break;
default:
g_assert_not_reached();
}
osync_trace(TRACE_EXIT, "%s", __func__);
}
/*! @brief Report a item
*
* When you use this function the item is marked as reported, so it will not get
* listed as deleted. Use this function if there are problems accessing an object for
* example so that the object does not get reported as deleted accidently.
*
* @param table The hashtable
* @param uid The uid to report
*
*/
void osync_hashtable_report(OSyncHashTable *table, const char *uid)
{
osync_trace(TRACE_ENTRY, "%s(%p, %s)", __func__, table, uid);
osync_hashtable_assert_loaded(table);
g_hash_table_insert(table->used_entries, g_strdup(uid), GINT_TO_POINTER(1));
osync_trace(TRACE_EXIT, "%s", __func__);
}
/*! @brief Report all deleted items
*
* @param table The hashtable
* @param context The context in which to report the changes
* @param objtype The object type which to report, NULL for all
*
*/
void osync_hashtable_report_deleted(OSyncHashTable *table, OSyncContext *context, const char *objtype)
{
osync_trace(TRACE_ENTRY, "%s(%p, %p, %s)", __func__, table, context, objtype);
osync_hashtable_assert_loaded(table);
char **uidarr = osync_db_get_deleted_hash(table, objtype);
int i = 0;
for (i = 0; uidarr[i]; i++) {
char *uid = uidarr[i];
OSyncChange *change = osync_change_new();
change->changetype = CHANGE_DELETED;
osync_change_set_objtype_string(change, objtype);
osync_change_set_uid(change, uid);
osync_context_report_change(context, change);
osync_hashtable_update_hash(table, change);
g_free(uid);
}
g_free(uidarr);
osync_trace(TRACE_EXIT, "%s", __func__);
}
/*! @brief Get the uid of all deleted items
*
* @param table The hashtable
* @param objtype The object type which to report, NULL for all
* @returns An Null terminated array of uids. The uids and this array have to be freed.
*
*/
char **osync_hashtable_get_deleted(OSyncHashTable *table, const char *objtype)
{
osync_trace(TRACE_ENTRY, "%s(%p, %s)", __func__, table, objtype);
osync_hashtable_assert_loaded(table);
char **retarr = osync_db_get_deleted_hash(table, objtype);
osync_trace(TRACE_EXIT, "%s: %p", __func__, retarr);
return retarr;
}
/*! @brief Get the hash value from the hash table
*
*/
void osync_hashtable_get_hash(OSyncHashTable *table, OSyncChange *chg)
{
char *orighash = NULL;
osync_db_get_hash(table, chg->uid, osync_change_get_objtype(chg)->name, &orighash);
osync_change_set_hash(chg, orighash);
g_free(orighash);
}
/*! @brief Gets the changetype for a given uid and hash
*
* This functions does not report the object so if you only use this function
* it will get reported as deleted! Please use osync_hashtable_report() for reporting
* and object.
*
* @param table The hashtable
* @param uid The uid to lookup
* @param hash The hash to compare
* @returns The changetype
*
*/
OSyncChangeType osync_hashtable_get_changetype(OSyncHashTable *table, const char *uid, const char *objtype, const char *hash)
{
osync_trace(TRACE_ENTRY, "%s(%p, %s, %s, %s)", __func__, table, uid, objtype, hash);
osync_hashtable_assert_loaded(table);
OSyncChangeType retval = CHANGE_UNMODIFIED;
char *orighash = NULL;
osync_db_get_hash(table, uid, objtype, &orighash);
osync_trace(TRACE_INTERNAL, "Comparing %s with %s", hash, orighash);
if (orighash) {
if (strcmp(hash, orighash) == 0)
retval = CHANGE_UNMODIFIED;
else
retval = CHANGE_MODIFIED;
} else
retval = CHANGE_ADDED;
osync_trace(TRACE_EXIT, "%s: %s", __func__, retval ? "TRUE" : "FALSE");
return retval;
}
/*! @brief Gets the changetype of an object and sets it directly
*
* This functions also call report
*
* @param table The hashtable
* @param change The change to check
* @returns TRUE if the object was not changed, FALSE if it was changed or added
*
*/
osync_bool osync_hashtable_detect_change(OSyncHashTable *table, OSyncChange *change)
{
osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, table, change);
osync_bool retval = FALSE;
change->changetype = osync_hashtable_get_changetype(table, change->uid, osync_objtype_get_name(osync_change_get_objtype(change)), change->hash);
if (change->changetype != CHANGE_UNMODIFIED)
retval = TRUE;
g_hash_table_insert(table->used_entries, g_strdup(change->uid), GINT_TO_POINTER(1));
osync_trace(TRACE_EXIT, "%s: %s", __func__, retval ? "TRUE" : "FALSE");
return retval;
}
/*! @brief Resets the hashtable for a given object type
*
* @param table The hashtable
* @param objtype The object type to slow-sync, NULL for all
*/
void osync_hashtable_set_slow_sync(OSyncHashTable *table, const char *objtype)
{
osync_hashtable_assert_loaded(table);
osync_db_reset_hash(table, objtype);
}
/*@}*/
syntax highlighted by Code2HTML, v. 0.9.1