#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "db.h"
#include <cx/utils.h>
#include <libidav/utils.h>
#include <libxml/encoding.h>
#include <libxml/xmlwriter.h>
#define xstreq(a,b) xmlStrEqual(
BAD_CAST a,
BAD_CAST b)
#ifdef _WIN32
#define ENV_HOME getenv(
"USERPROFILE")
#else
#define ENV_HOME getenv(
"HOME")
#endif
SyncDatabase* load_db(
char *name) {
char *dav_dir = util_concat_path(
ENV_HOME,
".dav");
char *db_file = util_concat_path(dav_dir, name);
free(dav_dir);
SyncDatabase *db = malloc(
sizeof(SyncDatabase));
db->resources = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
2048);
db->conflict = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
cxDefineDestructor(db->resources, local_resource_free);
cxDefineDestructor(db->conflict, local_resource_free);
xmlTextReaderPtr reader = xmlReaderForFile(db_file,
NULL,
0);
if(!reader) {
xmlDoc *doc = doc = xmlNewDoc(
BAD_CAST "1.0");
xmlNode *root = xmlNewNode(
NULL,
BAD_CAST "directory");
xmlDocSetRootElement(doc, root);
if(xmlSaveFormatFileEnc(db_file, doc,
"UTF-8",
1) == -
1) {
destroy_db(db);
db =
NULL;
}
xmlFreeDoc(doc);
free(db_file);
return db;
}
free(db_file);
int error =
0;
while(xmlTextReaderRead(reader)) {
int type = xmlTextReaderNodeType(reader);
const xmlChar *xmlName = xmlTextReaderConstName(reader);
if(type ==
XML_READER_TYPE_ELEMENT) {
if(xstreq(xmlName,
"resource")) {
LocalResource *res = process_resource(reader);
if(res) {
cxMapPut(db->resources, cx_hash_key_str(res->path), res);
}
else {
error =
1;
break;
}
}
else if(xstreq(xmlName,
"conflict")) {
LocalResource *res = process_conflict(reader);
if(res) {
cxMapPut(db->conflict, cx_hash_key_str(res->path), res);
}
else {
error =
1;
break;
}
}
}
}
xmlFreeTextReader(reader);
if(error) {
destroy_db(db);
return NULL;
}
else {
return db;
}
}
void process_parts(xmlTextReaderPtr reader, LocalResource *res) {
CxList *parts = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
FilePart *current_part =
NULL;
size_t count =
0;
int field = -
1;
int err =
0;
while(xmlTextReaderRead(reader)) {
int type = xmlTextReaderNodeType(reader);
const xmlChar *name = xmlTextReaderConstName(reader);
int depth = xmlTextReaderDepth(reader);
if(type ==
XML_READER_TYPE_ELEMENT) {
if(depth ==
3 && xstreq(name,
"part")) {
current_part = calloc(
1,
sizeof(FilePart));
current_part->block = count;
cxListAdd(parts, current_part);
count++;
}
else if(depth ==
4) {
if(xstreq(name,
"hash")) {
field =
0;
}
else if(xstreq(name,
"etag")) {
field =
1;
}
}
}
else if(type ==
XML_READER_TYPE_END_ELEMENT) {
if(depth ==
2) {
break;
}
else if(depth ==
3) {
if(current_part) {
if(!current_part->hash || !current_part->etag) {
err =
1;
}
}
current_part =
NULL;
}
field = -
1;
}
else if(type ==
XML_READER_TYPE_TEXT && depth ==
5 && current_part) {
const char *text = (
const char*)xmlTextReaderConstValue(reader);
if(field ==
0) {
current_part->hash = strdup(text);
}
else if(field ==
1) {
current_part->etag = strdup(text);
}
}
}
if(!err) {
FilePart *file_parts = calloc(count,
sizeof(FilePart));
CxIterator iter = cxListIterator(parts);
cx_foreach(FilePart*, p, iter) {
file_parts[iter.index] = *p;
free(p);
}
res->parts = file_parts;
res->numparts = count;
}
cxListDestroy(parts);
}
LocalResource* process_resource(xmlTextReaderPtr reader) {
LocalResource *res = calloc(
1,
sizeof(LocalResource));
int field = -
1;
while(xmlTextReaderRead(reader)) {
int type = xmlTextReaderNodeType(reader);
const xmlChar *name = xmlTextReaderConstName(reader);
if(type ==
XML_READER_TYPE_ELEMENT) {
if(xstreq(name,
"path")) {
field =
0;
}
else if(xstreq(name,
"etag")) {
field =
1;
}
else if(xstreq(name,
"lastmodified")) {
field =
2;
}
else if(xstreq(name,
"size")) {
field =
3;
}
else if(xstreq(name,
"tags-hash")) {
field =
4;
}
else if(xstreq(name,
"mode")) {
field =
5;
}
else if(xstreq(name,
"uid")) {
field =
6;
}
else if(xstreq(name,
"gid")) {
field =
7;
}
else if(xstreq(name,
"xattr-hash")) {
field =
8;
}
else if(xstreq(name,
"remote-tags-hash")) {
field =
9;
}
else if(xstreq(name,
"blocksize")) {
field =
10;
}
else if(xstreq(name,
"hash")) {
field =
11;
}
else if(xstreq(name,
"link")) {
field =
12;
}
else if(xstreq(name,
"localpath")) {
field =
13;
}
else if(xstreq(name,
"versioncontrol")) {
field =
14;
}
else if(xstreq(name,
"skipped")) {
res->skipped =
TRUE;
}
else if(xstreq(name,
"tags-updated")) {
res->tags_updated =
TRUE;
}
else if(xstreq(name,
"parts")) {
process_parts(reader, res);
}
else if(xstreq(name,
"isdirectory")) {
res->isdirectory =
1;
}
}
else if(type ==
XML_READER_TYPE_TEXT) {
const xmlChar *value = xmlTextReaderConstValue(reader);
switch(field) {
case 0: {
res->path = strdup((
char*)value);
break;
}
case 1: {
res->etag = strdup((
char*)value);
break;
}
case 2: {
char *endptr = (
char*)value;
time_t t = strtoll((
char*)value, &endptr,
10);
if(endptr == (
char*)value) {
fprintf(
stderr,
"lastmodified does not contain a number: %s\n", value);
}
else {
res->last_modified = t;
}
break;
}
case 3: {
res->size =
0;
int64_t filelen =
0;
if(util_strtoint((
char*)value, &filelen)) {
if(filelen >
0) {
res->size = (
size_t)filelen;
}
}
break;
}
case 4: {
res->tags_hash = strdup((
char*)value);
break;
}
case 5: {
char *end;
errno =
0;
long int mode = strtol((
char*)value, &end,
8);
if(errno ==
0) {
res->mode = (
mode_t)mode;
}
break;
}
case 6: {
uint64_t uid =
0;
if(util_strtouint((
char*)value, &uid)) {
res->uid = (
uid_t)uid;
}
break;
}
case 7: {
uint64_t gid =
0;
if(util_strtouint((
char*)value, &gid)) {
res->gid = (
gid_t)gid;
}
break;
}
case 8: {
res->xattr_hash = strdup((
char*)value);
break;
}
case 9: {
res->remote_tags_hash = strdup((
char*)value);
break;
}
case 10: {
int64_t blsz =
0;
if(util_strtoint((
char*)value, &blsz)) {
if(blsz < -
1) {
blsz = -
1;
}
if(blsz >
0 && blsz <
16) {
blsz =
0;
}
res->blocksize = blsz;
}
break;
}
case 11: {
res->hash = strdup((
char*)value);
break;
}
case 12: {
res->link_target = strdup((
char*)value);
break;
}
case 13: {
res->local_path = strdup((
char*)value);
}
case 14: {
res->versioncontrol = util_getboolean((
char*)value);
}
}
}
else if(
XML_READER_TYPE_END_ELEMENT) {
if(xstreq(name,
"resource")) {
break;
}
else {
field = -
1;
}
}
}
if(!res->path) {
return NULL;
}
else {
return res;
}
}
LocalResource* process_conflict(xmlTextReaderPtr reader) {
LocalResource *res = calloc(
1,
sizeof(LocalResource));
int field =
0;
while(xmlTextReaderRead(reader)) {
int type = xmlTextReaderNodeType(reader);
const xmlChar *name = xmlTextReaderConstName(reader);
if(type ==
XML_READER_TYPE_ELEMENT) {
if(xstreq(name,
"path")) {
field =
1;
}
else if(xstreq(name,
"source")) {
field =
2;
}
}
else if(type ==
XML_READER_TYPE_TEXT) {
const xmlChar *value = xmlTextReaderConstValue(reader);
switch(field) {
case 1: {
res->path = strdup((
const char*)value);
break;
}
case 2: {
res->conflict_source = strdup((
const char*)value);
break;
}
}
}
else if(
XML_READER_TYPE_END_ELEMENT) {
if(xstreq(name,
"conflict")) {
break;
}
else {
field =
0;
}
}
}
if(!res->path) {
return NULL;
}
else {
return res;
}
}
int store_db(SyncDatabase *db,
char *name,
uint32_t settings) {
char *dav_dir = util_concat_path(
ENV_HOME,
".dav");
char *db_file = util_concat_path(dav_dir, name);
free(dav_dir);
xmlTextWriterPtr writer = xmlNewTextWriterFilename(db_file,
0);
if(!writer) {
fprintf(stderr,
"Cannot write db file: %s\n", db_file);
free(db_file);
return -
1;
}
free(db_file);
int r =
0;
r = xmlTextWriterStartDocument(writer,
NULL,
"UTF-8",
NULL);
if(r <
0) {
xmlFreeTextWriter(writer);
return -
1;
}
xmlTextWriterStartElement(writer,
BAD_CAST "directory");
CxIterator iter = cxMapIteratorValues(db->resources);
cx_foreach(LocalResource*, res, iter) {
xmlTextWriterStartElement(writer,
BAD_CAST "resource");
r = xmlTextWriterWriteElement(
writer,
BAD_CAST "path",
BAD_CAST res->path);
if(r <
0) {
fprintf(stderr,
"Cannot write path: %s\n", res->path);
xmlFreeTextWriter(writer);
return -
1;
}
if(res->isdirectory) {
r = xmlTextWriterStartElement(writer,
BAD_CAST "isdirectory");
r += xmlTextWriterEndElement(writer);
if(r <
0) {
fprintf(stderr,
"Cannot write isdirectory\n");
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->etag) {
r = xmlTextWriterWriteElement(
writer,
BAD_CAST "etag",
BAD_CAST res->etag);
if(r <
0) {
fprintf(stderr,
"Cannot write etag: %s\n", res->etag);
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->hash) {
r = xmlTextWriterWriteElement(
writer,
BAD_CAST "hash",
BAD_CAST res->hash);
if(r <
0) {
fprintf(stderr,
"Cannot write hash: %s\n", res->hash);
xmlFreeTextWriter(writer);
return -
1;
}
}
r = xmlTextWriterWriteFormatElement(
writer,
BAD_CAST "lastmodified",
"%" PRId64,
(
int64_t)res->last_modified);
if(r <
0) {
fprintf(stderr,
"Cannot write lastmodified\n");
xmlFreeTextWriter(writer);
return -
1;
}
if(res->blocksize !=
0) {
r = xmlTextWriterWriteFormatElement(
writer,
BAD_CAST "mode",
"%" PRId64,
res->blocksize);
if(r <
0) {
fprintf(stderr,
"Cannot write blocksize\n");
xmlFreeTextWriter(writer);
return -
1;
}
}
if((settings &
DB_STORE_MODE) ==
DB_STORE_MODE) {
r = xmlTextWriterWriteFormatElement(
writer,
BAD_CAST "mode",
"%o",
(
int)res->mode);
if(r <
0) {
fprintf(stderr,
"Cannot write mode\n");
xmlFreeTextWriter(writer);
return -
1;
}
}
if((settings &
DB_STORE_OWNER) ==
DB_STORE_OWNER) {
r = xmlTextWriterWriteFormatElement(
writer,
BAD_CAST "uid",
"%u",
(
unsigned int)res->uid);
if(r <
0) {
fprintf(stderr,
"Cannot write uid\n");
xmlFreeTextWriter(writer);
return -
1;
}
r = xmlTextWriterWriteFormatElement(
writer,
BAD_CAST "gid",
"%u",
(
unsigned int)res->gid);
if(r <
0) {
fprintf(stderr,
"Cannot write gid\n");
xmlFreeTextWriter(writer);
return -
1;
}
}
r = xmlTextWriterWriteFormatElement(
writer,
BAD_CAST "size",
"%" PRId64,
(
int64_t)res->size);
if(r <
0) {
fprintf(stderr,
"Cannot write size\n");
xmlFreeTextWriter(writer);
return -
1;
}
if(res->tags_hash) {
r = xmlTextWriterWriteElement(
writer,
BAD_CAST "tags-hash",
BAD_CAST res->tags_hash);
if(r <
0) {
fprintf(stderr,
"Cannot write tags-hash: %s\n", res->tags_hash);
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->remote_tags_hash) {
r = xmlTextWriterWriteElement(
writer,
BAD_CAST "remote-tags-hash",
BAD_CAST res->remote_tags_hash);
if(r <
0) {
fprintf(stderr,
"Cannot write remote-tags-hash: %s\n", res->remote_tags_hash);
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->link_target) {
r = xmlTextWriterWriteElement(
writer,
BAD_CAST "link",
BAD_CAST res->link_target);
if(r <
0) {
fprintf(stderr,
"Cannot write link: %s\n", res->link_target);
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->local_path) {
r = xmlTextWriterWriteElement(
writer,
BAD_CAST "localpath",
BAD_CAST res->local_path);
if(r <
0) {
fprintf(stderr,
"Cannot write localpath: %s\n", res->local_path);
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->xattr_hash) {
r = xmlTextWriterWriteElement(
writer,
BAD_CAST "xattr-hash",
BAD_CAST res->xattr_hash);
if(r <
0) {
fprintf(stderr,
"Cannot write xattr-hash: %s\n", res->xattr_hash);
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->skipped) {
r = xmlTextWriterStartElement(writer,
BAD_CAST "skipped");
r += xmlTextWriterEndElement(writer);
if(r <
0) {
fprintf(stderr,
"Cannot write skipped\n");
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->tags_updated) {
r = xmlTextWriterStartElement(writer,
BAD_CAST "tags-updated");
r += xmlTextWriterEndElement(writer);
if(r <
0) {
fprintf(stderr,
"Cannot write tags-updated\n");
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->numparts >
0) {
r = xmlTextWriterStartElement(writer,
BAD_CAST "parts");
if(r <
0) {
xmlFreeTextWriter(writer);
return -
1;
}
for(
size_t i=
0;i<res->numparts;i++) {
FilePart p = res->parts[i];
r = xmlTextWriterStartElement(writer,
BAD_CAST "part");
if(r <
0) {
xmlFreeTextWriter(writer);
return -
1;
}
if(p.hash) {
r = xmlTextWriterWriteElement(writer,
BAD_CAST "hash",
BAD_CAST p.hash);
if(r <
0) {
xmlFreeTextWriter(writer);
return -
1;
}
}
if(p.etag) {
r = xmlTextWriterWriteElement(writer,
BAD_CAST "etag",
BAD_CAST p.etag);
if(r <
0) {
xmlFreeTextWriter(writer);
return -
1;
}
}
r = xmlTextWriterEndElement(writer);
if(r <
0) {
xmlFreeTextWriter(writer);
return -
1;
}
}
r = xmlTextWriterEndElement(writer);
if(r <
0) {
xmlFreeTextWriter(writer);
return -
1;
}
}
if(res->versioncontrol) {
r = xmlTextWriterWriteElement(
writer,
BAD_CAST "versioncontrol",
BAD_CAST "true");
if(r <
0) {
fprintf(stderr,
"Cannot write versioncontrol\n");
xmlFreeTextWriter(writer);
return -
1;
}
}
xmlTextWriterEndElement(writer);
}
iter = cxMapIteratorValues(db->conflict);
cx_foreach(LocalResource*, res, iter) {
xmlTextWriterStartElement(writer,
BAD_CAST "conflict");
xmlTextWriterWriteElement(
writer,
BAD_CAST "path",
BAD_CAST res->path);
if(res->conflict_source) {
xmlTextWriterWriteElement(
writer,
BAD_CAST "source",
BAD_CAST res->conflict_source);
}
xmlTextWriterEndElement(writer);
}
xmlTextWriterEndElement(writer);
r = xmlTextWriterEndDocument(writer);
if(r <
0) {
xmlFreeTextWriter(writer);
return -
1;
}
xmlFreeTextWriter(writer);
return 0;
}
void destroy_db(SyncDatabase *db) {
cxMapDestroy(db->resources);
cxMapDestroy(db->conflict);
free(db);
}
void local_resource_free(LocalResource *res) {
if(!res) {
return;
}
if(res->name) {
free(res->name);
}
if(res->path) {
free(res->path);
}
if(res->etag) {
free(res->etag);
}
if(res->cached_tags) {
cxBufferFree(res->cached_tags);
}
if(res->tags_hash) {
free(res->tags_hash);
}
if(res->prev_hash) {
free(res->prev_hash);
}
free(res);
}
static char* nullstrdup(
const char *s) {
return s ? strdup(s) :
NULL;
}
void local_resource_copy_parts(LocalResource *from, LocalResource *to) {
if(from->parts) {
to->numparts = from->numparts;
to->parts = calloc(from->numparts,
sizeof(FilePart));
for(
int i=
0;i<to->numparts;i++) {
FilePart s = from->parts[i];
FilePart p;
p.block = s.block;
p.hash = nullstrdup(s.hash);
p.etag = nullstrdup(s.etag);
to->parts[i] = p;
}
}
}
LocalResource* local_resource_copy(LocalResource *src,
const char *new_path) {
LocalResource *newres = calloc(
1,
sizeof(LocalResource));
newres->path = strdup(new_path);
newres->etag = nullstrdup(src->etag);
newres->hash = nullstrdup(src->hash);
newres->last_modified = src->last_modified;
newres->mode = src->mode;
newres->uid = src->uid;
newres->gid = src->gid;
newres->size = src->size;
newres->isdirectory = src->isdirectory;
newres->skipped = src->skipped;
newres->versioncontrol = src->versioncontrol;
if(src->xattr) {
XAttributes *xattr = calloc(
1,
sizeof(XAttributes));
xattr->hash = nullstrdup(src->xattr->hash);
xattr->nattr = src->xattr->nattr;
xattr->names = calloc(xattr->nattr,
sizeof(
char*));
xattr->values = calloc(xattr->nattr,
sizeof(cxmutstr));
for(
int i=
0;i<xattr->nattr;i++) {
xattr->names[i] = strdup(src->xattr->names[i]);
xattr->values[i] = cx_strdup(cx_strcast(src->xattr->values[i]));
}
newres->xattr = xattr;
}
newres->tags_hash = nullstrdup(src->tags_hash);
newres->xattr_hash = nullstrdup(src->xattr_hash);
newres->remote_tags_hash = nullstrdup(src->remote_tags_hash);
local_resource_copy_parts(src, newres);
newres->blocksize = src->blocksize;
newres->tags_updated = src->tags_updated;
newres->finfo_updated = src->finfo_updated;
newres->xattr_updated = src->xattr_updated;
newres->metadata_updated = src->metadata_updated;
return newres;
}
void filepart_free(FilePart *part) {
if(part->etag) {
free(part->etag);
}
if(part->hash) {
free(part->hash);
}
free(part);
}
CxMap* create_hash_index(SyncDatabase *db) {
CxMap *hmap = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, cxMapSize(db->resources) +
64);
CxIterator i = cxMapIteratorValues(db->resources);
cx_foreach(LocalResource*, res, i) {
if(res->hash) {
cxMapPut(hmap, cx_hash_key_str(res->hash), res);
}
}
return hmap;
}