#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <libxml/xmlerror.h>
#include <sys/types.h>
#include <cx/map.h>
#include <cx/utils.h>
#include <cx/list.h>
#include <cx/hash_map.h>
#include <cx/printf.h>
#ifndef _WIN32
#include <unistd.h>
#include <utime.h>
#include <pthread.h>
#else
#endif
#include <math.h>
#include <libidav/webdav.h>
#include <libidav/utils.h>
#include <libidav/crypto.h>
#include <libidav/session.h>
#include "sync.h"
#include "config.h"
#include "sopt.h"
#include "error.h"
#include "assistant.h"
#include "libxattr.h"
#include "tags.h"
#include "connect.h"
#include "system.h"
#ifdef _WIN32
#define strcasecmp _stricmp
#ifndef S_ISDIR
#define S_ISDIR(mode) ((mode) &
_S_IFMT) ==
_S_IFDIR
#define S_ISREG(mode) ((mode) &
_S_IFMT) ==
_S_IFREG
#endif
#endif
static DavContext *ctx;
static int sync_shutdown =
0;
static FILE *synclog;
static int orig_argc;
static char **orig_argv;
static void xmlerrorfnc(
void * c,
const char * msg, ... ) {
va_list ap;
va_start(ap, msg);
vfprintf(stderr, msg, ap);
va_end(ap);
}
static DavPropName defprops[] = {
{
"DAV:",
"getetag" },
{
DAV_NS,
"status" },
{
DAV_NS,
"content-hash" },
{
DAV_NS,
"split" },
{
DAV_PROPS_NS,
"finfo" },
{
DAV_PROPS_NS,
"tags" },
{
DAV_PROPS_NS,
"xattributes" },
{
DAV_PROPS_NS,
"link" }
};
static size_t numdefprops =
8 ;
void log_printf(
const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
cxmutstr str = cx_vasprintf(fmt, ap);
printf(
"%s", str.ptr);
if(synclog) {
fprintf(synclog,
"%s", str.ptr);
}
free(str.ptr);
va_end(ap);
}
void log_error(
const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
cxmutstr str = cx_vasprintf(fmt, ap);
fprintf(stderr,
"%s", str.ptr);
if(synclog) {
fprintf(synclog,
"%s", str.ptr);
}
free(str.ptr);
va_end(ap);
}
void log_resource_error(DavSession *sn,
const char *path) {
print_resource_error(sn, path);
if(synclog) {
print_resource_error_to_file(synclog, sn, path);
}
}
int logfile_open(SyncDirectory *dir) {
int ret =
0;
if(dir && dir->logfile) {
char *lf_path = dir->logfile;
char *lf_path_fr =
NULL;
if(dir->logfile[
0] !=
'/') {
lf_path = config_file_path(dir->logfile);
lf_path_fr = lf_path;
}
synclog = fopen(lf_path,
"a");
if(!synclog) {
fprintf(stderr,
"Cannot open logfile %s: %s\n", lf_path, strerror(errno));
ret =
1;
}
else {
time_t t = time(
NULL);
char *now = ctime(&t);
size_t len = strlen(now);
if(now[len-
1] ==
'\n') {
len--;
}
fprintf(synclog,
"[%.*s] ", (
int)len, now);
for(
int i=
0;i<orig_argc;i++) {
fprintf(synclog,
"%s ", orig_argv[i]);
}
fprintf(synclog,
"\n");
}
if(lf_path_fr) {
free(lf_path_fr);
}
}
return ret;
}
static int nullstrcmp(
const char *s1,
const char *s2) {
if(!s1 && s2) {
return -
1;
}
if(s1 && !s2) {
return 1;
}
if(!s1 && !s2) {
return 0;
}
return strcmp(s1, s2);
}
static char* nullstrdup(
const char *s) {
return s ? strdup(s) :
NULL;
}
static void nullfree(
void *p) {
if(p) {
free(p);
}
}
static CxIterator mapIteratorValues(CxMap *map) {
return cxMapIteratorValues(map ? map : cxEmptyMap);
}
static CxIterator listIterator(CxList *list) {
return cxListIterator(list ? list : cxEmptyList);
}
typedef void*(*clonefunc)(
void *elm,
void *userdata);
static CxMap* mapClone(
const CxAllocator *a, CxMap *map, clonefunc clone,
void *userdata) {
CxMap *newmap = cxHashMapCreate(
a,
cxMapIsStoringPointers(map) ?
CX_STORE_POINTERS : map->collection.elem_size,
cxMapSize(map) +
4
);
CxIterator i = cxMapIterator(map);
if(clone) {
cx_foreach(CxMapEntry*, entry, i) {
void *newdata = clone(entry->value, userdata);
cxMapPut(newmap, *entry->key, newdata);
}
}
else {
cx_foreach(CxMapEntry*, entry, i) {
cxMapPut(newmap, *entry->key, entry->value);
}
}
return newmap;
}
int dav_sync_main(
int argc,
char **argv);
#ifdef _WIN32
static char* wchar2utf8(
const wchar_t *wstr,
size_t wlen) {
size_t maxlen = wlen *
4;
char *ret = malloc(maxlen +
1);
int ret_len = WideCharToMultiByte(
CP_UTF8,
0,
wstr,
wlen,
ret,
maxlen,
NULL,
NULL);
ret[ret_len] =
0;
return ret;
}
int wmain(
int argc,
wchar_t **argv) {
char **argv_utf8 = calloc(argc,
sizeof(
char*));
for(
int i=
0;i<argc;i++) {
argv_utf8[i] = wchar2utf8(argv[i], wcslen(argv[i]));
}
int ret = dav_sync_main(argc, argv_utf8);
for(
int i=
0;i<argc;i++) {
free(argv_utf8[i]);
}
free(argv_utf8);
return ret;
}
#else
int main(
int argc,
char **argv) {
return dav_sync_main(argc, argv);
}
#endif
int dav_sync_main(
int argc,
char **argv) {
orig_argc = argc;
orig_argv = argv;
if(argc <
2) {
fprintf(stderr,
"Missing command\n");
print_usage(argv[
0]);
return -
1;
}
char *cmd = argv[
1];
CmdArgs *args = cmd_parse_args(argc -
2, argv +
2);
if(!args) {
print_usage(argv[
0]);
return -
1;
}
int ret =
EXIT_FAILURE;
if(!strcasecmp(cmd,
"version") || !strcasecmp(cmd,
"-version")
|| !strcasecmp(cmd,
"--version")) {
fprintf(stderr,
"dav-sync %s\n",
DAV_VERSION);
cmd_args_free(args);
return -
1;
}
xmlGenericErrorFunc fnc = xmlerrorfnc;
initGenericErrorDefaultFunc(&fnc);
sys_init();
ctx = dav_context_new();
int cfgret = load_config(ctx) || load_sync_config();
#ifndef _WIN32
struct sigaction act;
memset(&act,
0,
sizeof(
struct sigaction));
act.sa_handler =
SIG_IGN;
sigaction(
SIGPIPE, &act,
NULL);
pthread_mutex_t mutex =
PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
pthread_t tid;
#else
int tid;
int mutex;
#endif
if(!strcmp(cmd,
"check") || !strcmp(cmd,
"check-config")) {
if(!cfgret) {
fprintf(stdout,
"Configuration OK.\n");
ret =
EXIT_SUCCESS;
}
else {
ret =
EXIT_FAILURE;
}
}
else if(!cfgret) {
if(!strcmp(cmd,
"pull")) {
tid = start_sighandler(&mutex);
ret = cmd_pull(args,
FALSE);
stop_sighandler(&mutex, tid);
}
else if(!strcmp(cmd,
"push")) {
tid = start_sighandler(&mutex);
ret = cmd_push(args,
FALSE,
FALSE);
stop_sighandler(&mutex, tid);
}
else if(!strcmp(cmd,
"outgoing")) {
ret = cmd_push(args,
TRUE,
FALSE);
}
else if(!strcmp(cmd,
"archive")) {
tid = start_sighandler(&mutex);
ret = cmd_push(args,
FALSE,
TRUE);
stop_sighandler(&mutex, tid);
}
else if(!strcmp(cmd,
"restore")) {
tid = start_sighandler(&mutex);
ret = cmd_restore(args);
stop_sighandler(&mutex, tid);
}
else if(!strcmp(cmd,
"list-conflicts")) {
ret = cmd_list_conflicts(args);
}
else if(!strcmp(cmd,
"resolve-conflicts")) {
ret = cmd_resolve_conflicts(args);
}
else if(!strcmp(cmd,
"delete-conflicts")) {
ret = cmd_delete_conflicts(args);
}
else if(!strcmp(cmd,
"list-versions")) {
ret = cmd_list_versions(args);
}
else if(!strcmp(cmd,
"trash-info")) {
ret = cmd_trash_info(args);
}
else if(!strcmp(cmd,
"empty-trash")) {
ret = cmd_empty_trash(args);
}
else if(!strcmp(cmd,
"add-tag")) {
ret = cmd_add_tag(args);
}
else if(!strcmp(cmd,
"remove-tag")) {
ret = cmd_remove_tag(args);
}
else if(!strcmp(cmd,
"set-tags")) {
ret = cmd_set_tags(args);
}
else if(!strcmp(cmd,
"list-tags")) {
ret = cmd_list_tags(args);
}
else if(!strcmp(cmd,
"add-dir")
|| !strcmp(cmd,
"add-directory")) {
ret = cmd_add_directory(args);
}
else if(!strcmp(cmd,
"list-dirs")
|| !strcmp(cmd,
"list-directories")) {
ret = cmd_list_dirs();
}
else if(!strcmp(cmd,
"check-repos")
|| !strcmp(cmd,
"check-repositories")) {
ret = cmd_check_repositories(args);
}
else {
print_usage(argv[
0]);
}
}
cmd_args_free(args);
dav_context_destroy(ctx);
free_config();
free_sync_config();
curl_global_cleanup();
xmlCleanupParser();
sys_uninit();
return ret;
}
void print_usage(
char *cmd) {
fprintf(stderr,
"Usage: %s command [options] arguments...\n\n", cmd);
fprintf(stderr,
"Commands:\n");
fprintf(stderr,
" pull [-cldr] [-t <tags>] <directory>\n");
fprintf(stderr,
" push [-cldrSRM] [-t <tags>] <directory>\n");
fprintf(stderr,
" archive [-cldSRM] [-t <tags>] <directory>\n");
fprintf(stderr,
" restore [-ldRM] [-V <version>] [-s <directory>] [file...]\n");
fprintf(stderr,
" list-conflicts <directory>\n");
fprintf(stderr,
" resolve-conflicts <directory>\n");
fprintf(stderr,
" delete-conflicts <directory>\n");
fprintf(stderr,
" trash-info <directory>\n");
fprintf(stderr,
" empty-trash <directory>\n");
fprintf(stderr,
" list-versions [-s <syncdir>] <file>\n");
fprintf(stderr,
" add-tag [-s <syncdir>] <file> <tag>\n");
fprintf(stderr,
" remove-tag [-s <syncdir>] <file> <tag>\n");
fprintf(stderr,
" set-tags [-s <syncdir>] <file> [tags]\n");
fprintf(stderr,
" list-tags [-s <syncdir>] <file>\n\n");
fprintf(stderr,
"Options:\n");
fprintf(stderr,
" -c Disable conflict detection\n");
fprintf(stderr,
" -l Lock the repository before access\n");
fprintf(stderr,
" -d Don''t lock the repository\n");
fprintf(stderr,
" -t <tags> "
"Only sync files which have the specified tags\n");
fprintf(stderr,
" -r "
"Remove resources not matching the tag filter\n");
fprintf(stderr,
" -V <vers> Restore specific version\n");
fprintf(stderr,
" -S Save previous file version\n");
fprintf(stderr,
" -R Restore removed files\n");
fprintf(stderr,
" -M Restore modified files\n");
fprintf(stderr,
" -s <syncdir> Name of the syncdir the file is in\n");
fprintf(stderr,
" -v Verbose output (all commands)\n\n");
fprintf(stderr,
"Config commands:\n");
fprintf(stderr,
" add-directory\n");
fprintf(stderr,
" list-directories\n");
fprintf(stderr,
" check-config\n");
fprintf(stderr,
" check-repositories\n\n");
}
static void handlesig(
int sig) {
if(sync_shutdown) {
exit(-
1);
}
fprintf(stderr,
"abort\n");
sync_shutdown =
1;
}
#ifndef _WIN32
static void* sighandler(
void *data) {
signal(
SIGTERM, handlesig);
signal(
SIGINT, handlesig);
pthread_mutex_t *mutex = data;
pthread_mutex_lock(mutex);
return NULL;
}
pthread_t start_sighandler(
pthread_mutex_t *mutex) {
pthread_t tid;
if(pthread_create(&tid,
NULL, sighandler, mutex)) {
perror(
"pthread_create");
exit(-
1);
}
return tid;
}
void stop_sighandler(
pthread_mutex_t *mutex,
pthread_t tid) {
pthread_mutex_unlock(mutex);
void *data;
pthread_join(tid, &data);
}
#else
int start_sighandler(
int* mutex) {
return 0;
}
int stop_sighandler(
int* mutex,
int tid) {
return 0;
}
#endif
static char* create_local_path(SyncDirectory *dir,
const char *path) {
char *local_path = util_concat_path(dir->path, path);
size_t local_path_len = strlen(local_path);
if(local_path[local_path_len-
1] ==
'/') {
local_path[local_path_len-
1] =
'\0';
}
return local_path;
}
static int res_matches_filter(Filter *filter,
char *res_path) {
CxIterator i = cxListIterator(filter->include);
cx_foreach(
regex_t*, pattern, i) {
if (regexec(pattern, res_path,
0,
NULL,
0) ==
0) {
CxIterator e = cxListIterator(filter->exclude);
cx_foreach(
regex_t*, expat, e) {
if (regexec(expat, res_path,
0,
NULL,
0) ==
0) {
return 1;
}
}
return 0;
}
}
return 1;
}
static int res_matches_dir_filter(SyncDirectory *dir,
char *res_path) {
if (dir->trash) {
cxmutstr rpath = cx_mutstr(util_concat_path(dir->path, res_path));
if (util_path_isrelated(dir->trash, rpath.ptr)) {
free(rpath.ptr);
return 1;
}
free(rpath.ptr);
}
if (dir->versioning) {
if(util_path_isrelated(dir->versioning->collection, res_path)) {
return 1;
}
}
return res_matches_filter(&dir->filter, res_path);
}
static int res_matches_tags(DavResource *res, SyncTagFilter *tagfilter) {
if(!tagfilter || tagfilter->mode ==
DAV_SYNC_TAGFILTER_OFF) {
return 1;
}
if(res->iscollection) {
return 1;
}
DavXmlNode *tagsprop = dav_get_property_ns(res,
DAV_PROPS_NS,
"tags");
CxList *res_tags = parse_dav_xml_taglist(tagsprop);
int ret = matches_tagfilter(res_tags, tagfilter);
cxListDestroy(res_tags);
return ret;
}
static int localres_matches_tags(
SyncDirectory *dir,
LocalResource *res,
SyncTagFilter *tagfilter)
{
if(!tagfilter || tagfilter->mode ==
DAV_SYNC_TAGFILTER_OFF) {
return 1;
}
if(res->isdirectory) {
return 1;
}
DavBool changed =
0;
CxList *res_tags = sync_get_file_tags(dir, res, &changed,
NULL);
if(!res_tags) {
res_tags = cxEmptyList;
}
int ret = matches_tagfilter(res_tags, tagfilter);
cxListDestroy(res_tags);
return ret;
}
static int localres_cmp_path(LocalResource *a, LocalResource *b,
void *n) {
return strcmp(a->path, b->path);
}
static int localres_cmp_path_desc(LocalResource *a, LocalResource *b,
void *n) {
return -strcmp(a->path, b->path);
}
static DavSession* create_session(CmdArgs *a, DavContext *davctx, DavCfgRepository *repo,
char *collection) {
int flags = dav_repository_get_flags(repo);
DavBool find_collection =
TRUE;
if((flags &
DAV_SESSION_DECRYPT_NAME) !=
DAV_SESSION_DECRYPT_NAME) {
char *url = util_concat_path(repo->url.value.ptr, collection);
dav_repository_set_url(get_config(), repo, cx_str(url));
free(url);
collection =
NULL;
find_collection =
FALSE;
}
if(!collection || (collection[
0] ==
'/' && strlen(collection) ==
1)) {
find_collection =
FALSE;
}
DavSession *sn = connect_to_repo(davctx, repo, collection, request_auth, a);
sn->flags = flags;
sn->key = dav_context_get_key(davctx, repo->default_key.value.ptr);
curl_easy_setopt(sn->handle,
CURLOPT_HTTPAUTH, repo->authmethods);
curl_easy_setopt(sn->handle,
CURLOPT_SSLVERSION, repo->ssl_version);
if(repo->cert.value.ptr) {
curl_easy_setopt(sn->handle,
CURLOPT_CAPATH, repo->cert.value.ptr);
}
if(!repo->verification.value) {
curl_easy_setopt(sn->handle,
CURLOPT_SSL_VERIFYPEER,
0);
curl_easy_setopt(sn->handle,
CURLOPT_SSL_VERIFYHOST,
0);
}
if(find_collection) {
DavResource *col = dav_resource_new(sn, collection);
dav_exists(col);
char *newurl = util_concat_path(repo->url.value.ptr, util_resource_name(col->href));
dav_session_set_baseurl(sn, newurl);
free(newurl);
}
return sn;
}
static void print_allowed_cmds(SyncDirectory *dir) {
fprintf(stderr,
"Allowed commands: ");
char *sep =
"";
if((dir->allow_cmd &
SYNC_CMD_PULL) ==
SYNC_CMD_PULL) {
fprintf(stderr,
"pull");
sep =
", ";
}
if((dir->allow_cmd &
SYNC_CMD_PUSH) ==
SYNC_CMD_PUSH) {
fprintf(stderr,
"%spush", sep);
sep =
", ";
}
if((dir->allow_cmd &
SYNC_CMD_ARCHIVE) ==
SYNC_CMD_ARCHIVE) {
fprintf(stderr,
"%sarchive", sep);
}
fprintf(stderr,
"\n");
}
static void localres_keep(SyncDatabase *db,
const char *path) {
LocalResource *local = cxMapRemoveAndGet(db->resources, cx_hash_key_str(path));
if(local) {
local->keep =
TRUE;
}
}
static int xattr_filter(
const char *name, SyncDirectory *dir) {
if(
dir->tagconfig &&
dir->tagconfig->store ==
TAG_STORE_XATTR &&
!strcmp(dir->tagconfig->xattr_name, name))
{
return 0;
}
return 1;
}
void res2map(DavResource *root, CxMap *map) {
CxList *stack = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cxListInsert(stack,
0, root->children);
while(cxListSize(stack) >
0) {
DavResource *res = cxListAt(stack,
0);
cxListRemove(stack,
0);
while(res) {
cxMapPut(map, cx_hash_key_str(res->path), res);
if(res->children) {
cxListInsert(stack,
0, res->children);
}
res = res->next;
}
}
cxListDestroy(stack);
}
static CxHashKey resource_path_key(DavResource *res) {
CxHashKey key = {
NULL,
0,
0 };
if(res && res->path) {
cxstring res_path = cx_str(res->path);
if(res_path.length >
0 && res_path.ptr[res_path.length-
1] ==
'/') {
res_path.length--;
}
key = cx_hash_key(res_path.ptr, res_path.length);
}
return key;
}
int cmd_pull(CmdArgs *a, DavBool incoming) {
if(a->argc !=
1) {
fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
return -
1;
}
SyncTagFilter* tagfilter = parse_tagfilter_string(
cmd_getoption(a,
"tags"),
DAV_SYNC_TAGFILTER_SCOPE_RESOURCE);
if (!tagfilter) {
fprintf(stderr,
"Malformed tag filter\n");
return -
1;
}
SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
if(!dir) {
fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
return -
1;
}
if(scfg_check_dir(dir)) {
return -
1;
}
if(logfile_open(dir)) {
return -
1;
}
if((dir->allow_cmd &
SYNC_CMD_PULL) !=
SYNC_CMD_PULL) {
fprintf(stderr,
"Command ''pull'' is not allowed for this sync dir\n");
print_allowed_cmds(dir);
return -
1;
}
DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
if(!repo) {
fprintf(stderr,
"Unknown repository %s\n", dir->repository);
return -
1;
}
SyncDatabase *db = load_db(dir->database);
if(!db) {
fprintf(stderr,
"Cannot load database file: %s\n", dir->database);
return -
1;
}
remove_deleted_conflicts(dir, db);
CxMap *hashes =
NULL;
if(
SYNC_HASHING(dir)) {
hashes = create_hash_index(db);
}
DavSession *sn = create_session(a, ctx, repo, dir->collection);
cxMempoolRegister(sn->mp, db, (cx_destructor_func)destroy_db);
if (cmd_getoption(a,
"verbose")) {
curl_easy_setopt(sn->handle,
CURLOPT_VERBOSE,
1L);
curl_easy_setopt(sn->handle,
CURLOPT_STDERR, stderr);
sn->logfunc = dav_verbose_log;
}
char *locktokenfile =
NULL;
DavBool locked =
FALSE;
DavResource *root = dav_resource_new(sn,
"/");
root->iscollection =
TRUE;
if((dir->lockpush || cmd_getoption(a,
"lock")) && !cmd_getoption(a,
"nolock")) {
if(
dav_lock_t(root, dir->lock_timeout)) {
log_resource_error(sn,
"/");
dav_session_destroy(sn);
log_error(
"Abort\n");
return -
1;
}
DavLock *lock = dav_get_lock(sn,
"/");
if(lock) {
log_printf(
"Lock-Token: %s\n", lock->token);
}
locked =
TRUE;
locktokenfile = create_locktoken_file(dir->name, lock->token);
}
int ret =
0;
DavResource *ls = dav_query(sn,
"select D:getetag,idav:split,idav:status,`idav:content-hash`,idavprops:tags,idavprops:finfo,idavprops:xattributes,idavprops:link from / with depth = infinity");
if(!ls) {
log_resource_error(sn,
"/");
if(locked) {
if(dav_unlock(root)) {
log_resource_error(sn,
"/");
}
else {
locked =
FALSE;
}
}
log_error(
"Abort\n");
dav_session_destroy(sn);
return -
1;
}
if(!ls->iscollection) {
fprintf(stderr,
"%s is not a collection.\nAbort.\n", ls->path);
if(locked) {
if(dav_unlock(root)) {
log_resource_error(sn,
"/");
}
else {
locked =
FALSE;
}
}
dav_session_destroy(sn);
if(!locked && locktokenfile) {
remove(locktokenfile);
}
return -
1;
}
DavBool remove_file = cmd_getoption(a,
"remove") ?
1 :
0;
int sync_success =
0;
int sync_delete =
0;
int sync_error =
0;
int sync_conflict =
0;
CxList *res_modified = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxList *res_new = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxList *res_moved = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxList *res_link = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxList *res_conflict = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxList *res_mkdir = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxList *res_metadata = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxList *res_broken = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
CxMap *lres_removed = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
CxMap *dbres = mapClone(cxDefaultAllocator, db->resources,
NULL,
NULL);
CxList *stack = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cxListInsert(stack,
0, ls->children);
while(cxListSize(stack) >
0) {
DavResource *res = cxListAt(stack,
0);
cxListRemove(stack,
0);
while(res) {
DavBool res_filtered =
FALSE;
if (res_matches_dir_filter(dir, res->path)) {
res_filtered =
TRUE;
}
else {
CxIterator iter = cxListIterator(dir->filter.tags);
cx_foreach(SyncTagFilter *, tf, iter) {
if(!res_matches_tags(res, tf)) {
res_filtered =
TRUE;
break;
}
}
}
if(res_filtered) {
localres_keep(db, res->path);
res = res->next;
continue;
}
if (!res_matches_tags(res, tagfilter)) {
if(!remove_file) {
localres_keep(db, res->path);
}
res = res->next;
continue;
}
char *status = dav_get_string_property(res,
"idav:status");
if(status && !strcmp(status,
"broken")) {
localres_keep(db, res->path);
cxListAdd(res_broken, res);
res = res->next;
continue;
}
int change = resource_get_remote_change(a, res, dir, db);
switch(change) {
case REMOTE_NO_CHANGE:
break;
case REMOTE_CHANGE_MODIFIED: {
cxListAdd(res_modified, res);
break;
}
case REMOTE_CHANGE_NEW: {
cxListAdd(res_new, res);
break;
}
case REMOTE_CHANGE_DELETED:
break;
case REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED: {
cxListAdd(res_conflict, res);
break;
}
case REMOTE_CHANGE_METADATA: {
cxListAdd(res_metadata, res);
break;
}
case REMOTE_CHANGE_MKDIR: {
cxListAdd(res_mkdir, res);
break;
}
case REMOTE_CHANGE_LINK: {
cxListAdd(res_link, res);
break;
}
}
cxMapRemove(dbres, resource_path_key(res));
if(!dav_get_property_ns(res,
DAV_NS,
"split") && res->children) {
cxListInsert(stack,
0, res->children);
}
res = res->next;
}
}
CxIterator i = mapIteratorValues(dbres);
cx_foreach(LocalResource *, local, i) {
if (res_matches_dir_filter(dir, local->path)) {
continue;
}
if(!local->keep) {
cxMapPut(lres_removed, cx_hash_key_str(local->path), local);
}
}
i = cxListIterator(res_mkdir);
cx_foreach(DavResource *, res, i) {
if(sync_get_collection(a, dir, res, db)) {
sync_error++;
}
}
CxMap *conflicts = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, cxListSize(res_conflict)+
16);
i = cxListIterator(res_conflict);
cx_foreach(DavResource *, res, i) {
cxMapPut(conflicts, cx_hash_key_str(res->path), res);
}
if(
SYNC_HASHING(dir)) {
SYS_STAT s;
CxIterator mut_iter = cxListMutIterator(res_new);
cx_foreach(DavResource *, res, mut_iter) {
if(dav_get_property_ns(res,
DAV_PROPS_NS,
"link")) {
continue;
}
char *hash = sync_get_content_hash(res);
if(!hash) {
continue;
}
LocalResource *local = cxMapGet(hashes, cx_hash_key_str(hash));
if(!local) {
continue;
}
char *local_path = util_concat_path(dir->path, local_resource_path(local));
int staterr = sys_stat(local_path, &s);
free(local_path);
if(staterr) {
continue;
}
MovedFile *mf = malloc(
sizeof(MovedFile));
mf->content = local;
mf->resource = res;
if(cxMapRemoveAndGet(lres_removed, cx_hash_key_str(local->path))) {
mf->copy =
FALSE;
}
else {
mf->copy =
TRUE;
}
cxListAdd(res_moved, mf);
cxIteratorFlagRemoval(mut_iter);
}
}
i = cxListIterator(res_moved);
cx_foreach(MovedFile *, mf, i) {
if(sync_shutdown) {
break;
}
DavBool issplit = dav_get_property_ns(mf->resource,
DAV_NS,
"split") ?
1 :
0;
if(cxMapGet(conflicts, cx_hash_key_str(mf->resource->path))) {
rename_conflict_file(dir, db, mf->resource->path, issplit);
sync_conflict++;
}
if(sync_move_resource(a, dir, mf->resource, mf->content, mf->copy, db, &sync_success)) {
fprintf(stderr,
"%s failed: %s\n", mf->copy?
"copy":
"move", mf->resource->path);
sync_error++;
}
}
for(
int n=
0;n<
4;n++) {
CxList *list;
if(n ==
0) {
list = res_new;
}
else if(n ==
1) {
list = res_modified;
}
else if(n ==
2) {
list = res_conflict;
}
else {
list = res_link;
}
CxIterator iter = cxListIterator(list);
cx_foreach(DavResource *, res, iter) {
if(sync_shutdown) {
break;
}
DavBool issplit = dav_get_property_ns(res,
DAV_NS,
"split") ?
1 :
0;
if(cxMapGet(conflicts, cx_hash_key_str(res->path))) {
rename_conflict_file(dir, db, res->path, issplit);
sync_conflict++;
}
if(sync_get_resource(a, dir, res->path, res, db,
TRUE, &sync_success)) {
fprintf(stderr,
"resource download failed: %s\n", res->path);
sync_error++;
}
}
}
i = cxListIterator(res_metadata);
cx_foreach(DavResource *, res, i) {
if(sync_shutdown) {
break;
}
LocalResource *local = cxMapGet(db->resources, resource_path_key(res));
if(local) {
log_printf(
"update: %s\n", res->path);
char *res_path = resource_local_path(res);
char *local_path = create_local_path(dir, res->path);
free(res_path);
if(sync_store_metadata(dir, local_path, local, res)) {
fprintf(stderr,
"Metadata update failed: %s\n", res->path);
sync_error++;
}
else {
SYS_STAT s;
if(sys_stat(local_path, &s)) {
fprintf(stderr,
"Cannot stat file after update: %s\n", strerror(errno));
}
sync_set_metadata_from_stat(local, &s);
sync_success++;
}
free(local_path);
}
else {
fprintf(stderr,
"Cannot update metadata of file %s: not in database\n",
res->path);
}
}
CxList *rmdirs = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_pathlen_cmp,
CX_STORE_POINTERS);
i = cxMapIteratorValues(lres_removed);
cx_foreach(LocalResource *, removed_res, i) {
if(sync_shutdown) {
break;
}
int rmlocal_ret = sync_remove_local_resource(dir, removed_res);
if(rmlocal_ret == -
1) {
cxListAdd(rmdirs, removed_res);
}
else if(rmlocal_ret ==
0) {
LocalResource *local = cxMapRemoveAndGet(db->resources, cx_hash_key_str(removed_res->path));
if(local) {
local_resource_free(local);
}
sync_delete++;
}
}
cxMapDestroy(lres_removed);
cxListSort(rmdirs);
i = cxListIterator(rmdirs);
cx_foreach(LocalResource *, local_dir, i) {
if(!sync_remove_local_directory(dir, local_dir)) {
LocalResource *local = cxMapRemoveAndGet(db->resources, cx_hash_key_str(local_dir->path));
if(local) {
local_resource_free(local);
}
sync_delete++;
}
}
if(locked) {
if(dav_unlock(root)) {
log_resource_error(sn,
"/");
ret = -
1;
}
else {
locked =
FALSE;
}
}
if(store_db(db, dir->database, dir->db_settings)) {
fprintf(stderr,
"Cannot store sync db\n");
ret = -
2;
}
dav_session_destroy(sn);
if(!locked && locktokenfile) {
remove(locktokenfile);
}
if(ret != -
2) {
char *str_success = sync_success ==
1 ?
"file" :
"files";
char *str_delete = sync_delete ==
1 ?
"file" :
"files";
char *str_error = sync_error ==
1 ?
"error" :
"errors";
char *str_conflict = sync_conflict ==
1 ?
"conflict" :
"conflicts";
log_printf(
"Result: %d %s pulled, %d %s deleted, %d %s, %d %s\n",
sync_success, str_success,
sync_delete,str_delete,
sync_conflict, str_conflict,
sync_error, str_error);
}
return ret;
}
RemoteChangeType resource_get_remote_change(
CmdArgs *a,
DavResource *res,
SyncDirectory *dir,
SyncDatabase *db)
{
DavBool update_db =
FALSE;
char *etag = dav_get_string_property(res,
"D:getetag");
if(!etag) {
fprintf(stderr,
"Error: resource %s has no etag\n", res->path);
return REMOTE_NO_CHANGE;
}
char *hash = sync_get_content_hash(res);
DavBool issplit = dav_get_property(res,
"idav:split") ?
TRUE :
FALSE;
if(issplit) {
util_remove_trailing_pathseparator(res->path);
}
DavBool iscollection = res->iscollection && !issplit;
RemoteChangeType type = cmd_getoption(a,
"conflict") ?
REMOTE_CHANGE_MODIFIED :
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
LocalResource *local = cxMapGet(db->resources, resource_path_key(res));
char *local_path = create_local_path(dir, res->path);
char *link =
SYNC_SYMLINK(dir) ?
dav_get_string_property_ns(res,
DAV_PROPS_NS,
"link") :
NULL;
SYS_STAT s;
DavBool exists =
1;
if(sys_stat(local_path, &s)) {
if(errno !=
ENOENT) {
fprintf(stderr,
"Cannot stat file: %s\n", local_path);
free(local_path);
return REMOTE_NO_CHANGE;
}
exists =
0;
}
RemoteChangeType ret =
REMOTE_NO_CHANGE;
if(iscollection) {
if(!exists) {
ret =
REMOTE_CHANGE_MKDIR;
}
else if(local &&
S_ISDIR(s.st_mode)) {
local->isdirectory =
1;
}
else {
ret =
REMOTE_CHANGE_MKDIR;
}
}
else if(local) {
DavBool nochange =
FALSE;
if(
SYNC_SYMLINK(dir) && nullstrcmp(link, local->link_target)) {
ret =
REMOTE_CHANGE_LINK;
nochange =
TRUE;
if(local->link_target) {
LocalResource *local2 = local_resource_new(dir, db, local->path);
if(type ==
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED && nullstrcmp(local->link_target, local2->link_target)) {
ret =
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
}
local_resource_free(local2);
if(!nullstrcmp(link, local->link_target)) {
ret =
REMOTE_NO_CHANGE;
update_db =
TRUE;
}
}
}
else if(issplit && local->hash && hash) {
if(!strcmp(local->hash, hash)) {
nochange =
TRUE;
}
}
else if(local->etag) {
cxstring e = cx_str(etag);
if(cx_strprefix(e,
CX_STR(
"W/"))) {
e = cx_strsubs(e,
2);
}
if(!strcmp(e.ptr, local->etag)) {
nochange =
TRUE;
}
}
if(!nochange) {
if(!(exists && s.st_mtime != local->last_modified)) {
type =
REMOTE_CHANGE_MODIFIED;
}
ret = type;
}
}
else if(link) {
ret =
REMOTE_CHANGE_LINK;
if(exists && type ==
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) {
LocalResource *local2 = local_resource_new(dir, db, res->path);
if(local2) {
if(local2->link_target) {
if(strcmp(link, local2->link_target)) {
ret =
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
}
}
else {
ret =
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
}
local_resource_free(local2);
}
}
}
else if(exists) {
ret = type;
}
else {
ret =
REMOTE_CHANGE_NEW;
}
char *update_hash =
NULL;
if (!iscollection &&
!link &&
(ret ==
REMOTE_CHANGE_MODIFIED || ret ==
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) &&
exists &&
hash &&
!dir->pull_skip_hashing)
{
char *local_hash = util_file_hash(local_path);
if(local_hash) {
if(!strcmp(hash, local_hash)) {
ret =
REMOTE_NO_CHANGE;
update_db =
TRUE;
update_hash = local_hash;
if(local) {
if(local->hash) {
free(local->hash);
}
local->hash = local_hash;
}
}
else {
free(local_hash);
}
}
}
while(ret ==
REMOTE_NO_CHANGE && local) {
if(dir->tagconfig) {
DavXmlNode *tagsprop = dav_get_property_ns(res,
DAV_PROPS_NS,
"tags");
CxList *remote_tags =
NULL;
if(tagsprop) {
remote_tags = parse_dav_xml_taglist(tagsprop);
}
char *remote_hash = create_tags_hash(remote_tags);
if(nullstrcmp(remote_hash, local->remote_tags_hash)) {
ret =
REMOTE_CHANGE_METADATA;
}
if(remote_hash) {
free(remote_hash);
}
free_taglist(remote_tags);
if(ret ==
REMOTE_CHANGE_METADATA) {
break;
}
}
if((dir->metadata &
FINFO_XATTR) ==
FINFO_XATTR) {
DavXmlNode *xattr = dav_get_property_ns(res,
DAV_PROPS_NS,
"xattributes");
char *xattr_hash = get_xattr_hash(xattr);
if(nullstrcmp(xattr_hash, local->xattr_hash)) {
ret =
REMOTE_CHANGE_METADATA;
break;
}
}
DavXmlNode *finfo = dav_get_property_ns(res,
DAV_PROPS_NS,
"finfo");
if((dir->metadata &
FINFO_MODE) ==
FINFO_MODE) {
FileInfo f;
finfo_get_values(finfo, &f);
if(f.mode_set && f.mode != local->mode) {
ret =
REMOTE_CHANGE_METADATA;
break;
}
}
break;
}
if(ret ==
REMOTE_NO_CHANGE && update_db) {
if(!local) {
local = calloc(
1,
sizeof(LocalResource));
local->path = strdup(res->path);
cxMapPut(db->resources, cx_hash_key_str(local->path), local);
}
SYS_STAT statdata;
if(!sys_stat(local_path, &statdata)) {
sync_set_metadata_from_stat(local, &statdata);
}
else {
fprintf(stderr,
"stat failed for file: %s : %s", local_path, strerror(errno));
}
local_resource_set_etag(local, etag);
if(!local->hash) {
local->hash = update_hash;
}
if(link) {
nullfree(local->link_target);
local->link_target = link;
}
}
free(local_path);
return ret;
}
void sync_set_metadata_from_stat(LocalResource *local,
SYS_STAT *s) {
local->last_modified = s->st_mtime;
local->mode = s->st_mode &
07777;
local->uid = s->st_uid;
local->gid = s->st_gid;
local->size = s->st_size;
}
#ifdef _WIN32
#define fseeko fseek
#define ftello ftell
#endif
static CxList* sync_download_changed_parts(
DavResource *res,
LocalResource *local,
FILE *out,
size_t blocksize,
uint64_t *blockcount,
int64_t *truncate_file,
int *err)
{
CxList *updates = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cxDefineDestructor(updates, filepart_free);
size_t local_numparts = local ? local->numparts :
0;
fseeko(out,
0,
SEEK_END);
off_t end = ftello(out);
int error =
0;
CxBuffer *buf = cxBufferCreate(
NULL, blocksize, cxDefaultAllocator,
0);
int64_t maxsize = -
1;
DavResource *part = res->children;
uint64_t i =
0;
while(part) {
char *res_name = part->name;
while(res_name[
0] ==
'0' && res_name[
1] !=
'\0') {
res_name++;
}
uint64_t partnum =
0;
if(util_strtouint(res_name, &partnum)) {
DavBool download_part =
FALSE;
char *etag = dav_get_string_property_ns(part,
"DAV:",
"getetag");
if(partnum >= local_numparts) {
download_part =
TRUE;
}
else {
FilePart p = local->parts[partnum];
if(etag) {
if(nullstrcmp(etag, p.etag)) {
download_part =
TRUE;
}
}
}
uint64_t offset;
int mul_err = util_uint_mul(partnum, blocksize, &offset);
if(mul_err || offset >=
INT64_MAX) {
error =
1;
fprintf(stderr,
"Error: part number too high\n");
break;
}
int64_t block_end =
0;
if(download_part) {
if(fseeko(out, offset,
SEEK_SET)) {
error =
1;
fprintf(stderr,
"Error: fseek failed: %s\n", strerror(errno));
break;
}
buf->pos =
0;
buf->size =
0;
if(dav_get_content(part, buf,(dav_write_func)cxBufferWrite)) {
fprintf(stderr,
"Error: cannot download part: %s\n", part->name);
error =
1;
break;
}
if(fwrite(buf->space,
1, buf->size, out) ==
0) {
perror(
"write");
error =
1;
break;
}
FilePart *update = calloc(
1,
sizeof(FilePart));
update->block = partnum;
update->etag = etag ? strdup(etag) :
NULL;
update->hash = dav_create_hash(buf->space, buf->size);
cxListAdd(updates, update);
block_end = offset+buf->size;
}
else {
if(offset+blocksize > end) {
block_end = end;
}
else {
block_end = offset+blocksize;
}
}
if(block_end > maxsize) {
maxsize = block_end;
}
i++;
}
part = part->next;
}
cxBufferFree(buf);
if(error) {
*err =
1;
cxListDestroy(updates);
return NULL;
}
if(maxsize < end) {
*truncate_file = maxsize;
}
else {
*truncate_file = -
1;
}
*err =
0;
*blockcount = i;
return updates;
}
int copy_file(
const char *from,
const char *to) {
FILE *in = sys_fopen(from,
"rb");
if(!in) {
return 1;
}
FILE *out = sys_fopen(to,
"wb");
if(!out) {
fclose(in);
return 1;
}
cx_stream_copy(in, out, (cx_read_func)fread, (cx_write_func)fwrite);
fclose(in);
fclose(out);
return 0;
}
typedef int (*renamefunc)(
const char*,
const char*);
int sync_move_resource(
CmdArgs *a,
SyncDirectory *dir,
DavResource *res,
LocalResource *content,
DavBool copy,
SyncDatabase *db,
int *counter)
{
renamefunc fn = copy ? copy_file : sys_rename;
char *new_path = util_concat_path(dir->path, res->path);
char *old_path = util_concat_path(dir->path, local_resource_path(content));
log_printf(
"%s: %s -> %s\n", copy?
"copy":
"move", content->path, res->path);
if(fn(old_path, new_path)) {
free(new_path);
free(old_path);
return 1;
}
(*counter)++;
char *etag = dav_get_string_property(res,
"D:getetag");
char *content_hash = sync_get_content_hash(res);
LocalResource *local =
NULL;
if(copy) {
local = local_resource_copy(content, res->path);
}
else {
local = cxMapRemoveAndGet(db->resources, cx_hash_key_str(content->path));
if(!local) {
local = content;
}
free(content->path);
local->path = strdup(res->path);
}
cxMapPut(db->resources, cx_hash_key_str(local->path), local);
if(sync_store_metadata(dir, new_path, local, res)) {
fprintf(stderr,
"Cannot store metadata: %s\n", res->path);
}
if(local->hash) {
free(local->hash);
}
SYS_STAT s;
if(sys_stat(new_path, &s)) {
fprintf(stderr,
"Cannot stat file %s: %s\n", new_path, strerror(errno));
}
local_resource_set_etag(local, etag);
local->hash = nullstrdup(content_hash);
sync_set_metadata_from_stat(local, &s);
local->skipped =
FALSE;
return 0;
}
int sync_get_resource(
CmdArgs *a,
SyncDirectory *dir,
const char *path,
DavResource *res,
SyncDatabase *db,
DavBool update_db,
int *counter)
{
char *link =
SYNC_SYMLINK(dir) ?
dav_get_string_property_ns(res,
DAV_PROPS_NS,
"link") :
NULL;
LocalResource *local = cxMapGet(db->resources, cx_hash_key_str(path));
char *local_path;
if(link) {
char *res_path = resource_local_path(res);
local_path = create_local_path(dir, res_path);
free(res_path);
}
else {
local_path = create_local_path(dir, path);
}
char *etag = dav_get_string_property(res,
"D:getetag");
SYS_STAT s;
memset(&s,
0,
sizeof(
SYS_STAT));
char *blocksize_str = dav_get_string_property_ns(res,
DAV_NS,
"split");
uint64_t blocksize =
0;
DavBool issplit =
FALSE;
if(blocksize_str && !link) {
if(!util_strtouint(blocksize_str, &blocksize)) {
fprintf(stderr,
"Error: split property does not contain an integer.\n");
return 1;
}
issplit =
TRUE;
}
CxList *part_updates =
NULL;
uint64_t blockcount =
0;
char *content_hash =
NULL;
if(res->iscollection && !issplit) {
return 0;
}
int ret =
0;
char *tmp_path =
NULL;
FILE *out =
NULL;
if(!link) {
if(!issplit) {
tmp_path = create_tmp_download_path(local_path);
if(!tmp_path) {
fprintf(stderr,
"Cannot create tmp path for %s\n", local_path);
free(local_path);
return -
1;
}
out = sys_fopen(tmp_path ,
"wb");
}
else {
out = sys_fopen(local_path,
"r+b");
if(!out && errno ==
ENOENT) {
out = sys_fopen(local_path,
"wb");
}
}
if(!out) {
fprintf(stderr,
"Cannot open output file: %s\n", local_path);
free(local_path);
if(tmp_path) {
free(tmp_path);
}
return -
1;
}
}
int64_t truncate_file = -
1;
if(!link) {
log_printf(
"get: %s\n", path);
if(issplit) {
part_updates = sync_download_changed_parts(res, local, out, blocksize, &blockcount, &truncate_file, &ret);
}
else {
ret = dav_get_content(res, out, (dav_write_func)fwrite);
}
fclose(out);
}
else {
log_printf(
"link: %s -> %s\n", path, link);
if(sys_symlink(link, local_path)) {
perror(
"symlink");
ret =
1;
}
}
if(issplit || (
SYNC_HASHING(dir) && !link)) {
if(truncate_file >=
0) {
if(sys_truncate(local_path, truncate_file)) {
perror(
"truncate");
}
}
char *res_hash = sync_get_content_hash(res);
if(res_hash) {
content_hash = res_hash;
}
else {
content_hash = util_file_hash(local_path);
}
}
if(ret ==
0) {
(*counter)++;
if(tmp_path) {
if(dir->trash && dir->backuppull) {
move_to_trash(dir, local_path);
}
if(sys_rename(tmp_path, local_path)) {
fprintf(
stderr,
"Cannot rename file %s to %s\n",
tmp_path,
local_path);
perror(
"");
free(tmp_path);
free(local_path);
return -
1;
}
}
}
else if(tmp_path) {
if(sys_unlink(tmp_path)) {
fprintf(stderr,
"Cannot remove tmp file: %s\n", tmp_path);
}
}
if(update_db && ret ==
0) {
if(!local) {
local = calloc(
1,
sizeof(LocalResource));
local->path = strdup(path);
cxMapPut(db->resources, cx_hash_key_str(local->path), local);
}
if(sync_store_metadata(dir, local_path, local, res)) {
fprintf(stderr,
"Cannot store metadata: %s\n", path);
}
if(local->etag) {
free(local->etag);
local->etag =
NULL;
}
if(local->hash) {
free(local->hash);
local->hash =
NULL;
}
if(local->link_target) {
free(local->link_target);
local->link_target =
NULL;
}
stat_func statfn;
if(link) {
local->link_target = strdup(link);
statfn = sys_lstat;
}
else {
statfn = sys_stat;
}
update_parts(local, part_updates, blockcount);
if(statfn(local_path, &s)) {
fprintf(stderr,
"Cannot stat file %s: %s\n", local_path, strerror(errno));
}
if(!issplit) {
local_resource_set_etag(local, etag);
}
if(content_hash) {
local->hash = content_hash;
}
sync_set_metadata_from_stat(local, &s);
local->skipped =
FALSE;
}
if(tmp_path) {
free(tmp_path);
}
free(local_path);
return ret;
}
int sync_get_collection(
CmdArgs *a,
SyncDirectory *dir,
DavResource *res,
SyncDatabase *db)
{
cxstring res_path = cx_str(res->path);
if(res_path.length >
0 && res_path.ptr[res_path.length-
1] ==
'/') {
res_path.length--;
}
char *local_path = create_local_path(dir, res->path);
log_printf(
"get: %s\n", res->path);
if(sys_mkdir(local_path) && errno !=
EEXIST) {
fprintf(stderr,
"Cannot create directory %s: %s",
local_path, strerror(errno));
free(local_path);
return 1;
}
SYS_STAT s;
if(sys_stat(local_path, &s)) {
fprintf(stderr,
"Cannot stat directory %s: %s", local_path, strerror(errno));
free(local_path);
return 1;
}
LocalResource *local = cxMapGet(db->resources, cx_hash_key(res_path.ptr, res_path.length));
if(!local) {
local = calloc(
1,
sizeof(LocalResource));
local->path = cx_strdup(res_path).ptr;
cxMapPut(db->resources, cx_hash_key_str(local->path), local);
}
local->isdirectory =
1;
if(local->etag) {
free(local->etag);
local->etag =
NULL;
}
if(local->hash) {
free(local->hash);
local->hash =
NULL;
}
if(local->link_target) {
free(local->link_target);
local->link_target =
NULL;
}
local->skipped =
0;
local->size =
0;
sync_set_metadata_from_stat(local, &s);
if(sync_store_metadata(dir, local_path, local, res)) {
fprintf(stderr,
"Cannot store metadata: %s\n", res->path);
}
free(local_path);
return 0;
}
int sync_remove_local_resource(SyncDirectory *dir, LocalResource *res) {
char *local_path = create_local_path(dir, res->path);
SYS_STAT s;
if(sys_stat(local_path, &s)) {
free(local_path);
return -
2;
}
if(
S_ISDIR(s.st_mode)) {
free(local_path);
return -
1;
}
if(s.st_mtime != res->last_modified) {
free(local_path);
return -
2;
}
log_printf(
"delete: %s\n", res->path);
int ret =
0;
if(dir->trash) {
move_to_trash(dir, local_path);
}
else if(sys_unlink(local_path)) {
fprintf(stderr,
"Cannot remove file %s\n", local_path);
ret = -
2;
}
free(local_path);
return ret;
}
int sync_remove_local_directory(SyncDirectory *dir, LocalResource *res) {
int ret =
0;
char *local_path = create_local_path(dir, res->path);
log_printf(
"delete: %s\n", res->path);
if(rmdir(local_path)) {
if(errno !=
ENOTEMPTY) {
fprintf(stderr,
"rmdir: %s : %s\n", local_path, strerror(errno));
}
ret =
1;
}
free(local_path);
return ret;
}
void rename_conflict_file(SyncDirectory *dir, SyncDatabase *db,
char *path, DavBool copy) {
char *local_path = create_local_path(dir, path);
char *parent = util_parent_path(local_path);
renamefunc fn = copy ? copy_file : sys_rename;
int rev =
0;
SYS_STAT s;
int loop =
1;
do {
char *res_parent = util_parent_path(path);
const char *res_name = util_resource_name(path);
cxmutstr new_path = cx_asprintf(
"%sorig.%d.%s",
parent,
rev,
res_name);
cxmutstr new_res_path = cx_asprintf(
"%sorig.%d.%s",
res_parent,
rev,
res_name);
if(sys_stat(new_path.ptr, &s)) {
if(errno ==
ENOENT) {
loop =
0;
log_printf(
"conflict: %s\n", local_path);
if(fn(local_path, new_path.ptr)) {
fprintf(
stderr,
"Cannot rename file %s to %s\n",
local_path,
new_path.ptr);
}
else {
LocalResource *conflict = calloc(
1,
sizeof(LocalResource));
conflict->path = strdup(new_res_path.ptr);
conflict->conflict_source = strdup(path);
cxMapPut(db->conflict, cx_hash_key_str(new_res_path.ptr), conflict);
}
}
}
rev++;
free(res_parent);
free(new_path.ptr);
free(new_res_path.ptr);
}
while(loop);
free(parent);
free(local_path);
}
char* create_tmp_download_path(
char *path) {
char *new_path =
NULL;
char *parent = util_parent_path(path);
for (
int i=
0;;i++) {
cxmutstr np = cx_asprintf(
"%sdownload%d-%s",
parent,
i,
util_resource_name(path));
SYS_STAT s;
if(sys_stat(np.ptr, &s)) {
if(errno ==
ENOENT) {
new_path = np.ptr;
}
break;
}
free(np.ptr);
}
free(parent);
return new_path;
}
void move_to_trash(SyncDirectory *dir,
char *path) {
char *new_path =
NULL;
for (
int i=
0;;i++) {
cxmutstr np = cx_asprintf(
"%s%d-%s",
dir->trash,
i,
util_resource_name(path));
SYS_STAT s;
if(sys_stat(np.ptr, &s)) {
if(errno ==
ENOENT) {
new_path = np.ptr;
}
break;
}
free(np.ptr);
}
if(!new_path) {
fprintf(stderr,
"Cannot move file %s to trash.\n", path);
return;
}
if(sys_rename(path, new_path)) {
fprintf(
stderr,
"Cannot rename file %s to %s\n",
path,
new_path);
}
free(new_path);
}
static int res_isconflict(SyncDatabase *db, LocalResource *res) {
return cxMapGet(db->conflict, cx_hash_key_str(res->path)) ?
1 :
0;
}
int cmd_push(CmdArgs *a, DavBool outgoing, DavBool archive) {
if(a->argc !=
1) {
fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
return -
1;
}
SyncTagFilter* tagfilter = parse_tagfilter_string(
cmd_getoption(a,
"tags"),
DAV_SYNC_TAGFILTER_SCOPE_RESOURCE);
if (!tagfilter) {
fprintf(stderr,
"Malformed tag filter\n");
return -
1;
}
SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
if(!dir) {
fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
return -
1;
}
if(scfg_check_dir(dir)) {
return -
1;
}
if(logfile_open(dir)) {
return -
1;
}
if(cmd_getoption(a,
"snapshot")) {
if(dir->versioning) {
dir->versioning->always =
TRUE;
}
else {
fprintf(stderr,
"Error: versioning not configured for the sync directory\nAbort.\n");
return -
1;
}
}
int cmd = archive ?
SYNC_CMD_ARCHIVE :
SYNC_CMD_PUSH;
if((dir->allow_cmd & cmd) != cmd) {
fprintf(stderr,
"Command ''%s'' is not allowed for this sync dir\n", archive ?
"archive" :
"push");
print_allowed_cmds(dir);
return -
1;
}
DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
if(!repo) {
fprintf(stderr,
"Unkown repository %s\n", dir->name);
return -
1;
}
SyncDatabase *db = load_db(dir->database);
if(!db) {
fprintf(stderr,
"Cannot load database file: %s\n", dir->database);
return -
1;
}
remove_deleted_conflicts(dir, db);
DavSession *sn = create_session(a, ctx, repo, dir->collection);
cxMempoolRegister(sn->mp, db, (cx_destructor_func)destroy_db);
if (cmd_getoption(a,
"verbose")) {
curl_easy_setopt(sn->handle,
CURLOPT_VERBOSE,
1L);
curl_easy_setopt(sn->handle,
CURLOPT_STDERR, stderr);
}
if(
SYNC_STORE_HASH(dir)) {
sn->flags |=
DAV_SESSION_STORE_HASH;
}
DavBool restore_removed = cmd_getoption(a,
"restore-removed") ?
1 :
0;
DavBool restore_modified = cmd_getoption(a,
"restore-modified") ?
1 :
0;
DavBool restore = restore_removed || restore_modified;
int depth = restore ? -
1 :
0;
DavResource *root = dav_query(sn,
"select D:getetag,idav:status from / with depth = %d", depth);
if(!root) {
log_resource_error(sn,
"/");
dav_session_destroy(sn);
log_error(
"Abort\n");
return -
1;
}
CxMap *svrres =
NULL;
if(restore) {
svrres = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
1024);
res2map(root, svrres);
}
int cdt = cmd_getoption(a,
"conflict") ?
0 :
1;
DavBool locked =
FALSE;
char *locktokenfile =
NULL;
if((dir->lockpush || cmd_getoption(a,
"lock")) && !cmd_getoption(a,
"nolock") && !outgoing) {
if(
dav_lock_t(root, dir->lock_timeout)) {
log_resource_error(sn,
"/");
dav_session_destroy(sn);
log_error(
"Abort\n");
return -
1;
}
DavLock *lock = dav_get_lock(sn,
"/");
if(lock) {
log_printf(
"Lock-Token: %s\n", lock->token);
}
locked =
TRUE;
locktokenfile = create_locktoken_file(dir->name, lock->token);
}
DavBool remove_file = cmd_getoption(a,
"remove") ?
1 :
0;
CxMap *db_hashes =
NULL;
if(
SYNC_HASHING(dir)) {
db_hashes = create_hash_index(db);
}
int sync_success =
0;
int sync_delete =
0;
int sync_conflict =
0;
int sync_error =
0;
CxList *ls_new = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
CxList *ls_modified = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
CxList *ls_conflict = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
CxList *ls_update = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
CxList *ls_delete = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
CxList *ls_move = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
CxList *ls_copy = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
CxList *ls_mkcol = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
CxList *resources = local_scan(dir, db);
CxMap *resources_map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, cxListSize(resources)+
16);
CxIterator iter = cxListIterator(resources);
cx_foreach(LocalResource *, local_res, iter) {
if(res_matches_dir_filter(dir, local_res->path+
1)) {
continue;
}
if(dir->filter.tags) {
CxIterator tag_iter = cxListIterator(dir->filter.tags);
cx_foreach(SyncTagFilter *, tf, tag_iter) {
if(!localres_matches_tags(dir, local_res, tf)) {
continue;
}
}
}
cxMapPut(resources_map, cx_hash_key_str(local_res->path), local_res);
if(!localres_matches_tags(dir, local_res, tagfilter)) {
if(!remove_file) {
LocalResource *dbres = cxMapGet(
db->resources,
cx_hash_key_str(local_res->path));
if(dbres) {
dbres->keep =
TRUE;
}
}
continue;
}
if(res_isconflict(db, local_res)) {
cxListAdd(ls_conflict, local_res);
continue;
}
int is_changed = local_resource_is_changed(
dir,
db,
local_res,
svrres,
restore_removed,
restore_modified);
if(is_changed) {
if(local_res->isdirectory) {
cxListAdd(ls_mkcol, local_res);
}
else if(local_res->isnew) {
cxListAdd(ls_new, local_res);
}
else {
cxListAdd(ls_modified, local_res);
}
}
else if(local_res->metadata_updated) {
cxListAdd(ls_update, local_res);
}
if(local_res->isnew) {
if(local_resource_load_metadata(dir, local_res)) {
log_error(
"Failed to load metadata: %s\n",
local_resource_path(local_res));
}
}
}
if(
SYNC_STORE_HASH(dir)) {
CxIterator mut_iter = cxListMutIterator(ls_new);
cx_foreach(LocalResource *, local, mut_iter) {
if(local->isdirectory || local->link_target) {
continue;
}
char *local_path = util_concat_path(dir->path, local_resource_path(local));
char *hash = util_file_hash(local_path);
local->hash = hash;
LocalResource *origin = cxMapGet(db_hashes, cx_hash_key_str(hash));
if(origin) {
local->origin = local_resource_copy(origin, origin->path);
if(cxMapGet(resources_map, cx_hash_key_str(origin->path))) {
cxListAdd(ls_copy, local);
}
else {
cxListAdd(ls_move, local);
cxMapPut(resources_map, cx_hash_key_str(origin->path), local);
}
cxIteratorFlagRemoval(mut_iter);
}
free(local_path);
}
}
iter = cxMapIterator(db->resources);
CxList *removed_res = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cx_foreach(CxMapEntry *, entry, iter) {
LocalResource *local = entry->value;
if(res_matches_dir_filter(dir, local->path+
1)) {
cxMapRemove(db->resources, local->path);
continue;
}
CxIterator tag_iter = cxListIterator(dir->filter.tags);
cx_foreach(SyncTagFilter *, tf, tag_iter) {
if(!localres_matches_tags(dir, local, tf)) {
cxMapRemove(db->resources, local->path);
continue;
}
}
if(!cxMapGet(resources_map, *entry->key)) {
if(!archive) {
cxListAdd(ls_delete, local);
}
else {
cxListInsert(removed_res,
0, local);
}
}
}
iter = cxListIterator(removed_res);
cx_foreach(LocalResource *, local, iter) {
cxMapRemove(db->resources, local->path);
}
cxListSort(ls_new);
cxListSort(ls_modified);
cxListSort(ls_conflict);
cxListSort(ls_update);
cxListSort(ls_delete);
cxListSort(ls_move);
cxListSort(ls_copy);
cxListSort(ls_mkcol);
if(outgoing) {
print_outgoing(
a,
ls_new,
ls_modified,
ls_conflict,
ls_update,
ls_delete,
ls_move,
ls_copy,
ls_mkcol);
return 0;
}
int ret =
0;
int error =
0;
iter = cxListIterator(ls_mkcol);
cx_foreach(LocalResource *, local_res, iter) {
if(sync_shutdown) {
break;
}
DavResource *res = dav_resource_new(sn, local_res->path);
if(!res) {
log_resource_error(sn, local_res->path);
ret = -
1;
sync_error++;
}
int abort =
0;
dav_exists(res);
if(sn->error ==
DAV_NOT_FOUND) {
log_printf(
"mkcol: %s\n", local_res->path);
if(sync_mkdir(dir, res, local_res) && sn->error !=
DAV_METHOD_NOT_ALLOWED) {
log_resource_error(sn, res->path);
ret = -
1;
sync_error++;
error =
1;
abort =
1;
}
}
else if(sn->error !=
DAV_OK) {
log_resource_error(sn, local_res->path);
ret = -
1;
sync_error++;
error =
1;
abort =
1;
}
if(!abort) {
if(local_res->metadata_updated) {
sync_update_metadata(dir, sn, res, local_res);
}
LocalResource *dbres = cxMapGet(db->resources, cx_hash_key_str(local_res->path));
cxMapPut(db->resources, cx_hash_key_str(local_res->path), local_res);
}
dav_resource_free(res);
}
DavBool copy =
TRUE;
if(!ls_copy) {
copy =
FALSE;
ls_copy = ls_move;
}
iter = cxListIterator(ls_copy);
for(
int i=
0;i<
2;i++) {
cx_foreach(LocalResource*, local, iter) {
if(sync_shutdown) {
break;
}
int err =
0;
DavResource *res = dav_resource_new(sn, local->path);
if(dav_exists(res)) {
log_printf(
"conflict: %s\n", local->path);
local->last_modified =
0;
nullfree(local->etag);
local->etag =
NULL;
nullfree(local->hash);
local->hash =
NULL;
local->skipped =
TRUE;
sync_conflict++;
}
else {
DavResource *origin_res = dav_resource_new(sn, local->origin->path);
int origin_changed = remote_resource_is_changed(
sn,
dir,
db,
origin_res,
local->origin,
NULL);
if(origin_changed) {
cxListInsert(ls_modified,
0, local);
}
else {
log_printf(
"%s: %s -> %s\n", copy ?
"copy":
"move", local->origin->path, local->path);
err = sync_move_remote_resource(
dir,
db,
origin_res,
local,
copy,
&sync_success);
}
}
if(err) {
sync_error++;
log_resource_error(sn, res->path);
ret = -
1;
error =
1;
}
else {
LocalResource *dbres = cxMapGet(db->resources, cx_hash_key_str(local->path));
cxMapPut(db->resources, cx_hash_key_str(local->path), local);
}
}
copy =
FALSE;
iter = cxListIterator(ls_move);
}
iter = cxListIterator(ls_new);
for(
int i=
0;i<
2;i++) {
cx_foreach(LocalResource*, local_res, iter) {
if(sync_shutdown) {
break;
}
int err =
0;
DavResource *res = dav_resource_new(sn, local_res->path);
if(!res) {
log_resource_error(sn, local_res->path);
ret = -
1;
sync_error++;
}
else {
DavBool equal =
FALSE;
DavBool res_conflict =
FALSE;
int changed = remote_resource_is_changed(sn, dir, db, res, local_res, &equal);
if(equal) {
char *etag = dav_get_string_property(res,
"D:getetag");
if(local_res->metadata_updated) {
cxListInsert(ls_update,
0, local_res);
}
else if(etag) {
if(local_res->etag) {
free(local_res->etag);
}
local_res->etag = strdup(etag);
}
}
else if(cdt && changed) {
log_printf(
"conflict: %s\n", local_res->path);
local_res->last_modified =
0;
nullfree(local_res->etag);
local_res->etag =
NULL;
nullfree(local_res->hash);
local_res->hash =
NULL;
local_res->skipped =
TRUE;
sync_conflict++;
if(local_res->link_target) {
free(local_res->link_target);
local_res->link_target = local_res->link_target_db;
local_res->link_target_db =
NULL;
}
res_conflict =
TRUE;
}
else {
if(local_res->link_target) {
log_printf(
"link: %s -> %s\n",
local_res->path,
local_res->link_target);
}
else {
log_printf(
"put: %s\n", local_res->path);
}
if(sync_put_resource(dir, res, local_res, &sync_success)) {
sync_error++;
log_resource_error(sn, res->path);
ret = -
1;
error =
1;
err =
1;
}
}
if(!err) {
LocalResource *dbres = cxMapRemoveAndGet(db->resources, cx_hash_key_str(local_res->path));
if(!res_conflict || dbres) {
cxMapPut(db->resources, cx_hash_key_str(local_res->path), local_res);
}
}
}
dav_resource_free(res);
}
iter = cxListIterator(ls_modified);
}
iter = cxListIterator(ls_update);
cx_foreach(LocalResource *, local_res, iter) {
if(sync_shutdown) {
break;
}
DavResource *res = dav_resource_new(sn, local_res->path);
if(local_res->metadata_updated) {
log_printf(
"update: %s\n", local_res->path);
if(!sync_update_metadata(dir, sn, res, local_res)) {
LocalResource *dbres = cxMapGet(db->resources, cx_hash_key_str(local_res->path));
cxMapPut(db->resources, cx_hash_key_str(local_res->path), local_res);
}
}
}
cxListSort(ls_delete);
CxList *cols = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)localres_cmp_path_desc,
CX_STORE_POINTERS);
CxList *cols_del = cols;
CxList *col_list = cols;
CxList *deletelist = ls_delete;
for(
int i=
0;i<
2;i++) {
iter = cxListIterator(deletelist);
cx_foreach(LocalResource *, local, iter) {
if(sync_shutdown) {
break;
}
if(local->keep) {
continue;
}
if(sync_delete_remote_resource(dir, sn, local, &sync_delete, col_list)) {
if(sn->error !=
DAV_NOT_FOUND) {
log_resource_error(sn, local->path);
sync_error++;
break;
}
}
else {
LocalResource *dbres = cxMapRemoveAndGet(db->resources, cx_hash_key_str(local->path));
}
}
cxListSort(cols);
deletelist = cols;
col_list =
NULL;
}
cxListDestroy(cols_del);
if(locked) {
if(dav_unlock(root)) {
log_resource_error(sn,
"/");
ret = -
1;
}
else {
locked =
FALSE;
}
}
if(store_db(db, dir->database, dir->db_settings)) {
log_error(
"Cannot store sync db\n");
ret = -
2;
}
if(!locked && locktokenfile) {
remove(locktokenfile);
}
dav_session_destroy(sn);
if(ret != -
2) {
char *str_success = sync_success ==
1 ?
"file" :
"files";
char *str_delete = sync_delete ==
1 ?
"file" :
"files";
char *str_conflict = sync_conflict ==
1 ?
"conflict" :
"conflicts";
char *str_error = sync_error ==
1 ?
"error" :
"errors";
log_printf(
"Result: %d %s pushed, ", sync_success, str_success);
if(!archive) {
log_printf(
"%d %s deleted, ", sync_delete, str_delete);
}
log_printf(
"%d %s, %d %s\n",
sync_conflict, str_conflict, sync_error, str_error);
}
return ret;
}
int cmd_restore(CmdArgs *a) {
char *syncdir = cmd_getoption(a,
"syncdir");
if(!syncdir && a->argc ==
0) {
fprintf(stderr,
"No syncdir or files specified\n");
return -
1;
}
char *version = cmd_getoption(a,
"version");
if(version) {
if(a->argc !=
1) {
fprintf(stderr,
"If the -V option is enabled, only one file can be specified\n");
return -
1;
}
}
SyncDirectory *dir =
NULL;
CxMap *files =
NULL;
if(syncdir) {
dir = scfg_get_dir(syncdir);
}
if(logfile_open(dir)) {
return -
1;
}
LocalResource nres;
if(a->argc >
0) {
files = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, a->argc+
8);
SyncDirectory *sd =
NULL;
for(
int i=
0;i<a->argc;i++) {
SyncFile f;
int err = sync_get_file(a, a->argv[i], &f,
FALSE);
if(err) {
sync_print_get_file_err(a->argv[i], err);
return 1;
}
if(!sd) {
sd = f.dir;
}
else {
if(f.dir != sd) {
fprintf(stderr,
"Not all files are in the same syncdir\n");
return 1;
}
}
cxMapPut(files, cx_hash_key_str(f.path), &nres);
}
dir = sd;
}
if(!dir) {
fprintf(stderr,
"Unknown sync dir: %s\n", syncdir);
return -
1;
}
if(scfg_check_dir(dir)) {
return -
1;
}
if((dir->allow_cmd &
SYNC_CMD_RESTORE) !=
SYNC_CMD_RESTORE) {
fprintf(stderr,
"Command ''''restore'''' is not allowed for this sync dir\n");
print_allowed_cmds(dir);
return -
1;
}
DavBool restore_modified = cmd_getoption(a,
"restore-modified") ?
1 :
0;
DavBool restore_removed = cmd_getoption(a,
"restore-removed") ?
1 :
0;
if(!restore_modified && !restore_removed) {
restore_modified =
1;
restore_removed =
1;
}
SyncDatabase *db = load_db(dir->database);
if(!db) {
log_error(
"Cannot load database file: %s\n", dir->database);
return -
1;
}
remove_deleted_conflicts(dir, db);
CxList *modified = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)localres_cmp_path,
CX_STORE_POINTERS);
CxList *deleted = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)localres_cmp_path,
CX_STORE_POINTERS);
CxIterator resiter = cxMapIterator(files ? files : db->resources);
cx_foreach(CxMapEntry *, entry, resiter) {
LocalResource *resource = entry->value;
if(resource == &nres) {
resource = cxMapGet(db->resources, *entry->key);
if(!resource) {
continue;
}
}
char *file_path = create_local_path(dir, resource->path);
SYS_STAT s;
if(sys_stat(file_path, &s)) {
if(errno ==
ENOENT) {
if(restore_removed) {
cxListAdd(deleted, resource);
}
}
else {
log_error(
"Cannot stat file: %s\n", file_path);
log_error(
"%s\n", strerror(errno));
}
}
else {
if(files) {
cxListAdd(modified, resource);
}
else if(!resource->isdirectory && !
S_ISDIR(s.st_mode)) {
if(resource->last_modified != s.st_mtime || resource->size != s.st_size) {
if(restore_modified) {
cxListAdd(modified, resource);
}
}
}
}
free(file_path);
}
if(files) {
cxMapDestroy(files);
}
int ret =
0;
DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
if(!repo) {
log_error(
"Unkown repository %s\n", dir->name);
return -
1;
}
DavSession *sn = create_session(a, ctx, repo, dir->collection);
cxMempoolRegister(sn->mp, db, (cx_destructor_func)destroy_db);
if (cmd_getoption(a,
"verbose")) {
curl_easy_setopt(sn->handle,
CURLOPT_VERBOSE,
1L);
curl_easy_setopt(sn->handle,
CURLOPT_STDERR, stderr);
}
char *locktokenfile =
NULL;
DavBool locked =
FALSE;
DavResource *root = dav_resource_new(sn,
"/");
root->iscollection =
TRUE;
if((dir->lockpush || cmd_getoption(a,
"lock")) && !cmd_getoption(a,
"nolock")) {
if(
dav_lock_t(root, dir->lock_timeout)) {
log_resource_error(sn,
"/");
dav_session_destroy(sn);
log_error(
"Abort\n");
return -
1;
}
DavLock *lock = dav_get_lock(sn,
"/");
if(lock) {
log_printf(
"Lock-Token: %s\n", lock->token);
}
locked =
TRUE;
locktokenfile = create_locktoken_file(dir->name, lock->token);
}
int sync_success =
0;
int sync_error =
0;
cxListSort(deleted);
CxIterator iter = cxListIterator(modified);
for(
int i=
0;i<
2;i++) {
cx_foreach(LocalResource *, resource, iter) {
DavResource *res = dav_get(sn, resource->path,
"D:getetag,idav:status,idav:version-collection,idav:split,`idav:content-hash`,idavprops:tags,idavprops:finfo,idavprops:xattributes,idavprops:link");
if(!res) {
log_printf(
"skip: %s\n", resource->path);
continue;
}
char *status = dav_get_string_property(res,
"idav:status");
if(status && !strcmp(status,
"broken")) {
log_error(
"Resource %s broken\n", res->path);
continue;
}
DavResource *vres =
NULL;
DavBool update_local_entry =
TRUE;
if(version) {
if(dir->versioning->type ==
VERSIONING_SIMPLE) {
vres = versioning_simple_find(res, version);
}
else if(dir->versioning->type ==
VERSIONING_DELTAV) {
vres = versioning_deltav_find(res, version);
}
if(!vres) {
log_error(
"Cannot find specified version for resource %s\n", res->path);
ret =
1;
break;
}
update_local_entry =
FALSE;
}
else {
vres = res;
}
if(!sync_shutdown) {
if(resource->isdirectory) {
char *local_path = create_local_path(dir, res->path);
if(sys_mkdir(local_path) && errno !=
EEXIST) {
log_error(
"Cannot create directory %s: %s",
local_path, strerror(errno));
}
free(local_path);
}
else {
if(sync_get_resource(a, dir, res->path, vres, db, update_local_entry, &sync_success)) {
log_error(
"sync_get_resource failed for resource: %s\n", res->path);
sync_error++;
}
else if(!update_local_entry) {
LocalResource *lr = cxMapGet(db->resources, cx_hash_key_str(res->path));
if(lr) {
lr->last_modified =
0;
nullfree(lr->hash);
lr->hash =
NULL;
}
}
}
}
}
iter = cxListIterator(deleted);
}
if(locked) {
if(dav_unlock(root)) {
log_resource_error(sn,
"/");
ret = -
1;
}
else {
locked =
FALSE;
}
}
if(store_db(db, dir->database, dir->db_settings)) {
log_error(
"Cannot store sync db\n");
ret = -
2;
}
dav_session_destroy(sn);
if(!locked && locktokenfile) {
remove(locktokenfile);
}
if(ret != -
2) {
char *str_success = sync_success ==
1 ?
"file" :
"files";
char *str_error = sync_error ==
1 ?
"error" :
"errors";
log_printf(
"Result: %d %s pulled, %d %s\n",
sync_success, str_success,
sync_error, str_error);
}
return ret;
}
static void print_outgoging_file(LocalResource *res) {
char *lastmodified = util_date_str(res->last_modified);
char *size = util_size_str(
FALSE, res->size);
log_printf(
" %-49s %12s %10s\n", res->path+
1, lastmodified, size);
free(lastmodified);
free(size);
}
void print_outgoing(
CmdArgs *args,
CxList *ls_new,
CxList *ls_modified,
CxList *ls_conflict,
CxList *ls_update,
CxList *ls_delete,
CxList *ls_move,
CxList *ls_copy,
CxList *ls_mkcol)
{
int64_t total_size =
0;
size_t len_new = cxListSize(ls_new);
size_t len_mod = cxListSize(ls_modified);
size_t len_cnf = cxListSize(ls_conflict);
size_t len_upd = cxListSize(ls_update);
size_t len_del = cxListSize(ls_delete);
size_t len_mov = cxListSize(ls_move);
size_t len_cpy = cxListSize(ls_copy);
size_t len_mkc = cxListSize(ls_mkcol);
size_t total = len_new + len_mod + len_cnf + len_upd + len_del + len_mov + len_cpy + len_mkc;
if(total ==
0) {
log_printf(
"no changes\n");
return;
}
log_printf(
"%s\n",
"File Last Modified Size");
log_printf(
"%s\n",
"==============================================================================");
if(cxListSize(ls_mkcol) >
0) {
log_printf(
"Directories:\n");
CxIterator i = cxListIterator(ls_mkcol);
cx_foreach(LocalResource *, res, i) {
log_printf(
" %-49s\n", res->path+
1);
total_size += res->size;
}
}
if(cxListSize(ls_new) >
0) {
log_printf(
"New:\n");
CxIterator i = cxListIterator(ls_new);
cx_foreach(LocalResource *, res, i) {
print_outgoging_file(res);
total_size += res->size;
}
}
if(cxListSize(ls_modified) >
0) {
log_printf(
"Modified:\n");
CxIterator i = cxListIterator(ls_modified);
cx_foreach(LocalResource *, res, i) {
print_outgoging_file(res);
total_size += res->size;
}
}
if(cxListSize(ls_update) >
0) {
log_printf(
"Update:\n");
CxIterator i = cxListIterator(ls_update);
cx_foreach(LocalResource *, res, i) {
char *lastmodified = util_date_str(res->last_modified);
log_printf(
" %-49s %12s\n", res->path+
1, lastmodified);
free(lastmodified);
}
}
if(cxListSize(ls_delete) >
0) {
log_printf(
"Delete:\n");
CxIterator i = cxListIterator(ls_delete);
cx_foreach(LocalResource *, res, i) {
log_printf(
" %s\n", res->path+
1);
}
}
if(cxListSize(ls_copy) >
0) {
log_printf(
"Copy:\n");
CxIterator i = cxListIterator(ls_copy);
cx_foreach(LocalResource *, res, i) {
log_printf(
"%s -> %s\n", res->origin->path+
1, res->path);
}
}
if(cxListSize(ls_move) >
0) {
log_printf(
"Move:\n");
CxIterator i = cxListIterator(ls_move);
cx_foreach(LocalResource *, res, i) {
log_printf(
"%s -> %s\n", res->origin->path+
1, res->path);
}
}
if(cxListSize(ls_conflict) >
0) {
log_printf(
"Conflict\n");
CxIterator i = cxListIterator(ls_conflict);
cx_foreach(LocalResource *, res, i) {
log_printf(
" %s\n", res->path+
1);
}
}
char *total_size_str = util_size_str(
FALSE, total_size);
log_printf(
"\n");
if(len_new >
0) log_printf(
"new: %zu, ", len_new);
if(len_mod >
0) log_printf(
"modified: %zu, ", len_mod);
if(len_upd >
0) log_printf(
"updated: %zu, ", len_upd);
if(len_cpy >
0) log_printf(
"copied: %zu, ", len_cpy);
if(len_mov >
0) log_printf(
"moved: %zu, ", len_mov);
if(len_del >
0) log_printf(
"deleted: %zu, ", len_del);
if(len_cnf >
0) log_printf(
"conflicts: %zu, ", len_cnf);
log_printf(
"total size: %s\n", total_size_str);
free(total_size_str);
}
CxList* local_scan(SyncDirectory *dir, SyncDatabase *db) {
CxList *resources = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
char *path = strdup(
"/");
CxList *stack = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
cxListInsert(stack,
0, path);
while(cxListSize(stack) >
0) {
char *p = cxListAt(stack,
0);
cxListRemove(stack,
0);
char *local_path = create_local_path(dir, p);
SYS_DIR local_dir = sys_opendir(local_path);
if(!local_dir) {
log_error(
"Cannot open directory %s: %s\n", local_path, strerror(errno));
}
else {
SysDirEnt *ent;
while((ent = sys_readdir(local_dir)) !=
NULL) {
if(!strcmp(ent->name,
".") || !strcmp(ent->name,
"..")) {
continue;
}
char *new_path = util_concat_path(p, ent->name);
DavBool free_new_path =
TRUE;
LocalResource *res = local_resource_new(dir, db, new_path);
if(res) {
if(res->isdirectory) {
cxListAdd(resources, res);
cxListInsert(stack,
0, new_path);
free_new_path =
FALSE;
}
else {
cxListAdd(resources, res);
}
}
if(free_new_path) {
free(new_path);
}
}
sys_closedir(local_dir);
}
free(local_path);
free(p);
}
return resources;
}
LocalResource* local_resource_new(SyncDirectory *dir, SyncDatabase *db,
char *path) {
char *file_path = create_local_path(dir, path);
SYS_STAT s;
if(sys_lstat(file_path, &s)) {
log_error(
"Cannot stat file %s: %s\n", file_path, strerror(errno));
free(file_path);
return NULL;
}
LocalResource *res = calloc(
1,
sizeof(LocalResource));
res->mode = s.st_mode &
07777;
res->uid = s.st_uid;
res->gid = s.st_gid;
res->last_modified = s.st_mtime;
res->path = strdup(path);
if(!
S_ISDIR(s.st_mode)) {
res->size = s.st_size;
}
else {
res->isdirectory =
1;
}
int skip_file =
0;
if(
SYS_ISLINK(file_path, s)) {
char *lnkbuf = sys_readlink(file_path, &s);
#ifdef SYS_LINK_EXT
cxstring fpath = cx_str(file_path);
cxstring rpath = cx_str(path);
cxmutstr new_file_path = cx_strdup(cx_strsubsl(fpath,
0 , fpath.length-
4));
SYS_STAT nfp_s;
if(!sys_stat(new_file_path.ptr, &nfp_s)) {
free(lnkbuf);
lnkbuf =
NULL;
}
else {
cxmutstr new_path = cx_strdup(cx_strsubsl(rpath,
0, rpath.length-
4));
res->local_path = res->path;
res->path = new_path.ptr;
}
free(new_file_path.ptr);
#endif
if(lnkbuf) {
char *normalized =
NULL;
if(!util_path_isabsolut(lnkbuf)) {
char *link_parent = util_parent_path(res->path);
char *abs_link_parent = util_concat_path(dir->path, link_parent);
char *link = util_concat_path(abs_link_parent, lnkbuf);
normalized = util_path_normalize(link);
free(abs_link_parent);
free(link_parent);
free(link);
}
else {
normalized = util_path_normalize(lnkbuf);
}
char *dirpath = util_path_normalize(dir->path);
int isintern = util_path_isrelated(dirpath, normalized);
if(
SYNC_SYMLINK(dir) && isintern) {
char *rel = util_create_relative_path(normalized, file_path);
res->link_target = rel;
}
else {
#ifndef SYS_LINK_EXT
SYS_STAT targetstat;
if(!sys_stat(file_path, &targetstat)) {
res->isdirectory =
S_ISDIR(targetstat.st_mode);
int nofollowextern = (dir->symlink &
SYNC_SYMLINK_IGNORE_EXTERN) ==
SYNC_SYMLINK_IGNORE_EXTERN;
int nofollowintern = (dir->symlink &
SYNC_SYMLINK_IGNORE_INTERN) ==
SYNC_SYMLINK_IGNORE_INTERN;
if(isintern && nofollowintern) {
skip_file =
TRUE;
}
else if(!isintern && nofollowextern) {
skip_file =
TRUE;
}
}
else {
skip_file =
TRUE;
}
#endif
}
free(dirpath);
free(normalized);
free(lnkbuf);
}
}
if(!res->isdirectory && dir->push_strategy ==
PUSH_STRATEGY_HASH) {
res->hash = util_file_hash(file_path);
}
free(file_path);
if(skip_file) {
local_resource_free(res);
res =
NULL;
}
return res;
}
char* local_resource_path(LocalResource *res) {
return res->local_path ? res->local_path : res->path;
}
int local_resource_is_changed(
SyncDirectory *dir,
SyncDatabase *db,
LocalResource *res,
CxMap *svrres,
DavBool restore_removed,
DavBool restore_modified)
{
LocalResource *db_res = cxMapGet(db->resources, cx_hash_key_str(res->path));
res->tags_updated =
0;
if(db_res) {
res->tags_updated = db_res->tags_updated;
if(db_res->etag) {
res->etag = strdup(db_res->etag);
}
if(db_res->tags_hash) {
res->tags_hash = strdup(db_res->tags_hash);
}
if(db_res->remote_tags_hash) {
res->remote_tags_hash = strdup(db_res->remote_tags_hash);
}
if(db_res->xattr_hash) {
res->xattr_hash = strdup(db_res->xattr_hash);
}
if(db_res->hash) {
res->prev_hash = strdup(db_res->hash);
}
if(db_res->link_target) {
res->link_target_db = db_res->link_target;
}
res->versioncontrol = db_res->versioncontrol;
if(db_res->parts) {
local_resource_copy_parts(db_res, res);
}
if(svrres) {
DavResource *remote = cxMapGet(svrres, cx_hash_key_str(res->path));
if(restore_removed && !remote) {
return 1;
}
if(!res->isdirectory && restore_modified && remote) {
char *etag = dav_get_string_property(remote,
"D:getetag");
if(!etag || (db_res->etag && strcmp(etag, db_res->etag))) {
res->restore =
TRUE;
return 1;
}
}
}
if(db_res->tags_updated) {
res->tags_updated =
1;
res->metadata_updated =
1;
}
else if(dir->tagconfig && dir->tagconfig->detect_changes ) {
CxBuffer *tags = sync_get_file_tag_data(dir, res);
if(tags) {
if(db_res->tags_hash) {
char *hash = dav_create_hash(tags->space, tags->size);
if(strcmp(hash, db_res->tags_hash)) {
res->tags_updated =
1;
}
free(hash);
}
else {
res->tags_updated =
1;
}
}
else if(db_res->tags_hash) {
res->tags_updated =
1;
}
res->metadata_updated = res->tags_updated;
}
if((dir->metadata &
FINFO_MTIME) ==
FINFO_MTIME) {
if(db_res->last_modified != res->last_modified) {
res->finfo_updated =
1;
res->metadata_updated =
1;
}
}
if((dir->metadata &
FINFO_MODE) ==
FINFO_MODE) {
if(db_res->mode != res->mode) {
res->finfo_updated =
1;
res->metadata_updated =
1;
}
}
if((dir->metadata &
FINFO_OWNER) ==
FINFO_OWNER) {
if(db_res->uid != res->uid || db_res->gid != res->gid) {
res->finfo_updated =
1;
res->metadata_updated =
1;
}
}
if((dir->metadata &
FINFO_XATTR) ==
FINFO_XATTR) {
char *path = create_local_path(dir, local_resource_path(db_res));
XAttributes *xattr = file_get_attributes(path, (xattr_filter_func)xattr_filter, dir);
if((db_res->xattr_hash && !xattr) ||
(!db_res->xattr_hash && xattr) ||
(xattr && db_res->xattr_hash && strcmp(xattr->hash, db_res->xattr_hash)))
{
res->metadata_updated =
1;
res->xattr_updated =
1;
res->xattr = xattr;
}
else if(xattr) {
xattributes_free(xattr);
}
}
if(nullstrcmp(db_res->link_target, res->link_target)) {
res->link_updated =
1;
}
else {
if(db_res->hash && res->hash) {
if(!strcmp(db_res->hash, res->hash)) {
return 0;
}
}
else if(
db_res->last_modified == res->last_modified &&
db_res->size == res->size &&
db_res->isdirectory == res->isdirectory)
{
return 0;
}
else if(
SYNC_HASHING(dir)) {
char *local_path = util_concat_path(dir->path, local_resource_path(res));
res->hash = util_file_hash(local_path);
free(local_path);
if(res->hash && db_res->hash && !strcmp(res->hash, db_res->hash)) {
return 0;
}
}
}
}
else {
res->tags_updated =
1;
res->finfo_updated =
1;
res->xattr_updated =
1;
res->metadata_updated =
1;
res->isnew =
1;
}
return 1;
}
int remote_resource_is_changed(
DavSession *sn,
SyncDirectory *dir,
SyncDatabase *db,
DavResource *remote,
LocalResource *res,
DavBool *equal)
{
if(equal) {
*equal =
FALSE;
}
DavPropName properties[] = {
{
"DAV:",
"getetag"},
{
DAV_NS,
"version-collection"},
{
DAV_NS,
"content-hash"},
{
DAV_NS,
"split" },
{
DAV_PROPS_NS,
"tags"},
{
DAV_PROPS_NS,
"link" }
};
int err = dav_load_prop(remote, properties,
6);
if(res->restore) {
return 0;
}
if(remote->iscollection && !res->parts) {
return 1;
}
char *link = dav_get_string_property_ns(remote,
DAV_PROPS_NS,
"link");
int ret =
0;
if(err ==
0) {
char *etag = dav_get_string_property(remote,
"D:getetag");
char *hash = sync_get_content_hash(remote);
if(res->link_target_db || link) {
ret = nullstrcmp(res->link_target_db, link);
if(ret && equal) {
*equal = !nullstrcmp(res->link_target, link);
}
return ret;
}
if(hash && res->hash && equal) {
if(!strcmp(hash, res->hash)) {
*equal =
TRUE;
return 0;
}
}
if(hash && res->prev_hash) {
if(strcmp(hash, res->prev_hash)) {
ret =
1;
}
}
else if(!res->etag) {
ret =
1;
}
else if(etag) {
cxstring e = cx_str(etag);
if(cx_strprefix(e,
CX_STR(
"W/"))) {
e = cx_strsubs(e,
2);
}
if(strcmp(e.ptr, res->etag)) {
ret =
1;
}
}
else {
fprintf(stderr,
"Warning: resource %s has no etag\n", remote->href);
}
}
return ret;
}
int local_resource_load_metadata(SyncDirectory *dir, LocalResource *res) {
if((dir->metadata &
FINFO_XATTR) ==
FINFO_XATTR) {
char *path = create_local_path(dir, local_resource_path(res));
XAttributes *xattr = file_get_attributes(path, (xattr_filter_func)xattr_filter, dir);
res->xattr = xattr;
free(path);
}
return 0;
}
void local_resource_set_etag(LocalResource *local,
const char *etag) {
if(local->etag) {
free(local->etag);
}
if(!etag) {
local->etag =
NULL;
return;
}
cxstring e = cx_str(etag);
if(cx_strprefix(e,
CX_STR(
"W/"))) {
e = cx_strsubs(e,
2);
}
local->etag = cx_strdup(e).ptr;
}
char* resource_local_path(DavResource *res) {
cxstring path = cx_str(res->path);
if(path.length >
0 && path.ptr[path.length-
1] ==
'/') {
path.length--;
}
#ifdef SYS_LINK_EXT
if(dav_get_property_ns(res,
DAV_PROPS_NS,
"link")) {
return cx_asprintf(
"%.*s%s", (
int)path.length, path.ptr,
SYS_LINK_EXT).ptr;
}
else {
return cx_strdup(path).ptr;
}
#else
return cx_strdup(path).ptr;
#endif
}
size_t resource_get_blocksize(SyncDirectory *dir, LocalResource *local, DavResource *res,
off_t filesize) {
size_t local_blocksize =
0;
if(local->blocksize <
0) {
return 0;
}
else if(local->blocksize >
0) {
local_blocksize = (
size_t)local->blocksize;
}
else if(dir->splitconfig) {
CxIterator i = cxListIterator(dir->splitconfig);
cx_foreach(SplitConfig *, sc, i) {
if(sc->filter) {
if(res_matches_filter(sc->filter, local->path)) {
continue;
}
}
if(sc->minsize >
0) {
if(filesize < sc->minsize) {
continue;
}
}
local_blocksize = sc->blocksize;
break;
}
}
size_t svr_blocksize =
0;
char *svr_blocksize_str = dav_get_string_property_ns(res,
DAV_NS,
"split");
if(svr_blocksize_str) {
uint64_t i =
0;
if(util_strtouint(svr_blocksize_str, &i)) {
svr_blocksize = (
size_t)i;
}
}
if(local_blocksize >
0 && svr_blocksize >
0 && local_blocksize != svr_blocksize) {
fprintf(stderr,
"Warning: Blocksize mismatch: %s: local: %zu server: %zu\n", local->path, local_blocksize, svr_blocksize);
return svr_blocksize;
}
else if(local_blocksize >
0) {
return local_blocksize;
}
else if(svr_blocksize >
0) {
return svr_blocksize;
}
return 0;
}
int resource_pathlen_cmp(LocalResource *res1, LocalResource *res2,
void *n) {
size_t s1 = strlen(res1->path);
size_t s2 = strlen(res2->path);
if(s1 < s2) {
return 1;
}
else if(s1 > s2) {
return -
1;
}
else {
return 0;
}
}
int resource_path_cmp(LocalResource *res1, LocalResource *res2,
void *n) {
return strcmp(res1->path, res2->path);
}
DavResource *versioning_simple_find(DavResource *res,
const char *version) {
char *vcol_href = dav_get_string_property_ns(res,
DAV_NS,
VERSION_PATH_PROPERTY);
if(!vcol_href) {
return NULL;
}
DavResource *vcol = dav_resource_new_href(res->session, vcol_href);
if(!vcol) {
return NULL;
}
if(dav_load_prop(vcol, defprops, numdefprops)) {
log_resource_error(res->session, vcol->path);
dav_resource_free(vcol);
return NULL;
}
DavResource *ret =
NULL;
DavResource *child = vcol->children;
while(child) {
DavResource *next = child->next;
if(!strcmp(child->name, version)) {
ret = child;
}
else {
dav_resource_free(child);
}
child = next;
}
dav_resource_free(vcol);
return ret;
}
DavResource* versioning_deltav_find(DavResource *res,
const char *version) {
DavResource *list = dav_versiontree(res,
"D:getetag,idav:status,idav:split,idavprops:link,idavprops:finfo,idavprops:xattributes,idavprops:tags");
DavResource *ret =
NULL;
while(list) {
DavResource *next = list->next;
if(!ret) {
char *vname = dav_get_string_property(list,
"D:version-name");
if(vname && !strcmp(vname, version)) {
ret = list;
}
}
if(list != ret) {
dav_resource_free(list);
}
list = next;
}
return ret;
}
int sync_set_status(DavResource *res,
char *status) {
DavResource *resource = dav_resource_new(res->session, res->path);
dav_set_string_property(resource,
"idav:status", status);
int ret = dav_store(resource);
dav_resource_free(resource);
return ret;
}
int sync_remove_status(DavResource *res) {
DavResource *resource = dav_resource_new(res->session, res->path);
dav_remove_property(resource,
"idav:status");
int ret = dav_store(resource);
dav_resource_free(resource);
return ret;
}
int sync_store_metadata(SyncDirectory *dir,
const char *path, LocalResource *local, DavResource *res) {
int ret =
0;
DavXmlNode *fileinfo = dav_get_property_ns(res,
DAV_PROPS_NS,
"finfo");
if(fileinfo) {
FileInfo f;
finfo_get_values(fileinfo, &f);
if((dir->metadata &
FINFO_MTIME) ==
FINFO_MTIME && f.date_set) {
#ifndef _WIN32
struct utimbuf t;
t.actime = f.last_modified;
t.modtime = f.last_modified;
if(utime(path, &t)) {
log_error(
"utime failed for file: %s : %s\n", path, strerror(errno));
ret =
1;
}
else {
local->last_modified = f.last_modified;
}
#else
local->last_modified =
0;
#endif
}
if((dir->metadata &
FINFO_MODE) ==
FINFO_MODE && f.mode_set) {
if(chmod(path, f.mode)) {
log_error(
"chmod failed for file: %s : %s\n", path, strerror(errno));
ret =
1;
}
else {
local->mode = f.mode;
}
}
}
if((dir->metadata &
FINFO_XATTR) ==
FINFO_XATTR) {
DavXmlNode *xattr_prop = dav_get_property_ns(res,
DAV_PROPS_NS,
"xattributes");
XAttributes *xattr =
NULL;
if(xattr_prop) {
xattr = xml_get_attributes(xattr_prop);
}
if(!sync_store_xattr(dir, path, xattr)) {
if(local->xattr_hash) {
free(local->xattr_hash);
}
local->xattr_hash = xattr ? xattr->hash :
NULL;
}
}
if(sync_store_tags(dir, path, local, res)) {
ret =
1;
}
return ret;
}
int sync_store_xattr(SyncDirectory *dir,
const char *path, XAttributes *xattr) {
ssize_t nelm =
0;
char **list = xattr_list(path, &nelm);
CxMap *current_xattr =
NULL;
if(nelm >
0) {
current_xattr = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, nelm +
8);
for(
int i=
0;i<nelm;i++) {
cxMapPut(current_xattr, cx_hash_key_str(list[i]), list[i]);
}
}
if(list) {
free(list);
}
size_t nattr = xattr ? xattr->nattr :
0;
for(
int i=
0;i<nattr;i++) {
cxmutstr value = xattr->values[i];
if(xattr_set(path, xattr->names[i], value.ptr, value.length)) {
log_error(
"Cannot store xattr ''%s'' for file: %s\n",
xattr->names[i],
path);
}
if(current_xattr) {
char *removed_value = cxMapRemoveAndGet(current_xattr, cx_hash_key_str(xattr->names[i]));
if(removed_value) {
free(removed_value);
}
}
}
if(current_xattr) {
CxIterator i = cxMapIteratorValues(current_xattr);
cx_foreach(
char *, value, i) {
(
void)xattr_remove(path, value);
free(value);
}
cxMapDestroy(current_xattr);
}
return 0;
}
int sync_store_tags(SyncDirectory *dir,
const char *path, LocalResource *local, DavResource *res) {
if(!dir->tagconfig) {
return 0;
}
char *remote_hash =
NULL;
CxList *tags =
NULL;
if(dir->tagconfig) {
DavXmlNode *tagsprop = dav_get_property_ns(res,
DAV_PROPS_NS,
"tags");
if(tagsprop) {
tags = parse_dav_xml_taglist(tagsprop);
remote_hash = create_tags_hash(tags);
}
}
DavBool store_tags =
FALSE;
DavBool tags_changed =
FALSE;
CxList *local_tags = sync_get_file_tags(dir, local, &tags_changed,
NULL);
if(tags_changed) {
switch(dir->tagconfig->conflict) {
case TAG_NO_CONFLICT: {
store_tags =
TRUE;
break;
}
case TAG_KEEP_LOCAL: {
store_tags =
FALSE;
break;
}
case TAG_KEEP_REMOTE: {
store_tags =
TRUE;
local->tags_updated =
FALSE;
break;
}
case TAG_MERGE: {
CxList *new_tags = merge_tags(local_tags, tags);
tags = new_tags;
store_tags =
TRUE;
local->tags_updated =
TRUE;
break;
}
}
}
else {
if(!compare_taglists(tags, local_tags)) {
store_tags =
TRUE;
}
}
if(!store_tags) {
nullfree(local->remote_tags_hash);
local->remote_tags_hash = remote_hash;
return 0;
}
int ret = sync_store_tags_local(dir, local, path, tags);
if(!ret) {
if(local->remote_tags_hash) {
free(local->remote_tags_hash);
}
local->remote_tags_hash = remote_hash;
}
return ret;
}
int sync_store_tags_local(SyncDirectory *dir, LocalResource *local,
const char *path, CxList *tags) {
int ret =
0;
if(dir->tagconfig->store ==
TAG_STORE_XATTR) {
CxBuffer *data =
NULL;
if(tags) {
switch(dir->tagconfig->local_format) {
default:
break;
case TAG_FORMAT_TEXT: {
data = create_text_taglist(tags);
break;
}
case TAG_FORMAT_CSV: {
data = create_csv_taglist(tags);
break;
}
case TAG_FORMAT_MACOS: {
data = create_macos_taglist(tags);
break;
}
}
if(data) {
char *data_hash = dav_create_hash(data->space, data->size);
int update =
1;
if(local) {
if(!local->tags_hash || strcmp(data_hash, local->tags_hash)) {
}
else {
update =
0;
}
}
if(update) {
ret = xattr_set(path, dir->tagconfig->xattr_name, data->space, data->pos);
if(local) {
if(local->tags_hash) {
free(local->tags_hash);
local->tags_hash =
NULL;
}
local->tags_hash = data_hash;
}
}
else {
free(data_hash);
}
cxBufferFree(data);
}
else {
ret = -
1;
}
}
else {
if(local) {
}
xattr_remove(path, dir->tagconfig->xattr_name);
}
}
if(!ret && local) {
local->tags_updated =
0;
}
return ret;
}
CxBuffer* sync_get_file_tag_data(SyncDirectory *dir, LocalResource *res) {
if(!dir->tagconfig) {
return NULL;
}
if(res->cached_tags) {
return res->cached_tags;
}
CxBuffer *buf =
NULL;
if(dir->tagconfig->store ==
TAG_STORE_XATTR) {
ssize_t tag_length =
0;
char *local_path = create_local_path(dir, local_resource_path(res));
char* tag_data = xattr_get(
local_path,
dir->tagconfig->xattr_name,
&tag_length);
free(local_path);
if(tag_length >
0) {
buf = cxBufferCreate(tag_data, (
size_t)tag_length, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS);
buf->size = (
size_t)tag_length;
}
}
res->cached_tags = buf;
return buf;
}
CxList* sync_get_file_tags(SyncDirectory *dir, LocalResource *res, DavBool *changed,
char **newhash) {
if(changed) *changed =
FALSE;
CxList *tags =
NULL;
if(!res) {
return NULL;
}
if(!dir->tagconfig) {
return NULL;
}
if(changed && res->tags_updated) {
*changed =
TRUE;
}
if(dir->tagconfig->store ==
TAG_STORE_XATTR) {
CxBuffer *tag_buf = res->cached_tags ?
res->cached_tags :
sync_get_file_tag_data(dir, res);
if(tag_buf) {
char *new_hash = dav_create_hash(tag_buf->space, tag_buf->size);
if(res->tags_hash) {
if(changed && strcmp(res->tags_hash, new_hash)) {
*changed =
TRUE;
}
free(res->tags_hash);
res->tags_hash =
NULL;
}
else {
if(changed) *changed =
TRUE;
}
if(newhash) {
*newhash = new_hash;
}
else {
free(new_hash);
}
switch(dir->tagconfig->local_format) {
default:
break;
case TAG_FORMAT_TEXT: {
tags = parse_text_taglist(tag_buf->space, tag_buf->size);
break;
}
case TAG_FORMAT_CSV: {
tags = parse_csv_taglist(tag_buf->space, tag_buf->size);
break;
}
case TAG_FORMAT_MACOS: {
tags = parse_macos_taglist(tag_buf->space, tag_buf->size);
break;
}
}
res->cached_tags = tag_buf;
}
else if(res->tags_hash) {
if(changed) *changed =
TRUE;
}
}
return tags;
}
static int file_seek(
FILE *f,
curl_off_t offset,
int origin) {
int ret = fseek(f, offset, origin);
return ret ==
0 ?
CURL_SEEKFUNC_OK :
CURL_SEEKFUNC_CANTSEEK;
}
size_t myread(
void *ptr,
size_t size,
size_t nmemb,
FILE *f) {
size_t ret = fread(ptr, size, nmemb, f);
return ret;
}
int gen_random_name(
char *buf,
size_t len) {
unsigned char name_prefix[
8];
memset(name_prefix,
0,
8);
dav_rand_bytes(name_prefix,
8);
char *pre = util_hexstr(name_prefix,
8);
int64_t ts = (
int64_t)time(
NULL);
int w = snprintf(buf, len,
"%""-%s"PRId64, ts, pre);
free(pre);
return w >= len;
}
#define VBEGIN_ERROR_MKCOL 1
#define VBEGIN_ERROR_MOVE 2
#define VBEGIN_ERROR_PROPPATCH 3
#define VBEGIN_ERROR_CHECKOUT 4
int versioning_begin(SyncDirectory *dir, DavResource *res,
int *exists,
int *versionized) {
int ret =
0;
*versionized =
0;
if(dir->versioning->type ==
VERSIONING_SIMPLE && res->exists) {
DavResource *history_collection = dav_resource_new(
res->session,
dir->versioning->collection);
char *history_href =
NULL;
char *vcol_path = dav_get_string_property_ns(res,
DAV_NS,
VERSION_PATH_PROPERTY);
if(!vcol_path) {
DavResource *history_res =
NULL;
while(!history_res) {
char history_res_name[
128];
gen_random_name(history_res_name,
128);
history_res = dav_resource_new_child(
res->session,
history_collection,
history_res_name);
if(dav_exists(history_res)) {
dav_resource_free(history_res);
history_res =
NULL;
}
}
history_res->iscollection =
TRUE;
if(dav_create(history_res)) {
dav_resource_free(history_res);
dav_resource_free(history_collection);
return VBEGIN_ERROR_MKCOL;
}
history_href = strdup(history_res->href);
dav_resource_free(history_res);
}
else {
history_href = vcol_path;
}
DavResource *version_res =
NULL;
while(!version_res) {
char version_name[
128];
gen_random_name(version_name,
128);
char *href = util_concat_path(history_href, version_name);
version_res = dav_resource_new_href(res->session, href);
free(href);
char *dest = util_get_url(res->session, version_res->href);
int err = dav_moveto(res, dest,
FALSE);
free(dest);
if(err) {
dav_resource_free(version_res);
version_res =
NULL;
if(res->session->error !=
DAV_PRECONDITION_FAILED) {
ret =
VBEGIN_ERROR_MOVE;
break;
}
}
else {
*exists =
0;
}
}
if(!ret) {
*versionized =
1;
dav_set_string_property_ns(version_res,
DAV_NS,
"origin", res->href);
if(dav_store(version_res)) {
ret =
VBEGIN_ERROR_PROPPATCH;
}
dav_resource_free(version_res);
dav_set_string_property_ns(
res,
DAV_NS,
VERSION_PATH_PROPERTY,
history_href);
}
if(vcol_path != history_href) {
free(history_href);
}
dav_resource_free(history_collection);
}
else if(dir->versioning->type ==
VERSIONING_DELTAV && res->exists){
if(dav_checkout(res)) {
ret =
VBEGIN_ERROR_CHECKOUT;
}
else {
*versionized =
1;
}
}
return ret;
}
int versioning_init(SyncDirectory *dir, LocalResource *local, DavResource *res) {
if(local->versioncontrol) {
return 0;
}
int ret =
0;
if(dir->versioning->type ==
VERSIONING_DELTAV) {
if(dav_versioncontrol(res)) {
ret =
1;
}
else {
local->versioncontrol =
1;
}
}
return ret;
}
int versioning_end(SyncDirectory *dir, DavResource *res) {
if(dir->versioning->type ==
VERSIONING_DELTAV) {
return dav_checkin(res);
}
else {
return 0;
}
}
int versioning_delete_begin(SyncDirectory *dir, DavResource *res,
int *exists,
int *versionized) {
*versionized =
0;
if(dir->versioning->type ==
VERSIONING_SIMPLE) {
versioning_begin(dir, res, exists, versionized);
}
else {
*exists =
1;
}
return 0;
}
int versioning_delete_end(SyncDirectory *dir, DavResource *res) {
return 0;
}
static void update_metadata_hashes(LocalResource *local, MetadataHashes hashes) {
if(hashes.update_tags) {
if(local->tags_hash) {
free(local->tags_hash);
local->tags_hash =
NULL;
}
local->tags_hash = hashes.tags;
}
if(hashes.update_tags_remote) {
if(local->remote_tags_hash) {
free(local->remote_tags_hash);
}
local->remote_tags_hash = hashes.tags_remote;
}
if(hashes.update_xattr) {
if(local->xattr_hash) {
free(local->xattr_hash);
}
local->xattr_hash = hashes.xattr;
}
}
#define LOG10 log10
static CxList* upload_parts(
LocalResource *local,
DavResource *res,
FILE *in,
uint64_t filesize,
size_t blocksize,
uint64_t *blockcount,
int *err)
{
if(res->exists) {
if(!res->iscollection) {
if(dav_delete(res)) {
log_resource_error(res->session, res->path);
*err =
1;
return NULL;
}
res->exists =
0;
return upload_parts(local, res, in, filesize, blocksize, blockcount, err);
}
}
else {
res->iscollection =
1;
if(dav_create(res)) {
log_resource_error(res->session, res->path);
*err =
1;
return NULL;
}
}
res->exists =
1;
if(!res->href) {
log_error(
"href is NULL\n");
*err =
1;
return NULL;
}
char *buffer = malloc(blocksize);
if(!buffer) {
fprintf(stderr,
"Out of memory\n");
*err =
1;
return NULL;
}
int nblocks = filesize / blocksize;
int digits =
LOG10((
double)nblocks) +
1;
if(digits >
127) {
log_error(
"Too many parts\n");
*err =
1;
free(buffer);
return NULL;
}
CxMap *updated_parts_map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, (nblocks/
2)+
64);
cxDefineDestructor(updated_parts_map, filepart_free);
int blockindex =
0;
int uploaded_parts =
0;
size_t r;
uint32_t session_flags = res->session->flags;
res->session->flags ^=
DAV_SESSION_ENCRYPT_NAME;
DAV_SHA_CTX *sha = dav_hash_init();
while((r = fread(buffer,
1, blocksize, in)) >
0) {
dav_hash_update(sha, buffer, r);
int upload_block =
0;
char *block_hash = dav_create_hash(buffer, r);
if(blockindex >= local->numparts) {
upload_block =
1;
}
else {
FilePart part = local->parts[blockindex];
if(!strcmp(part.hash, block_hash)) {
free(block_hash);
block_hash =
NULL;
}
else {
upload_block =
1;
}
}
if(upload_block) {
char name[
128];
snprintf(name,
128,
"%0*d", digits, blockindex);
char *part_href = util_concat_path(res->href, name);
DavResource *part = dav_resource_new_href(res->session, part_href);
free(part_href);
dav_set_content_data(part, buffer, r);
if(dav_store(part)) {
*err =
1;
log_resource_error(res->session, part->path);
}
else {
FilePart *f = calloc(
1,
sizeof(FilePart));
f->block = blockindex;
f->hash = block_hash;
cxMapPut(updated_parts_map, cx_hash_key_str(name), f);
}
dav_resource_free(part);
uploaded_parts++;
}
if(*err) {
break;
}
blockindex++;
}
*blockcount = blockindex;
res->session->flags = session_flags;
free(buffer);
if(*err) {
cxMapDestroy(updated_parts_map);
return NULL;
}
unsigned char content_hash[
DAV_SHA256_DIGEST_LENGTH];
dav_hash_final(sha, content_hash);
sync_set_content_hash(res, content_hash);
local->hash = util_hexstr(content_hash,
DAV_SHA256_DIGEST_LENGTH);
CxList *updated_parts = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
DavResource *parts = dav_query(res->session,
"select D:getetag from %s order by name", res->path);
if(!parts) {
log_resource_error(res->session, parts->path);
*err =
1;
cxMapDestroy(updated_parts_map);
return NULL;
}
DavResource *part = parts->children;
while(part) {
FilePart *fp = cxMapRemoveAndGet(updated_parts_map, cx_hash_key_str(part->name));
if(fp) {
char *etag = dav_get_string_property(part,
"D:getetag");
if(etag) {
if(strlen(etag) >
2 && etag[
0] ==
'W' && etag[
1] ==
'/') {
etag = etag +
2;
}
fp->etag = strdup(etag);
cxListAdd(updated_parts, fp);
}
}
else {
uint64_t name_partnum =
0;
char *res_name = part->name;
while(res_name[
0] ==
'0' && res_name[
1] !=
'\0') {
res_name++;
}
DavBool delete_part =
0;
if(strlen(part->name) != digits) {
delete_part =
1;
}
else if(util_strtouint(res_name, &name_partnum)) {
if(name_partnum >= blockindex) {
delete_part =
1;
}
}
if(delete_part) {
if(dav_delete(part)) {
log_resource_error(part->session, part->path);
}
}
}
part = part->next;
}
dav_resource_free_all(parts);
cxMapDestroy(updated_parts_map);
*err =
0;
return updated_parts;
}
void update_parts(LocalResource *local, CxList *updates,
uint64_t numparts) {
size_t old_num = local->numparts;
if(old_num > numparts) {
for(
size_t i=numparts;i<old_num;i++) {
FilePart p = local->parts[i];
if(p.etag) {
free(p.etag);
}
if(p.hash) {
free(p.hash);
}
}
}
if(numparts != local->numparts) {
local->parts = realloc(local->parts, numparts *
sizeof(FilePart));
local->numparts = numparts;
}
if(!updates) {
return;
}
CxIterator i = cxListIterator(updates);
cx_foreach(FilePart *, p, i) {
if(p->block >= numparts) {
continue;
}
FilePart *old = &local->parts[p->block];
if(p->block < old_num) {
if(old->hash) {
free(old->hash);
old->hash =
NULL;
}
if(old->etag) {
free(old->etag);
old->etag =
NULL;
}
}
old->block = p->block;
old->hash = p->hash;
old->etag = p->etag;
free(p);
}
}
int sync_put_resource(
SyncDirectory *dir,
DavResource *res,
LocalResource *local,
int *counter)
{
char *local_path = create_local_path(dir, local_resource_path(local));
SYS_STAT s;
if(sys_stat(local_path, &s)) {
log_error(
"Cannot stat file: %s: %s\n", local_path, strerror(errno));
free(local_path);
return -
1;
}
DavBool islink = local->link_target ?
1 :
0;
if(!local->link_target && local->link_updated) {
dav_remove_property_ns(res,
DAV_PROPS_NS,
"link");
}
size_t split_blocksize = resource_get_blocksize(dir, local, res, s.st_size);
FILE *in = sys_fopen(local_path,
"rb");
if(!in) {
log_error(
"Cannot open file %s: %s\n", local_path, strerror(errno));
free(local_path);
return -
1;
}
DavBool issplit = split_blocksize ==
0 ?
FALSE :
TRUE;
int split_err =
0;
CxList *parts =
NULL;
uint64_t blockcount =
0;
if(islink) {
dav_set_string_property_ns(res,
DAV_PROPS_NS,
"link", local->link_target);
}
else if(issplit) {
char blocksize_str[
32];
snprintf(blocksize_str,
32,
"%zu", split_blocksize);
dav_set_string_property_ns(res,
DAV_NS,
"split", blocksize_str);
parts = upload_parts(
local,
res,
in,
s.st_size,
split_blocksize,
&blockcount,
&split_err);
}
else {
dav_set_content(res, in, (dav_read_func)myread, (dav_seek_func)file_seek);
dav_set_content_length(res, s.st_size);
}
if(split_err) {
free(local_path);
return -
1;
}
MetadataHashes hashes;
hashes = sync_set_metadata_properties(dir, res->session, res, local,
FALSE);
int exists = res->exists;
int vend_required =
0;
if(dir->versioning && dir->versioning->always && !issplit) {
if(exists && versioning_init(dir, local, res)) {
log_error(
"Cannot activate versioncontrol for resource: %s\n", res->href);
free(local_path);
return -
1;
}
else {
int err = versioning_begin(dir, res, &exists, &vend_required);
if(err) {
log_error(
"Cannot store version for resource: %s\n", res->href);
free(local_path);
return -
1;
}
}
}
int ret = -
2;
dir->max_retry =
2;
for(
int i=
0;i<=dir->max_retry;i++) {
if(!exists && dav_create(res)) {
continue;
}
exists =
1;
if(dav_store(res)) {
continue;
}
ret =
0;
break;
}
if(vend_required) {
if(versioning_end(dir, res)) {
log_error(
"Cannot checkin resource\n");
ret =
1;
}
}
if(ret ==
0) {
(*counter)++;
local->tags_updated =
0;
update_metadata_hashes(local, hashes);
update_parts(local, parts, blockcount);
DavResource *up_res = dav_get(res->session, res->path,
"D:getetag,idav:status");
if(up_res) {
if(up_res->contentlength < s.st_size && !issplit && !islink) {
log_error(
"Incomplete Upload: %s\n", local_path);
ret = -
1;
sync_set_status(res,
"broken");
}
else {
char *etag = dav_get_string_property(up_res,
"D:getetag");
local_resource_set_etag(local, etag);
if(!issplit &&
SYNC_STORE_HASH(dir)) {
if(local->hash) {
free(local->hash);
}
local->hash = util_file_hash(local_path);
}
if(dav_get_string_property(up_res,
"idav:status")) {
sync_remove_status(up_res);
}
dav_resource_free(up_res);
}
}
}
else {
ret = -
1;
sync_set_status(res,
"broken");
}
fclose(in);
free(local_path);
return ret;
}
int sync_mkdir(SyncDirectory *dir, DavResource *res, LocalResource *local) {
res->iscollection =
1;
int ret = -
1;
for(
int i=
0;i<=dir->max_retry;i++) {
if(dav_create(res)) {
continue;
}
ret =
0;
break;
}
return ret;
}
int sync_move_remote_resource(
SyncDirectory *dir,
SyncDatabase *db,
DavResource *origin,
LocalResource *local,
DavBool copy,
int *counter)
{
char *local_path = create_local_path(dir, local->path);
SYS_STAT s;
if(sys_stat(local_path, &s)) {
log_error(
"Cannot stat file: %s: %s\n", local_path, strerror(errno));
free(local_path);
return -
1;
}
free(local_path);
int result =
0;
if(copy) {
result = dav_copy_o(origin, local->path,
FALSE);
}
else {
result = dav_move_o(origin, local->path,
FALSE);
}
if(result !=
0) {
return result;
}
LocalResource *local_origin = local->origin;
if(!copy) {
cxMapRemove(db->resources, cx_hash_key_str(local_origin->path));
}
DavResource *up_res = dav_resource_new(origin->session, local->path);
if(!up_res) {
return 1;
}
sync_set_metadata_from_stat(local, &s);
MetadataHashes hashes;
hashes = sync_set_metadata_properties(dir, up_res->session, up_res, local,
TRUE);
if(dav_store(up_res)) {
log_error(
"Error: cannot store resource metadata\n");
}
DavPropName p;
p.ns =
"DAV:";
p.name =
"getetag";
if(!dav_load_prop(up_res, &p,
1)) {
(*counter)++;
char *etag = dav_get_string_property(up_res,
"D:getetag");
local_resource_set_etag(local, etag);
local->last_modified = s.st_mtime;
}
else {
result =
1;
}
dav_resource_free(up_res);
return result;
}
int sync_delete_remote_resource(
SyncDirectory *dir,
DavSession *sn,
LocalResource *local_res,
int *counter,
CxList *cols)
{
DavResource *res = dav_get(sn, local_res->path,
"D:getetag,idav:split");
if(!res) {
return sn->error ==
DAV_NOT_FOUND ?
0 :
1;
}
int ret =
0;
sn->error =
DAV_OK;
if(res->iscollection) {
DavXmlNode *split = dav_get_property_ns(res,
DAV_NS,
"split");
if(cols) {
cxListAdd(cols, local_res);
}
else if(split || !res->children) {
log_printf(
"delete: %s\n", res->path);
if(dav_delete(res)) {
ret =
1;
log_error(
"Cannot delete collection %s\n", res->path);
}
else {
(*counter)++;
}
}
}
else {
char *etag = dav_get_string_property(res,
"D:getetag");
if(etag) {
if(strlen(etag) >
2 && etag[
0] ==
'W' && etag[
1] ==
'/') {
etag = etag +
2;
}
}
if(!nullstrcmp(etag, local_res->etag)) {
log_printf(
"delete: %s\n", res->path);
int exists =
1;
int vend_required =
0;
if(dir->versioning && dir->versioning->always) {
if(versioning_delete_begin(dir, res, &exists, &vend_required)) {
log_error(
"Cannot save resource version before deletion\n");
ret =
1;
}
}
if(!ret && dav_delete(res) && exists) {
if(sn->error !=
DAV_NOT_FOUND) {
log_error(
"Cannot delete resource %s\n", res->path);
ret =
1;
}
}
else {
(*counter)++;
}
if(vend_required) {
versioning_delete_end(dir, res);
}
}
}
dav_resource_free(res);
return ret;
}
MetadataHashes sync_set_metadata_properties(
SyncDirectory *dir,
DavSession *sn,
DavResource *res,
LocalResource *local,
DavBool force)
{
if(force) {
local->tags_updated =
1;
local->finfo_updated =
1;
local->xattr_updated =
1;
}
MetadataHashes hashes = {
NULL,
NULL,
NULL,
0,
0,
0};
if(dir->tagconfig) {
DavBool changed =
0;
char *tags_hash =
NULL;
CxList *tags = sync_get_file_tags(dir, local, &changed, &tags_hash);
char *new_remote_hash = nullstrdup(tags_hash);
if(changed || local->tags_updated) {
DavBool store_tags =
TRUE;
DavPropName p;
p.ns =
DAV_PROPS_NS;
p.name =
"tags";
if(dav_load_prop(res, &p,
1) && sn->error !=
DAV_NOT_FOUND) {
log_resource_error(sn, res->path);
}
CxList *remote_tags =
NULL;
DavXmlNode *tagsprop = dav_get_property_ns(res,
DAV_PROPS_NS,
"tags");
if(tagsprop) {
remote_tags = parse_dav_xml_taglist(tagsprop);
}
char *remote_hash = create_tags_hash(remote_tags);
if(nullstrcmp(remote_hash, local->remote_tags_hash)) {
int conflict_resolution = force ?
TAG_NO_CONFLICT : dir->tagconfig->conflict;
switch(conflict_resolution) {
case TAG_NO_CONFLICT:
break;
case TAG_KEEP_LOCAL:
break;
case TAG_KEEP_REMOTE: {
store_tags =
FALSE;
local->tags_updated =
FALSE;
break;
}
case TAG_MERGE: {
CxList *new_tags = merge_tags(tags, remote_tags);
free_taglist(tags);
tags = new_tags;
nullfree(tags_hash);
nullfree(new_remote_hash);
tags_hash = create_tags_hash(tags);
new_remote_hash = nullstrdup(tags_hash);
break;
}
}
}
nullfree(remote_hash);
if(dir->tagconfig->local_format ==
TAG_FORMAT_CSV) {
add_tag_colors(tags, remote_tags);
}
if(store_tags) {
if(tags) {
DavXmlNode *tagprop = create_xml_taglist(tags);
dav_set_property_ns(res,
DAV_PROPS_NS,
"tags", tagprop);
}
else {
dav_remove_property_ns(res,
DAV_PROPS_NS,
"tags");
}
hashes.tags = tags_hash;
hashes.update_tags =
1;
hashes.tags_remote = new_remote_hash;
hashes.update_tags_remote =
1;
}
free_taglist(remote_tags);
}
else {
if(tags_hash) {
free(tags_hash);
}
}
free_taglist(tags);
}
if(local->finfo_updated) {
struct stat s;
s.st_mode = local->mode;
s.st_mtime = local->last_modified;
s.st_uid = local->uid;
s.st_gid = local->gid;
resource_set_finfo_s(&s, res, dir->metadata);
}
if(local->xattr_updated) {
if(local->xattr) {
resource_set_xattr(res, local->xattr);
hashes.xattr = local->xattr ? strdup(local->xattr->hash) :
NULL;
hashes.update_xattr =
1;
}
else {
dav_remove_property(res,
"idavprops:xattributes");
if(local->xattr_hash) {
free(local->xattr_hash);
local->xattr_hash =
NULL;
}
}
}
local->tags_updated =
0;
return hashes;
}
int sync_update_metadata(
SyncDirectory *dir,
DavSession *sn,
DavResource *res,
LocalResource *local)
{
MetadataHashes hashes = sync_set_metadata_properties(dir, sn, res, local,
FALSE);
int err =
0;
if(dav_store(res)) {
log_resource_error(sn, local->path);
err =
1;
}
else {
update_metadata_hashes(local, hashes);
local->tags_updated =
0;
}
return err;
}
void remove_deleted_conflicts(SyncDirectory *dir, SyncDatabase *db) {
char **dc = calloc(
sizeof(
void*), cxMapSize(db->conflict));
int numdc =
0;
CxIterator i = cxMapIteratorValues(db->conflict);
cx_foreach(LocalResource *, res, i) {
char *path = create_local_path(dir, res->path);
SYS_STAT s;
if(sys_stat(path, &s)) {
if(errno ==
ENOENT) {
dc[numdc] = res->path;
numdc++;
}
else {
log_error(
"Cannot stat file: %s: %s\n", path, strerror(errno));
}
}
free(path);
}
for(
int i=
0;i<numdc;i++) {
cxMapRemove(db->conflict, cx_hash_key_str(dc[i]));
}
free(dc);
}
static void resolve_skipped(SyncDatabase *db) {
CxIterator i = cxMapIteratorValues(db->resources);
int skipped =
0;
cx_foreach(LocalResource *, res, i) {
if(res->skipped) {
skipped++;
log_error(
"skipped from push: %s\n", res->path);
}
}
if(skipped >
0) {
log_error(
" To resolve conflict resources skipped by push run dav-sync pull first\n"
" before resolve-conflicts or delete-conflicts.\n\n");
}
}
int cmd_resolve_conflicts(CmdArgs *a) {
if(a->argc !=
1) {
log_error(
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
return -
1;
}
SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
if(!dir) {
log_error(
"Unknown sync dir: %s\n", a->argv[
0]);
return -
1;
}
if(scfg_check_dir(dir)) {
return -
1;
}
if(logfile_open(dir)) {
return -
1;
}
SyncDatabase *db = load_db(dir->database);
if(!db) {
log_error(
"Cannot load database file: %s\n", dir->database);
return -
1;
}
resolve_skipped(db);
int ret =
0;
size_t num_conflict = cxMapSize(db->conflict);
cxMapClear(db->conflict);
if(store_db(db, dir->database, dir->db_settings)) {
log_error(
"Cannot store sync db\n");
log_error(
"Abort\n");
ret = -
2;
}
destroy_db(db);
if(ret != -
2) {
char *str_conflict = num_conflict ==
1 ?
"conflict" :
"conflicts";
log_printf(
"Result: %zu %s resolved\n", num_conflict, str_conflict);
}
return ret;
}
int cmd_delete_conflicts(CmdArgs *a) {
if(a->argc !=
1) {
fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
return -
1;
}
SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
if(!dir) {
fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
return -
1;
}
if(scfg_check_dir(dir)) {
return -
1;
}
if(logfile_open(dir)) {
return -
1;
}
SyncDatabase *db = load_db(dir->database);
if(!db) {
log_error(
"Cannot load database file: %s\n", dir->database);
return -
1;
}
resolve_skipped(db);
int num_del =
0;
int num_err =
0;
int ret =
0;
CxIterator i = cxMapIteratorValues(db->conflict);
cx_foreach(LocalResource*, res, i) {
log_printf(
"delete: %s\n", res->path);
char *path = create_local_path(dir, res->path);
if(sys_unlink(path)) {
if(errno !=
ENOENT) {
log_error(
"unlink: %s", strerror(errno));
num_err++;
}
}
else {
num_del++;
}
free(path);
}
cxMapClear(db->conflict);
if(store_db(db, dir->database, dir->db_settings)) {
log_error(
"Cannot store sync db\n");
log_error(
"Abort\n");
ret = -
1;
}
destroy_db(db);
if(ret ==
0) {
char *str_delete = num_del ==
1 ?
"file" :
"files";
char *str_error = num_err ==
1 ?
"error" :
"errors";
log_printf(
"Result: %d conflict %s deleted, %d %s\n",
num_del, str_delete,
num_err, str_error);
}
return ret;
}
int cmd_list_conflicts(CmdArgs *a) {
if(a->argc !=
1) {
fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
return -
1;
}
SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
if(!dir) {
fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
return -
1;
}
if(scfg_check_dir(dir)) {
return -
1;
}
SyncDatabase *db = load_db(dir->database);
if(!db) {
fprintf(stderr,
"Cannot load database file: %s\n", dir->database);
return -
1;
}
remove_deleted_conflicts(dir, db);
CxIterator i = cxMapIteratorValues(db->conflict);
CxList* conflict_sources = cxLinkedListCreate(
cxDefaultAllocator,
(cx_compare_func) strcmp,
CX_STORE_POINTERS
);
cx_foreach(LocalResource *, res, i) {
cxListAdd(conflict_sources, res->conflict_source);
}
cxListSort(conflict_sources);
i = cxListIterator(conflict_sources);
char *prev =
"";
cx_foreach(
char *, path, i) {
if(!strcmp(path, prev)) {
continue;
}
log_printf(
"%s\n", path);
prev = path;
}
destroy_db(db);
return 0;
}
static char* size_str(
uint64_t size) {
char *str = malloc(
16);
if(size < 0x400) {
snprintf(str,
16,
"%" PRIu64
" bytes", size);
}
else if(size < 0x100000) {
float s = (
float)size/0x400;
int diff = (s*
100 - (
int)s*
100);
if(diff >
90) {
diff =
0;
s +=
0.10f;
}
if(size < 0x2800 && diff !=
0) {
snprintf(str,
16,
"%.1f KiB", s);
}
else {
snprintf(str,
16,
"%.0f KiB", s);
}
}
else if(size < 0x40000000) {
float s = (
float)size/0x100000;
int diff = (s*
100 - (
int)s*
100);
if(diff >
90) {
diff =
0;
s +=
0.10f;
}
if(size < 0xa00000 && diff !=
0) {
snprintf(str,
16,
"%.1f MiB", s);
}
else {
size /= 0x100000;
snprintf(str,
16,
"%.0f MiB", s);
}
}
else if(size < 0x1000000000ULL) {
float s = (
float)size/0x40000000;
int diff = (s*
100 - (
int)s*
100);
if(diff >
90) {
diff =
0;
s +=
0.10f;
}
if(size < 0x280000000 && diff !=
0) {
snprintf(str,
16,
"%.1f GiB", s);
}
else {
size /= 0x40000000;
snprintf(str,
16,
"%.0f GiB", s);
}
}
else {
size /=
1024;
float s = (
float)size/0x40000000;
int diff = (s*
100 - (
int)s*
100);
if(diff >
90) {
diff =
0;
s +=
0.10f;
}
if(size < 0x280000000 && diff !=
0) {
snprintf(str,
16,
"%.1f TiB", s);
}
else {
size /= 0x40000000;
snprintf(str,
16,
"%.0f TiB", s);
}
}
return str;
}
void print_resource_version(DavResource *res,
char *name) {
time_t now = res->lastmodified;
struct tm *date = gmtime(&now);
char str[
32];
putenv(
"LC_TIME=C");
size_t len = strftime(str,
32,
"%a, %d %b %Y %H:%M:%S GMT", date);
log_printf(
"name: %s\n", name);
log_printf(
"lastmodified: %s\n", str);
char *server = util_url_base(res->session->base_url);
char *url = util_concat_path(server, res->href);
log_printf(
"url: %s\n", url);
free(server);
free(url);
}
int cmd_list_versions(CmdArgs *a) {
if(a->argc !=
1) {
fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
return -
1;
}
SyncFile file;
int ret =
0;
char *path = a->argv[
0];
int err = sync_get_file(a, path, &file,
TRUE);
if(err) {
sync_print_get_file_err(path, err);
return 1;
}
SyncDirectory *dir = file.dir;
if(!dir->versioning) {
fprintf(stderr,
"No versioning configured for syncdir %s\n", dir->name);
}
DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
if(!repo) {
fprintf(stderr,
"Unknown repository %s\n", dir->repository);
return -
1;
}
SyncDatabase *db = load_db(dir->database);
if(!db) {
fprintf(stderr,
"Cannot load database file: %s\n", dir->database);
return -
1;
}
remove_deleted_conflicts(dir, db);
DavSession *sn = create_session(a, ctx, repo, dir->collection);
cxMempoolRegister(sn->mp, db, (cx_destructor_func)destroy_db);
if (cmd_getoption(a,
"verbose")) {
curl_easy_setopt(sn->handle,
CURLOPT_VERBOSE,
1L);
curl_easy_setopt(sn->handle,
CURLOPT_STDERR, stderr);
}
DavResource *res = dav_resource_new(sn, file.path);
if(dir->versioning->type ==
VERSIONING_SIMPLE) {
do {
DavPropName p;
p.ns =
DAV_NS;
p.name =
VERSION_PATH_PROPERTY;
if(dav_load_prop(res, &p,
1)) {
print_resource_error(sn, file.path);
ret =
1;
break;
}
char *vcol_href = dav_get_string_property_ns(res,
DAV_NS,
VERSION_PATH_PROPERTY);
if(!vcol_href) {
ret =
1;
break;
}
DavResource *vcol = dav_resource_new_href(sn, vcol_href);
if(!vcol) {
ret =
1;
break;
}
if(dav_load_prop(vcol,
NULL,
0)) {
print_resource_error(sn, vcol->path);
ret =
1;
break;
}
DavResource *child = vcol->children;
CxList *children = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)strcmp,
CX_STORE_POINTERS);
while(child) {
cxListAdd(children, child);
child = child->next;
}
cxListSort(children);
DavBool first =
1;
CxIterator i = cxListIterator(children);
cx_foreach(DavResource *, c, i) {
if(!first) {
putchar(
'\n');
}
print_resource_version(c, c->name);
first =
0;
}
cxListDestroy(children);
}
while(
0);
}
else if(dir->versioning->type ==
VERSIONING_DELTAV) {
DavResource *versions = dav_versiontree(res,
NULL);
DavResource *v = versions;
DavBool first =
1;
while(v) {
if(!first) {
putchar(
'\n');
}
char *vname = dav_get_string_property(v,
"D:version-name");
print_resource_version(v, vname);
first =
0;
v = v->next;
}
}
free(file.path);
dav_session_destroy(sn);
return ret;
}
int cmd_trash_info(CmdArgs *a) {
if(a->argc !=
1) {
fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
return -
1;
}
SyncDirectory *syncdir = scfg_get_dir(a->argv[
0]);
if(!syncdir) {
fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
return -
1;
}
if(scfg_check_dir(syncdir)) {
return -
1;
}
if(!syncdir->trash) {
log_printf(
"trash not configured for %s\n", syncdir->name);
return 0;
}
SYS_DIR dir = sys_opendir(syncdir->trash);
if(!dir) {
fprintf(stderr,
"cannot open trash directory: %s\n", syncdir->trash);
perror(
"opendir");
return -
1;
}
uint64_t trashsize =
0;
int count =
0;
SysDirEnt *ent;
while((ent = sys_readdir(dir)) !=
NULL) {
if(!strcmp(ent->name,
".") || !strcmp(ent->name,
"..")) {
continue;
}
char *path = util_concat_path(syncdir->trash, ent->name);
SYS_STAT s;
if(sys_stat(path, &s)) {
perror(
"stat");
}
else {
trashsize += s.st_size;
}
count++;
free(path);
}
sys_closedir(dir);
log_printf(
"path: %s\n", syncdir->trash);
log_printf(
"%d %s\n", count, count ==
1 ?
"file" :
"files");
char *sizestr = size_str(trashsize);
log_printf(
"%s\n", sizestr);
free(sizestr);
return 0;
}
int cmd_empty_trash(CmdArgs *a) {
if(a->argc !=
1) {
fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
return -
1;
}
SyncDirectory *syncdir = scfg_get_dir(a->argv[
0]);
if(!syncdir) {
fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
return -
1;
}
if(logfile_open(syncdir)) {
return -
1;
}
if(!syncdir->trash) {
log_error(
"trash not configured for %s\n", syncdir->name);
return -
1;
}
SYS_DIR dir = sys_opendir(syncdir->trash);
if(!dir) {
log_error(
"cannot open trash directory: %s\n", syncdir->trash);
log_error(
"opendir: %s\n", strerror(errno));
return -
1;
}
SysDirEnt *ent;
while((ent = sys_readdir(dir)) !=
NULL) {
if(!strcmp(ent->name,
".") || !strcmp(ent->name,
"..")) {
continue;
}
char *path = util_concat_path(syncdir->trash, ent->name);
log_printf(
"delete: %s\n", path);
SYS_STAT s;
if(sys_stat(path, &s)) {
log_error(
"stat: %s\n", strerror(errno));
free(path);
continue;
}
if(
S_ISDIR(s.st_mode)) {
if(rmdir(path)) {
log_error(
"rmdir: %s\n", strerror(errno));
}
}
else {
if(sys_unlink(path)) {
log_error(
"unlink: %s\n", strerror(errno));
}
}
free(path);
}
sys_closedir(dir);
return 0;
}
#define CMD_TAG_ADD 0
#define CMD_TAG_REMOVE 1
#define CMD_TAG_SET 2
#define CMD_TAG_LIST 3
int cmd_add_tag(CmdArgs *args) {
if(args->argc !=
2) {
fprintf(stderr,
"Too %s arguments\n", args->argc <=
1 ?
"few" :
"many");
return -
1;
}
return cmd_tagop(args,
CMD_TAG_ADD);
}
int cmd_remove_tag(CmdArgs *args) {
if(args->argc !=
2) {
fprintf(stderr,
"Too %s arguments\n", args->argc <=
1 ?
"few" :
"many");
return -
1;
}
return cmd_tagop(args,
CMD_TAG_REMOVE);
}
int cmd_set_tags(CmdArgs *args) {
if(args->argc <
1 || args->argc >
2) {
fprintf(stderr,
"Too %s arguments\n", args->argc <
1 ?
"few" :
"many");
return -
1;
}
return cmd_tagop(args,
CMD_TAG_SET);
}
int cmd_list_tags(CmdArgs *args) {
if(args->argc !=
1) {
fprintf(stderr,
"Too %s arguments\n", args->argc <=
1 ?
"few" :
"many");
return -
1;
}
return cmd_tagop(args,
CMD_TAG_LIST);
}
int cmd_tagop(CmdArgs *args,
int cmd) {
SyncFile file;
int ret =
0;
char *path = args->argv[
0];
int err = sync_get_file(args, path, &file,
TRUE);
if(err) {
sync_print_get_file_err(path, err);
return -
1;
}
if(!file.dir->tagconfig) {
fprintf(stderr,
"Tags are not supported for this sync directory\n");
return -
1;
}
SyncDatabase *db = load_db(file.dir->database);
if(!db) {
fprintf(stderr,
"Cannot load sync directory database\n");
return -
1;
}
LocalResource *newres =
NULL;
LocalResource *localres = cxMapGet(db->resources, cx_hash_key_str(file.path));
if(!localres) {
newres = calloc(
1,
sizeof(LocalResource));
newres->path = strdup(file.path);
localres = newres;
}
CxList *tags =
NULL;
DavBool store_tags =
FALSE;
if(cmd !=
CMD_TAG_SET) {
char *tag = args->argv[
1];
char *tagcolor =
NULL;
tags = sync_get_file_tags(file.dir, localres,
NULL,
NULL);
CxIterator i = cxListIterator(tags ? tags : cxEmptyList);
int x = -
1;
cx_foreach(DavTag *, t, i) {
if(cmd ==
CMD_TAG_LIST) {
log_printf(
"%s\n", t->name);
}
else if(!strcmp(t->name, tag)) {
x = i.index;
break;
}
}
if(cmd ==
CMD_TAG_ADD) {
if(x <
0) {
DavTag *newtag = malloc(
sizeof(DavTag));
newtag->name = tag;
newtag->color = tagcolor;
if(!tags) {
tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
}
cxListAdd(tags, newtag);
store_tags =
TRUE;
}
}
else if(cmd ==
CMD_TAG_REMOVE) {
if(x >=
0) {
cxListRemove(tags, x);
}
store_tags =
TRUE;
}
}
else {
if(args->argc ==
2) {
char *tags_str = args->argv[
1];
tags = parse_csv_taglist(tags_str, strlen(tags_str));
store_tags =
TRUE;
}
else if (args->argc ==
1) {
store_tags =
TRUE;
}
else {
fprintf(stderr,
"Too many arguments\n");
ret = -
1;
}
}
if(store_tags) {
if(sync_store_tags_local(file.dir,
NULL, path, tags)) {
fprintf(stderr,
"Cannot store tags\n");
}
if(localres) {
localres->tags_updated =
TRUE;
if(!tags) {
if(localres->tags_hash) {
free(localres->tags_hash);
localres->tags_hash =
NULL;
}
localres->tags_hash =
NULL;
}
}
}
if(newres) {
local_resource_free(newres);
}
if(store_db(db, file.dir->database, file.dir->db_settings)) {
fprintf(stderr,
"Cannot store sync db\n");
ret = -
2;
}
free(file.path);
return ret;
}
int isfileindir(SyncDirectory *dir,
const char *path, SyncFile *f) {
char *fullpath;
if(path[
0] !=
'/') {
size_t wdlen =
256;
char *wd = malloc(wdlen);
while(!getcwd(wd, wdlen)) {
if(errno ==
ERANGE) {
wdlen *=
2;
char *newbuf = realloc(wd, wdlen);
if (newbuf) {
wd = newbuf;
}
else {
free(wd);
return 0;
}
}
else {
free(wd);
return 0;
}
}
fullpath = util_concat_path(wd, path);
free(wd);
}
else {
fullpath = strdup(path);
}
DavBool not_in_dir =
0;
cxstring fp = cx_str(fullpath);
cxstring dp = cx_str(dir->path);
if(fp.length == dp.length) {
if(cx_strcmp(fp, dp)) {
not_in_dir =
1;
}
}
else if(fp.length < dp.length) {
not_in_dir =
1;
}
else {
if(!cx_strprefix(fp, dp)) {
not_in_dir =
1;
}
else {
if(dp.ptr[dp.length-
1] ==
'/') {
dp.length--;
}
if(fp.ptr[dp.length] !=
'/') {
not_in_dir =
1;
}
}
}
if(not_in_dir) {
free(fullpath);
return 0;
}
f->dir = dir;
f->path = util_concat_path(
"/", fullpath + strlen(dir->path));
free(fullpath);
return 1;
}
int sync_get_file(CmdArgs *args,
const char *path, SyncFile *f, DavBool dostat) {
if(dostat) {
SYS_STAT s;
if(sys_stat(path, &s)) {
switch(errno) {
case EACCES:
return 2;
case ENOENT:
return 1;
default:
return 3;
}
}
}
char *sdir = cmd_getoption(args,
"syncdir");
if(sdir) {
SyncDirectory *dir = scfg_get_dir(sdir);
if(!dir) {
return 6;
}
if(!isfileindir(dir, path, f)) {
return 4;
}
}
else {
SyncDirectory *target =
NULL;
CxIterator i = scfg_directory_iterator();
cx_foreach(SyncDirectory *, dir, i) {
if(isfileindir(dir, path, f)) {
if(target) {
return 5;
}
else {
target = dir;
}
}
}
if(!target) {
return 4;
}
}
return 0;
}
void sync_print_get_file_err(
const char *path,
int err) {
switch(err) {
case 1: log_error(
"File %s: not found\n", path);
break;
case 2: log_error(
"File %s: permission denied\n", path);
break;
case 3: log_error(
"File %s: stat failed: %s\n", path, strerror(errno));
break;
case 4: log_error(
"File %s is not in any syncdir\n", path);
break;
case 5: log_error(
"File %s is in multiple syncdirs\n", path);
break;
case 6: log_error(
"Syncdir not found\n");
break;
}
}
int cmd_add_directory(CmdArgs *args) {
DavConfig *davconfig = get_config();
if(!davconfig->repositories) {
fprintf(stderr,
"No repositories available. Run ''dav add-repository'' first.\n");
fprintf(stderr,
"Abort\n");
return -
1;
}
log_printf(
"Each sync directory must have an unique name.\n");
char *name = assistant_getcfg(
"name");
if(!name) {
fprintf(stderr,
"Abort\n");
return -
1;
}
if(scfg_get_dir(name)) {
fprintf(stderr,
"Directory %s already exists.\nAbort\n", name);
return -
1;
}
log_printf(
"Enter local directory path.\n");
char *path = assistant_getcfg(
"path");
if(!path) {
fprintf(stderr,
"Abort\n");
return -
1;
}
log_printf(
"Specify webdav repository.\n");
int i =
0;
for (DavCfgRepository *r = davconfig->repositories; r !=
NULL; r = r->next) {
log_printf(
"%d) %s\n", i, r->name.value.ptr);
i++;
}
char *repository = assistant_getcfg(
"repository");
char *reponame =
NULL;
if(!repository) {
fprintf(stderr,
"Abort\n");
return -
1;
}
int64_t reponum =
0;
if(util_strtoint(repository, &reponum)) {
if(reponum <
0) {
fprintf(stderr,
"Wrong input.\nAbort\n");
return -
1;
}
DavCfgRepository *r = cx_linked_list_at(davconfig->repositories,
0,
offsetof(DavCfgRepository, next),
reponum);
if(r !=
NULL) {
reponame = r->name.value.ptr;
}
else {
fprintf(stderr,
"Wrong input.\nAbort\n");
return -
1;
}
}
else {
if(dav_config_get_repository(davconfig, cx_str(repository))) {
reponame = repository;
}
else {
fprintf(stderr,
"Repository %s doesn''t exist.\nAbort\n", repository);
return -
1;
}
}
log_printf(
"Enter collection relative to the repository base url.\n");
char *collection = assistant_getdefcfg(
"collection",
"/");
char *db = generate_db_name(name);
SyncDirectory dir;
memset(&dir,
0,
sizeof(SyncDirectory));
dir.name = name;
dir.path = path;
dir.repository = reponame;
dir.collection = collection;
dir.trash =
".trash";
dir.database = db;
int ret =
0;
if(add_directory(&dir)) {
fprintf(stderr,
"Cannot write sync.xml\n");
ret = -
1;
}
else {
log_printf(
"\nAdded directory: %s (%s)\n", name, path);
}
free(name);
free(path);
free(repository);
free(collection);
free(db);
return ret;
}
int cmd_list_dirs() {
CxIterator iter = scfg_directory_iterator();
cx_foreach(SyncDirectory *, dir, iter) {
log_printf(
"%s\n", dir->name);
}
return 0;
}
int cmd_check_repositories(CmdArgs *a) {
int ret =
EXIT_SUCCESS;
CxList *reponames = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
{
CxIterator iter = scfg_directory_iterator();
cx_foreach(SyncDirectory *, dir, iter) {
cxListAdd(reponames, dir->repository);
}
}
CxIterator iter = cxListIterator(reponames);
cx_foreach(
char *, reponame, iter) {
log_printf(
"Checking %s... ", reponame);
DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(reponame));
if (!repo) {
log_printf(
" not found in config.xml!\n");
ret =
EXIT_FAILURE;
}
else {
DavSession *sn = create_session(a, ctx, repo, repo->url.value.ptr);
if (sn) {
DavResource *res = dav_query(sn,
"select - from / with depth = 0");
if (res) {
log_printf(
"OK.\n");
dav_resource_free(res);
}
else {
log_printf(
"unavailable!\n");
ret =
EXIT_FAILURE;
}
dav_session_destroy(sn);
}
else {
log_printf(
"cannot create session!\n");
ret =
EXIT_FAILURE;
}
}
}
cxListDestroy(reponames);
return ret;
}
char* create_locktoken_file(
const char *syncdirname,
const char *locktoken) {
cxmutstr fname = cx_asprintf(
"locktoken-%s.txt", syncdirname);
char *path = config_file_path(fname.ptr);
free(fname.ptr);
FILE *file = sys_fopen(path,
"w");
if(file) {
log_error(
"%s\n", locktoken);
fclose(file);
return path;
}
else {
log_error(
"Cannot create locktoken file: %s", strerror(errno));
free(path);
return NULL;
}
}
char* sync_get_content_hash(DavResource *res) {
uint32_t flags = res->session->flags;
if((flags &
DAV_SESSION_ENCRYPT_CONTENT) ==
DAV_SESSION_ENCRYPT_CONTENT) {
char *enc_hash = dav_get_string_property_ns(res,
DAV_NS,
"crypto-hash");
char *keyname = dav_get_string_property_ns(res,
DAV_NS,
"crypto-key");
if(enc_hash && keyname) {
DavKey *key = dav_context_get_key(res->session->context, keyname);
if(!key) {
return NULL;
}
size_t len =
0;
char *dec_hash = aes_decrypt(enc_hash, &len, key);
if(!dec_hash) {
return NULL;
}
char *hex_hash = util_hexstr((
unsigned char*)dec_hash, len);
free(dec_hash);
return hex_hash;
}
}
else {
char *hash = dav_get_string_property_ns(res,
DAV_NS,
"content-hash");
if(hash) {
return strdup(hash);
}
}
return NULL;
}
void sync_set_content_hash(DavResource *res,
const unsigned char *hashdata) {
uint32_t flags = res->session->flags;
if((flags &
DAV_SESSION_ENCRYPT_CONTENT) ==
DAV_SESSION_ENCRYPT_CONTENT) {
if(res->session->key) {
char *enc_hash = aes_encrypt((
const char*)hashdata,
DAV_SHA256_DIGEST_LENGTH, res->session->key);
if(enc_hash) {
dav_set_string_property_ns(res,
DAV_NS,
"crypto-hash", enc_hash);
free(enc_hash);
}
}
}
else {
char *hex_hash = util_hexstr(hashdata,
DAV_SHA256_DIGEST_LENGTH);
dav_set_string_property_ns(res,
DAV_NS,
"content-hash", hex_hash);
free(hex_hash);
}
}