mirror of
https://github.com/chylex/Nextcloud-Desktop.git
synced 2025-04-09 19:15:43 +02:00
Replace the custom rbtree by a std::map
This commit is contained in:
parent
72e44ce3d7
commit
d66c2b5fae
src
csync
csync.cppcsync_private.hcsync_reconcile.cppcsync_rename.cppcsync_rename.hcsync_statedb.cppcsync_update.cpp
std
libsync
test/csync
@ -51,36 +51,6 @@
|
||||
#include "csync_rename.h"
|
||||
#include "c_jhash.h"
|
||||
|
||||
static int _key_cmp(const void *key, const void *data) {
|
||||
uint64_t a;
|
||||
csync_file_stat_t *b;
|
||||
|
||||
a = *(uint64_t *) (key);
|
||||
b = (csync_file_stat_t *) data;
|
||||
|
||||
if (a < b->phash) {
|
||||
return -1;
|
||||
} else if (a > b->phash) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _data_cmp(const void *key, const void *data) {
|
||||
csync_file_stat_t *a, *b;
|
||||
|
||||
a = (csync_file_stat_t *) key;
|
||||
b = (csync_file_stat_t *) data;
|
||||
|
||||
if (a->phash < b->phash) {
|
||||
return -1;
|
||||
} else if (a->phash > b->phash) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
csync_s::csync_s(const char *localUri, const char *db_file) {
|
||||
size_t len = 0;
|
||||
@ -91,9 +61,6 @@ csync_s::csync_s(const char *localUri, const char *db_file) {
|
||||
|
||||
local.uri = c_strndup(localUri, len);
|
||||
|
||||
c_rbtree_create(&local.tree, _key_cmp, _data_cmp);
|
||||
c_rbtree_create(&remote.tree, _key_cmp, _data_cmp);
|
||||
|
||||
statedb.file = c_strdup(db_file);
|
||||
}
|
||||
|
||||
@ -137,7 +104,7 @@ int csync_update(CSYNC *ctx) {
|
||||
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG,
|
||||
"Update detection for local replica took %.2f seconds walking %zu files.",
|
||||
c_secdiff(finish, start), c_rbtree_size(ctx->local.tree));
|
||||
c_secdiff(finish, start), ctx->local.files.size());
|
||||
csync_memstat_check();
|
||||
|
||||
/* update detection for remote replica */
|
||||
@ -157,7 +124,7 @@ int csync_update(CSYNC *ctx) {
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG,
|
||||
"Update detection for remote replica took %.2f seconds "
|
||||
"walking %zu files.",
|
||||
c_secdiff(finish, start), c_rbtree_size(ctx->remote.tree));
|
||||
c_secdiff(finish, start), ctx->remote.files.size());
|
||||
csync_memstat_check();
|
||||
|
||||
ctx->status |= CSYNC_STATUS_UPDATE;
|
||||
@ -195,7 +162,7 @@ int csync_reconcile(CSYNC *ctx) {
|
||||
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG,
|
||||
"Reconciliation for local replica took %.2f seconds visiting %zu files.",
|
||||
c_secdiff(finish, start), c_rbtree_size(ctx->local.tree));
|
||||
c_secdiff(finish, start), ctx->local.files.size());
|
||||
|
||||
if (rc < 0) {
|
||||
if (!CSYNC_STATUS_IS_OK(ctx->status_code)) {
|
||||
@ -215,7 +182,7 @@ int csync_reconcile(CSYNC *ctx) {
|
||||
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG,
|
||||
"Reconciliation for remote replica took %.2f seconds visiting %zu files.",
|
||||
c_secdiff(finish, start), c_rbtree_size(ctx->remote.tree));
|
||||
c_secdiff(finish, start), ctx->remote.files.size());
|
||||
|
||||
if (rc < 0) {
|
||||
if (!CSYNC_STATUS_IS_OK(ctx->status_code)) {
|
||||
@ -236,17 +203,11 @@ out:
|
||||
/*
|
||||
* local visitor which calls the user visitor with repacked stat info.
|
||||
*/
|
||||
static int _csync_treewalk_visitor(void *obj, void *data) {
|
||||
static int _csync_treewalk_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
int rc = 0;
|
||||
csync_file_stat_t *cur = NULL;
|
||||
CSYNC *ctx = NULL;
|
||||
csync_treewalk_visit_func *visitor = NULL;
|
||||
_csync_treewalk_context *twctx = NULL;
|
||||
c_rbtree_t *other_tree = NULL;
|
||||
c_rbnode_t *other_node = NULL;
|
||||
|
||||
cur = (csync_file_stat_t *) obj;
|
||||
ctx = (CSYNC *) data;
|
||||
csync_s::FileMap *other_tree = nullptr;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return -1;
|
||||
@ -255,51 +216,33 @@ static int _csync_treewalk_visitor(void *obj, void *data) {
|
||||
/* we need the opposite tree! */
|
||||
switch (ctx->current) {
|
||||
case LOCAL_REPLICA:
|
||||
other_tree = ctx->remote.tree;
|
||||
other_tree = &ctx->remote.files;
|
||||
break;
|
||||
case REMOTE_REPLICA:
|
||||
other_tree = ctx->local.tree;
|
||||
other_tree = &ctx->local.files;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
other_node = c_rbtree_find(other_tree, &cur->phash);
|
||||
csync_s::FileMap::const_iterator other_file_it = other_tree->find(cur->path);
|
||||
|
||||
if (!other_node) {
|
||||
if (other_file_it == other_tree->cend()) {
|
||||
/* Check the renamed path as well. */
|
||||
int len;
|
||||
uint64_t h = 0;
|
||||
char *renamed_path = csync_rename_adjust_path(ctx, cur->path);
|
||||
|
||||
if (!c_streq(renamed_path, cur->path)) {
|
||||
len = strlen( renamed_path );
|
||||
h = c_jhash64((uint8_t *) renamed_path, len, 0);
|
||||
other_node = c_rbtree_find(other_tree, &h);
|
||||
}
|
||||
SAFE_FREE(renamed_path);
|
||||
QByteArray renamed_path = csync_rename_adjust_path(ctx, cur->path);
|
||||
if (renamed_path != cur->path)
|
||||
other_file_it = other_tree->find(renamed_path);
|
||||
}
|
||||
|
||||
if (!other_node) {
|
||||
if (other_file_it == other_tree->cend()) {
|
||||
/* Check the source path as well. */
|
||||
int len;
|
||||
uint64_t h = 0;
|
||||
char *renamed_path = csync_rename_adjust_path_source(ctx, cur->path);
|
||||
|
||||
if (!c_streq(renamed_path, cur->path)) {
|
||||
len = strlen( renamed_path );
|
||||
h = c_jhash64((uint8_t *) renamed_path, len, 0);
|
||||
other_node = c_rbtree_find(other_tree, &h);
|
||||
}
|
||||
SAFE_FREE(renamed_path);
|
||||
QByteArray renamed_path = csync_rename_adjust_path_source(ctx, cur->path);
|
||||
if (renamed_path != cur->path)
|
||||
other_file_it = other_tree->find(renamed_path);
|
||||
}
|
||||
|
||||
csync_file_stat_t *other = other_node ? (csync_file_stat_t*)other_node->data : NULL;
|
||||
csync_file_stat_t *other = (other_file_it != other_tree->cend()) ? other_file_it->second.get() : NULL;
|
||||
|
||||
if (obj == NULL || data == NULL) {
|
||||
ctx->status_code = CSYNC_STATUS_PARAM_ERROR;
|
||||
return -1;
|
||||
}
|
||||
ctx->status_code = CSYNC_STATUS_OK;
|
||||
|
||||
twctx = (_csync_treewalk_context*) ctx->callbacks.userdata;
|
||||
@ -331,28 +274,24 @@ static int _csync_treewalk_visitor(void *obj, void *data) {
|
||||
* which calls the local _csync_treewalk_visitor in this module.
|
||||
* The user visitor is called from there.
|
||||
*/
|
||||
static int _csync_walk_tree(CSYNC *ctx, c_rbtree_t *tree, csync_treewalk_visit_func *visitor, int filter)
|
||||
static int _csync_walk_tree(CSYNC *ctx, csync_s::FileMap *tree, csync_treewalk_visit_func *visitor, int filter)
|
||||
{
|
||||
_csync_treewalk_context tw_ctx;
|
||||
int rc = -1;
|
||||
int rc = 0;
|
||||
|
||||
if (ctx == NULL) {
|
||||
errno = EBADF;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (visitor == NULL || tree == NULL) {
|
||||
ctx->status_code = CSYNC_STATUS_PARAM_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
tw_ctx.userdata = ctx->callbacks.userdata;
|
||||
tw_ctx.user_visitor = visitor;
|
||||
tw_ctx.instruction_filter = filter;
|
||||
|
||||
ctx->callbacks.userdata = &tw_ctx;
|
||||
|
||||
rc = c_rbtree_walk(tree, (void*) ctx, _csync_treewalk_visitor);
|
||||
for (auto &pair : *tree) {
|
||||
if (_csync_treewalk_visitor(pair.second.get(), ctx) < 0) {
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc < 0 ) {
|
||||
if( ctx->status_code == CSYNC_STATUS_OK )
|
||||
ctx->status_code = csync_errno_to_status(errno, CSYNC_STATUS_TREE_ERROR);
|
||||
@ -367,18 +306,9 @@ static int _csync_walk_tree(CSYNC *ctx, c_rbtree_t *tree, csync_treewalk_visit_f
|
||||
*/
|
||||
int csync_walk_remote_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter)
|
||||
{
|
||||
c_rbtree_t *tree = NULL;
|
||||
int rc = -1;
|
||||
|
||||
if(ctx != NULL) {
|
||||
ctx->status_code = CSYNC_STATUS_OK;
|
||||
ctx->current = REMOTE_REPLICA;
|
||||
tree = ctx->remote.tree;
|
||||
}
|
||||
|
||||
/* all error handling in the called function */
|
||||
rc = _csync_walk_tree(ctx, tree, visitor, filter);
|
||||
return rc;
|
||||
ctx->status_code = CSYNC_STATUS_OK;
|
||||
ctx->current = REMOTE_REPLICA;
|
||||
return _csync_walk_tree(ctx, &ctx->remote.files, visitor, filter);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -386,25 +316,9 @@ int csync_walk_remote_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int
|
||||
*/
|
||||
int csync_walk_local_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter)
|
||||
{
|
||||
c_rbtree_t *tree = NULL;
|
||||
int rc = -1;
|
||||
|
||||
if (ctx != NULL) {
|
||||
ctx->status_code = CSYNC_STATUS_OK;
|
||||
ctx->current = LOCAL_REPLICA;
|
||||
tree = ctx->local.tree;
|
||||
}
|
||||
|
||||
/* all error handling in the called function */
|
||||
rc = _csync_walk_tree(ctx, tree, visitor, filter);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void _tree_destructor(void *data) {
|
||||
csync_file_stat_t *freedata = NULL;
|
||||
|
||||
freedata = (csync_file_stat_t *) data;
|
||||
delete freedata;
|
||||
ctx->status_code = CSYNC_STATUS_OK;
|
||||
ctx->current = LOCAL_REPLICA;
|
||||
return _csync_walk_tree(ctx, &ctx->local.files, visitor, filter);
|
||||
}
|
||||
|
||||
int csync_s::reinitialize() {
|
||||
@ -419,26 +333,12 @@ int csync_s::reinitialize() {
|
||||
}
|
||||
statedb.db = NULL;
|
||||
|
||||
/* destroy the rbtrees */
|
||||
if (c_rbtree_size(local.tree) > 0) {
|
||||
c_rbtree_destroy(local.tree, _tree_destructor);
|
||||
}
|
||||
|
||||
if (c_rbtree_size(remote.tree) > 0) {
|
||||
c_rbtree_destroy(remote.tree, _tree_destructor);
|
||||
}
|
||||
|
||||
/* free memory */
|
||||
c_rbtree_free(local.tree);
|
||||
c_rbtree_free(remote.tree);
|
||||
|
||||
remote.read_from_db = 0;
|
||||
read_remote_from_db = true;
|
||||
db_is_empty = false;
|
||||
|
||||
/* Create new trees */
|
||||
c_rbtree_create(&local.tree, _key_cmp, _data_cmp);
|
||||
c_rbtree_create(&remote.tree, _key_cmp, _data_cmp);
|
||||
local.files.clear();
|
||||
remote.files.clear();
|
||||
|
||||
status = CSYNC_STATUS_INIT;
|
||||
SAFE_FREE(error_string);
|
||||
@ -454,19 +354,6 @@ csync_s::~csync_s() {
|
||||
}
|
||||
statedb.db = NULL;
|
||||
|
||||
/* destroy the rbtrees */
|
||||
if (c_rbtree_size(local.tree) > 0) {
|
||||
c_rbtree_destroy(local.tree, _tree_destructor);
|
||||
}
|
||||
|
||||
if (c_rbtree_size(remote.tree) > 0) {
|
||||
c_rbtree_destroy(remote.tree, _tree_destructor);
|
||||
}
|
||||
|
||||
/* free memory */
|
||||
c_rbtree_free(local.tree);
|
||||
c_rbtree_free(remote.tree);
|
||||
|
||||
SAFE_FREE(statedb.file);
|
||||
SAFE_FREE(local.uri);
|
||||
SAFE_FREE(error_string);
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <sqlite3.h>
|
||||
#include <map>
|
||||
|
||||
#include "config_csync.h"
|
||||
#include "std/c_lib.h"
|
||||
@ -69,6 +70,14 @@ enum csync_replica_e {
|
||||
* @brief csync public structure
|
||||
*/
|
||||
struct OCSYNC_EXPORT csync_s {
|
||||
class FileMap : public std::map<QByteArray, std::unique_ptr<csync_file_stat_t>> {
|
||||
public:
|
||||
csync_file_stat_t *findFile(const QByteArray &key) const {
|
||||
auto it = find(key);
|
||||
return it != end() ? it->second.get() : nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct {
|
||||
csync_auth_callback auth_function = nullptr;
|
||||
void *userdata = nullptr;
|
||||
@ -76,8 +85,8 @@ struct OCSYNC_EXPORT csync_s {
|
||||
void *update_callback_userdata = nullptr;
|
||||
|
||||
/* hooks for checking the white list (uses the update_callback_userdata) */
|
||||
int (*checkSelectiveSyncBlackListHook)(void*, const char*) = nullptr;
|
||||
int (*checkSelectiveSyncNewFolderHook)(void*, const char* /* path */, const char* /* remotePerm */) = nullptr;
|
||||
int (*checkSelectiveSyncBlackListHook)(void*, const QByteArray &) = nullptr;
|
||||
int (*checkSelectiveSyncNewFolderHook)(void*, const QByteArray &/* path */, const QByteArray &/* remotePerm */) = nullptr;
|
||||
|
||||
|
||||
csync_vio_opendir_hook remote_opendir_hook = nullptr;
|
||||
@ -105,17 +114,17 @@ struct OCSYNC_EXPORT csync_s {
|
||||
} statedb;
|
||||
|
||||
struct {
|
||||
std::map<std::string, std::string> folder_renamed_to; // map from->to
|
||||
std::map<std::string, std::string> folder_renamed_from; // map to->from
|
||||
std::map<QByteArray, QByteArray> folder_renamed_to; // map from->to
|
||||
std::map<QByteArray, QByteArray> folder_renamed_from; // map to->from
|
||||
} renames;
|
||||
|
||||
struct {
|
||||
char *uri = nullptr;
|
||||
c_rbtree_t *tree = nullptr;
|
||||
FileMap files;
|
||||
} local;
|
||||
|
||||
struct {
|
||||
c_rbtree_t *tree = nullptr;
|
||||
FileMap files;
|
||||
bool read_from_db = false;
|
||||
QByteArray root_perms; /* Permission of the root folder. (Since the root folder is not in the db tree, we need to keep a separate entry.) */
|
||||
} remote;
|
||||
@ -151,6 +160,13 @@ struct OCSYNC_EXPORT csync_s {
|
||||
csync_s(const char *localUri, const char *db_file);
|
||||
~csync_s();
|
||||
int reinitialize();
|
||||
|
||||
// For some reason MSVC references the copy constructor and/or the assignment operator
|
||||
// if a class is exported. This is a problem since unique_ptr isn't copyable.
|
||||
// Explicitly disable them to fix the issue.
|
||||
// https://social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/e39ab33d-1aaf-4125-b6de-50410d9ced1d
|
||||
csync_s(const csync_s &) = delete;
|
||||
csync_s &operator=(const csync_s &) = delete;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -37,33 +37,28 @@
|
||||
|
||||
/* Check if a file is ignored because one parent is ignored.
|
||||
* return the node of the ignored directoy if it's the case, or NULL if it is not ignored */
|
||||
static c_rbnode_t *_csync_check_ignored(c_rbtree_t *tree, const char *path, int pathlen) {
|
||||
uint64_t h = 0;
|
||||
c_rbnode_t *node = NULL;
|
||||
|
||||
static csync_file_stat_t *_csync_check_ignored(csync_s::FileMap *tree, const QByteArray &path) {
|
||||
/* compute the size of the parent directory */
|
||||
int parentlen = pathlen - 1;
|
||||
while (parentlen > 0 && path[parentlen] != '/') {
|
||||
int parentlen = path.size() - 1;
|
||||
while (parentlen > 0 && path.at(parentlen) != '/') {
|
||||
parentlen--;
|
||||
}
|
||||
if (parentlen <= 0) {
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
h = c_jhash64((uint8_t *) path, parentlen, 0);
|
||||
node = c_rbtree_find(tree, &h);
|
||||
if (node) {
|
||||
csync_file_stat_t *n = (csync_file_stat_t*)node->data;
|
||||
if (n->instruction == CSYNC_INSTRUCTION_IGNORE) {
|
||||
QByteArray parentPath = path.left(parentlen);
|
||||
csync_file_stat_t *fs = tree->findFile(parentPath);
|
||||
if (fs) {
|
||||
if (fs->instruction == CSYNC_INSTRUCTION_IGNORE) {
|
||||
/* Yes, we are ignored */
|
||||
return node;
|
||||
return fs;
|
||||
} else {
|
||||
/* Not ignored */
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
/* Try if the parent itself is ignored */
|
||||
return _csync_check_ignored(tree, path, parentlen);
|
||||
return _csync_check_ignored(tree, parentPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +78,7 @@ static bool _csync_is_collision_safe_hash(const char *checksum_header)
|
||||
/**
|
||||
* The main function in the reconcile pass.
|
||||
*
|
||||
* It's called for each entry in the local and remote rbtrees by
|
||||
* It's called for each entry in the local and remote files by
|
||||
* csync_reconcile()
|
||||
*
|
||||
* Before the reconcile phase the trees already know about changes
|
||||
@ -107,52 +102,37 @@ static bool _csync_is_collision_safe_hash(const char *checksum_header)
|
||||
* (timestamp is newer), it is not overwritten. If both files, on the
|
||||
* source and the destination, have been changed, the newer file wins.
|
||||
*/
|
||||
static int _csync_merge_algorithm_visitor(void *obj, void *data) {
|
||||
csync_file_stat_t *cur = NULL;
|
||||
csync_file_stat_t *other = NULL;
|
||||
static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
std::unique_ptr<csync_file_stat_t> tmp;
|
||||
uint64_t h = 0;
|
||||
int len = 0;
|
||||
|
||||
CSYNC *ctx = NULL;
|
||||
c_rbtree_t *tree = NULL;
|
||||
c_rbnode_t *node = NULL;
|
||||
|
||||
cur = (csync_file_stat_t *) obj;
|
||||
ctx = (CSYNC *) data;
|
||||
csync_s::FileMap *other_tree = nullptr;
|
||||
|
||||
/* we need the opposite tree! */
|
||||
switch (ctx->current) {
|
||||
case LOCAL_REPLICA:
|
||||
tree = ctx->remote.tree;
|
||||
other_tree = &ctx->remote.files;
|
||||
break;
|
||||
case REMOTE_REPLICA:
|
||||
tree = ctx->local.tree;
|
||||
other_tree = &ctx->local.files;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
node = c_rbtree_find(tree, &cur->phash);
|
||||
csync_file_stat_t *other = other_tree->findFile(cur->path);;
|
||||
|
||||
if (!node) {
|
||||
if (!other) {
|
||||
/* Check the renamed path as well. */
|
||||
char *renamed_path = csync_rename_adjust_path(ctx, cur->path);
|
||||
if (renamed_path != cur->path) {
|
||||
len = strlen( renamed_path );
|
||||
h = c_jhash64((uint8_t *) renamed_path, len, 0);
|
||||
node = c_rbtree_find(tree, &h);
|
||||
}
|
||||
SAFE_FREE(renamed_path);
|
||||
other = other_tree->findFile(csync_rename_adjust_path(ctx, cur->path));
|
||||
}
|
||||
if (!node) {
|
||||
if (!other) {
|
||||
/* Check if it is ignored */
|
||||
node = _csync_check_ignored(tree, cur->path, cur->path.size());
|
||||
other = _csync_check_ignored(other_tree, cur->path);
|
||||
/* If it is ignored, other->instruction will be IGNORE so this one will also be ignored */
|
||||
}
|
||||
|
||||
/* file only found on current replica */
|
||||
if (node == NULL) {
|
||||
if (!other) {
|
||||
switch(cur->instruction) {
|
||||
/* file has been modified */
|
||||
case CSYNC_INSTRUCTION_EVAL:
|
||||
@ -187,26 +167,20 @@ static int _csync_merge_algorithm_visitor(void *obj, void *data) {
|
||||
}
|
||||
|
||||
if( tmp ) {
|
||||
len = strlen( tmp->path );
|
||||
if( len > 0 ) {
|
||||
h = c_jhash64((uint8_t *) tmp->path.constData(), len, 0);
|
||||
if( !tmp->path.isEmpty() ) {
|
||||
/* First, check that the file is NOT in our tree (another file with the same name was added) */
|
||||
node = c_rbtree_find(ctx->current == REMOTE_REPLICA ? ctx->remote.tree : ctx->local.tree, &h);
|
||||
if (node) {
|
||||
csync_s::FileMap *our_tree = ctx->current == REMOTE_REPLICA ? &ctx->remote.files : &ctx->local.files;
|
||||
if (our_tree->findFile(tmp->path)) {
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Origin found in our tree : %s", tmp->path.constData());
|
||||
} else {
|
||||
/* Find the temporar file in the other tree. */
|
||||
node = c_rbtree_find(tree, &h);
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "PHash of temporary opposite (%s): %" PRIu64 " %s",
|
||||
tmp->path.constData() , h, node ? "found": "not found" );
|
||||
if (node) {
|
||||
other = (csync_file_stat_t*)node->data;
|
||||
} else {
|
||||
/* the renamed file could not be found in the opposite tree. That is because it
|
||||
* is not longer existing there, maybe because it was renamed or deleted.
|
||||
* The journal is cleaned up later after propagation.
|
||||
*/
|
||||
}
|
||||
/* Find the temporar file in the other tree.
|
||||
* If the renamed file could not be found in the opposite tree, that is because it
|
||||
* is not longer existing there, maybe because it was renamed or deleted.
|
||||
* The journal is cleaned up later after propagation.
|
||||
*/
|
||||
other = other_tree->findFile(tmp->path);
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Temporary opposite (%s) %s",
|
||||
tmp->path.constData() , other ? "found": "not found" );
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,7 +224,6 @@ static int _csync_merge_algorithm_visitor(void *obj, void *data) {
|
||||
/*
|
||||
* file found on the other replica
|
||||
*/
|
||||
other = (csync_file_stat_t *) node->data;
|
||||
|
||||
switch (cur->instruction) {
|
||||
case CSYNC_INSTRUCTION_UPDATE_METADATA:
|
||||
@ -395,25 +368,26 @@ static int _csync_merge_algorithm_visitor(void *obj, void *data) {
|
||||
}
|
||||
|
||||
int csync_reconcile_updates(CSYNC *ctx) {
|
||||
int rc;
|
||||
c_rbtree_t *tree = NULL;
|
||||
csync_s::FileMap *tree = nullptr;
|
||||
|
||||
switch (ctx->current) {
|
||||
case LOCAL_REPLICA:
|
||||
tree = ctx->local.tree;
|
||||
tree = &ctx->local.files;
|
||||
break;
|
||||
case REMOTE_REPLICA:
|
||||
tree = ctx->remote.tree;
|
||||
tree = &ctx->remote.files;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
rc = c_rbtree_walk(tree, (void *) ctx, _csync_merge_algorithm_visitor);
|
||||
if( rc < 0 ) {
|
||||
ctx->status_code = CSYNC_STATUS_RECONCILE_ERROR;
|
||||
for (auto &pair : *tree) {
|
||||
if (_csync_merge_algorithm_visitor(pair.second.get(), ctx) < 0) {
|
||||
ctx->status_code = CSYNC_STATUS_RECONCILE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* vim: set ts=8 sw=2 et cindent: */
|
||||
|
@ -21,45 +21,43 @@
|
||||
#include "csync_private.h"
|
||||
#include "csync_rename.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
static std::string _parentDir(const std::string &path) {
|
||||
static QByteArray _parentDir(const QByteArray &path) {
|
||||
int len = path.length();
|
||||
while(len > 0 && path[len-1]!='/') len--;
|
||||
while(len > 0 && path[len-1]=='/') len--;
|
||||
return path.substr(0, len);
|
||||
while(len > 0 && path.at(len-1)!='/') len--;
|
||||
while(len > 0 && path.at(len-1)=='/') len--;
|
||||
return path.left(len);
|
||||
}
|
||||
|
||||
void csync_rename_record(CSYNC* ctx, const char* from, const char* to)
|
||||
void csync_rename_record(CSYNC* ctx, const QByteArray &from, const QByteArray &to)
|
||||
{
|
||||
ctx->renames.folder_renamed_to[from] = to;
|
||||
ctx->renames.folder_renamed_from[to] = from;
|
||||
}
|
||||
|
||||
char* csync_rename_adjust_path(CSYNC* ctx, const char* path)
|
||||
QByteArray csync_rename_adjust_path(CSYNC* ctx, const QByteArray &path)
|
||||
{
|
||||
for (std::string p = _parentDir(path); !p.empty(); p = _parentDir(p)) {
|
||||
std::map< std::string, std::string >::iterator it = ctx->renames.folder_renamed_to.find(p);
|
||||
for (QByteArray p = _parentDir(path); !p.isEmpty(); p = _parentDir(p)) {
|
||||
std::map< QByteArray, QByteArray >::iterator it = ctx->renames.folder_renamed_to.find(p);
|
||||
if (it != ctx->renames.folder_renamed_to.end()) {
|
||||
std::string rep = it->second + (path + p.length());
|
||||
return c_strdup(rep.c_str());
|
||||
QByteArray rep = it->second + path.mid(p.length());
|
||||
return rep;
|
||||
}
|
||||
}
|
||||
return c_strdup(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
char* csync_rename_adjust_path_source(CSYNC* ctx, const char* path)
|
||||
QByteArray csync_rename_adjust_path_source(CSYNC* ctx, const QByteArray &path)
|
||||
{
|
||||
for (std::string p = _parentDir(path); !p.empty(); p = _parentDir(p)) {
|
||||
std::map< std::string, std::string >::iterator it = ctx->renames.folder_renamed_from.find(p);
|
||||
for (QByteArray p = _parentDir(path); !p.isEmpty(); p = _parentDir(p)) {
|
||||
std::map< QByteArray, QByteArray >::iterator it = ctx->renames.folder_renamed_from.find(p);
|
||||
if (it != ctx->renames.folder_renamed_from.end()) {
|
||||
std::string rep = it->second + (path + p.length());
|
||||
return c_strdup(rep.c_str());
|
||||
QByteArray rep = it->second + path.mid(p.length());
|
||||
return rep;
|
||||
}
|
||||
}
|
||||
return c_strdup(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
bool csync_rename_count(CSYNC *ctx) {
|
||||
|
@ -23,9 +23,9 @@
|
||||
#include "csync.h"
|
||||
|
||||
/* Return the final destination path of a given patch in case of renames */
|
||||
char OCSYNC_EXPORT *csync_rename_adjust_path(CSYNC *ctx, const char *path);
|
||||
QByteArray OCSYNC_EXPORT csync_rename_adjust_path(CSYNC *ctx, const QByteArray &path);
|
||||
/* Return the source of a given path in case of renames */
|
||||
char OCSYNC_EXPORT *csync_rename_adjust_path_source(CSYNC *ctx, const char *path);
|
||||
void OCSYNC_EXPORT csync_rename_record(CSYNC *ctx, const char *from, const char *to);
|
||||
QByteArray OCSYNC_EXPORT csync_rename_adjust_path_source(CSYNC *ctx, const QByteArray &path);
|
||||
void OCSYNC_EXPORT csync_rename_record(CSYNC *ctx, const QByteArray &from, const QByteArray &to);
|
||||
/* Return the amount of renamed item recorded */
|
||||
bool OCSYNC_EXPORT csync_rename_count(CSYNC *ctx);
|
||||
|
@ -486,18 +486,15 @@ int csync_statedb_get_below_path( CSYNC *ctx, const char *path ) {
|
||||
}
|
||||
|
||||
/* store into result list. */
|
||||
if (c_rbtree_insert(ctx->remote.tree, (void *) st.release()) < 0) {
|
||||
st.reset();
|
||||
ctx->status_code = CSYNC_STATUS_TREE_ERROR;
|
||||
break;
|
||||
}
|
||||
QByteArray path = st->path;
|
||||
ctx->remote.files[path] = std::move(st);
|
||||
cnt++;
|
||||
}
|
||||
} while( rc == SQLITE_ROW );
|
||||
|
||||
ctx->statedb.lastReturnValue = rc;
|
||||
if( rc != SQLITE_DONE ) {
|
||||
ctx->status_code = CSYNC_STATUS_TREE_ERROR;
|
||||
ctx->status_code = CSYNC_STATUS_STATEDB_LOAD_ERROR;
|
||||
} else {
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "%" PRId64 " entries read below path %s from db.", cnt, path);
|
||||
}
|
||||
|
@ -383,18 +383,13 @@ out:
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "file: %s, instruction: %s <<=", fs->path.constData(),
|
||||
csync_instruction_str(fs->instruction));
|
||||
|
||||
QByteArray path = fs->path;
|
||||
switch (ctx->current) {
|
||||
case LOCAL_REPLICA:
|
||||
if (c_rbtree_insert(ctx->local.tree, (void *) fs.release()) < 0) {
|
||||
ctx->status_code = CSYNC_STATUS_TREE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
ctx->local.files[path] = std::move(fs);
|
||||
break;
|
||||
case REMOTE_REPLICA:
|
||||
if (c_rbtree_insert(ctx->remote.tree, (void *) fs.release()) < 0) {
|
||||
ctx->status_code = CSYNC_STATUS_TREE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
ctx->remote.files[path] = std::move(fs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -17,7 +17,6 @@ set(CSTDLIB_LINK_LIBRARIES
|
||||
set(cstdlib_SRCS
|
||||
c_alloc.c
|
||||
c_path.c
|
||||
c_rbtree.c
|
||||
c_string.c
|
||||
c_time.c
|
||||
c_utf8.cpp
|
||||
|
@ -24,7 +24,6 @@
|
||||
#include "c_macro.h"
|
||||
#include "c_alloc.h"
|
||||
#include "c_path.h"
|
||||
#include "c_rbtree.h"
|
||||
#include "c_string.h"
|
||||
#include "c_time.h"
|
||||
#include "c_private.h"
|
||||
|
@ -1,743 +0,0 @@
|
||||
/*
|
||||
* cynapses libc functions
|
||||
*
|
||||
* Copyright (c) 2003-2004 by Andrew Suffield <asuffield@debian.org>
|
||||
* Copyright (c) 2008-2013 by Andreas Schneider <asn@cryptomilk.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code was originally released under GPL but Andrew Suffield agreed to
|
||||
* change it to LGPL.
|
||||
*/
|
||||
|
||||
/*
|
||||
* static function don't have NULL pointer checks, segfaults are intended.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "c_alloc.h"
|
||||
#include "c_rbtree.h"
|
||||
|
||||
#define NIL &_sentinel /* all leafs are sentinels */
|
||||
static c_rbnode_t _sentinel = {NULL, NIL, NIL, NULL, NULL, BLACK};
|
||||
|
||||
void c_rbtree_create(c_rbtree_t **rbtree, c_rbtree_compare_func *key_compare, c_rbtree_compare_func *data_compare) {
|
||||
assert(rbtree);
|
||||
assert(key_compare);
|
||||
assert(data_compare);
|
||||
|
||||
c_rbtree_t *tree = NULL;
|
||||
|
||||
tree = c_malloc(sizeof(*tree));
|
||||
tree->root = NIL;
|
||||
tree->key_compare = key_compare;
|
||||
tree->data_compare = data_compare;
|
||||
tree->size = 0;
|
||||
|
||||
*rbtree = tree;
|
||||
}
|
||||
|
||||
static c_rbnode_t *_rbtree_subtree_dup(const c_rbnode_t *node, c_rbtree_t *new_tree, c_rbnode_t *new_parent) {
|
||||
c_rbnode_t *new_node = NULL;
|
||||
|
||||
new_node = (c_rbnode_t*) c_malloc(sizeof(c_rbnode_t));
|
||||
|
||||
new_node->tree = new_tree;
|
||||
new_node->data = node->data;
|
||||
new_node->color = node->color;
|
||||
new_node->parent = new_parent;
|
||||
|
||||
if (node->left == NIL) {
|
||||
new_node->left = NIL;
|
||||
} else {
|
||||
new_node->left = _rbtree_subtree_dup(node->left, new_tree, new_node);
|
||||
}
|
||||
|
||||
if (node->right == NIL) {
|
||||
new_node->right = NIL;
|
||||
} else {
|
||||
new_node->right = _rbtree_subtree_dup(node->right, new_tree, new_node);
|
||||
}
|
||||
|
||||
return new_node;
|
||||
}
|
||||
|
||||
c_rbtree_t *c_rbtree_dup(const c_rbtree_t *tree) {
|
||||
c_rbtree_t *new_tree = NULL;
|
||||
|
||||
new_tree = (c_rbtree_t*) c_malloc(sizeof(c_rbtree_t));
|
||||
|
||||
new_tree->key_compare = tree->key_compare;
|
||||
new_tree->data_compare = tree->data_compare;
|
||||
new_tree->size = tree->size;
|
||||
new_tree->root = _rbtree_subtree_dup(tree->root, new_tree, NULL);
|
||||
|
||||
return new_tree;
|
||||
}
|
||||
|
||||
static int _rbtree_subtree_free(c_rbnode_t *node) {
|
||||
assert(node);
|
||||
|
||||
if (node->left != NIL) {
|
||||
if (_rbtree_subtree_free(node->left) < 0) {
|
||||
/* TODO: set errno? ECANCELED? */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (node->right != NIL) {
|
||||
if (_rbtree_subtree_free(node->right) < 0) {
|
||||
/* TODO: set errno? ECANCELED? */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
SAFE_FREE(node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int c_rbtree_free(c_rbtree_t *tree) {
|
||||
if (tree == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (tree->root != NIL) {
|
||||
_rbtree_subtree_free(tree->root);
|
||||
}
|
||||
|
||||
SAFE_FREE(tree);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _rbtree_subtree_walk(c_rbnode_t *node, void *data, c_rbtree_visit_func *visitor) {
|
||||
assert(node);
|
||||
assert(data);
|
||||
assert(visitor);
|
||||
|
||||
if (node == NIL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_rbtree_subtree_walk(node->left, data, visitor) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((*visitor)(node->data, data) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_rbtree_subtree_walk(node->right, data, visitor) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int c_rbtree_walk(c_rbtree_t *tree, void *data, c_rbtree_visit_func *visitor) {
|
||||
if (tree == NULL || data == NULL || visitor == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_rbtree_subtree_walk(tree->root, data, visitor) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static c_rbnode_t *_rbtree_subtree_head(c_rbnode_t *node) {
|
||||
assert(node);
|
||||
|
||||
if (node == NIL) {
|
||||
return node;
|
||||
}
|
||||
|
||||
while (node->left != NIL) {
|
||||
node = node->left;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static c_rbnode_t *_rbtree_subtree_tail(c_rbnode_t *node) {
|
||||
assert(node);
|
||||
|
||||
if (node == NIL) {
|
||||
return node;
|
||||
}
|
||||
|
||||
while (node->right != NIL) {
|
||||
node = node->right;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
c_rbnode_t *c_rbtree_head(c_rbtree_t *tree) {
|
||||
c_rbnode_t *node = NULL;
|
||||
|
||||
if (tree == NULL) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node = _rbtree_subtree_head(tree->root);
|
||||
|
||||
return node != NIL ? node : NULL;
|
||||
}
|
||||
|
||||
c_rbnode_t *c_rbtree_tail(c_rbtree_t *tree) {
|
||||
c_rbnode_t *node = NULL;
|
||||
|
||||
if (tree == NULL) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node = _rbtree_subtree_tail(tree->root);
|
||||
|
||||
return node != NIL ? node : NULL;
|
||||
}
|
||||
|
||||
c_rbnode_t *c_rbtree_node_next(c_rbnode_t *node) {
|
||||
c_rbnode_t *parent = NULL;
|
||||
|
||||
if (node == NULL) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (node->right != NIL) {
|
||||
c_rbnode_t *next = NULL;
|
||||
next = _rbtree_subtree_head(node->right);
|
||||
|
||||
return next != NIL ? next : NULL;
|
||||
}
|
||||
|
||||
parent = node->parent;
|
||||
while (parent && node == parent->right) {
|
||||
node = parent;
|
||||
parent = node->parent;
|
||||
}
|
||||
|
||||
return parent != NULL ? parent : NULL;
|
||||
}
|
||||
|
||||
c_rbnode_t *c_rbtree_node_prev(c_rbnode_t *node) {
|
||||
c_rbnode_t *parent = NULL;
|
||||
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (node->left != NIL) {
|
||||
c_rbnode_t *prev = NULL;
|
||||
prev = _rbtree_subtree_tail(node->left);
|
||||
return prev != NIL ? prev : NULL;
|
||||
}
|
||||
|
||||
parent = node->parent;
|
||||
while (parent && node == parent->left) {
|
||||
node = parent;
|
||||
parent = node->parent;
|
||||
}
|
||||
|
||||
return parent != NULL ? parent : NULL;
|
||||
}
|
||||
|
||||
c_rbnode_t *c_rbtree_find(c_rbtree_t *tree, const void *key) {
|
||||
int cmp = 0;
|
||||
c_rbnode_t *node = NULL;
|
||||
|
||||
if (tree == NULL) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
node = tree->root;
|
||||
|
||||
while (node != NIL) {
|
||||
cmp = tree->key_compare(key, node->data);
|
||||
if (cmp == 0) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (cmp < 0) {
|
||||
node = node->left;
|
||||
} else {
|
||||
node = node->right;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void _rbtree_subtree_left_rotate(c_rbnode_t *x) {
|
||||
c_rbnode_t *y = NULL;
|
||||
|
||||
assert(x);
|
||||
|
||||
y = x->right;
|
||||
|
||||
/* establish x-right link */
|
||||
x->right = y->left;
|
||||
|
||||
if (y->left != NIL) {
|
||||
y->left->parent = x;
|
||||
}
|
||||
|
||||
/* establish y->parent link */
|
||||
if (y != NIL) {
|
||||
y->parent = x->parent;
|
||||
}
|
||||
|
||||
if (x->parent) {
|
||||
if (x == x->parent->left) {
|
||||
x->parent->left = y;
|
||||
} else {
|
||||
x->parent->right = y;
|
||||
}
|
||||
} else {
|
||||
x->tree->root = y;
|
||||
}
|
||||
|
||||
/* link x and y */
|
||||
y->left = x;
|
||||
if (x != NIL) {
|
||||
x->parent = y;
|
||||
}
|
||||
}
|
||||
|
||||
/* rotat node x to the right */
|
||||
static void _rbtree_subtree_right_rotate(c_rbnode_t *x) {
|
||||
c_rbnode_t *y = NULL;
|
||||
|
||||
assert(x);
|
||||
|
||||
y = x->left;
|
||||
|
||||
/* establish x->left link */
|
||||
x->left = y->right;
|
||||
|
||||
if (y->right != NIL) {
|
||||
y->right->parent = x;
|
||||
}
|
||||
|
||||
/* establish y->parent link */
|
||||
if (y != NIL) {
|
||||
y->parent = x->parent;
|
||||
}
|
||||
|
||||
if (x->parent) {
|
||||
if (x == x->parent->right) {
|
||||
x->parent->right = y;
|
||||
} else {
|
||||
x->parent->left = y;
|
||||
}
|
||||
} else {
|
||||
x->tree->root = y;
|
||||
}
|
||||
|
||||
/* link x and y */
|
||||
y->right = x;
|
||||
if (x != NIL) {
|
||||
x->parent = y;
|
||||
}
|
||||
}
|
||||
|
||||
int c_rbtree_insert(c_rbtree_t *tree, void *data) {
|
||||
int cmp = 0;
|
||||
c_rbnode_t *current = NULL;
|
||||
c_rbnode_t *parent = NULL;
|
||||
c_rbnode_t *x = NULL;
|
||||
|
||||
if (tree == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* First we do a classic binary tree insert */
|
||||
current = tree->root;
|
||||
parent = NULL;
|
||||
|
||||
while (current != NIL) {
|
||||
cmp = tree->data_compare(data, current->data);
|
||||
parent = current;
|
||||
if (cmp == 0) {
|
||||
return 1;
|
||||
} else if (cmp < 0) {
|
||||
current = current->left;
|
||||
} else {
|
||||
current = current->right;
|
||||
}
|
||||
}
|
||||
|
||||
x = (c_rbnode_t *) c_malloc(sizeof(c_rbnode_t));
|
||||
|
||||
x->tree = tree;
|
||||
x->data = data;
|
||||
x->parent = parent;
|
||||
x->left = NIL;
|
||||
x->right = NIL;
|
||||
x->color = RED;
|
||||
|
||||
if (parent) {
|
||||
/* Note that cmp still contains the comparison of data with
|
||||
* parent->data, from the last pass through the loop above
|
||||
*/
|
||||
if (cmp < 0) {
|
||||
parent->left = x;
|
||||
} else {
|
||||
parent->right = x;
|
||||
}
|
||||
} else {
|
||||
tree->root = x;
|
||||
}
|
||||
|
||||
/* Insert fixup - check red-black properties */
|
||||
while (x != tree->root && x->parent->color == RED) {
|
||||
/* we have a violation */
|
||||
if (x->parent == x->parent->parent->left) {
|
||||
c_rbnode_t *y = NULL;
|
||||
|
||||
y = x->parent->parent->right;
|
||||
if (y->color == RED) {
|
||||
x->parent->color = BLACK;
|
||||
y->color = BLACK;
|
||||
x->parent->parent->color = RED;
|
||||
x = x->parent->parent;
|
||||
} else {
|
||||
/* uncle is back */
|
||||
if (x == x->parent->right) {
|
||||
/* make x a left child */
|
||||
x = x->parent;
|
||||
_rbtree_subtree_left_rotate(x);
|
||||
}
|
||||
x->parent->color = BLACK;
|
||||
x->parent->parent->color = RED;
|
||||
_rbtree_subtree_right_rotate(x->parent->parent);
|
||||
}
|
||||
} else {
|
||||
c_rbnode_t *y = NULL;
|
||||
|
||||
y = x->parent->parent->left;
|
||||
if (y->color == RED) {
|
||||
x->parent->color = BLACK;
|
||||
y->color = BLACK;
|
||||
x->parent->parent->color = RED;
|
||||
x = x->parent->parent;
|
||||
} else {
|
||||
/* uncle is back */
|
||||
if (x == x->parent->left) {
|
||||
x = x->parent;
|
||||
_rbtree_subtree_right_rotate(x);
|
||||
}
|
||||
x->parent->color = BLACK;
|
||||
x->parent->parent->color = RED;
|
||||
_rbtree_subtree_left_rotate(x->parent->parent);
|
||||
}
|
||||
}
|
||||
} /* end while */
|
||||
tree->root->color = BLACK;
|
||||
|
||||
tree->size++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int c_rbtree_node_delete(c_rbnode_t *node) {
|
||||
c_rbtree_t *tree;
|
||||
c_rbnode_t *y;
|
||||
c_rbnode_t *x;
|
||||
|
||||
if (node == NULL || node == NIL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
tree = node->tree;
|
||||
|
||||
if (node->left == NIL || node->right == NIL) {
|
||||
/* y has a NIL node as a child */
|
||||
y = node;
|
||||
} else {
|
||||
/* find tree successor with a NIL node as a child */
|
||||
y = node;
|
||||
while(y->left != NIL) {
|
||||
y = y->left;
|
||||
}
|
||||
}
|
||||
|
||||
/* x is y's only child */
|
||||
if (y->left != NIL) {
|
||||
x = y->left;
|
||||
} else {
|
||||
x = y->right;
|
||||
}
|
||||
|
||||
/* remove y from the parent chain */
|
||||
x->parent = y->parent;
|
||||
|
||||
if (y->parent) {
|
||||
if (y == y->parent->left) {
|
||||
y->parent->left = x;
|
||||
} else {
|
||||
y->parent->right = x;
|
||||
}
|
||||
} else {
|
||||
y->tree->root = x;
|
||||
}
|
||||
|
||||
/* If y is not the node we're deleting, splice it in place of that
|
||||
* node
|
||||
*
|
||||
* The traditional code would call for us to simply copy y->data, but
|
||||
* that would invalidate the wrong pointer - there might be external
|
||||
* references to this node, and we must preserve its address.
|
||||
*/
|
||||
if (y != node) {
|
||||
/* Update y */
|
||||
y->parent = node->parent;
|
||||
y->left = node->left;
|
||||
y->right = node->right;
|
||||
|
||||
/* Update the children and the parent */
|
||||
if (y->left != NIL) {
|
||||
y->left->parent = y;
|
||||
}
|
||||
if (y->right != NIL) {
|
||||
y->right->parent = y;
|
||||
}
|
||||
if (y->parent != NULL) {
|
||||
if (node == y->parent->left) {
|
||||
y->parent->left = y;
|
||||
} else {
|
||||
y->parent->right = y;
|
||||
}
|
||||
} else {
|
||||
y->tree->root = y;
|
||||
}
|
||||
}
|
||||
|
||||
if (y->color == BLACK) {
|
||||
while (x != y->tree->root && x->color == BLACK) {
|
||||
if (x == x->parent->left) {
|
||||
c_rbnode_t *w = NULL;
|
||||
|
||||
w = x->parent->right;
|
||||
|
||||
if (w->color == RED) {
|
||||
w->color = BLACK;
|
||||
x->parent->color = RED;
|
||||
_rbtree_subtree_left_rotate(x->parent);
|
||||
w = x->parent->right;
|
||||
}
|
||||
|
||||
if (w->left->color == BLACK && w->right->color == BLACK) {
|
||||
w->color = RED;
|
||||
x = x->parent;
|
||||
} else {
|
||||
if (w->right->color == BLACK) {
|
||||
w->left->color = BLACK;
|
||||
w->color = RED;
|
||||
_rbtree_subtree_right_rotate(w);
|
||||
w = x->parent->right;
|
||||
}
|
||||
w->color = x->parent->color;
|
||||
x->parent->color = BLACK;
|
||||
w->right->color = BLACK;
|
||||
_rbtree_subtree_left_rotate(x->parent);
|
||||
x = y->tree->root;
|
||||
}
|
||||
} else {
|
||||
c_rbnode_t *w = NULL;
|
||||
|
||||
w = x->parent->left;
|
||||
if (w->color == RED) {
|
||||
w->color = BLACK;
|
||||
x->parent->color = RED;
|
||||
_rbtree_subtree_right_rotate(x->parent);
|
||||
w = x->parent->left;
|
||||
}
|
||||
|
||||
if (w->right->color == BLACK && w->left->color == BLACK) {
|
||||
w->color = RED;
|
||||
x = x->parent;
|
||||
} else {
|
||||
if (w->left->color == BLACK) {
|
||||
w->right->color = BLACK;
|
||||
w->color = RED;
|
||||
_rbtree_subtree_left_rotate(w);
|
||||
w = x->parent->left;
|
||||
}
|
||||
w->color = x->parent->color;
|
||||
x->parent->color = BLACK;
|
||||
w->left->color = BLACK;
|
||||
_rbtree_subtree_right_rotate(x->parent);
|
||||
x = y->tree->root;
|
||||
}
|
||||
}
|
||||
}
|
||||
x->color = BLACK;
|
||||
} /* end if: y->color == BLACK */
|
||||
|
||||
/* node has now been spliced out of the tree */
|
||||
SAFE_FREE(y);
|
||||
tree->size--;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _rbtree_subtree_check_black_height(c_rbnode_t *node) {
|
||||
int left = 0;
|
||||
int right = 0;
|
||||
|
||||
assert(node);
|
||||
|
||||
if (node == NIL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
left = _rbtree_subtree_check_black_height(node->left);
|
||||
right = _rbtree_subtree_check_black_height(node->right);
|
||||
if (left != right) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return left + (node->color == BLACK);
|
||||
}
|
||||
|
||||
int c_rbtree_check_sanity(c_rbtree_t *tree) {
|
||||
c_rbnode_t *node = NULL;
|
||||
c_rbnode_t *prev = NULL;
|
||||
c_rbnode_t *next = NULL;
|
||||
c_rbnode_t *tail = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
if (tree == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (! tree->key_compare || ! tree->data_compare) {
|
||||
errno = EINVAL;
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Iterate the tree */
|
||||
tail = c_rbtree_tail(tree);
|
||||
for (node = c_rbtree_head(tree); node; node = next) {
|
||||
if (node->tree != tree) {
|
||||
return -4;
|
||||
}
|
||||
|
||||
/* We should never see a nil while iterating */
|
||||
if (node == NIL) {
|
||||
return -5;
|
||||
}
|
||||
|
||||
/* node == tree-root iff node->parent == NIL */
|
||||
if (node == tree->root) {
|
||||
if (node->parent != NULL) {
|
||||
return -6;
|
||||
}
|
||||
} else {
|
||||
if (node->parent == NULL) {
|
||||
return -7;
|
||||
}
|
||||
}
|
||||
|
||||
/* Invertability of the iterate functions */
|
||||
if (prev != c_rbtree_node_prev(node)) {
|
||||
return -8;
|
||||
}
|
||||
|
||||
/* Check the iteration sequence */
|
||||
if (prev) {
|
||||
if (tree->data_compare(prev->data, node->data) > 0) {
|
||||
return -9;
|
||||
}
|
||||
|
||||
/* And the other way around, to make sure data_compare is stable */
|
||||
if (tree->data_compare(node->data, prev->data) < 0) {
|
||||
return -10;
|
||||
}
|
||||
}
|
||||
|
||||
/* The binary tree property */
|
||||
if (node->left != NIL) {
|
||||
if (tree->data_compare(node->left->data, node->data) > 0) {
|
||||
return -11;
|
||||
}
|
||||
|
||||
if (tree->data_compare(node->data, node->left->data) < 0) {
|
||||
return -11;
|
||||
}
|
||||
}
|
||||
|
||||
if (node->right != NIL) {
|
||||
if (tree->data_compare(node->data, node->right->data) > 0) {
|
||||
return -12;
|
||||
}
|
||||
|
||||
if (tree->data_compare(node->right->data, node->data) < 0) {
|
||||
return -13;
|
||||
}
|
||||
}
|
||||
|
||||
/* Red-black tree property 3: red nodes have black children */
|
||||
if (node->color == RED) {
|
||||
if (node->left->color == RED) {
|
||||
return -14;
|
||||
}
|
||||
if (node->right->color == RED) {
|
||||
return -15;
|
||||
}
|
||||
}
|
||||
|
||||
/* next == NULL if node == tail */
|
||||
next = c_rbtree_node_next(node);
|
||||
if (next) {
|
||||
if (node == tail) {
|
||||
return -16;
|
||||
}
|
||||
} else {
|
||||
if (node != tail) {
|
||||
return -17;
|
||||
}
|
||||
}
|
||||
|
||||
prev = node;
|
||||
size++;
|
||||
} /* end for loop */
|
||||
|
||||
if (size != tree->size) {
|
||||
return -18;
|
||||
}
|
||||
|
||||
if (_rbtree_subtree_check_black_height(tree->root) < 0) {
|
||||
return -19;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,318 +0,0 @@
|
||||
/*
|
||||
* cynapses libc functions
|
||||
*
|
||||
* Copyright (c) 2003-2004 by Andrew Suffield <asuffield@debian.org>
|
||||
* Copyright (c) 2008-2013 by Andreas Schneider <asn@cryptomilk.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file c_rbtree.h
|
||||
*
|
||||
* @brief Interface of the cynapses libc red-black tree implementation
|
||||
*
|
||||
* A red-black tree is a type of self-balancing binary search tree. It is
|
||||
* complex, but has good worst-case running time for its operations and is
|
||||
* efficient in practice: it can search, insert, and delete in O(log n)
|
||||
* time, where n is the number of elements in the tree.
|
||||
*
|
||||
* In red-black trees, the leaf nodes are not relevant and do not contain
|
||||
* data. Therefore we use a sentinal node to save memory. All references
|
||||
* from internal nodes to leaf nodes instead point to the sentinel node.
|
||||
*
|
||||
* In a red-black tree each node has a color attribute, the value of which
|
||||
* is either red or black. In addition to the ordinary requirements imposed
|
||||
* on binary search trees, the following additional requirements of any
|
||||
* valid red-black tree apply:
|
||||
*
|
||||
* 1. A node is either red or black.
|
||||
* 2. The root is black.
|
||||
* 3. All leaves are black, even when the parent is black
|
||||
* (The leaves are the null children.)
|
||||
* 4. Both children of every red node are black.
|
||||
* 5. Every simple path from a node to a descendant leaf contains the same
|
||||
* number of black nodes, either counting or not counting the null black
|
||||
* nodes. (Counting or not counting the null black nodes does not affect
|
||||
* the structure as long as the choice is used consistently.).
|
||||
*
|
||||
* These constraints enforce a critical property of red-black trees: that the
|
||||
* longest path from the root to a leaf is no more than twice as long as the
|
||||
* shortest path from the root to a leaf in that tree. The result is that the
|
||||
* tree is roughly balanced. Since operations such as inserting, deleting, and
|
||||
* finding values requires worst-case time proportional to the height of the
|
||||
* tree, this theoretical upper bound on the height allows red-black trees to
|
||||
* be efficient in the worst-case, unlike ordinary binary search trees.
|
||||
*
|
||||
* http://en.wikipedia.org/wiki/Red-black_tree
|
||||
*
|
||||
* @defgroup cynRBTreeInternals cynapses libc red-black tree functions
|
||||
* @ingroup cynLibraryAPI
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
#ifndef _C_RBTREE_H
|
||||
#define _C_RBTREE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Forward declarations */
|
||||
struct c_rbtree_s; typedef struct c_rbtree_s c_rbtree_t;
|
||||
struct c_rbnode_s; typedef struct c_rbnode_s c_rbnode_t;
|
||||
|
||||
/**
|
||||
* Define the two colors for the red-black tree
|
||||
*/
|
||||
enum xrbcolor_e { BLACK = 0, RED }; typedef enum xrbcolor_e xrbcolor_t;
|
||||
|
||||
/**
|
||||
* @brief Callback function to compare a key with the data from a
|
||||
* red-black tree node.
|
||||
*
|
||||
* @param key key as a generic pointer
|
||||
* @param data data as a generic pointer
|
||||
*
|
||||
* @return It returns an integer less than, equal to, or greater than zero
|
||||
* depending on the key or data you use. The function is similar
|
||||
* to strcmp().
|
||||
*/
|
||||
typedef int c_rbtree_compare_func(const void *key, const void *data);
|
||||
|
||||
/**
|
||||
* @brief Visit function for the c_rbtree_walk() function.
|
||||
*
|
||||
* This function will be called by c_rbtree_walk() for every node. It is up to
|
||||
* the developer what the function does. The fist parameter is a node object
|
||||
* the second can be of any kind.
|
||||
*
|
||||
* @param obj The node data that will be passed by c_rbtree_walk().
|
||||
* @param data Generic data pointer.
|
||||
*
|
||||
* @return 0 on success, < 0 on error. You should set errno.
|
||||
*
|
||||
*/
|
||||
typedef int c_rbtree_visit_func(void *, void *);
|
||||
|
||||
/**
|
||||
* Structure that represents a red-black tree
|
||||
*/
|
||||
struct c_rbtree_s {
|
||||
c_rbnode_t *root;
|
||||
c_rbtree_compare_func *key_compare;
|
||||
c_rbtree_compare_func *data_compare;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure that represents a node of a red-black tree
|
||||
*/
|
||||
struct c_rbnode_s {
|
||||
c_rbtree_t *tree;
|
||||
c_rbnode_t *left;
|
||||
c_rbnode_t *right;
|
||||
c_rbnode_t *parent;
|
||||
void *data;
|
||||
xrbcolor_t color;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create the red-black tree
|
||||
*
|
||||
* @param rbtree The pointer to assign the allocated memory.
|
||||
*
|
||||
* @param key_compare Callback function to compare a key with the data
|
||||
* inside a reb-black tree node.
|
||||
*
|
||||
* @param data_compare Callback function to compare a key as data with thee
|
||||
* data inside a red-black tree node.
|
||||
*/
|
||||
void c_rbtree_create(c_rbtree_t **rbtree, c_rbtree_compare_func *key_compare, c_rbtree_compare_func *data_compare);
|
||||
|
||||
/**
|
||||
* @brief Duplicate a red-black tree.
|
||||
*
|
||||
* @param tree Tree to duplicate.
|
||||
*
|
||||
* @return Pointer to a new allocated duplicated rbtree. NULL if an error
|
||||
* occurred.
|
||||
*/
|
||||
c_rbtree_t *c_rbtree_dup(const c_rbtree_t *tree);
|
||||
|
||||
/**
|
||||
* @brief Free the structure of a red-black tree.
|
||||
*
|
||||
* You should call c_rbtree_destroy() before you call this function.
|
||||
*
|
||||
* @param tree The tree to free.
|
||||
*
|
||||
* @return 0 on success, less than 0 if an error occurred.
|
||||
*/
|
||||
int c_rbtree_free(c_rbtree_t *tree);
|
||||
|
||||
/**
|
||||
* @brief Destroy the content and the nodes of an red-black tree.
|
||||
*
|
||||
* This is far from the most efficient way to walk a tree, but it is
|
||||
* the *safest* way to destroy a tree - the destructor can do almost
|
||||
* anything (as long as it does not create an infinite loop) to the
|
||||
* tree structure without risk.
|
||||
*
|
||||
* If for some strange reason you need a faster destructor (think
|
||||
* twice - speed and memory deallocation don't mix well) then consider
|
||||
* stashing an llist of dataects and destroying that instead, and just
|
||||
* using c_rbtree_free() on your tree.
|
||||
*
|
||||
* @param T The tree to destroy.
|
||||
* @param DESTRUCTOR The destructor to call on a node to destroy.
|
||||
*/
|
||||
#define c_rbtree_destroy(T, DESTRUCTOR) \
|
||||
do { \
|
||||
if (T) { \
|
||||
c_rbnode_t *_c_rbtree_temp; \
|
||||
while ((_c_rbtree_temp = c_rbtree_head(T))) { \
|
||||
(DESTRUCTOR)(_c_rbtree_temp->data); \
|
||||
if (_c_rbtree_temp == c_rbtree_head(T)) { \
|
||||
c_rbtree_node_delete(_c_rbtree_temp); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
SAFE_FREE(T); \
|
||||
} while (0);
|
||||
|
||||
/**
|
||||
* @brief Inserts a node into a red black tree.
|
||||
*
|
||||
* @param tree The red black tree to insert the node.
|
||||
* @param data The data to insert into the tree.
|
||||
*
|
||||
* @return 0 on success, 1 if a duplicate has been found and < 0 if an error
|
||||
* occurred with errno set.
|
||||
* EINVAL if a null pointer has been passed as the tree.
|
||||
* ENOMEM if there is no memory left.
|
||||
*/
|
||||
int c_rbtree_insert(c_rbtree_t *tree, void *data);
|
||||
|
||||
/**
|
||||
* @brief Find a node in a red-black tree.
|
||||
*
|
||||
* c_rbtree_find() is searching for the given key in a red-black tree and
|
||||
* returns the node if the key has been found.
|
||||
*
|
||||
* @param tree The tree to search.
|
||||
* @param key The key to search for.
|
||||
*
|
||||
* @return If the key was found the node will be returned. On error NULL
|
||||
* will be returned.
|
||||
*/
|
||||
c_rbnode_t *c_rbtree_find(c_rbtree_t *tree, const void *key);
|
||||
|
||||
/**
|
||||
* @brief Get the head of the red-black tree.
|
||||
*
|
||||
* @param tree The tree to get the head for.
|
||||
*
|
||||
* @return The head node. NULL if an error occurred.
|
||||
*/
|
||||
c_rbnode_t *c_rbtree_head(c_rbtree_t *tree);
|
||||
|
||||
/**
|
||||
* @brief Get the tail of the red-black tree.
|
||||
*
|
||||
* @param tree The tree to get the tail for.
|
||||
*
|
||||
* @return The tail node. NULL if an error occurred.
|
||||
*/
|
||||
c_rbnode_t *c_rbtree_tail(c_rbtree_t *tree);
|
||||
|
||||
/**
|
||||
* @brief Get the size of the red-black tree.
|
||||
*
|
||||
* @param T The tree to get the size from.
|
||||
*
|
||||
* @return The size of the red-black tree.
|
||||
*/
|
||||
#define c_rbtree_size(T) (T) == NULL ? 0 : ((T)->size)
|
||||
|
||||
/**
|
||||
* @brief Walk over a red-black tree.
|
||||
*
|
||||
* Walk over a red-black tree calling a visitor function for each node found.
|
||||
*
|
||||
* @param tree Tree to walk.
|
||||
* @param data Data which should be passed to the visitor function.
|
||||
* @param visitor Visitor function. This will be called for each node passed.
|
||||
*
|
||||
* @return 0 on sucess, less than 0 if an error occurred.
|
||||
*/
|
||||
int c_rbtree_walk(c_rbtree_t *tree, void *data, c_rbtree_visit_func *visitor);
|
||||
|
||||
/**
|
||||
* @brief Delete a node in a red-black tree.
|
||||
*
|
||||
* @param node Node which should be deleted.
|
||||
*
|
||||
* @return 0 on success, -1 if an error occurred.
|
||||
*/
|
||||
int c_rbtree_node_delete(c_rbnode_t *node);
|
||||
|
||||
/**
|
||||
* @brief Get the next node.
|
||||
*
|
||||
* @param node The node of which you want the next node.
|
||||
*
|
||||
* @return The next node, NULL if an error occurred.
|
||||
*/
|
||||
c_rbnode_t *c_rbtree_node_next(c_rbnode_t *node);
|
||||
|
||||
/**
|
||||
* @brief Get the previous node.
|
||||
*
|
||||
* @param node The node of which you want the previous node.
|
||||
*
|
||||
* @return The previous node, NULL if an error occurred.
|
||||
*/
|
||||
c_rbnode_t *c_rbtree_node_prev(c_rbnode_t *node);
|
||||
|
||||
/**
|
||||
* @brief Get the data of a node.
|
||||
*
|
||||
* @param N The node to get the data from.
|
||||
*
|
||||
* @return The data, NULL if an error occurred.
|
||||
*/
|
||||
#define c_rbtree_node_data(N) ((N) ? ((N)->data) : NULL)
|
||||
|
||||
/**
|
||||
* @brief Perform a sanity check for a red-black tree.
|
||||
*
|
||||
* This is mostly for testing purposes.
|
||||
*
|
||||
* @param tree The tree to check.
|
||||
*
|
||||
* @return 0 on success, less than 0 if an error occurred.
|
||||
*/
|
||||
int c_rbtree_check_sanity(c_rbtree_t *tree);
|
||||
|
||||
/**
|
||||
* }@
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _C_RBTREE_H */
|
@ -59,7 +59,7 @@ static bool findPathInList(const QStringList &list, const QString &path)
|
||||
return pathSlash.startsWith(*it);
|
||||
}
|
||||
|
||||
bool DiscoveryJob::isInSelectiveSyncBlackList(const char *path) const
|
||||
bool DiscoveryJob::isInSelectiveSyncBlackList(const QByteArray &path) const
|
||||
{
|
||||
if (_selectiveSyncBlackList.isEmpty()) {
|
||||
// If there is no black list, everything is allowed
|
||||
@ -73,24 +73,23 @@ bool DiscoveryJob::isInSelectiveSyncBlackList(const char *path) const
|
||||
|
||||
// Also try to adjust the path if there was renames
|
||||
if (csync_rename_count(_csync_ctx)) {
|
||||
QScopedPointer<char, QScopedPointerPodDeleter> adjusted(
|
||||
csync_rename_adjust_path_source(_csync_ctx, path));
|
||||
if (strcmp(adjusted.data(), path) != 0) {
|
||||
return findPathInList(_selectiveSyncBlackList, QString::fromUtf8(adjusted.data()));
|
||||
QByteArray adjusted = csync_rename_adjust_path_source(_csync_ctx, path);
|
||||
if (adjusted != path) {
|
||||
return findPathInList(_selectiveSyncBlackList, QString::fromUtf8(adjusted));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int DiscoveryJob::isInSelectiveSyncBlackListCallback(void *data, const char *path)
|
||||
int DiscoveryJob::isInSelectiveSyncBlackListCallback(void *data, const QByteArray &path)
|
||||
{
|
||||
return static_cast<DiscoveryJob *>(data)->isInSelectiveSyncBlackList(path);
|
||||
}
|
||||
|
||||
bool DiscoveryJob::checkSelectiveSyncNewFolder(const QString &path, const char *remotePerm)
|
||||
bool DiscoveryJob::checkSelectiveSyncNewFolder(const QString &path, const QByteArray &remotePerm)
|
||||
{
|
||||
if (_syncOptions._confirmExternalStorage && std::strchr(remotePerm, 'M')) {
|
||||
if (_syncOptions._confirmExternalStorage && remotePerm.contains('M')) {
|
||||
// 'M' in the permission means external storage.
|
||||
|
||||
/* Note: DiscoverySingleDirectoryJob::directoryListingIteratedSlot make sure that only the
|
||||
@ -145,7 +144,7 @@ bool DiscoveryJob::checkSelectiveSyncNewFolder(const QString &path, const char *
|
||||
}
|
||||
}
|
||||
|
||||
int DiscoveryJob::checkSelectiveSyncNewFolderCallback(void *data, const char *path, const char *remotePerm)
|
||||
int DiscoveryJob::checkSelectiveSyncNewFolderCallback(void *data, const QByteArray &path, const QByteArray &remotePerm)
|
||||
{
|
||||
return static_cast<DiscoveryJob *>(data)->checkSelectiveSyncNewFolder(QString::fromUtf8(path), remotePerm);
|
||||
}
|
||||
|
@ -210,10 +210,10 @@ class DiscoveryJob : public QObject
|
||||
* return true if the given path should be ignored,
|
||||
* false if the path should be synced
|
||||
*/
|
||||
bool isInSelectiveSyncBlackList(const char *path) const;
|
||||
static int isInSelectiveSyncBlackListCallback(void *, const char *);
|
||||
bool checkSelectiveSyncNewFolder(const QString &path, const char *remotePerm);
|
||||
static int checkSelectiveSyncNewFolderCallback(void *data, const char *path, const char *remotePerm);
|
||||
bool isInSelectiveSyncBlackList(const QByteArray &path) const;
|
||||
static int isInSelectiveSyncBlackListCallback(void *, const QByteArray &);
|
||||
bool checkSelectiveSyncNewFolder(const QString &path, const QByteArray &remotePerm);
|
||||
static int checkSelectiveSyncNewFolderCallback(void *data, const QByteArray &path, const QByteArray &remotePerm);
|
||||
|
||||
// Just for progress
|
||||
static void update_job_update_callback(bool local,
|
||||
|
@ -128,9 +128,6 @@ QString SyncEngine::csyncErrorToString(CSYNC_STATUS err)
|
||||
case CSYNC_STATUS_NO_MODULE:
|
||||
errStr = tr("<p>The %1 plugin for csync could not be loaded.<br/>Please verify the installation!</p>").arg(qApp->applicationName());
|
||||
break;
|
||||
case CSYNC_STATUS_TREE_ERROR:
|
||||
errStr = tr("CSync got an error while processing internal trees.");
|
||||
break;
|
||||
case CSYNC_STATUS_PARAM_ERROR:
|
||||
errStr = tr("CSync fatal parameter error.");
|
||||
break;
|
||||
@ -347,7 +344,7 @@ int SyncEngine::treewalkRemote(csync_file_stat_t *file, csync_file_stat_t *other
|
||||
* Called on each entry in the local and remote trees by
|
||||
* csync_walk_local_tree()/csync_walk_remote_tree().
|
||||
*
|
||||
* It merges the two csync rbtrees into a single map of SyncFileItems.
|
||||
* It merges the two csync file trees into a single map of SyncFileItems.
|
||||
*
|
||||
* See doc/dev/sync-algorithm.md for an overview.
|
||||
*/
|
||||
@ -359,7 +356,7 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other,
|
||||
QTextCodec::ConverterState utf8State;
|
||||
static QTextCodec *codec = QTextCodec::codecForName("UTF-8");
|
||||
ASSERT(codec);
|
||||
QString fileUtf8 = codec->toUnicode(file->path, qstrlen(file->path), &utf8State);
|
||||
QString fileUtf8 = codec->toUnicode(file->path, file->path.size(), &utf8State);
|
||||
QString renameTarget;
|
||||
QString key = fileUtf8;
|
||||
|
||||
@ -368,7 +365,7 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other,
|
||||
qCWarning(lcEngine) << "File ignored because of invalid utf-8 sequence: " << file->path;
|
||||
instruction = CSYNC_INSTRUCTION_IGNORE;
|
||||
} else {
|
||||
renameTarget = codec->toUnicode(file->rename_path, qstrlen(file->rename_path), &utf8State);
|
||||
renameTarget = codec->toUnicode(file->rename_path, file->rename_path.size(), &utf8State);
|
||||
if (utf8State.invalidChars > 0 || utf8State.remainingChars > 0) {
|
||||
qCWarning(lcEngine) << "File ignored because of invalid utf-8 sequence in the rename_path: " << file->path << file->rename_path;
|
||||
instruction = CSYNC_INSTRUCTION_IGNORE;
|
||||
@ -937,7 +934,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
|
||||
_backInTimeFiles = 0;
|
||||
bool walkOk = true;
|
||||
_remotePerms.clear();
|
||||
_remotePerms.reserve(c_rbtree_size(_csync_ctx->remote.tree));
|
||||
_remotePerms.reserve(_csync_ctx->remote.files.size());
|
||||
_seenFiles.clear();
|
||||
_temporarilyUnavailablePaths.clear();
|
||||
_renamedFolders.clear();
|
||||
|
@ -23,7 +23,6 @@ set(TEST_TARGET_LIBRARIES ${TORTURE_LIBRARY})
|
||||
add_cmocka_test(check_std_c_alloc std_tests/check_std_c_alloc.c ${TEST_TARGET_LIBRARIES})
|
||||
add_cmocka_test(check_std_c_jhash std_tests/check_std_c_jhash.c ${TEST_TARGET_LIBRARIES})
|
||||
add_cmocka_test(check_std_c_path std_tests/check_std_c_path.c ${TEST_TARGET_LIBRARIES})
|
||||
add_cmocka_test(check_std_c_rbtree std_tests/check_std_c_rbtree.c ${TEST_TARGET_LIBRARIES})
|
||||
add_cmocka_test(check_std_c_str std_tests/check_std_c_str.c ${TEST_TARGET_LIBRARIES})
|
||||
add_cmocka_test(check_std_c_time std_tests/check_std_c_time.c ${TEST_TARGET_LIBRARIES})
|
||||
|
||||
|
@ -146,19 +146,18 @@ static void check_csync_statedb_drop_tables(void **state)
|
||||
static void check_csync_statedb_insert_metadata(void **state)
|
||||
{
|
||||
CSYNC *csync = (CSYNC*)*state;
|
||||
csync_file_stat_t *st;
|
||||
std::unique_ptr<csync_file_stat_t> st;
|
||||
int i, rc = 0;
|
||||
|
||||
// rc = csync_statedb_create_tables(csync->statedb.db);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
for (i = 0; i < 100; i++) {
|
||||
st = new csync_file_stat_t;
|
||||
st.reset(new csync_file_stat_t);
|
||||
st->path = QString("file_%1").arg(i).toUtf8();
|
||||
st->phash = i;
|
||||
|
||||
rc = c_rbtree_insert(csync->local.tree, (void *) st);
|
||||
assert_int_equal(rc, 0);
|
||||
csync->local.files[st->path] = std::move(st);
|
||||
}
|
||||
|
||||
// rc = csync_statedb_insert_metadata(csync, csync->statedb.db);
|
||||
@ -168,15 +167,15 @@ static void check_csync_statedb_insert_metadata(void **state)
|
||||
static void check_csync_statedb_write(void **state)
|
||||
{
|
||||
CSYNC *csync = (CSYNC*)*state;
|
||||
csync_file_stat_t *st;
|
||||
int i, rc;
|
||||
std::unique_ptr<csync_file_stat_t> st;
|
||||
int i, rc = 0;
|
||||
|
||||
for (i = 0; i < 100; i++) {
|
||||
st = new csync_file_stat_t;
|
||||
st.reset(new csync_file_stat_t);
|
||||
st->path = QString("file_%1").arg(i).toUtf8();
|
||||
st->phash = i;
|
||||
|
||||
rc = c_rbtree_insert(csync->local.tree, (void *) st);
|
||||
csync->local.files[st->path] = std::move(st);
|
||||
assert_int_equal(rc, 0);
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ static void check_csync_detect_update(void **state)
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
/* the instruction should be set to new */
|
||||
st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root);
|
||||
st = csync->local.files.begin()->second.get();
|
||||
assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW);
|
||||
|
||||
/* create a statedb */
|
||||
@ -250,7 +250,7 @@ static void check_csync_detect_update_db_none(void **state)
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
/* the instruction should be set to new */
|
||||
st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root);
|
||||
st = csync->local.files.begin()->second.get();
|
||||
assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW);
|
||||
|
||||
|
||||
@ -271,7 +271,7 @@ static void check_csync_detect_update_db_eval(void **state)
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
/* the instruction should be set to new */
|
||||
st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root);
|
||||
st = csync->local.files.begin()->second.get();
|
||||
assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW);
|
||||
|
||||
/* create a statedb */
|
||||
@ -296,7 +296,7 @@ static void check_csync_detect_update_db_rename(void **state)
|
||||
/* the instruction should be set to rename */
|
||||
/*
|
||||
* temporarily broken.
|
||||
st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root);
|
||||
st = csync->local.files.begin()->second.get();
|
||||
assert_int_equal(st->instruction, CSYNC_INSTRUCTION_RENAME);
|
||||
|
||||
st->instruction = CSYNC_INSTRUCTION_UPDATED;
|
||||
@ -318,7 +318,7 @@ static void check_csync_detect_update_db_new(void **state)
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
/* the instruction should be set to new */
|
||||
st = (csync_file_stat_t*)c_rbtree_node_data(csync->local.tree->root);
|
||||
st = csync->local.files.begin()->second.get();
|
||||
assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW);
|
||||
|
||||
|
||||
|
@ -1,366 +0,0 @@
|
||||
/*
|
||||
* libcsync -- a library to sync a directory with another
|
||||
*
|
||||
* Copyright (c) 2008-2013 by Andreas Schneider <asn@cryptomilk.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "torture.h"
|
||||
|
||||
#include "std/c_alloc.h"
|
||||
#include "std/c_rbtree.h"
|
||||
|
||||
typedef struct test_s {
|
||||
int key;
|
||||
int number;
|
||||
} test_t;
|
||||
|
||||
static int data_cmp(const void *key, const void *data) {
|
||||
test_t *a, *b;
|
||||
|
||||
a = (test_t *) key;
|
||||
b = (test_t *) data;
|
||||
|
||||
if (a->key < b->key) {
|
||||
return -1;
|
||||
} else if (a->key > b->key) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int key_cmp(const void *key, const void *data) {
|
||||
int a;
|
||||
test_t *b;
|
||||
|
||||
a = POINTER_TO_INT(key);
|
||||
b = (test_t *) data;
|
||||
|
||||
if (a < b->key) {
|
||||
return -1;
|
||||
} else if (a > b->key) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int visitor(void *obj, void *data) {
|
||||
test_t *a;
|
||||
test_t *b;
|
||||
|
||||
a = (test_t *) obj;
|
||||
b = (test_t *) data;
|
||||
|
||||
if (a->key == b->key) {
|
||||
a->number = 42;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void destructor(void *data) {
|
||||
test_t *freedata = NULL;
|
||||
|
||||
freedata = (test_t *) data;
|
||||
SAFE_FREE(freedata);
|
||||
}
|
||||
|
||||
static int setup(void **state) {
|
||||
c_rbtree_t *tree = NULL;
|
||||
|
||||
c_rbtree_create(&tree, key_cmp, data_cmp);
|
||||
|
||||
*state = tree;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int setup_complete_tree(void **state) {
|
||||
c_rbtree_t *tree = NULL;
|
||||
int i = 0;
|
||||
int rc;
|
||||
|
||||
c_rbtree_create(&tree, key_cmp, data_cmp);
|
||||
|
||||
for (i = 0; i < 100; i++) {
|
||||
test_t *testdata = NULL;
|
||||
|
||||
testdata = c_malloc(sizeof(test_t));
|
||||
assert_non_null(testdata);
|
||||
|
||||
testdata->key = i;
|
||||
|
||||
rc = c_rbtree_insert(tree, (void *) testdata);
|
||||
assert_int_equal(rc, 0);
|
||||
}
|
||||
|
||||
*state = tree;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int teardown(void **state) {
|
||||
c_rbtree_t *tree = *state;
|
||||
|
||||
c_rbtree_destroy(tree, destructor);
|
||||
c_rbtree_free(tree);
|
||||
|
||||
*state = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void check_c_rbtree_create_free(void **state)
|
||||
{
|
||||
c_rbtree_t *tree = NULL;
|
||||
int rc;
|
||||
|
||||
(void) state; /* unused */
|
||||
|
||||
c_rbtree_create(&tree, key_cmp, data_cmp);
|
||||
assert_int_equal(tree->size, 0);
|
||||
|
||||
rc = c_rbtree_free(tree);
|
||||
assert_int_equal(rc, 0);
|
||||
}
|
||||
|
||||
static void check_c_rbtree_free_null(void **state)
|
||||
{
|
||||
int rc;
|
||||
|
||||
(void) state; /* unused */
|
||||
|
||||
rc = c_rbtree_free(NULL);
|
||||
assert_int_equal(rc, -1);
|
||||
}
|
||||
|
||||
static void check_c_rbtree_insert_delete(void **state)
|
||||
{
|
||||
c_rbtree_t *tree = NULL;
|
||||
c_rbnode_t *node = NULL;
|
||||
test_t *testdata = NULL;
|
||||
int rc;
|
||||
|
||||
(void) state; /* unused */
|
||||
|
||||
c_rbtree_create(&tree, key_cmp, data_cmp);
|
||||
|
||||
testdata = malloc(sizeof(test_t));
|
||||
testdata->key = 42;
|
||||
|
||||
rc = c_rbtree_insert(tree, (void *) testdata);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
node = c_rbtree_head(tree);
|
||||
assert_non_null(node);
|
||||
|
||||
testdata = c_rbtree_node_data(node);
|
||||
SAFE_FREE(testdata);
|
||||
rc = c_rbtree_node_delete(node);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
c_rbtree_free(tree);
|
||||
}
|
||||
|
||||
static void check_c_rbtree_insert_random(void **state)
|
||||
{
|
||||
c_rbtree_t *tree = *state;
|
||||
int i = 0, rc;
|
||||
|
||||
for (i = 0; i < 100; i++) {
|
||||
test_t *testdata = NULL;
|
||||
|
||||
testdata = malloc(sizeof(test_t));
|
||||
assert_non_null(testdata);
|
||||
|
||||
testdata->key = i;
|
||||
|
||||
rc = c_rbtree_insert(tree, testdata);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
}
|
||||
rc = c_rbtree_check_sanity(tree);
|
||||
assert_int_equal(rc, 0);
|
||||
}
|
||||
|
||||
static void check_c_rbtree_insert_duplicate(void **state)
|
||||
{
|
||||
c_rbtree_t *tree = *state;
|
||||
test_t *testdata;
|
||||
int rc;
|
||||
|
||||
testdata = malloc(sizeof(test_t));
|
||||
assert_non_null(testdata);
|
||||
|
||||
testdata->key = 42;
|
||||
|
||||
rc = c_rbtree_insert(tree, (void *) testdata);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
/* add again */
|
||||
testdata = malloc(sizeof(test_t));
|
||||
assert_non_null(testdata);
|
||||
|
||||
testdata->key = 42;
|
||||
|
||||
/* check for duplicate */
|
||||
rc = c_rbtree_insert(tree, (void *) testdata);
|
||||
assert_int_equal(rc, 1);
|
||||
|
||||
free(testdata);
|
||||
}
|
||||
|
||||
static void check_c_rbtree_find(void **state)
|
||||
{
|
||||
c_rbtree_t *tree = *state;
|
||||
int rc, i = 42;
|
||||
c_rbnode_t *node;
|
||||
test_t *testdata;
|
||||
|
||||
rc = c_rbtree_check_sanity(tree);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
/* find the node with the key 42 */
|
||||
node = c_rbtree_find(tree, (void *) &i);
|
||||
assert_non_null(node);
|
||||
|
||||
testdata = (test_t *) c_rbtree_node_data(node);
|
||||
assert_int_equal(testdata->key, 42);
|
||||
}
|
||||
|
||||
static void check_c_rbtree_delete(void **state)
|
||||
{
|
||||
c_rbtree_t *tree = *state;
|
||||
int rc, i = 42;
|
||||
c_rbnode_t *node = NULL;
|
||||
test_t *freedata = NULL;
|
||||
|
||||
rc = c_rbtree_check_sanity(tree);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
node = c_rbtree_find(tree, (void *) &i);
|
||||
assert_non_null(node);
|
||||
|
||||
freedata = (test_t *) c_rbtree_node_data(node);
|
||||
free(freedata);
|
||||
rc = c_rbtree_node_delete(node);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
rc = c_rbtree_check_sanity(tree);
|
||||
assert_int_equal(rc, 0);
|
||||
}
|
||||
|
||||
static void check_c_rbtree_walk(void **state)
|
||||
{
|
||||
c_rbtree_t *tree = *state;
|
||||
int rc, i = 42;
|
||||
test_t *testdata;
|
||||
c_rbnode_t *node;
|
||||
|
||||
rc = c_rbtree_check_sanity(tree);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
testdata = (test_t *) c_malloc(sizeof(test_t));
|
||||
testdata->key = 42;
|
||||
|
||||
rc = c_rbtree_walk(tree, testdata, visitor);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
/* find the node with the key 42 */
|
||||
node = c_rbtree_find(tree, (void *) &i);
|
||||
assert_non_null(node);
|
||||
free(testdata);
|
||||
|
||||
testdata = (test_t *) c_rbtree_node_data(node);
|
||||
assert_int_equal(testdata->number, 42);
|
||||
}
|
||||
|
||||
static void check_c_rbtree_walk_null(void **state)
|
||||
{
|
||||
c_rbtree_t *tree = *state;
|
||||
int rc, i = 42;
|
||||
test_t *testdata;
|
||||
c_rbnode_t *node;
|
||||
|
||||
rc = c_rbtree_check_sanity(tree);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
testdata = (test_t *) malloc(sizeof(test_t));
|
||||
testdata->key = 42;
|
||||
|
||||
rc = c_rbtree_walk(NULL, testdata, visitor);
|
||||
assert_int_equal(rc, -1);
|
||||
assert_int_equal(errno, EINVAL);
|
||||
|
||||
rc = c_rbtree_walk(tree, NULL, visitor);
|
||||
assert_int_equal(rc, -1);
|
||||
assert_int_equal(errno, EINVAL);
|
||||
|
||||
rc = c_rbtree_walk(tree, testdata, NULL);
|
||||
assert_int_equal(rc, -1);
|
||||
assert_int_equal(errno, EINVAL);
|
||||
|
||||
/* find the node with the key 42 */
|
||||
node = c_rbtree_find(tree, (void *) &i);
|
||||
assert_non_null(node);
|
||||
|
||||
free(testdata);
|
||||
}
|
||||
|
||||
static void check_c_rbtree_dup(void **state)
|
||||
{
|
||||
c_rbtree_t *tree = *state;
|
||||
c_rbtree_t *duptree = NULL;
|
||||
int rc = -1;
|
||||
|
||||
duptree = c_rbtree_dup(tree);
|
||||
assert_non_null(duptree);
|
||||
|
||||
rc = c_rbtree_check_sanity(duptree);
|
||||
assert_int_equal(rc, 0);
|
||||
|
||||
c_rbtree_free(duptree);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void check_c_rbtree_x)
|
||||
{
|
||||
int rc = -1;
|
||||
|
||||
rc = c_rbtree_check_sanity(tree);
|
||||
fail_unless(rc == 0, "c_rbtree_check_sanity failed with return code %d", rc);
|
||||
}
|
||||
#endif
|
||||
|
||||
int torture_run_tests(void)
|
||||
{
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(check_c_rbtree_create_free),
|
||||
cmocka_unit_test(check_c_rbtree_free_null),
|
||||
cmocka_unit_test(check_c_rbtree_insert_delete),
|
||||
cmocka_unit_test_setup_teardown(check_c_rbtree_insert_random, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(check_c_rbtree_insert_duplicate, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(check_c_rbtree_find, setup_complete_tree, teardown),
|
||||
cmocka_unit_test_setup_teardown(check_c_rbtree_delete, setup_complete_tree, teardown),
|
||||
cmocka_unit_test_setup_teardown(check_c_rbtree_walk, setup_complete_tree, teardown),
|
||||
cmocka_unit_test_setup_teardown(check_c_rbtree_walk_null, setup_complete_tree, teardown),
|
||||
cmocka_unit_test_setup_teardown(check_c_rbtree_dup, setup_complete_tree, teardown),
|
||||
};
|
||||
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user