Write the hash cache back to disk whenever it is dirty when the daemon exits.
[doldaconnect.git] / daemon / client.c
index 1e659ab..46766e9 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  Dolda Connect - Modular multiuser Direct Connect-style client
- *  Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *  Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
  *  
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@
 */
 #include <stdlib.h>
 #include <stdio.h>
-#include <malloc.h>
 #include <wchar.h>
 #include <string.h>
 #include <errno.h>
@@ -36,9 +35,9 @@
 #include "log.h"
 #include "utils.h"
 #include "module.h"
-#include "tiger.h"
 #include "net.h"
 #include "sysevents.h"
+#include <tiger.h>
 
 struct scanstate
 {
@@ -56,13 +55,36 @@ struct scanqueue
 static int conf_share(int argc, wchar_t **argv);
 static void freecache(struct sharecache *node);
 static void checkhashes(void);
+static void writehashcache(int now);
 
 static struct configvar myvars[] =
 {
+    /** The default nick name to use. The nickname can also be
+     * specified for individual hubs, overriding this setting. */
     {CONF_VAR_STRING, "defnick", {.str = L"DoldaConnect user"}},
+    /** When scanning shares, this bitmask is consulted for every
+     * regular file. Unless the file's mode has the bits specified by
+     * this mask set, it will not be shared. */
     {CONF_VAR_INT, "scanfilemask", {.num = 0004}},
+    /** When scanning shares, this bitmask is consulted for every
+     * directory encountered. Unless the directory's mode has the bits
+     * specified by this mask set, it will be ignored and any files
+     * under it will not be shared. */
     {CONF_VAR_INT, "scandirmask", {.num = 0005}},
+    /** The filename to use for the hash cache (see the FILES section
+     * for more information). */
     {CONF_VAR_STRING, "hashcache", {.str = L"dc-hashcache"}},
+    /** Writes of the hash cache and file lists are delayed for an
+     * amount of time, in order to minimize the time spent on I/O wait
+     * while hashing many small files. This variable sets the amount
+     * of time, in seconds. */
+    {CONF_VAR_INT, "hashwritedelay", {.num = 300}},
+    /** The amount of time, in seconds, to wait before automatically
+     * rescanning the shared directories for changes. Set to zero (the
+     * default) to disable automatic rescanning. (Broken shares are
+     * always rescanned upon detection, regardless of this
+     * setting.) */
+    {CONF_VAR_INT, "rescandelay", {.num = 0}},
     {CONF_VAR_END}
 };
 
@@ -76,12 +98,15 @@ static struct scanstate *scanjob = NULL;
 static struct scanqueue *scanqueue = NULL;
 static struct sharepoint *shares = NULL;
 static struct hashcache *hashcache = NULL;
+static struct timer *hashwritetimer = NULL;
 /* Set initially to -1, but changed to 0 the first time run() is
  * called. This is to avoid forking a hash job before daemonizing,
  * since that would make the daemon unable to wait() for the hash
  * job. */
 static pid_t hashjob = -1;
 struct sharecache *shareroot = NULL;
+static struct timer *scantimer = NULL;
+int sharedfiles = 0;
 unsigned long long sharesize = 0;
 GCBCHAIN(sharechangecb, unsigned long long);
 
@@ -242,52 +267,6 @@ static void freehashcache(struct hashcache *hc)
     free(hc);
 }
 
-static char *findhashcachefile(int filldef)
-{
-    static char ret[128];
-    char *hcname;
-    
-    if(getenv("HOME") != NULL)
-    {
-       snprintf(ret, sizeof(ret), "%s/.dc-hashcache", getenv("HOME"));
-       if(!access(ret, R_OK))
-           return(ret);
-    }
-    if((hcname = icswcstombs(confgetstr("cli", "hashcache"), NULL, NULL)) == NULL)
-    {
-       flog(LOG_WARNING, "could not convert hash cache name into local charset: %s", strerror(errno));
-       return(NULL);
-    }
-    if(strchr(hcname, '/') != NULL)
-    {
-       if(!access(hcname, R_OK))
-       {
-           strcpy(ret, hcname);
-           return(ret);
-       }
-    } else {
-       snprintf(ret, sizeof(ret), "/etc/%s", hcname);
-       if(!access(ret, R_OK))
-           return(ret);
-       snprintf(ret, sizeof(ret), "/usr/etc/%s", hcname);
-       if(!access(ret, R_OK))
-           return(ret);
-       snprintf(ret, sizeof(ret), "/usr/local/etc/%s", hcname);
-       if(!access(ret, R_OK))
-           return(ret);
-    }
-    if(filldef)
-    {
-       if(getenv("HOME") != NULL)
-           snprintf(ret, sizeof(ret), "%s/.dc-hashcache", getenv("HOME"));
-       else
-           snprintf(ret, sizeof(ret), "/etc/%s", hcname);
-       return(ret);
-    } else {
-       return(NULL);
-    }
-}
-
 static struct hashcache *findhashcache(dev_t dev, ino_t inode)
 {
     struct hashcache *hc;
@@ -310,7 +289,7 @@ static void readhashcache(void)
     struct hashcache *hc;
     size_t len;
     
-    if((hcname = findhashcachefile(0)) == NULL)
+    if((hcname = findfile(icswcstombs(confgetstr("cli", "hashcache"), NULL, NULL), NULL, 0)) == NULL)
        return;
     if((stream = fopen(hcname, "r")) == NULL)
     {
@@ -364,14 +343,29 @@ static void readhashcache(void)
     fclose(stream);
 }
 
-static void writehashcache(void)
+static void hashtimercb(int cancelled, void *uudata)
+{
+    hashwritetimer = NULL;
+    if(!cancelled)
+       writehashcache(1);
+}
+
+static void writehashcache(int now)
 {
     char *buf;
     char *hcname;
     FILE *stream;
     struct hashcache *hc;
     
-    hcname = findhashcachefile(1);
+    if(!now)
+    {
+       if(hashwritetimer == NULL)
+           hashwritetimer = timercallback(ntime() + confgetint("cli", "hashwritedelay"), (void (*)(int, void *))hashtimercb, NULL);
+       return;
+    }
+    if(hashwritetimer != NULL)
+       canceltimer(hashwritetimer);
+    hcname = findfile(icswcstombs(confgetstr("cli", "hashcache"), NULL, NULL), NULL, 1);
     if((stream = fopen(hcname, "w")) == NULL)
     {
        flog(LOG_WARNING, "could not write hash cache %s: %s", hcname, strerror(errno));
@@ -385,7 +379,7 @@ static void writehashcache(void)
     for(hc = hashcache; hc != NULL; hc = hc->next)
     {
        buf = base64encode(hc->tth, 24);
-       fprintf(stream, "%lli %lli %li tth %s\n", hc->dev, (long long)hc->inode, hc->mtime, buf);
+       fprintf(stream, "%lli %lli %li tth %s\n", (long long)hc->dev, (long long)hc->inode, hc->mtime, buf);
        free(buf);
     }
     fclose(stream);
@@ -443,7 +437,7 @@ static void hashread(struct socket *sk, void *uudata)
            buf = base64decode(wv[3], NULL);
            memcpy(hc->tth, buf, 24);
            free(buf);
-           writehashcache();
+           writehashcache(0);
        }
        memmove(hashbuf, lp, hashbufdata -= (lp - hashbuf));
     }
@@ -518,7 +512,7 @@ static int hashfile(char *path)
        }
        synctigertree(&tth);
        restigertree(&tth, digest);
