/*
 * libkarma/riocp.c
 *
 * Copyright (c) Frank Zschockelt <libkarma@freakysoft.de> 2004-2005
 *               Enrique Vidal 2004-2005
 *               Keith Bennett <keith@mcs.st-and.ac.uk> 2006
 *
 * You may distribute and modify this program under the terms of 
 * the GNU GPL, version 2 or later.
 *
 */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#ifdef LINUX
#include <ftw.h>
#else
#include "ftw.h"
#endif
#include <locale.h>
#include <langinfo.h>

#include <lkarma.h>
#include "pathedit.h"

#define CHECK(x) (x<0)?"failed":"ok"

#define defaultCodeset "ISO8859-15"

#define ssdpRetry 3                     /* number of retries allowed for SSDP */

#define UNK_NAME "-UnKnown-"

void usage(int complete);
void currentKarma(int i, int *k);
int treecopy(const char *file, const struct stat *sb, int flag);
uint32_t * searchStr(int exact, char *searchString, char *unkName);
double getUsedSize(int karma);

void deleteFids(uint32_t *vp);
int isodigit (char d);
char esc(char c);
char *expand(char *str);
void downloadFids(uint32_t *vp, int doDownload, char *unkName,
                  char* useTuneProps, char* useTaxiProps, char* usePlstProps,
                  char* edPathStr1, char* edPathStr2, char* edPathStr3,
                  int squeezeRep, int expandPlaylists, char *playListName);


void usage(int complete)
{
    printf("usage: riocp [-h] [-a {path|hostname|ip}] [-p passWord] "
                        "[-s searchString ]\n"
           "             [-u props] [-x props] [-l props] "
                        "[-c charSet] [-e set1 set2 set3]\n"
           "             [-D dummyName] [-U fdb.gz] [-b] [-d] [-f] [-F]\n"
           "             [-H] [-L] [-q] [-r] [-R] [-w] [files_to_upload ...]\n"
           "\n");
    if(!complete) return;
    printf("  \"-s\" AND/OR-search karma DB according to a \"searchString\";\n"
           "       \"searchString\" syntax: <property>=<searchStr>[{,|+}...], "
           "where: \n"
           "       - <searchStr> is a substring of the <property> value\n"
           "         (see ~/.openrio/*/fileinfo for possible <property> names)"
           "\n"
           "       - \',\' and \'+\' mean AND and OR, respectively\n"
           "       - each AND/OR operator is applied to the previous search "
           "results\n");
    printf("  \"-S\" Same as \"-s\" but <searchStr> must be an exact match\n"
           "\n");
    printf("  \"-u\" use properties to build download path for <type>=\'tune\'."
           " Default=\'%%f.%%c\'\n"
           "  \"-x\" like -u for <type>=\'taxi\',.    Default=\"-u props\"\n"
           "  \"-l\" like -u for <type>=\'playlist\'. Default=\"-x props\"\n");
    printf("       \"props\" are formatting strings (as in \"printf\"), where\n"
           "       \'%%T\', \'%%g\', \'%%a\', \'%%s\', \'%%f\', \'%%t\', "
           "\'%%n\', \'%%c\' are, resp., values of\n"
           "       <type>, <genre>, <artist>, <source>, <fid>, <title>, "
           "<tracknr>, <codec>\n"
           "       If -L enabled, \'%%l\' is the <playListName> in -l props\n"
           "\n");
    printf("  \"-e\" edit the download pathname using \"set1\" and "
           "\"set2\" as in unix \'tr\'\n"
           "       - if \"set3\" is empty (\"\"): just translate as in \'tr\';"
           "\n"
           "       - otherwise, further delete all chars given in \"set3\"\n"
           "  \"-q\" squeeze repeated chars (given in \"set2\"), after the "
           "optional delete\n\n");
    printf("  \"-D\" Use dummyName for missing or empty props. (def. -UnKnown-)"
           "\n");
    printf("  \"-d\" download the files searched for with \"-s\"\n"
           "  \"-r\" remove these files (after the optional download)\n"
           "  \"-R\" same as -r, but do not ask for confirmation\n");
    printf("  \"-b\" get database by scanning *1 files, instead of smalldb\n");
    printf("  \"-w\" don't rewrite the smalldb database file\n");
    printf("  \"-f\" force the files to be written even if they are dupes\n");
    printf("  \"-F\" Use filenames in ~/.openrio/.../" FDB_FILENAME
           " for download\n");
    printf("  \"-H\" Download files listed in ~/.openrio/.../" FDB_FILENAME
           "\n       that match the current hostname\n");
    printf("  \"-L\" For playlists, print/download the listed tunes, "
           "rather than the lists\n");
    printf("  \"-U\" Upload files listed in the given fdb.gz file\n");
}

