| 63 | | /*@}*/ |
|---|
| 64 | | |
|---|
| 65 | | /** |
|---|
| 66 | | * @defgroup OSyncHashtableAPI OpenSync Hashtables |
|---|
| 67 | | * @ingroup OSyncPublic |
|---|
| 68 | | * @brief A Hashtable can be used to detect changes |
|---|
| 69 | | * |
|---|
| 70 | | * Hashtables can be used to detect changes since the last invocation. They do this |
|---|
| 71 | | * by keeping track of all reported uids and the hashes of the objects. |
|---|
| 72 | | * |
|---|
| 73 | | * A hash is a string that changes when an object is updated or when the content of |
|---|
| 74 | | * the object changes. So hashes can either be a real hash like an MD5, or something |
|---|
| 75 | | * like a timestamp. The only important thing is that the hash changes when the item |
|---|
| 76 | | * gets updated. |
|---|
| 77 | | * |
|---|
| 78 | | * The hashtable is created or loaded from a .db file using the osync_hashtable_new() |
|---|
| 79 | | * function. |
|---|
| 80 | | * |
|---|
| 81 | | * Now you can query and alter the table. You can ask if a item has changed by doing: |
|---|
| 82 | | * - osync_hashtable_get_changetype() to get the changetype of a certain uid and hash |
|---|
| 83 | | * - or the convience function osync_hashtable_detect_change() which calls |
|---|
| 84 | | * osync_hashtable_get_changetype() and sets this changetype on the change object and then |
|---|
| 85 | | * automatically calls osync_hashtable_report() |
|---|
| 86 | | * After you reported all objects you can query the table for the deleted objects using |
|---|
| 87 | | * osync_hashtable_get_deleted() or osync_hashtable_report_deleted() |
|---|
| 88 | | * |
|---|
| 89 | | * After you are finished using the hashtable, call: |
|---|
| 90 | | * - osync_hashtable_free() |
|---|
| 91 | | * |
|---|
| 92 | | * The hashtable works like this: |
|---|
| 93 | | * |
|---|
| 94 | | * First the items are reported with a certain uid or hash. If the uid does not yet |
|---|
| 95 | | * exist in the database it is reported as ADDED. If the uid exists and the hash is different |
|---|
| 96 | | * it is reported as MODIFIED. If the uid exists but the hash is the same it means that the |
|---|
| 97 | | * object is UNMODIFIED. |
|---|
| 98 | | * |
|---|
| 99 | | * To be able to report deleted objects the hashtables keeps track of the uids you reported. |
|---|
| 100 | | * After you are done with asking the hashtable for changes you can ask it for deleted objects. |
|---|
| 101 | | * All items that are in the hashtable but where not reported by you have to be DELETED. |
|---|
| 102 | | * |
|---|
| 103 | | */ |
|---|
| 104 | | /*@{*/ |
|---|
| 105 | | |
|---|
| 106 | | /*! @brief Loads or creates a hashtable |
|---|
| 107 | | * |
|---|
| 108 | | * Hashtables can be used to detect what has been changed since |
|---|
| 109 | | * the last sync |
|---|
| 110 | | * |
|---|
| 111 | | * @param path the full path and file name of the hashtable .db file to load from or create |
|---|
| 112 | | * @param objtype the object type of the hashtable |
|---|
| 113 | | * @param error An error struct |
|---|
| 114 | | * @returns A new hashtable, or NULL if an error occurred. |
|---|
| 115 | | * |
|---|
| 116 | | */ |
|---|
| 117 | | OSyncHashTable *osync_hashtable_new(const char *path, const char *objtype, OSyncError **error) |
|---|
| 118 | | { |
|---|
| 119 | | osync_trace(TRACE_ENTRY, "%s(%s, %s, %p)", __func__, path, objtype, error); |
|---|
| 120 | | |
|---|
| 121 | | OSyncHashTable *table = osync_try_malloc0(sizeof(OSyncHashTable), error); |
|---|
| 122 | | if (!table) { |
|---|
| 123 | | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
|---|
| 124 | | return NULL; |
|---|
| 125 | | } |
|---|
| 126 | | |
|---|
| 127 | | table->used_entries = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
|---|
| 128 | | |
|---|
| 129 | | table->dbhandle = osync_db_new(error); |
|---|
| 130 | | if (!table->dbhandle) |
|---|
| 131 | | goto error; |
|---|
| 132 | | |
|---|
| 133 | | if (!osync_db_open(table->dbhandle, path, error)) |
|---|
| 134 | | goto error_and_free; |
|---|
| 135 | | |
|---|
| 136 | | table->tablename = g_strdup_printf("tbl_hash_%s", objtype); |
|---|
| 137 | | |
|---|
| 138 | | int ret = osync_db_exists(table->dbhandle, table->tablename, error); |
|---|
| 139 | | if (ret > 0) { |
|---|
| 140 | | goto end; |
|---|
| 141 | | } else if (ret < 0) { |
|---|
| 142 | | goto error_and_free; |
|---|
| 143 | | } |
|---|
| 144 | | /* if ret == 0 then table does not exist yet. contiune and create one. */ |
|---|
| 145 | | |
|---|
| 146 | | if (!osync_hashtable_create(table, objtype, error)) |
|---|
| 147 | | goto error_and_free; |
|---|
| 148 | | |
|---|
| 149 | | end: |
|---|
| 150 | | osync_trace(TRACE_EXIT, "%s: %p", __func__, table); |
|---|
| 151 | | return table; |
|---|
| 152 | | |
|---|
| 153 | | error_and_free: |
|---|
| 154 | | g_free(table->dbhandle); |
|---|
| 155 | | g_free(table); |
|---|
| 156 | | error: |
|---|
| 157 | | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
|---|
| 158 | | return FALSE; |
|---|
| 159 | | |
|---|
| 160 | | } |
|---|
| 161 | | |
|---|
| 162 | | /*! @brief Frees a hashtable |
|---|
| 163 | | * |
|---|
| 164 | | * @param table The hashtable to free |
|---|
| 165 | | * |
|---|
| 166 | | */ |
|---|
| 167 | | void osync_hashtable_free(OSyncHashTable *table) |
|---|
| 168 | | { |
|---|
| 169 | | osync_trace(TRACE_ENTRY, "%s(%p)", __func__, table); |
|---|
| 170 | | osync_assert(table); |
|---|
| 171 | | |
|---|
| 172 | | if (!osync_db_close(table->dbhandle, NULL)) |
|---|
| 173 | | osync_trace(TRACE_INTERNAL, "Can't close database"); |
|---|
| 174 | | |
|---|
| 175 | | |
|---|
| 176 | | g_hash_table_destroy(table->used_entries); |
|---|
| 177 | | |
|---|
| 178 | | g_free(table->tablename); |
|---|
| 179 | | g_free(table->dbhandle); |
|---|
| 180 | | g_free(table); |
|---|
| 181 | | |
|---|
| 182 | | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| 183 | | } |
|---|
| 184 | | |
|---|
| 185 | | /*! @brief Prepares the hashtable for a slowsync and flush the entire hashtable |
|---|
| 186 | | * |
|---|
| 187 | | * This function should be called to prepare the hashtable for a slowsync. |
|---|
| 188 | | * The entire database, which stores the values of the hashtable beyond the |
|---|
| 189 | | * synchronization, gets flushed. |
|---|
| 190 | | * |
|---|
| 191 | | * @param table The hashtable |
|---|
| 192 | | * @param error An error struct |
|---|
| 193 | | * @returns TRUE on success, or FALSE if an error occurred. |
|---|
| 194 | | * |
|---|
| 195 | | */ |
|---|
| 196 | | osync_bool osync_hashtable_slowsync(OSyncHashTable *table, OSyncError **error) |
|---|
| 197 | | { |
|---|
| 198 | | osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, table, error); |
|---|
| 199 | | osync_assert(table); |
|---|
| 200 | | osync_assert(table->dbhandle); |
|---|
| 201 | | |
|---|
| 202 | | if (!osync_db_reset(table->dbhandle, table->tablename, error)) { |
|---|
| 203 | | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
|---|
| 204 | | return FALSE; |
|---|
| 205 | | } |
|---|
| 206 | | |
|---|
| 207 | | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| 208 | | return TRUE; |
|---|
| 209 | | } |
|---|
| 210 | | |
|---|
| 259 | | |
|---|
| 260 | | char *query = g_strdup_printf("SELECT * FROM %s", table->tablename); |
|---|
| 261 | | int ret = osync_db_count(table->dbhandle, query, NULL); |
|---|
| 262 | | g_free(query); |
|---|
| 263 | | |
|---|
| 264 | | if (ret < 0) { |
|---|
| 265 | | osync_trace(TRACE_EXIT_ERROR, "%s: Cannot count number of hashtable entries!", __func__); |
|---|
| 266 | | return -1; |
|---|
| 267 | | } |
|---|
| 268 | | |
|---|
| 269 | | osync_trace(TRACE_EXIT, "%s: %i", __func__, ret); |
|---|
| 270 | | return ret; |
|---|
| 271 | | } |
|---|
| 272 | | |
|---|
| 273 | | /*! @brief Gets the nth entry from the table |
|---|
| 274 | | * |
|---|
| 275 | | * This is mainly useful for debugging or special purposes |
|---|
| 276 | | * |
|---|
| 277 | | * @param table The hashtable |
|---|
| 278 | | * @param nth The number of the entry to return |
|---|
| 279 | | * @param uid A pointer to a char * that will hold the uid. The caller is responsible for freeing this. |
|---|
| 280 | | * @param hash A pointer to a char * that will hold the hash. The caller is responsible for freeing this. |
|---|
| 281 | | * @returns TRUE if successful, FALSE otherwise |
|---|
| 282 | | * |
|---|
| 283 | | */ |
|---|
| 284 | | osync_bool osync_hashtable_nth_entry(OSyncHashTable *table, int nth, char **uid, char **hash) |
|---|
| 285 | | { |
|---|
| 286 | | osync_assert(table); |
|---|
| 287 | | osync_assert(table->dbhandle); |
|---|
| 288 | | |
|---|
| 289 | | GList *list = NULL; |
|---|
| 290 | | OSyncError *error = NULL; |
|---|
| 291 | | |
|---|
| 292 | | |
|---|
| 293 | | char *query = g_strdup_printf("SELECT uid, hash FROM %s LIMIT 1 OFFSET %i", table->tablename, nth); |
|---|
| 294 | | list = osync_db_query_table(table->dbhandle, query, &error); |
|---|
| 295 | | g_free(query); |
|---|
| 296 | | |
|---|
| 297 | | if (osync_error_is_set(&error)) { |
|---|
| 298 | | osync_trace(TRACE_EXIT_ERROR, "%s: Cannot get #%i entry from hashtable: %s", __func__, nth, osync_error_print(&error)); |
|---|
| 299 | | osync_error_unref(&error); |
|---|
| 300 | | return FALSE; |
|---|
| 301 | | } |
|---|
| 302 | | |
|---|
| 303 | | GList *column = list->data; |
|---|
| 304 | | |
|---|
| 305 | | *uid = g_strdup((char*)g_list_nth_data(column, 0)); |
|---|
| 306 | | *hash = g_strdup((char*)g_list_nth_data(column, 1)); |
|---|
| 307 | | |
|---|
| 308 | | osync_db_free_list(list); |
|---|
| 309 | | |
|---|
| 310 | | return TRUE; |
|---|
| 311 | | } |
|---|
| 312 | | |
|---|
| 313 | | /*! @brief Gets the hash value for given uid |
|---|
| 314 | | * |
|---|
| 315 | | * @param table The hashtable |
|---|
| 316 | | * @param uid The uid to lookup |
|---|
| 317 | | * @returns The hash. Has to be freed by the caller. |
|---|
| 318 | | */ |
|---|
| 319 | | char *osync_hashtable_get_hash(OSyncHashTable *table, const char *uid) |
|---|
| 320 | | { |
|---|
| 321 | | osync_assert(uid); |
|---|
| 322 | | osync_assert(table); |
|---|
| 323 | | osync_assert(table->dbhandle); |
|---|
| 324 | | |
|---|
| 325 | | char *hash = NULL; |
|---|
| 326 | | GList *list = NULL; |
|---|
| 327 | | OSyncError *error = NULL; |
|---|
| 328 | | char *escaped_uid = osync_db_sql_escape(uid); |
|---|
| 329 | | |
|---|
| 330 | | char *query = g_strdup_printf("SELECT hash FROM %s WHERE uid= '%s' LIMIT 1", |
|---|
| 331 | | table->tablename, escaped_uid); |
|---|
| 332 | | list = osync_db_query_table(table->dbhandle, query, &error); |
|---|
| 333 | | g_free(query); |
|---|
| 334 | | g_free(escaped_uid); |
|---|
| 335 | | |
|---|
| 336 | | if (osync_error_is_set(&error)) { |
|---|
| 337 | | osync_trace(TRACE_EXIT_ERROR, "%s: Cannot get hash for '%s': %s", |
|---|
| 338 | | __func__, uid, osync_error_print(&error)); |
|---|
| 339 | | osync_error_unref(&error); |
|---|
| 340 | | return NULL; |
|---|
| 341 | | } |
|---|
| 342 | | |
|---|
| 343 | | if (list && list->data) { |
|---|
| 344 | | GList *column = list->data; |
|---|
| 345 | | hash = g_strdup((char *)g_list_nth_data(column, 0)); |
|---|
| 346 | | } |
|---|
| 347 | | |
|---|
| 348 | | osync_db_free_list(list); |
|---|
| 349 | | |
|---|
| 350 | | return hash; |
|---|
| 351 | | } |
|---|
| 352 | | |
|---|
| 353 | | /*! @brief Write the hash value for given uid |
|---|
| 354 | | * |
|---|
| 355 | | * @param table The hashtable |
|---|
| 356 | | * @param uid The uid |
|---|
| 357 | | * @param hash The hash |
|---|
| 358 | | */ |
|---|
| 359 | | void osync_hashtable_write(OSyncHashTable *table, const char *uid, const char *hash) |
|---|
| 360 | | { |
|---|
| 361 | | osync_trace(TRACE_ENTRY, "%s(%p, %s, %s)", __func__, table, uid, hash); |
|---|
| 362 | | osync_assert(table); |
|---|
| 363 | | osync_assert(table->dbhandle); |
|---|
| 364 | | |
|---|
| 365 | | char *escaped_uid = osync_db_sql_escape(uid); |
|---|
| 366 | | char *escaped_hash = osync_db_sql_escape(hash); |
|---|
| 367 | | char *query = g_strdup_printf("REPLACE INTO %s ('uid', 'hash') VALUES('%s', '%s')", table->tablename, escaped_uid, escaped_hash); |
|---|
| 368 | | g_free(escaped_uid); |
|---|
| 369 | | g_free(escaped_hash); |
|---|
| 370 | | |
|---|
| 371 | | if (!osync_db_query(table->dbhandle, query, NULL)) { |
|---|
| 372 | | g_free(query); |
|---|
| 373 | | osync_trace(TRACE_EXIT, "%s: Cannot write hashtable entry.", __func__); |
|---|
| 374 | | return; |
|---|
| 375 | | } |
|---|
| 376 | | g_free(query); |
|---|
| 377 | | |
|---|
| 378 | | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| 379 | | } |
|---|
| 380 | | |
|---|
| 381 | | /*! @brief Delete hashtable entries for given uid |
|---|
| 382 | | * |
|---|
| 383 | | * @param table The hashtable |
|---|
| 384 | | * @param uid The uid of the entry to delete |
|---|
| 385 | | */ |
|---|
| 386 | | void osync_hashtable_delete(OSyncHashTable *table, const char *uid) |
|---|
| 387 | | { |
|---|
| 388 | | osync_trace(TRACE_ENTRY, "%s(%p, %s)", __func__, table, uid); |
|---|
| 389 | | osync_assert(table); |
|---|
| 390 | | osync_assert(table->dbhandle); |
|---|
| 391 | | |
|---|
| 392 | | char *escaped_uid = osync_db_sql_escape(uid); |
|---|
| 393 | | char *query = g_strdup_printf("DELETE FROM %s WHERE uid='%s'", table->tablename, escaped_uid); |
|---|
| 394 | | g_free(escaped_uid); |
|---|
| 395 | | |
|---|
| 396 | | if (!osync_db_query(table->dbhandle, query, NULL)) { |
|---|
| 397 | | g_free(query); |
|---|
| 398 | | osync_trace(TRACE_EXIT_ERROR, "%s: Cannot delete hashtable entry.", __func__); |
|---|
| 399 | | return; |
|---|
| 400 | | /* TODO: Error handling */ |
|---|
| 401 | | } |
|---|
| 402 | | g_free(query); |
|---|
| 403 | | |
|---|
| 404 | | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| 405 | | } |
|---|
| 406 | | |
|---|
| 407 | | /*! @brief Update the hash for a entry |
|---|
| 408 | | * |
|---|
| 409 | | * Updates the hash for a entry in the hashtable. Do this after you see that a hash |
|---|
| 410 | | * has changed, for example after reading it during get_changes or after you |
|---|
| 411 | | * wrote it |
|---|
| 412 | | * |
|---|
| 413 | | * @param table The hashtable |
|---|
| 414 | | * @param type The type of change (added, modified, etc.) |
|---|
| 415 | | * @param uid the uid of the changed entry |
|---|
| 416 | | * @param hash the new hash of the changed entry |
|---|
| 417 | | */ |
|---|
| 418 | | void osync_hashtable_update_hash(OSyncHashTable *table, OSyncChangeType type, const char *uid, const char *hash) |
|---|
| 419 | | { |
|---|
| 420 | | osync_trace(TRACE_ENTRY, "%s(%p, %i, %s, %s)", __func__, table, type, uid, hash); |
|---|
| 421 | | osync_assert(table); |
|---|
| 422 | | osync_assert(table->dbhandle); |
|---|
| 423 | | |
|---|
| 424 | | switch (type) { |
|---|
| 425 | | case OSYNC_CHANGE_TYPE_DELETED: |
|---|
| 426 | | osync_hashtable_delete(table, uid); |
|---|
| 427 | | break; |
|---|
| 428 | | case OSYNC_CHANGE_TYPE_UNMODIFIED: |
|---|
| 429 | | case OSYNC_CHANGE_TYPE_UNKNOWN: |
|---|
| 430 | | case OSYNC_CHANGE_TYPE_MODIFIED: |
|---|
| 431 | | case OSYNC_CHANGE_TYPE_ADDED: |
|---|
| 432 | | osync_hashtable_write(table, uid, hash); |
|---|
| 433 | | break; |
|---|
| 434 | | } |
|---|
| 435 | | |
|---|
| 436 | | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| 437 | | } |
|---|
| | 111 | |
|---|
| | 112 | /* Only free the internal hashtable of reported entries. |
|---|
| | 113 | Don't flush the real database. */ |
|---|
| | 114 | #if GLIB_CHECK_VERSION(2,12,0) |
|---|
| | 115 | g_hash_table_remove_all(table->db_entries); |
|---|
| | 116 | #else |
|---|
| | 117 | g_hash_table_foreach_remove(table->db_entries, remove_entry, NULL); |
|---|
| | 118 | #endif |
|---|
| | 119 | |
|---|
| | 120 | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| | 121 | } |
|---|
| | 122 | |
|---|
| 446 | | * @param uid The uid to report |
|---|
| 447 | | * |
|---|
| 448 | | */ |
|---|
| 449 | | void osync_hashtable_report(OSyncHashTable *table, const char *uid) |
|---|
| 450 | | { |
|---|
| 451 | | osync_trace(TRACE_ENTRY, "%s(%p, %s)", __func__, table, uid); |
|---|
| 452 | | osync_assert(table); |
|---|
| 453 | | osync_assert(table->dbhandle); |
|---|
| 454 | | |
|---|
| 455 | | g_hash_table_insert(table->used_entries, g_strdup(uid), GINT_TO_POINTER(1)); |
|---|
| 456 | | |
|---|
| 457 | | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| 458 | | } |
|---|
| 459 | | |
|---|
| 460 | | /*! @brief Get the uid of all deleted items |
|---|
| 461 | | * |
|---|
| 462 | | * @param table The hashtable |
|---|
| 463 | | * @returns A null-terminated array of uids. The uids and this array have to be freed by the caller. |
|---|
| 464 | | * |
|---|
| 465 | | */ |
|---|
| 466 | | char **osync_hashtable_get_deleted(OSyncHashTable *table) |
|---|
| 467 | | { |
|---|
| | 131 | * @param change The change to report |
|---|
| | 132 | * |
|---|
| | 133 | */ |
|---|
| | 134 | static void osync_hashtable_report(OSyncHashTable *table, OSyncChange *change) |
|---|
| | 135 | { |
|---|
| | 136 | osync_assert(table); |
|---|
| | 137 | osync_assert(table->dbhandle); |
|---|
| | 138 | osync_assert(change); |
|---|
| | 139 | |
|---|
| | 140 | osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, table, change); |
|---|
| | 141 | |
|---|
| | 142 | /* uid get freed when hashtable get's destroyed */ |
|---|
| | 143 | char *uid = g_strdup(osync_change_get_uid(change)); |
|---|
| | 144 | |
|---|
| | 145 | g_hash_table_insert(table->reported_entries, uid, GINT_TO_POINTER(1)); |
|---|
| | 146 | |
|---|
| | 147 | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| | 148 | } |
|---|
| | 149 | |
|---|
| | 150 | /* TODO */ |
|---|
| | 151 | static void _osync_hashtable_prepare_insert_query(const char *uid, const char *hash, void *user_data) |
|---|
| | 152 | { |
|---|
| | 153 | OSyncHashTable *table = user_data; |
|---|
| | 154 | |
|---|
| | 155 | char *escaped_uid = osync_db_sql_escape(uid); |
|---|
| | 156 | char *escaped_hash = osync_db_sql_escape(hash); |
|---|
| | 157 | |
|---|
| | 158 | g_string_append_printf(table->query, |
|---|
| | 159 | "REPLACE INTO %s ('uid', 'hash') VALUES('%s', '%s');", |
|---|
| | 160 | table->name, escaped_uid, escaped_hash); |
|---|
| | 161 | |
|---|
| | 162 | g_free(escaped_uid); |
|---|
| | 163 | g_free(escaped_hash); |
|---|
| | 164 | } |
|---|
| | 165 | |
|---|
| | 166 | /*@}*/ |
|---|
| | 167 | |
|---|
| | 168 | /** |
|---|
| | 169 | * @defgroup OSyncHashtableAPI OpenSync Hashtables |
|---|
| | 170 | * @ingroup OSyncPublic |
|---|
| | 171 | * @brief A Hashtable can be used to detect changes |
|---|
| | 172 | * |
|---|
| | 173 | * Hashtables can be used to detect changes since the last invocation. They do this |
|---|
| | 174 | * by keeping track of all reported uids and the hashes of the objects. |
|---|
| | 175 | * |
|---|
| | 176 | * A hash is a string that changes when an object is updated or when the content of |
|---|
| | 177 | * the object changes. So hashes can either be a real hash like an MD5, or something |
|---|
| | 178 | * like a timestamp. The only important thing is that the hash changes when the item |
|---|
| | 179 | * gets updated. |
|---|
| | 180 | * |
|---|
| | 181 | * The hashtable is created from a .db file using the osync_hashtable_new() function. |
|---|
| | 182 | * |
|---|
| | 183 | * With osync_hashtable_load() the persinent database gets read and loads all hashtable |
|---|
| | 184 | * entries into memory. |
|---|
| | 185 | * |
|---|
| | 186 | * Now you can query and alter the hashtable in memory. You can ask if a item has changed |
|---|
| | 187 | * by doing: |
|---|
| | 188 | * |
|---|
| | 189 | * - osync_hashtable_get_changetype() |
|---|
| | 190 | * To get the changetype of a certain OSyncChange object. Don't forget to update the hash for |
|---|
| | 191 | * the change in advance. Update your OSyncChange with this detect changetype with |
|---|
| | 192 | * osync_change_set_changetype() |
|---|
| | 193 | * |
|---|
| | 194 | * - osync_hashtable_update_change() |
|---|
| | 195 | * When the changetype got updated for the OSyncChange object, update the hash entry with |
|---|
| | 196 | * calling osync_hashtable_update_change(). Call this function even if the entry has changetype |
|---|
| | 197 | * unmodified. Otherwise the hashtable will report this entry later as deleted. |
|---|
| | 198 | * |
|---|
| | 199 | * - osync_hashtable_get_deleted() |
|---|
| | 200 | * Once all available changes got reported call osync_hashtable_get_deleted() to get an OSyncList |
|---|
| | 201 | * of changes which got deleted since last sync. Entries get determined as deleted if they |
|---|
| | 202 | * got not reported as osync_hashtable_update_change(), independent of the changetype. |
|---|
| | 203 | * |
|---|
| | 204 | * - osync_hashtable_save() |
|---|
| | 205 | * For performance reason the hashtable in memory got only stored persistence with calling |
|---|
| | 206 | * osync_hashtable_save(). Call this function everytime when the synchronization finished. |
|---|
| | 207 | * This is usually inside the sync_done() function. |
|---|
| | 208 | * |
|---|
| | 209 | * After you are finished using the hashtable, call: |
|---|
| | 210 | * - osync_hashtable_unref() |
|---|
| | 211 | * |
|---|
| | 212 | * The hashtable works like this: |
|---|
| | 213 | * |
|---|
| | 214 | * First the items are reported with a certain uid or hash. If the uid does not yet |
|---|
| | 215 | * exist in the database it is reported as ADDED. If the uid exists and the hash is different |
|---|
| | 216 | * it is reported as MODIFIED. If the uid exists but the hash is the same it means that the |
|---|
| | 217 | * object is UNMODIFIED. |
|---|
| | 218 | * |
|---|
| | 219 | * To be able to report deleted objects the hashtables keeps track of the uids you reported. |
|---|
| | 220 | * After you are done with asking the hashtable for changes you can ask it for deleted objects. |
|---|
| | 221 | * All items that are in the hashtable but where not reported by you have to be DELETED. |
|---|
| | 222 | * |
|---|
| | 223 | */ |
|---|
| | 224 | /*@{*/ |
|---|
| | 225 | |
|---|
| | 226 | /*! @brief Loads or creates a hashtable |
|---|
| | 227 | * |
|---|
| | 228 | * Hashtables can be used to detect what has been changed since |
|---|
| | 229 | * the last sync |
|---|
| | 230 | * |
|---|
| | 231 | * @param path the full path and file name of the hashtable .db file to load from or create |
|---|
| | 232 | * @param objtype the object type of the hashtable |
|---|
| | 233 | * @param error An error struct |
|---|
| | 234 | * @returns A new hashtable, or NULL if an error occurred. |
|---|
| | 235 | * |
|---|
| | 236 | */ |
|---|
| | 237 | OSyncHashTable *osync_hashtable_new(const char *path, const char *objtype, OSyncError **error) |
|---|
| | 238 | { |
|---|
| | 239 | osync_trace(TRACE_ENTRY, "%s(%s, %p)", __func__, path, error); |
|---|
| | 240 | |
|---|
| | 241 | OSyncHashTable *table = osync_try_malloc0(sizeof(OSyncHashTable), error); |
|---|
| | 242 | if (!table) |
|---|
| | 243 | goto error; |
|---|
| | 244 | |
|---|
| | 245 | table->ref_count = 1; |
|---|
| | 246 | |
|---|
| | 247 | |
|---|
| | 248 | table->reported_entries = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
|---|
| | 249 | table->db_entries = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); |
|---|
| | 250 | |
|---|
| | 251 | table->dbhandle = osync_db_new(error); |
|---|
| | 252 | if (!table->dbhandle) |
|---|
| | 253 | goto error_and_free_db; |
|---|
| | 254 | |
|---|
| | 255 | if (!osync_db_open(table->dbhandle, path, error)) |
|---|
| | 256 | goto error_and_free; |
|---|
| | 257 | |
|---|
| | 258 | table->name = g_strdup_printf(OSYNC_HASHTABLE_DB_PREFIX"%s", objtype); |
|---|
| | 259 | |
|---|
| | 260 | int ret = osync_db_table_exists(table->dbhandle, table->name, error); |
|---|
| | 261 | /* greater then 0 means evrything is O.k. */ |
|---|
| | 262 | |
|---|
| | 263 | if (ret < 0) |
|---|
| | 264 | goto error; |
|---|
| | 265 | else if (ret == 0) |
|---|
| | 266 | /* if ret == 0 then table does not exist yet. contiune and create one. */ |
|---|
| | 267 | if (!osync_hashtable_create(table, error)) |
|---|
| | 268 | goto error; |
|---|
| | 269 | |
|---|
| | 270 | |
|---|
| | 271 | osync_trace(TRACE_EXIT, "%s: %p", __func__, table); |
|---|
| | 272 | return table; |
|---|
| | 273 | |
|---|
| | 274 | error_and_free_db: |
|---|
| | 275 | g_free(table->dbhandle); |
|---|
| | 276 | error_and_free: |
|---|
| | 277 | g_free(table); |
|---|
| | 278 | error: |
|---|
| | 279 | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
|---|
| | 280 | return FALSE; |
|---|
| | 281 | |
|---|
| | 282 | } |
|---|
| | 283 | |
|---|
| | 284 | /*! @brief Increase the refernece count of the hashtable. |
|---|
| | 285 | * |
|---|
| | 286 | * @param table The hashtable to increase the reference count |
|---|
| | 287 | * @returns Pointer to increased hashtable object |
|---|
| | 288 | */ |
|---|
| | 289 | OSyncHashTable *osync_hashtable_ref(OSyncHashTable *table) |
|---|
| | 290 | { |
|---|
| | 291 | osync_assert(table); |
|---|
| | 292 | |
|---|
| | 293 | g_atomic_int_inc(&(table->ref_count)); |
|---|
| | 294 | |
|---|
| | 295 | return table; |
|---|
| | 296 | } |
|---|
| | 297 | |
|---|
| | 298 | /*! @brief Decrease the reference count of the hastable. Hashtable |
|---|
| | 299 | * gets freed if the reference count get less then one. |
|---|
| | 300 | * |
|---|
| | 301 | * @param table The hashtable to decrease the reference count |
|---|
| | 302 | * |
|---|
| | 303 | */ |
|---|
| | 304 | void osync_hashtable_unref(OSyncHashTable *table) |
|---|
| | 305 | { |
|---|
| | 306 | osync_assert(table); |
|---|
| | 307 | |
|---|
| | 308 | if (g_atomic_int_dec_and_test(&(table->ref_count))) { |
|---|
| | 309 | osync_trace(TRACE_ENTRY, "%s(%p)", __func__, table); |
|---|
| | 310 | |
|---|
| | 311 | OSyncError *error = NULL; |
|---|
| | 312 | |
|---|
| | 313 | if (!osync_db_close(table->dbhandle, &error)) { |
|---|
| | 314 | osync_trace(TRACE_ERROR, "Couldn't close database: %s", osync_error_print(&error)); |
|---|
| | 315 | osync_error_unref(&error); |
|---|
| | 316 | } |
|---|
| | 317 | |
|---|
| | 318 | g_hash_table_destroy(table->reported_entries); |
|---|
| | 319 | g_hash_table_destroy(table->db_entries); |
|---|
| | 320 | |
|---|
| | 321 | g_free(table->name); |
|---|
| | 322 | g_free(table->dbhandle); |
|---|
| | 323 | g_free(table); |
|---|
| | 324 | |
|---|
| | 325 | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| | 326 | } |
|---|
| | 327 | |
|---|
| | 328 | } |
|---|
| | 329 | |
|---|
| | 330 | osync_bool osync_hashtable_load(OSyncHashTable *table, OSyncError **error) |
|---|
| | 331 | { |
|---|
| | 332 | osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, table, error); |
|---|
| | 333 | |
|---|
| | 334 | char *query; |
|---|
| | 335 | OSyncList *row, *result; |
|---|
| | 336 | |
|---|
| | 337 | query = g_strdup_printf("SELECT uid, hash FROM %s", table->name); |
|---|
| | 338 | result = osync_db_query_table(table->dbhandle, query, error); |
|---|
| | 339 | g_free(query); |
|---|
| | 340 | |
|---|
| | 341 | /* If result is NULL, this means no entries - just check for error. */ |
|---|
| | 342 | if (osync_error_is_set(error)) |
|---|
| | 343 | goto error; |
|---|
| | 344 | |
|---|
| | 345 | for (row = result; row; row = row->next) { |
|---|
| | 346 | OSyncList *column = row->data; |
|---|
| | 347 | |
|---|
| | 348 | char *uid = g_strdup(osync_list_nth_data(column, 0)); |
|---|
| | 349 | char *hash = g_strdup(osync_list_nth_data(column, 1)); |
|---|
| | 350 | |
|---|
| | 351 | g_hash_table_insert(table->db_entries, uid, hash); |
|---|
| | 352 | } |
|---|
| | 353 | |
|---|
| | 354 | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| | 355 | return TRUE; |
|---|
| | 356 | |
|---|
| | 357 | error: |
|---|
| | 358 | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
|---|
| | 359 | return FALSE; |
|---|
| | 360 | } |
|---|
| | 361 | |
|---|
| | 362 | osync_bool osync_hashtable_save(OSyncHashTable *table, OSyncError **error) |
|---|
| | 363 | { |
|---|
| | 364 | osync_trace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, table, error); |
|---|
| | 365 | |
|---|
| | 366 | osync_bool ret; |
|---|
| | 367 | |
|---|
| | 368 | /* Should only be used by this function */ |
|---|
| | 369 | osync_assert(!table->query); |
|---|
| | 370 | |
|---|
| | 371 | table->query = g_string_new("BEGIN TRANSACTION;"); |
|---|
| | 372 | g_string_append_printf(table->query, "DELETE FROM %s;", table->name); |
|---|
| | 373 | |
|---|
| | 374 | osync_hashtable_foreach(table, _osync_hashtable_prepare_insert_query, table); |
|---|
| | 375 | |
|---|
| | 376 | table->query = g_string_append(table->query, "COMMIT TRANSACTION;"); |
|---|
| | 377 | |
|---|
| | 378 | char *query = g_string_free(table->query, FALSE); |
|---|
| | 379 | ret = osync_db_query(table->dbhandle, query, error); |
|---|
| | 380 | g_free(query); |
|---|
| | 381 | |
|---|
| | 382 | table->query = NULL; |
|---|
| | 383 | |
|---|
| | 384 | if (!ret) |
|---|
| | 385 | goto error; |
|---|
| | 386 | |
|---|
| | 387 | osync_hashtable_reset_reports(table); |
|---|
| | 388 | |
|---|
| | 389 | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| | 390 | return TRUE; |
|---|
| | 391 | |
|---|
| | 392 | error: |
|---|
| | 393 | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
|---|
| | 394 | return FALSE; |
|---|
| | 395 | } |
|---|
| | 396 | |
|---|
| | 397 | |
|---|
| | 398 | /*! @brief Prepares the hashtable for a slowsync and flush the entire hashtable |
|---|
| | 399 | * |
|---|
| | 400 | * This function should be called to prepare the hashtable for a slowsync. |
|---|
| | 401 | * The entire database, which stores the values of the hashtable beyond the |
|---|
| | 402 | * synchronization, gets flushed. |
|---|
| | 403 | * |
|---|
| | 404 | * @param table The hashtable |
|---|
| | 405 | * @param error An error struct |
|---|
| | 406 | * @returns TRUE on success, or FALSE if an error occurred. |
|---|
| | 407 | * |
|---|
| | 408 | */ |
|---|
| | 409 | osync_bool osync_hashtable_slowsync(OSyncHashTable *table, OSyncError **error) |
|---|
| | 410 | { |
|---|
| | 411 | osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, table, error); |
|---|
| | 412 | osync_assert(table); |
|---|
| | 413 | osync_assert(table->dbhandle); |
|---|
| | 414 | |
|---|
| | 415 | osync_bool ret = FALSE; |
|---|
| | 416 | |
|---|
| | 417 | /* Reset persistent hashtable in database */ |
|---|
| | 418 | if (!osync_db_reset_table(table->dbhandle, table->name, error)) |
|---|
| | 419 | goto error; |
|---|
| | 420 | |
|---|
| | 421 | /* Reset hashtable in memory */ |
|---|
| | 422 | osync_hashtable_reset(table); |
|---|
| | 423 | |
|---|
| | 424 | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| | 425 | return TRUE; |
|---|
| | 426 | |
|---|
| | 427 | error: |
|---|
| | 428 | osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error)); |
|---|
| | 429 | return FALSE; |
|---|
| | 430 | |
|---|
| | 431 | } |
|---|
| | 432 | |
|---|
| | 433 | /*! @brief Update the an entry |
|---|
| | 434 | * |
|---|
| | 435 | * Updates the entry in the hashtable. Use this even if the change entry |
|---|
| | 436 | * is unmodified! Usually this function get called in get_changes(). In some |
|---|
| | 437 | * rare cases this get even called inside of the commit() plugin functions, |
|---|
| | 438 | * to update the UID inside the hashtable of a changed entry. |
|---|
| | 439 | * |
|---|
| | 440 | * @param table The hashtable |
|---|
| | 441 | * @param type The type of change (added, modified, etc.) |
|---|
| | 442 | */ |
|---|
| | 443 | void osync_hashtable_update_change(OSyncHashTable *table, OSyncChange *change) |
|---|
| | 444 | { |
|---|
| | 445 | osync_assert(table); |
|---|
| | 446 | osync_assert(table->dbhandle); |
|---|
| | 447 | osync_assert(change); |
|---|
| | 448 | |
|---|
| | 449 | osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, table, change); |
|---|
| | 450 | |
|---|
| | 451 | const char *uid = osync_change_get_uid(change); |
|---|
| | 452 | const char *hash = osync_change_get_hash(change); |
|---|
| | 453 | |
|---|
| | 454 | switch (osync_change_get_changetype(change)) { |
|---|
| | 455 | case OSYNC_CHANGE_TYPE_DELETED: |
|---|
| | 456 | g_hash_table_remove(table->db_entries, uid); |
|---|
| | 457 | break; |
|---|
| | 458 | case OSYNC_CHANGE_TYPE_UNMODIFIED: |
|---|
| | 459 | /* Nothing to do. Just ignore. */ |
|---|
| | 460 | break; |
|---|
| | 461 | case OSYNC_CHANGE_TYPE_UNKNOWN: |
|---|
| | 462 | /* Someone violets against the rules of the hashtable API! |
|---|
| | 463 | |
|---|
| | 464 | Changetype needs to get set before calling this function! |
|---|
| | 465 | Even if the change entry got not modified, then the change type |
|---|
| | 466 | should get set at least to OSYNC_CHANGE_TYPE_UNMODIFIED. Otherwise: |
|---|
| | 467 | |
|---|
| | 468 | BOOOOOOOOOOOOOOOOOOOOOM! |
|---|
| | 469 | |
|---|
| | 470 | */ |
|---|
| | 471 | osync_assert_msg(FALSE, "Got called with unknown changetype. This looks like a plugin makes wrong use of a hashtable. Please, contact the plugin author!"); |
|---|
| | 472 | break; |
|---|
| | 473 | case OSYNC_CHANGE_TYPE_MODIFIED: |
|---|
| | 474 | /* This works even if the UID/key is new to the hashtable */ |
|---|
| | 475 | g_hash_table_replace(table->db_entries, g_strdup(uid), g_strdup(hash)); |
|---|
| | 476 | break; |
|---|
| | 477 | case OSYNC_CHANGE_TYPE_ADDED: |
|---|
| | 478 | g_hash_table_insert(table->db_entries, g_strdup(uid), g_strdup(hash)); |
|---|
| | 479 | break; |
|---|
| | 480 | } |
|---|
| | 481 | |
|---|
| | 482 | osync_hashtable_report(table, change); |
|---|
| | 483 | |
|---|
| | 484 | osync_trace(TRACE_EXIT, "%s", __func__); |
|---|
| | 485 | } |
|---|
| | 486 | |
|---|
| | 487 | /*! @brief Get a list of uids which deleted |
|---|
| | 488 | * |
|---|
| | 489 | * @param table The hashtable |
|---|
| | 490 | * @returns OSyncList containing UIDs of deleted entries. Caller is responsible for freeing the ist, |
|---|
| | 491 | * not the content, with osync_list_free() . |
|---|
| | 492 | * |
|---|
| | 493 | */ |
|---|
| | 494 | OSyncList *osync_hashtable_get_deleted(OSyncHashTable *table) |
|---|
| | 495 | { |
|---|
| | 496 | osync_assert(table); |
|---|
| | 497 | osync_assert(table->dbhandle); |
|---|
| | 498 | |
|---|