#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libidav/utils.h>
#include <cx/hash_map.h>
#include <cx/utils.h>
#include <cx/linked_list.h>
#include <cx/printf.h>
#include "scfg.h"
#include "config.h"
#define xstreq(a,b) xmlStrEqual(
BAD_CAST a,
BAD_CAST b)
#define print_error(lineno, ...) \
do {\
fprintf(stderr,
"Error (sync.xml line %u): ", lineno); \
fprintf(stderr,
__VA_ARGS__); \
fprintf(stderr,
"Abort.\n"); \
}
while(
0);
#define print_warning(lineno, ...) \
do {\
fprintf(stderr,
"Warning (sync.xml line %u): ", lineno); \
fprintf(stderr,
__VA_ARGS__); \
}
while(
0);
#ifdef _WIN32
#define ENV_HOME getenv(
"USERPROFILE")
#else
#define ENV_HOME getenv(
"HOME")
#endif
static CxMap *directories;
CxIterator scfg_directory_iterator() {
return cxMapIteratorValues(directories);
}
static int create_default_sync_config(
char *file) {
FILE *out = fopen(file,
"w");
if(!out) {
perror(
"Cannot create config file");
return -
1;
}
fputs(
"<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n", out);
fputs(
"<configuration>\n", out);
fputs(
"</configuration>\n", out);
fclose(out);
return 0;
}
static void add_regex_pattern(CxList *list,
char *value,
unsigned short xmlline) {
regex_t regex;
if (regcomp(®ex, value,
REG_EXTENDED|
REG_NOSUB)) {
print_warning(xmlline,
"Invalid regular expression (%s) ... skipped\n", value);
}
else {
cxListAdd(list, ®ex);
}
}
static int scfg_load_filter(
xmlNode *node,
CxList *include,
CxList *exclude,
CxList *tags)
{
node = node->children;
while(node) {
if(node->type ==
XML_ELEMENT_NODE) {
char *value = util_xml_get_text(node);
if(xstreq(node->name,
"include")) {
if(value) {
add_regex_pattern(include, value, node->line);
}
}
else if(xstreq(node->name,
"exclude")) {
if(value) {
add_regex_pattern(exclude, value, node->line);
}
}
else if(xstreq(node->name,
"tags")) {
if(value) {
SyncTagFilter *tagfilter = parse_tagfilter_string(
value,
DAV_SYNC_TAGFILTER_SCOPE_RESOURCE);
if(!tagfilter) {
print_error(
node->line,
"malformed tag filter: %s\n",
value);
return 1;
}
else {
xmlChar *scope = xmlGetNoNsProp(node,
BAD_CAST "scope");
if(scope) {
if(xstreq(scope,
"resource"))
{
tagfilter->scope =
DAV_SYNC_TAGFILTER_SCOPE_RESOURCE;
}
else if(xstreq(scope,
"collection")) {
tagfilter->scope =
DAV_SYNC_TAGFILTER_SCOPE_COLLECTION;
}
else if(xstreq(scope,
"all")) {
tagfilter->scope =
DAV_SYNC_TAGFILTER_SCOPE_RESOURCE
|
DAV_SYNC_TAGFILTER_SCOPE_COLLECTION;
}
else {
tagfilter->scope =
DAV_SYNC_TAGFILTER_SCOPE_RESOURCE;
}
}
xmlFree(scope);
cxListAdd(tags, tagfilter);
}
}
}
else {
print_error(node->line,
"unknown filter config element: %s\n", node->name);
return 1;
}
if(!value) {
print_error(node->line,
"missing value for filter: %s\n", node->name);
return 1;
}
}
node = node->next;
}
return 0;
}
Filter* parse_filter(xmlNode *node) {
CxList *include = cxLinkedListCreate(cxDefaultAllocator,
NULL,
sizeof(
regex_t));
CxList *exclude = cxLinkedListCreate(cxDefaultAllocator,
NULL,
sizeof(
regex_t));
CxList *tags = cxLinkedListCreate(cxDefaultAllocator,
NULL,
CX_STORE_POINTERS);
cxDefineDestructor(include, regfree);
cxDefineDestructor(exclude, regfree);
if(scfg_load_filter(node, include, exclude, tags)) {
return NULL;
}
Filter *filter = malloc(
sizeof(Filter));
filter->include = include;
filter->exclude = exclude;
filter->tags = tags;
return filter;
}
void init_default_filter(Filter *filter) {
if(cxListSize(filter->include) ==
0) {
regex_t matchall;
regcomp(&matchall,
".*",
REG_NOSUB);
cxListAdd(filter->include, &matchall);
}
}
static TagFormat str2tagformat(
const char *str) {
if(!strcmp(str,
"text")) {
return TAG_FORMAT_TEXT;
}
else if(!strcmp(str,
"csv")) {
return TAG_FORMAT_CSV;
}
else if(!strcmp(str,
"xml")) {
return TAG_FORMAT_XML;
}
else if(!strcmp(str,
"macos")) {
return TAG_FORMAT_MACOS;
}
return TAG_FORMAT_UNKNOWN;
}
#define CHECK_VALUE_RET_NULL(element, value)
if(!(value)) \
{print_error(element->line,
"missing value element: %s\n", element->name);
return NULL;}
static TagConfig* parse_tagconfig(xmlNode *node) {
TagConfig conf;
conf.store =
TAG_STORE_XATTR;
conf.local_format =
TAG_FORMAT_TEXT;
conf.server_format =
TAG_FORMAT_XML;
conf.xattr_name =
NULL;
conf.detect_changes = false;
conf.conflict =
TAG_NO_CONFLICT;
xmlNode *c = node->children;
while(c) {
if(c->type ==
XML_ELEMENT_NODE) {
char *value = util_xml_get_text(c);
if(xstreq(c->name,
"local-store")) {
CHECK_VALUE_RET_NULL(c, value);
if(xstreq(value,
"xattr")) {
conf.store =
TAG_STORE_XATTR;
}
else {
return NULL;
}
xmlChar *format = xmlGetNoNsProp(node,
BAD_CAST "format");
if(format) {
conf.local_format = str2tagformat((
char*)format);
xmlFree(format);
}
}
else if(xstreq(c->name,
"detect-changes")) {
CHECK_VALUE_RET_NULL(c, value);
conf.detect_changes = util_getboolean(value);
}
else if(xstreq(c->name,
"xattr-name")) {
if(!value) {
return NULL;
}
conf.xattr_name = strdup(value);
}
else if(xstreq(c->name,
"on-conflict")) {
CHECK_VALUE_RET_NULL(c, value);
if(xstreq(value,
"no_conflict")) {
conf.conflict =
TAG_NO_CONFLICT;
}
else if(xstreq(value,
"keep_local")) {
conf.conflict =
TAG_KEEP_LOCAL;
}
else if(xstreq(value,
"keep_remote")) {
conf.conflict =
TAG_KEEP_REMOTE;
}
else if(xstreq(value,
"merge")) {
conf.conflict =
TAG_MERGE;
}
else {
fprintf(stderr,
"on-conflict: unknown value: %s\n", value);
return NULL;
}
}
}
c = c->next;
}
if(conf.store ==
TAG_STORE_XATTR && !conf.xattr_name) {
switch(conf.local_format) {
default:
case TAG_FORMAT_TEXT:
case TAG_FORMAT_CSV:
case TAG_FORMAT_XML: conf.xattr_name = strdup(
DEFAULT_TAG_XATTR);
break;
case TAG_FORMAT_MACOS: conf.xattr_name = strdup(
MACOS_TAG_XATTR);
break;
}
}
TagConfig *tagconfig = malloc(
sizeof(TagConfig));
*tagconfig = conf;
return tagconfig;
}
static SplitConfig* parse_split(xmlNode *node) {
Filter *filter =
NULL;
char *minsize =
NULL;
char *blocksize =
NULL;
xmlNode *c = node->children;
while(c) {
if(xstreq(c->name,
"filter")) {
filter = parse_filter(node);
if(filter->tags) {
fprintf(stderr,
"splitconfig: tag filter not supported\n");
free_filter(*filter);
free(filter);
return NULL;
}
}
else if(xstreq(c->name,
"minsize")) {
minsize = util_xml_get_text(c);
}
else if(xstreq(c->name,
"blocksize")) {
blocksize = util_xml_get_text(c);
}
c = c->next;
}
uint64_t sz =
0;
if(!blocksize) {
fprintf(stderr,
"splitconfig: no blocksize specified\n");
return NULL;
}
size_t bsz_len = strlen(blocksize);
if(bsz_len <
2) {
fprintf(stderr,
"splitconfig: blocksize too small\n");
return NULL;
}
if(!util_szstrtouint(blocksize, &sz)) {
fprintf(stderr,
"splitconfig: blocksize is not a number\n");
return NULL;
}
if(!filter && !minsize) {
fprintf(stderr,
"splitconfig: filter or minsize must be specified\n");
return NULL;
}
int64_t minsz = -
1;
if(minsize) {
uint64_t m;
if(!util_szstrtouint(minsize, &m)) {
fprintf(stderr,
"splitconfig: minsize is not a number\n");
return NULL;
}
minsz = (
int64_t)m;
}
SplitConfig *sc = calloc(
1,
sizeof(SplitConfig));
if(filter) {
init_default_filter(filter);
}
sc->minsize = minsz;
sc->blocksize = (
size_t)sz;
return sc;
}
static CxList* parse_splitconfig(xmlNode *node,
int *error) {
CxList *splitconfig = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
int err =
0;
xmlNode *c = node->children;
while(c) {
if(c->type ==
XML_ELEMENT_NODE && xstreq(c->name,
"split")) {
SplitConfig *sc = parse_split(c);
if(sc) {
cxListAdd(splitconfig, sc);
}
else {
err =
1;
break;
}
}
c = c->next;
}
if(error) {
*error = err;
}
return splitconfig;
}
static Versioning* parse_versioning_config(xmlNode *node) {
Versioning v;
v.always =
FALSE;
v.type =
VERSIONING_SIMPLE;
v.collection =
VERSIONING_DEFAULT_PATH;
int err =
0;
xmlChar *attr_type = xmlGetNoNsProp(node,
BAD_CAST "type");
xmlChar *attr_always = xmlGetNoNsProp(node,
BAD_CAST "always");
if(attr_type) {
if(xstreq(attr_type,
"simple")) {
v.type =
VERSIONING_SIMPLE;
}
else if(xstreq(attr_type,
"deltav")) {
v.type =
VERSIONING_DELTAV;
}
else {
print_error(node->line,
"type attribute: unknown value: %s\n",
attr_type);
err =
1;
}
xmlFree(attr_type);
}
if(attr_always) {
v.always = util_getboolean((
const char*)attr_always);
xmlFree(attr_always);
}
if(err) {
return NULL;
}
xmlNode *c = node->children;
while(c) {
if(c->type ==
XML_ELEMENT_NODE) {
char *value = util_xml_get_text(c);
if(xstreq(c->name,
"history")) {
CHECK_VALUE_RET_NULL(c, value);
v.collection = value;
}
}
c = c->next;
}
v.collection = strdup(v.collection);
Versioning *versioning = malloc(
sizeof(Versioning));
*versioning = v;
return versioning;
}
static int scfg_load_directory(xmlNode *node) {
char *name =
NULL;
char *path =
NULL;
char *trash =
NULL;
char *collection =
NULL;
char *repository =
NULL;
char *database =
NULL;
char *logfile =
NULL;
TagConfig *tagconfig =
NULL;
Versioning *versioning =
NULL;
CxList *include = cxLinkedListCreateSimple(
sizeof(
regex_t));
CxList *exclude = cxLinkedListCreateSimple(
sizeof(
regex_t));
CxList *tagfilter = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxList *splitconfig =
NULL;
int max_retry =
0;
int allow_cmd =
SYNC_CMD_PULL |
SYNC_CMD_PUSH
|
SYNC_CMD_ARCHIVE |
SYNC_CMD_RESTORE;
bool backuppull = false;
bool lockpull = false;
bool lockpush = false;
bool hashing = false;
bool store_hash = false;
bool pull_skip_hashing = false;
time_t lock_timeout =
0;
uint32_t metadata =
0;
uint32_t symlink =
0;
PushStrategy pushstrat =
PUSH_STRATEGY_METADATA;
unsigned short parentlineno = node->line;
node = node->children;
while(node) {
if(node->type ==
XML_ELEMENT_NODE) {
char *value = util_xml_get_text(node);
if(!value && !xstreq(node->name,
"versioning")) {
print_error(node->line,
"missing value for directory element: %s\n",
node->name);
return 1;
}
if(xstreq(node->name,
"name")) {
name = value;
}
else if(xstreq(node->name,
"path")) {
path = value;
}
else if(xstreq(node->name,
"trash")) {
trash = value;
}
else if(xstreq(node->name,
"collection")) {
collection = value;
}
else if(xstreq(node->name,
"repository")) {
repository = value;
}
else if(xstreq(node->name,
"filter")) {
if(scfg_load_filter(node, include, exclude, tagfilter)) {
return 1;
}
}
else if(xstreq(node->name,
"database")) {
database = value;
}
else if(xstreq(node->name,
"logfile")) {
logfile = value;
}
else if(xstreq(node->name,
"tagconfig")) {
tagconfig = parse_tagconfig(node);
}
else if(xstreq(node->name,
"splitconfig")) {
int err =
0;
splitconfig = parse_splitconfig(node, &err);
if(err) {
return 1;
}
}
else if(xstreq(node->name,
"metadata")) {
uint32_t md =
0;
const char *delims =
" ,\t\r\n";
char *metadatastr = strdup(value);
char *m = strtok(metadatastr, delims);
while(m) {
if(!strcmp(m,
"mtime")) {
md |=
FINFO_MTIME;
}
else if(!strcmp(m,
"mode")) {
md |=
FINFO_MODE;
}
else if(!strcmp(m,
"owner")) {
md |=
FINFO_OWNER;
}
else if(!strcmp(m,
"xattr")) {
md |=
FINFO_XATTR;
}
else if(!strcmp(m,
"all")) {
md |=
FINFO_MTIME |
FINFO_MODE |
FINFO_OWNER |
FINFO_XATTR;
}
m = strtok(
NULL, delims);
}
free(metadatastr);
metadata = md;
}
else if(xstreq(node->name,
"versioning")) {
versioning = parse_versioning_config(node);
}
else if(xstreq(node->name,
"max-retry")) {
int64_t i;
if(util_strtoint(value, &i) && i >=
0) {
max_retry = (
int)i;
}
else {
print_warning(node->line,
"unsigned integer value "
"expected in <max-retry> element\n");
}
}
else if(xstreq(node->name,
"allow-cmd")) {
int cmds =
0;
const char *delims =
" ,\t\r\n";
char *cmdstr = strdup(value);
char *cmd = strtok(cmdstr, delims);
while(cmd) {
if(!strcmp(cmd,
"pull")) {
cmds |=
SYNC_CMD_PULL;
}
else if(!strcmp(cmd,
"push")) {
cmds |=
SYNC_CMD_PUSH;
}
else if(!strcmp(cmd,
"archive")) {
cmds |=
SYNC_CMD_ARCHIVE;
}
else if(!strcmp(cmd,
"restore")) {
cmds |=
SYNC_CMD_RESTORE;
}
cmd = strtok(
NULL, delims);
}
free(cmdstr);
allow_cmd = cmds;
}
else if(xstreq(node->name,
"backup-on-pull")) {
backuppull = util_getboolean(value);
}
else if(xstreq(node->name,
"lock-pull")) {
lockpull = util_getboolean(value);
}
else if(xstreq(node->name,
"lock-push")) {
lockpush = util_getboolean(value);
}
else if(xstreq(node->name,
"lock-timeout")) {
int64_t t =
0;
if(util_strtoint(value, &t)) {
lock_timeout = (
time_t)t;
}
else {
print_warning(node->line,
"integer value "
"expected in <lock-timeout> element\n");
}
}
else if(xstreq(node->name,
"hashing")) {
hashing = util_getboolean(value);
store_hash = hashing;
}
else if(xstreq(node->name,
"push-strategy")) {
if(value) {
if(xstreq(value,
"metadata")) {
pushstrat =
PUSH_STRATEGY_METADATA;
}
else if(xstreq(value,
"hash")) {
pushstrat =
PUSH_STRATEGY_HASH;
}
}
}
else if(xstreq(node->name,
"symlink-intern")) {
if(!value) {
print_error(node->line,
"missing value");
}
else if(xstreq(value,
"sync")) {
symlink |=
SYNC_SYMLINK_SYNC;
}
else if(xstreq(value,
"follow")) {
}
else if(xstreq(value,
"ignore")) {
symlink |=
SYNC_SYMLINK_IGNORE_INTERN;
}
else {
print_error(node->line,
"unknown value: %s\n", value);
}
}
else if(xstreq(node->name,
"symlink-extern")) {
if(!value) {
print_error(node->line,
"missing value");
}
else if(xstreq(value,
"follow")) {
}
else if(xstreq(value,
"ignore")) {
symlink |=
SYNC_SYMLINK_IGNORE_EXTERN;
}
else {
print_error(node->line,
"unknown value: %s\n", value);
}
}
else {
print_error(node->line,
"unknown directory config element: %s\n", node->name);
return 1;
}
}
node = node->next;
}
if(!name) {
print_error(parentlineno,
"missing name element for directory\n");
return 1;
}
if(!path) {
print_error(parentlineno,
"missing path element for directory %s\n", name);
return 1;
}
if(!repository) {
print_error(parentlineno,
"missing repository element for directory %s\n", name);
return 1;
}
if(!database) {
print_error(parentlineno,
"missing database element for directory %s\n", name);
return 1;
}
SyncDirectory *dir = calloc(
1,
sizeof(SyncDirectory));
dir->name = strdup(name);
dir->path = scfg_create_path(path);
dir->collection = collection ? strdup(collection) :
NULL;
dir->repository = strdup(repository);
dir->database = strdup(database);
dir->logfile = logfile ? strdup(logfile) :
NULL;
dir->tagconfig = tagconfig;
dir->versioning = versioning;
dir->max_retry = max_retry;
dir->allow_cmd = allow_cmd;
dir->backuppull = backuppull;
dir->lockpull = lockpull;
dir->lockpush = lockpush;
dir->hashing = hashing;
dir->store_hash = store_hash;
dir->pull_skip_hashing = pull_skip_hashing;
dir->lock_timeout = lock_timeout;
dir->metadata = metadata;
dir->splitconfig = splitconfig;
dir->symlink = symlink;
dir->push_strategy = pushstrat;
if((metadata &
FINFO_MODE) ==
FINFO_MODE) {
dir->db_settings =
DB_STORE_MODE;
}
if((metadata &
FINFO_OWNER) ==
FINFO_OWNER) {
dir->db_settings |=
DB_STORE_OWNER;
}
dir->filter.include = include;
dir->filter.exclude = exclude;
dir->filter.tags = tagfilter;
init_default_filter(&dir->filter);
if (trash && cx_strtrim(cx_str(trash)).length >
0) {
if (trash[
0] ==
'/' || trash[
0] ==
'$') {
dir->trash = scfg_create_path(trash);
}
else {
char *t = util_concat_path(dir->path, trash);
dir->trash = util_concat_path(t,
"/");
free(t);
}
if(dir->trash[strlen(dir->trash)-
1] !=
'/') {
char *t = dir->trash;
dir->trash = util_concat_path(t,
"/");
free(t);
}
}
else {
dir->trash =
NULL;
}
cxMapPut(directories, cx_hash_key_str(name), dir);
return 0;
}
int load_sync_config() {
directories = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
8);
if(check_config_dir()) {
fprintf(stderr,
"Cannot create .dav directory\n");
return 1;
}
char *file = util_concat_path(
ENV_HOME,
".dav/sync.xml");
struct stat s;
if(stat(file, &s)) {
switch(errno) {
case ENOENT: {
if(create_default_sync_config(file)) {
return 1;
}
break;
}
default: {
perror(
"Cannot load sync.xml");
}
}
free(file);
return 0;
}
xmlDoc *doc = xmlReadFile(file,
NULL,
0);
if(!doc) {
fprintf(stderr,
"Cannot load sync.xml\n");
free(file);
return -
1;
}
int ret =
0;
xmlNode *node = xmlDocGetRootElement(doc)->children;
while(node && !ret) {
if(node->type ==
XML_ELEMENT_NODE) {
if(xstreq(node->name,
"directory")) {
ret = scfg_load_directory(node);
}
else {
print_error(node->line,
"unknown config element: %s\n", node->name);
ret =
1;
}
}
node = node->next;
}
xmlFreeDoc(doc);
free(file);
return ret;
}
SyncDirectory* scfg_get_dir(
const char *name) {
return cxMapGet(directories, cx_hash_key_str(name));
}
int scfg_check_dir(SyncDirectory *dir) {
struct stat s;
if(stat(dir->path, &s)) {
int err = errno;
if(err ==
ENOENT) {
fprintf(stderr,
"directory %s does not exist.\n", dir->path);
}
else {
fprintf(stderr,
"Cannot stat directory %s.\n", dir->path);
perror(
NULL);
}
fprintf(stderr,
"Abort.\n");
return -
1;
}
if(dir->trash) {
mode_t mode =
S_IRWXU |
S_IRGRP |
S_IXGRP |
S_IROTH |
S_IXOTH;
if (util_mkdir(dir->trash, mode)) {
if (errno !=
EEXIST) {
fprintf(stderr,
"Cannot create trash directory: %s\nAbort.\n", dir->trash);
return -
1;
}
}
}
return 0;
}
char* scfg_create_path(
const char *cfg) {
if(!cfg) {
return NULL;
}
if(cfg[
0] !=
'$') {
return strdup(cfg);
}
cxstring s = cx_str(cfg);
cxstring path = cx_strchr(cx_str(cfg),
'/');
char *localpath =
NULL;
if(path.length >
0) {
cxstring var = cx_strsubsl(s,
1, path.ptr - s.ptr -
1);
if(var.length >
0) {
char *env = cx_strdup(var).ptr;
char *envval = getenv(env);
free(env);
if(envval) {
localpath = util_concat_path(envval, path.ptr);
}
else {
fprintf(
stderr,
"Environment variable %.*s not set.\nAbort.\n",
(
int)var.length,
var.ptr);
exit(-
1);
}
}
else {
localpath = cx_strdup(path).ptr;
}
}
else {
char *envval = getenv(cfg +
1);
if(envval) {
localpath = strdup(envval);
}
else {
fprintf(
stderr,
"Environment variable %s not set.\nAbort.\n",
cfg);
exit(-
1);
}
}
return localpath;
}
int add_directory(SyncDirectory *dir) {
char *file = util_concat_path(
ENV_HOME,
".dav/sync.xml");
xmlDoc *doc = xmlReadFile(file,
NULL,
0);
if(!doc) {
free(file);
fprintf(stderr,
"Cannot load config.xml\n");
return 1;
}
xmlNode *root = xmlDocGetRootElement(doc);
xmlNode *dirNode = xmlNewNode(
NULL,
BAD_CAST "directory");
xmlNodeAddContent(dirNode,
BAD_CAST "\n\t\t");
xmlNewTextChild(dirNode,
NULL,
BAD_CAST "name",
BAD_CAST dir->name);
xmlNodeAddContent(dirNode,
BAD_CAST "\n\t\t");
xmlNewTextChild(dirNode,
NULL,
BAD_CAST "path",
BAD_CAST dir->path);
xmlNodeAddContent(dirNode,
BAD_CAST "\n\t\t");
xmlNewTextChild(dirNode,
NULL,
BAD_CAST "repository",
BAD_CAST dir->repository);
xmlNodeAddContent(dirNode,
BAD_CAST "\n\t\t");
xmlNewTextChild(dirNode,
NULL,
BAD_CAST "collection",
BAD_CAST dir->collection);
xmlNodeAddContent(dirNode,
BAD_CAST "\n\t\t");
if(dir->trash) {
xmlNewTextChild(dirNode,
NULL,
BAD_CAST "trash",
BAD_CAST dir->trash);
xmlNodeAddContent(dirNode,
BAD_CAST "\n\t\t");
}
xmlNewTextChild(dirNode,
NULL,
BAD_CAST "database",
BAD_CAST dir->database);
xmlNodeAddContent(dirNode,
BAD_CAST "\n\t");
xmlNodeAddContent(root,
BAD_CAST "\n\t");
xmlAddChild(root, dirNode);
xmlNodeAddContent(root,
BAD_CAST "\n");
int ret =
0;
if(xmlSaveFormatFileEnc(file, doc,
"UTF-8",
1) == -
1) {
ret =
1;
}
xmlFreeDoc(doc);
free(file);
return ret;
}
char* generate_db_name(
const char *basename) {
char *dbname =
NULL;
int count = -
1;
while(!dbname) {
cxmutstr name = count <
0 ?
cx_asprintf(
"%s-db.xml", basename) :
cx_asprintf(
"%s%d-db.xml", basename, count);
count++;
CxIterator i = cxMapIteratorValues(directories);
bool unique = true;
cx_foreach(SyncDirectory *, dir, i) {
if(!cx_strcmp(cx_strcast(name), cx_str(dir->database))) {
unique = false;
break;
}
}
if(unique) {
dbname = name.ptr;
break;
}
}
return dbname;
}
void free_filter(Filter filter) {
cxListDestroy(filter.include);
cxListDestroy(filter.exclude);
cxListDestroy(filter.tags);
}
void free_sync_config() {
if(directories) {
CxIterator i = cxMapIteratorValues(directories);
cx_foreach(SyncDirectory *, dir, i) {
free(dir->name);
free(dir->path);
free(dir->repository);
free(dir->database);
if(dir->collection) {
free(dir->collection);
}
if(dir->trash) {
free(dir->trash);
}
free_filter(dir->filter);
free(dir);
}
cxMapDestroy(directories);
}
}