void currentKarma(int i, int *k) 
{
    static int karma;
    if (i)
        karma=*k;
    else
        *k=karma;
}

static void fdb_download(int checkhost)
{
    int karma, i, len;
    uint32_t * fdb;
    char * filename, * p;
    char ourhostname[255], hostname[255], path[PATH_MAX];

    currentKarma(0, &karma);
    fdb=lk_properties_andOrSearch(EXACT|ORS, NULL, "path", "");
    if (!fdb)
        return;

    if (checkhost) {
        if (gethostname(ourhostname, 255) != 0) ourhostname[0] = '\0';
        len = strlen(ourhostname) + 1;
    }

    for (i=0; fdb[i]; i++) {
        filename=lk_properties_get_property(fdb[i], "path");
        if (!filename)
            continue;
        memcpy(hostname, filename, 255);
        for (p=hostname; *p != '\0'; p++)
            if (*p == ':')
                break;
        *p = '\0';
        if (checkhost && memcmp(hostname, ourhostname, len) != 0)
            continue;
        memcpy(path, p+1, PATH_MAX);
        printf("Downloading fid %i to %s\n", fdb[i], path);
        fprintf(stderr, "lk_rio_read %s\n", 
        CHECK(lk_rio_read(karma, fdb[i], path)));
    }
    free(fdb);
}

void fdb_upload(char *fdb)
{
    char **list, **file;

    list = lk_fdb_getlist(fdb);
    for (file=list; *file; file++) {
        CHECK(ftw(*file, treecopy, 1));
        free(*file);
    }
    if (list)
        free(list);
}

int treecopy(const char *file, const struct stat *sb, int flag)
{
    static int first = 1;
    static int karma;
    int fid;

    if(first){   
        currentKarma(0, &karma);
        first=0;
    }
    if(flag==FTW_F) {
        fid = lk_rio_write(karma, file);
        printf("Uploading fid %d, %s: %s\n", fid, file, (fid>0)?"ok":"failed");
    }
    return 0;
}

uint32_t * searchStr(int exact, char *searchString, char *unkName)
{
    int sizec=0, andOr=-1;                     /* andOr = 1 (and), 0 (or) */
    uint32_t *vc=NULL, *vp=NULL;              /* vc/vp : current/previous */
    char *keyValue=NULL, *key=NULL;
    char *opp=NULL, op='\0';    /* op: logical operator;  opp: op pointer */

    if (searchString==NULL) return NULL;
    op = ((opp = strpbrk(searchString, ",+"))==NULL) ? '\0' : opp[0];
    keyValue=strsep(&searchString, "+,");              /* searchString -> */
                                                       /* past "," or "+" */

    while (keyValue!=NULL) {
        key=utf8_to_codeset(keyValue);
        fprintf(stderr, "%9s FIDs with \"%-30s\":", 
                (andOr==-1)?"Searching":(andOr?"ANDing":"ORing"),key);
        free(key);
        if(andOr==-1) andOr = ORS;
        if(exact) andOr = EXACT|andOr;

        key=strsep(&keyValue, "=");           /* keyValue -> past the "=" */

        if(strcmp(keyValue, unkName) == 0){
            keyValue = NULL;
            andOr = EXACT|andOr;
        }

        if (vp){
            vc=lk_properties_andOrSearch(andOr, vp, key, keyValue);
            /* frees vp */
            vp=NULL;
        }else vc=lk_properties_andOrSearch(andOr, NULL, key, keyValue);

        sizec=0;
        if(vc != NULL) {
            while(vc[sizec] != 0) sizec++;                  /* size of vc */
            vp = malloc((sizec+1)*sizeof(uint32_t));
            memcpy(vp, vc, (sizec+1)*sizeof(uint32_t));     /* copy vp=vc */
            free(vc);
        }
        fprintf(stderr, " %4d selected\n", sizec);

        if (searchString==NULL) break;

        andOr = (op==',');
        op = ((opp = strpbrk(searchString, ",+"))==NULL) ? '\0' : opp[0];
        keyValue=strsep(&searchString, "+,");          /* searchString -> */
                                                       /* past "," or "+" */
    }
    return vp;
}