-       ret = snprintf(buf, sizeof(buf), "%lli %lli %li %s\n", sb.st_dev, (long long)sb.st_ino, sb.st_mtime, base64encode(digest, 24));
+       ret = snprintf(buf, sizeof(buf), "%lli %lli %li %s\n", (long long)sb.st_dev, (long long)sb.st_ino, sb.st_mtime, base64encode(digest, 24));
        write(1, buf, ret);
        exit(0);
     }
@@ -535,13 +529,14 @@ static int hashfile(char *path)
  */
 static void checkhashes(void)
 {
-    struct sharecache *node;
+    struct sharecache *node, *next;
     struct hashcache *hc;
     char *path;
     
     node = shareroot->child;
-    for(node = shareroot->child; node != NULL; node = nextscnode(node))
+    for(node = shareroot->child; node != NULL; node = next)
     {
+       next = nextscnode(node);
        if(node->f.b.type != FILE_REG)
            continue;
        if(!node->f.b.hastth)
@@ -654,6 +649,8 @@ static void freecache(struct sharecache *node)
     CBCHAINDOCB(node, share_delete, node);
     CBCHAINFREE(node, share_delete);
     sharesize -= node->size;
+    if(node->f.b.type == FILE_REG)
+       sharedfiles--;
     if(node->path != NULL)
        free(node->path);
     if(node->name != NULL)
@@ -944,6 +941,7 @@ int doscan(int quantum)
            if(S_ISREG(sb.st_mode))
            {
                sharesize += (n->size = sb.st_size);
+               sharedfiles++;
            } else {
                n->size = 0;
            }
@@ -982,6 +980,18 @@ int doscan(int quantum)
     return(1);
 }
 
+static void rescancb(int cancelled, void *uudata)
+{
+    scantimer = NULL;
+    if(!cancelled)
+    {
+       if(scanqueue == NULL)
+           scanshares();
+       else if(confgetint("cli", "rescandelay") > 0)
+           scantimer = timercallback(ntime() + confgetint("cli", "rescandelay"), (void (*)(int, void *))rescancb, NULL);
+    }
+}
+
 void scanshares(void)
 {
     struct sharepoint *cur;
@@ -1012,6 +1022,10 @@ void scanshares(void)
        }
        queuescan(node);
     }
+    if(scantimer != NULL)
+       canceltimer(scantimer);
+    if(confgetint("cli", "rescandelay") > 0)
+       scantimer = timercallback(ntime() + confgetint("cli", "rescandelay"), (void (*)(int, void *))rescancb, NULL);
 }
 
 static void preinit(int hup)
@@ -1029,6 +1043,15 @@ static void preinit(int hup)
     }
 }
 
+static int rsdelayupdate(struct configvar *var, void *uudata)
+{
+    if(scantimer != NULL)
+       canceltimer(scantimer);
+    if(confgetint("cli", "rescandelay") > 0)
+       scantimer = timercallback(ntime() + var->val.num, (void (*)(int, void *))rescancb, NULL);
+    return(0);
+}
+
 static int init(int hup)
 {
     struct sharepoint *cur, *next;
@@ -1042,7 +1065,10 @@ static int init(int hup)
     }
     scanshares();
     if(!hup)
+    {
        while(doscan(100));
+       CBREG(confgetvar("cli", "rescandelay"), conf_update, rsdelayupdate, NULL, NULL);
+    }
     return(0);
 }
 
@@ -1060,6 +1086,8 @@ static void terminate(void)
 {
     if(hashjob != 0)
        kill(hashjob, SIGHUP);
+    if(hashwritetimer != NULL)
+       writehashcache(1);
     while(shares != NULL)
        freesharepoint(shares);
     freecache(shareroot);