void deleteFids(uint32_t *vp) {
    int karma, c;

    currentKarma(0, &karma);
    for (c=0; vp[c]!=0; c++) 
    if (vp[c]!=256) {                           /* ignoring root playlist */
        fprintf(stderr, "lk_karma_delete_file(fid=%d): %s\n", vp[c],
                CHECK(lk_karma_delete_file(karma, vp[c])));
        lk_properties_del_property(vp[c]);
    }
}

/* -------------------------------------------------------------------------- */
/* ------------------ Pathname editing: now in pathedit.c ------------------- */
/* -------------------------------------------------------------------------- */

static uint32_t *addPlayListContents( uint32_t PLfid, uint32_t *previousFids) {
    int i, j, count;
    uint32_t fid, fidGeneration, *vp = previousFids;
    playlist *pl;
    
    pl = lk_playlist_fid_read(PLfid);  
    count = lk_playlist_count(pl);
    printf("#---- Contents of playlist \"%s\" (fid=%d, %d entries)\n", 
                                               pl->name, pl->fid, count);
    j=0;
    if(!vp) {
        vp = malloc(count*sizeof(uint32_t)+1);  
    } else {
        while(vp[j]!=0)
	    j++;
        vp = realloc(vp, (j+count)*sizeof(uint32_t)+1);
    }
    for (i=0; i<count; i++, j++) {
        lk_playlist_get_item(pl, i, &fid, &fidGeneration);
        vp[j] = fid;
    }
    vp[j] = 0;
    return vp;
}

void downloadFids(uint32_t *vp, int doDownload, char *unkName,
                  char* useTuneProps, char* useTaxiProps, char* usePlstProps, 
                  char* edPathStr1, char* edPathStr2, char* edPathStr3, 
                  int squeezeRep, int expandPlaylists, char *playListName)
{
#define FLEN 512
    int karma, i, j, len, pos;
    char c, cc, *useProps=NULL, filename[FLEN];
    char *type, *genre, *artist,*source, *title, *tracknr, *codec;
    char *s;
    uint32_t fid, *vl;

# define STRCAT(a,b) do { \
        s = ((b&&strlen(b))?b:unkName); \
        len = strlen(s); \
        if (pos+len+1 < FLEN) { \
            memcpy((a)+pos, s, len); \
            pos += len; \
        } \
    } while(0)

    currentKarma(0, &karma);
    if(doDownload != 0 && vp[0] != 0) 
        printf("# Fid Name\n#------------------------------------------\n");
    for (j=0; vp[j]!=0; j++) {
        fid=vp[j];
        type=lk_properties_get_property(fid, "type");
        if(type==NULL) {
            fprintf(stderr, "**Could not get type of fid=%d\n", fid);
            return;
        }
        if(strcmp((char *)type, "playlist") == 0) {
            if(expandPlaylists) {
		vl = addPlayListContents(fid, NULL);
 	        title = lk_properties_get_property(fid, "title");
		downloadFids(vl,           doDownload,   unkName,
                             usePlstProps, usePlstProps, usePlstProps, 
                             edPathStr1,   edPathStr2,   edPathStr3, 
                             squeezeRep,   expandPlaylists, title);
		free(vl);
                continue;
	    }
	    else
	        useProps=strdup(usePlstProps);
	}
        if(strcmp((char *)type, "taxi") == 0)
            useProps=strdup(useTaxiProps);
        if(strcmp((char *)type, "tune") == 0)
            useProps=strdup(useTuneProps);

        pos = 0;

        for (i=0; useProps[i]!='\0'; i++) {
            cc=(useProps[i]);
            if (cc != '%') {           /* just copy all non-special chars */
                if (pos+2 < FLEN) {
                    memcpy(filename+pos, useProps+i, 1);
                    pos++;
                }
                continue;
            }
            /* if(cc == '%') */
            c=useProps[++i];
            switch(c) {
                case '\0':
                    fprintf(stderr, "** Format error: %s\n", useProps);
                    i--;
                    break;
                case '%':
                    if (pos+2 < FLEN) {
                        memcpy(filename+pos, useProps+i, 1);
                        pos++;
                    }
                    break;
                case 'T':                                       /* <type> */
                    STRCAT(filename, type);
                    break;
                case 'g':                                      /*< genre> */
                    genre=lk_properties_get_property(fid, "genre");
                    STRCAT(filename, genre);
                    break;
                case 'a':                                     /* <artist> */
                    artist=lk_properties_get_property(fid, "artist");
                    artist=utf8_to_codeset(artist);
                    STRCAT(filename, artist);
                    if(artist) free(artist);
                    break;
                case 's':                             /* <source> (album) */
                    source=lk_properties_get_property(fid, "source");
                    source=utf8_to_codeset(source);
                    STRCAT(filename, source);
                    if(source) free(source);
                    break;
                case 'f':                                        /* <fid> */
                    STRCAT(filename, simple_itoa(fid));
                    break;
                case 't':                                      /* <title> */
                    title=lk_properties_get_property(fid, "title");
                    title=utf8_to_codeset(title);
                    STRCAT(filename, title);
                    if(title)
                        free(title);
                    else{
                        fprintf(stderr,
                                "*Could not get title of fid=%d\n", fid);
                    }
                    break;
                case 'n':                                    /* <tracknr> */
                    tracknr=lk_properties_get_property(fid, "tracknr");
                    STRCAT(filename, tracknr);
                    break;
                case 'c':                           /* <codec>|"playlist" */
                    if(strcmp((char *)type, "playlist") == 0) 
                        STRCAT(filename, type);
                    else{
                        codec=lk_properties_get_property(fid, "codec");
                        STRCAT(filename, codec);
                        if(!codec){
                            fprintf(stderr,
                                    "*Could not get codec of fid=%d\n", fid);
                            return;
                        }
                    }
                    break;
                case 'l':                           /* playlist name */
                    if(expandPlaylists && playListName)
		         STRCAT(filename, playListName);
                    break;
                default:
                    fprintf(stderr, "* Unknown property \'%c\' - ignored\n", c);
                    break;
            } /* switch(c) */
        } /* for i=0 ...  */

        filename[pos] = '\0';

        free(useProps);

        /* 'tr'-editing filename */
        if(edPathStr1 && edPathStr2 && edPathStr3)   
            tr(filename, edPathStr1, edPathStr2, edPathStr3, squeezeRep);

        if(doDownload) {
            fprintf(stderr, "Downloading fid %i to %s\n", fid, filename);

            if(strcmp((char *)type, "playlist") == 0) {
                fprintf(stderr, "lk_rio_read_playlist %s\n",
                        CHECK(lk_rio_read_playlist(karma, fid, filename)));
            }else{
                fprintf(stderr, "lk_rio_read %s\n", 
                        CHECK(lk_rio_read(karma, fid, filename)));
            }
        }else{
            printf("%5i %s\n", fid, filename);
        }
        filename[0]='\0';
        if((!doDownload) && (vp[j+1]==0)) 
            printf("#---- -------------------------------------\n");
    }
}

double getUsedSize(int karma)
{
    /* int rio; uint32_t storage_id;  */
    uint32_t n_files, highest_file_id;
    uint64_t s_size, f_space;
    double size, freeSpace;

    n_files=s_size=f_space=highest_file_id=0;
    lk_karma_get_storage_details(karma, 0, &n_files, &s_size,
                                 &f_space, &highest_file_id);
    size = s_size; 
    freeSpace = f_space;
/*  fprintf(stderr, "%E", size - freeSpace); */
    return(size - freeSpace);
}

/* ************************************************************************** */
int main(int argc, char * argv[])
{
    int i, c, r;
    char *settings;
    char *host=NULL;
    char *usercodeset=NULL;/*="ISO8859-15";*/
    char *psw=NULL;    /* "psw" is a bit better to search than old "p" ;) */
    char *searchString=NULL;
    char *tunePropsToUse="%f.%c"; /* props string for tune download paths */
    char *taxiPropsToUse=NULL;    /* props string for taxi download paths */
    char *plstPropsToUse=NULL;    /* props string for playlst downld paths*/
    char *editPathStr1=NULL;
    char *editPathStr2=NULL;
    char *editPathStr3=NULL;
    char *unkName=UNK_NAME;          /* Dummy name for missing properties */
    int  expandPlaylists=0;
    int  squeezeRepeats=0;
    int  download=0;
    int  fdbDownload=0;
    int  removeFiles=0;
    int  exact=0;
    int  useSmalldb=1;
    int  writeSmalldb=1;
    int  checkHost=0;
    int  ret=0;
    char yesNot='N';
    int  karma;
    uint16_t port;
    uint32_t *vp=NULL;                                  /* vector of fids */
    int dbChanged;
    char *utf;
    char *device=NULL;
    char *fdb=NULL;
    
    while ((c = getopt(argc, argv, "dhqrRbwfFHLa:c:e:p:s:S:u:x:l:D:U:")) 
                                                                   != -1) {
        switch(c) {
            case 'h': case '?':
                usage(1);
                return -1;
                break;
            case 'd':
                download=1;
                break;
            case 'r':
                removeFiles=1;
                break;
            case 'R':
                removeFiles=2;        /* don't ask confirmation questions */
                break;
            case 'b':
                useSmalldb=0;
                break;
            case 'w':
                writeSmalldb=0;
                break;
            case 'f':
                lk_karma_write_dupes(1);
                break;
            case 'F':
                fdbDownload=1;
                checkHost=0;
                break;
            case 'H':
                fdbDownload=1;
                checkHost=1;
                break;
            case 'a':
                host=strdup(optarg);
                break;
            case 'c':
                usercodeset=strdup(optarg);
                break;
            case 'e':
                editPathStr1=strdup(optarg); 
                if (optind>argc-2) 
                    {usage(1); return -1;}
                editPathStr2=strdup(argv[optind]); optind++; 
                editPathStr3=strdup(argv[optind]); optind++;
                break;
            case 'q':
                squeezeRepeats=1;
                break;
            case 'p':
                psw=strdup(optarg);
                break;
            case 's':
                searchString=strdup(optarg);
                break;
            case 'S':
                searchString=strdup(optarg); exact = 1;
                break;
            case 'u':
                tunePropsToUse=strdup(optarg);
                break;
            case 'x':
                taxiPropsToUse=strdup(optarg);
                break;
            case 'l':
                plstPropsToUse=strdup(optarg);
                break;
            case 'D':
                unkName=strdup(optarg);
                break;
            case 'L':
		expandPlaylists = 1;
                break;
            case 'U':
                fdb=strdup(optarg);
                break;
            default:
                fprintf(stderr, "unknown option %c\n", c);
                break;
        }
    }
    if(taxiPropsToUse==NULL) taxiPropsToUse=strdup(tunePropsToUse);
    if(plstPropsToUse==NULL) plstPropsToUse=strdup(taxiPropsToUse);

    lk_errors_autoprint(0);

    if(useSmalldb)
        lk_karma_use_smalldb();

    if(!host) { /* 'host' means a net host name, an IP, or mount directory */
        /* trying to find a USB/OMFS-mounted Karma */
        lk_errors_number();
        lk_mountSearch_discover(&device, &host);
        host?fprintf(stderr, "Karma found: %s mounted on %s\n", device, host)
            :fprintf(stderr, "No mounted Karma found; trying Ethernet.\n");
    }
    if (!host) {
        /* trying to find an Ethernet-connected Karma */
        fprintf(stderr, "SSDP:");
        for(i=0; (i<=ssdpRetry)&&(host==NULL); i++) {
            lk_errors_number();
            lk_ssdp_discover(&host, &port);
            host?fprintf(stderr, " %s:%i\n", host, port)
                :fprintf(stderr, ".");
        }
        if(!host) 
            fprintf(stderr, " no Karma found.\n");
    }
    if (!host) {
        lk_errors_p("*", " -- Can't connect to Rio Karma\n");
        usage(0);
        if(psw) free(psw);
        if(searchString) free(searchString);
        if(usercodeset) free(usercodeset);
        return -1;
    }
    karma=lk_karma_connect(host);
    lk_errors_p("","");
    free(host);

    if(karma < 0) {
        fprintf(stderr, "unable to connect to rio karma\n\n"); 
        usage(0);
        if(psw) free(psw);
        if(searchString) free(searchString);
        if(usercodeset) free(usercodeset);
        return -1;
    }
    
    currentKarma(1,&karma);
    
    /*lk_properties_init("ISO8859-15");*/
    if(usercodeset != NULL){
        lk_properties_init();
        utf8_set_codeset(usercodeset);
        if (lk_errors_p("", "; Trying locale...")) {
            free(usercodeset);
            usercodeset = NULL;
        }
    }
    if(usercodeset == NULL){
        if (! setlocale(LC_CTYPE, ""))
            fprintf(stderr,"* Warning: setlocale failed.");
        if(strlen(usercodeset = nl_langinfo(CODESET)) == 0) {
            fprintf(stderr, "* Warning: nl_langinfo problem; "
                    "using default codeset %s\n", defaultCodeset);
            usercodeset = defaultCodeset;
        }
        lk_properties_init();
        utf8_set_codeset(usercodeset);
        if(! lk_errors_p("",""))
            fprintf(stderr, "* Using %s codeset.\n", usercodeset);
        /*free(usercodeset);*/
    }
    fprintf(stderr, "Using %s codeset.\n", usercodeset);
    lk_errors_autoprint(1);               /* Setting auto-print-errors ON */
 
    if(psw){
        fprintf(stderr, "lk_karma_authenticate: %i\n", 
                lk_karma_authenticate(karma, psw));
        free(psw);
    }else
        fprintf(stderr, "lk_karma_authenticate: %i\n", 
                lk_karma_authenticate(karma, ""));

    r = lk_karma_request_io_lock(karma,IO_LOCK_R);
    fprintf(stderr, "lk_karma_request_io_lock: %s\n", CHECK(r));
    if(r == -1){
        printf("Access Denied!\n");
        lk_karma_hangup(karma);
        close(karma);
        lk_properties_destroy();
        utf8_destroy();
        return -1;
    }

    lk_karma_get_device_settings(karma, &settings);
    dbChanged = lk_properties_cache_obsolete();
    if(dbChanged) {
        fprintf(stderr, "Downloading Rio database... ");
        fflush(stdout);
        if(getUsedSize(karma)>3E9) fprintf(stderr, " please wait... ");
        fflush(stderr);
    }
    free(settings);
    if(!r) lk_karma_load_database(karma);
    if(dbChanged) fprintf(stderr, "Done!\n");

    if(fdbDownload)
        fdb_download(checkHost);

    if (searchString != NULL){
        utf=utf8_from_codeset(searchString);
        free(searchString);
        vp=searchStr(exact, utf, unkName);
        free(utf);
    }

    if(vp)
        downloadFids(vp, download, unkName, tunePropsToUse, taxiPropsToUse,
                     plstPropsToUse, editPathStr1, editPathStr2, editPathStr3,
                     squeezeRepeats, expandPlaylists, NULL);
    if(taxiPropsToUse) free(taxiPropsToUse);
    if(plstPropsToUse) free(plstPropsToUse);

    if(removeFiles>0 && vp) {
        yesNot='Y';
        for (i=0; vp[i]!=0; i++) continue;
        if(i>0) {
            printf("Ready to remove %d files; ", i);
            if(removeFiles==1) {
                printf("proceed (Y/N)? ");
                scanf("%c", &yesNot);
            }else{
                printf("\n");
            }
            if(yesNot!='Y' && yesNot!='y') 
                printf("Remove aborted!\n");
            else {
                fprintf(stderr, "lk_karma_request_io_lock (W): %s\n", 
                        CHECK(lk_karma_request_io_lock(karma,IO_LOCK_W)));
                deleteFids(vp);
            }
        } else fprintf(stderr, "No files to remove!\n");
    }

    if(optind < argc || fdb){
        fprintf(stderr, "lk_karma_request_io_lock (W): %s\n", 
                CHECK(lk_karma_request_io_lock(karma,IO_LOCK_W)));
        if(lk_synchronize_necessary(karma)){
            lk_karma_get_all_file_details(karma, &psw);
            lk_properties_import(psw);
            free(psw);
        }
    }
    if(optind < argc){
        for(; optind<argc; optind++)
            ftw(argv[optind], treecopy, 1);
    }
    if (fdb) {
        fdb_upload(fdb);
    }

    lk_properties_save();
    if (writeSmalldb) {
        ret = lk_karma_write_smalldb();
        if (ret)
            fprintf(stderr, "error writing smalldb file\n");
    }
    fprintf(stderr, "lk_karma_release_io_lock: %s\n", 
            CHECK(lk_karma_release_io_lock(karma)));

    fprintf(stderr, "lk_karma_hangup: %s\n", CHECK(lk_karma_hangup(karma)));
    close(karma);
    free(vp);

    lk_properties_destroy();
    utf8_destroy();
    return ret;
}
