text: /* Copyright (c) 2013-2014 Anton Titov.
   * Copyright (c) 2013-2014 pCloud Ltd.
   * All rights reserved.
   * 
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *     * Redistributions of source code must retain the above copyright
   *       notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above copyright
   *       notice, this list of conditions and the following disclaimer in the
   *       documentation and/or other materials provided with the distribution.
   *     * Neither the name of pCloud Ltd nor the
   *       names of its contributors may be used to endorse or promote products
   *       derived from this software without specific prior written permission.
   * 
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL pCloud Ltd BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "pcompat.h"
  #include "pdiff.h"
  #include "pstatus.h"
  #include "psettings.h"
  #include "plibs.h"
  #include "papi.h"
  #include "ptimer.h"
  #include "psyncer.h"
  #include "ptasks.h"
  #include "pfolder.h"
  #include "psyncer.h"
  #include "pdownload.h"
  #include "pcallbacks.h"
  #include "pfileops.h"
  #include "pfsxattr.h"
  #include "pfs.h"
  #include <ctype.h>
  
  #define PSYNC_SQL_DOWNLOAD "synctype&"NTO_STR(PSYNC_DOWNLOAD_ONLY)"="NTO_STR(PSYNC_DOWNLOAD_ONLY)
  
  static uint64_t used_quota=0, current_quota=0;
  static psync_uint_t needdownload=0;
  static psync_socket_t exceptionsockwrite=INVALID_SOCKET;
  static pthread_mutex_t diff_mutex=PTHREAD_MUTEX_INITIALIZER;
  static int initialdownload=0;
  
  static void psync_diff_refresh_fs_add_folder(psync_folderid_t folderid);
  
  static binresult *get_userinfo_user_digest(psync_socket *sock, const char *username, size_t userlen, const char *pwddig, const char *digest, uint32_t diglen,
                                             const char *device){
    binparam params[]={P_STR("timeformat", "timestamp"), 
                        P_LSTR("username", username, userlen),
                        P_LSTR("digest", digest, diglen),
                        P_LSTR("passworddigest", pwddig, PSYNC_SHA1_DIGEST_HEXLEN),
                        P_STR("device", device),
                        P_BOOL("getauth", 1),
                        P_NUM("os", P_OS_ID)};
    return send_command(sock, "userinfo", params);
  }
  
  static binresult *get_userinfo_user_pass(psync_socket *sock, const char *username, const char *password, const char *device){
    binparam empty_params[]={P_STR("MS", "sucks")};
    psync_sha1_ctx ctx;
    binresult *res, *ret;
    const binresult *dig;
    unsigned char *uc;
    size_t ul, i;
    unsigned char sha1bin[PSYNC_SHA1_DIGEST_LEN];
    char sha1hex[PSYNC_SHA1_DIGEST_HEXLEN];
    res=send_command(sock, "getdigest", empty_params);
    if (!res)
      return res;
    if (psync_find_result(res, "result", PARAM_NUM)->num!=0){
      psync_free(res);
      return NULL;
    }
    dig=psync_find_result(res, "digest", PARAM_STR);
    debug(D_NOTICE, "got digest %s", dig->str);
    ul=strlen(username);
    uc=psync_new_cnt(unsigned char, ul);
    for (i=0; i<ul; i++)
      uc[i]=tolower(username[i]);
    psync_sha1(uc, ul, sha1bin);
    psync_free(uc);
    psync_binhex(sha1hex, sha1bin, PSYNC_SHA1_DIGEST_LEN);
    psync_sha1_init(&ctx);
    psync_sha1_update(&ctx, password, strlen(password));
    psync_sha1_update(&ctx, sha1hex, PSYNC_SHA1_DIGEST_HEXLEN);
    psync_sha1_update(&ctx, dig->str, dig->length);
    psync_sha1_final(sha1bin, &ctx);
    psync_binhex(sha1hex, sha1bin, PSYNC_SHA1_DIGEST_LEN);
    ret=get_userinfo_user_digest(sock, username, ul, sha1hex, dig->str, dig->length, device);
    psync_free(res);
    return ret;
  }
  
  static psync_socket *get_connected_socket(){
    char *auth, *user, *pass;
    psync_socket *sock;
    binresult *res;
    const binresult *cres;
    psync_sql_res *q;
    char *device;
    uint64_t result, userid, luserid;
    int saveauth;
    auth=user=pass=NULL;
    while (1){
      psync_free(auth);
      psync_free(user);
      psync_free(pass);
      psync_wait_status(PSTATUS_TYPE_RUN, PSTATUS_RUN_RUN|PSTATUS_RUN_PAUSE);
      auth=psync_sql_cellstr("SELECT value FROM setting WHERE id='auth'");
      user=psync_sql_cellstr("SELECT value FROM setting WHERE id='user'");
      pass=psync_sql_cellstr("SELECT value FROM setting WHERE id='pass'");
      if (!auth && psync_my_auth[0])
        auth=psync_strdup(psync_my_auth);
      if (!user && psync_my_user)
        user=psync_strdup(psync_my_user);
      if (!pass && psync_my_pass)
        pass=psync_strdup(psync_my_pass);
      if (!auth && (!pass || !user)){
        psync_set_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_REQUIRED);
        psync_wait_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_PROVIDED);
        continue;
      }
      psync_set_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_PROVIDED);
      saveauth=psync_setting_get_bool(_PS(saveauth));
      sock=psync_api_connect(psync_setting_get_bool(_PS(usessl)));
      if (unlikely_log(!sock)){
        psync_set_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_OFFLINE);
        psync_milisleep(PSYNC_SLEEP_BEFORE_RECONNECT);
        continue;
      }
      device=psync_deviceid();
      if (user && pass)
        res=get_userinfo_user_pass(sock, user, pass, device);
      else {
        binparam params[]={P_STR("timeformat", "timestamp"), 
                           P_STR("auth", auth),
                           P_STR("device", device),
                           P_BOOL("getauth", 1),
                           P_NUM("os", P_OS_ID)};
        res=send_command(sock, "userinfo", params);
      }
      psync_free(device);
      if (unlikely_log(!res)){
        psync_socket_close(sock);
        psync_set_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_OFFLINE);
        psync_milisleep(PSYNC_SLEEP_BEFORE_RECONNECT);
        psync_api_conn_fail_inc();
        continue;
      }
      psync_api_conn_fail_reset();
      result=psync_find_result(res, "result", PARAM_NUM)->num;
      if (unlikely(result)){
        debug(D_NOTICE, "userinfo returned error %lu %s", (unsigned long)result, psync_find_result(res, "error", PARAM_STR)->str);
        psync_socket_close(sock);
        psync_free(res);
        if (result==2000){
          if (user && pass)
            psync_set_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_BADLOGIN);
          else
            psync_set_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_BADTOKEN);
          psync_wait_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_PROVIDED);
        }
        else if (result==4000)
          psync_milisleep(5*60*1000);
        else
          psync_milisleep(PSYNC_SLEEP_BEFORE_RECONNECT);
        continue;
      }
      psync_my_userid=userid=psync_find_result(res, "userid", PARAM_NUM)->num;
      current_quota=psync_find_result(res, "quota", PARAM_NUM)->num;
      luserid=psync_sql_cellint("SELECT value FROM setting WHERE id='userid'", 0);
      psync_sql_start_transaction();
      strcpy(psync_my_auth, psync_find_result(res, "auth", PARAM_STR)->str);
      if (luserid){
        if (unlikely_log(luserid!=userid)){
          psync_sql_rollback_transaction();
          debug(D_NOTICE, "user mistmatch, db userid=%lu, connected userid=%lu", (unsigned long)luserid, (unsigned long)userid);
          psync_socket_close(sock);
          psync_free(res);
          psync_set_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_MISMATCH);
          psync_wait_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_PROVIDED);
          continue;
        }
        if (saveauth){
          q=psync_sql_prep_statement("REPLACE INTO setting (id, value) VALUES ('auth', ?)");
          psync_sql_bind_string(q, 1, psync_my_auth);
          psync_sql_run_free(q);
        }
      }
      else{
        used_quota=0;
        q=psync_sql_prep_statement("REPLACE INTO setting (id, value) VALUES (?, ?)");
        psync_sql_bind_string(q, 1, "userid");
        psync_sql_bind_uint(q, 2, userid);
        psync_sql_run(q);
        psync_sql_bind_string(q, 1, "quota");
        psync_sql_bind_uint(q, 2, current_quota);
        psync_sql_run(q);
        psync_sql_bind_string(q, 1, "usedquota");
        psync_sql_bind_uint(q, 2, 0);
        psync_sql_run(q);
        result=psync_find_result(res, "premium", PARAM_BOOL)->num;
        psync_sql_bind_string(q, 1, "premium");
        psync_sql_bind_uint(q, 2, result);
        psync_sql_run(q);
        if (result)
          result=psync_find_result(res, "premiumexpires", PARAM_NUM)->num;
        else
          result=0;
        psync_sql_bind_string(q, 1, "premiumexpires");
        psync_sql_bind_uint(q, 2, result);
        psync_sql_run(q);
        psync_sql_bind_string(q, 1, "emailverified");
        psync_sql_bind_uint(q, 2, psync_find_result(res, "emailverified", PARAM_BOOL)->num);
        psync_sql_run(q);
        psync_sql_bind_string(q, 1, "registered");
        psync_sql_bind_uint(q, 2, psync_find_result(res, "registered", PARAM_NUM)->num);
        psync_sql_run(q);
        psync_sql_bind_string(q, 1, "username");
        psync_sql_bind_string(q, 2, psync_find_result(res, "email", PARAM_STR)->str);
        psync_sql_run(q);
        psync_sql_bind_string(q, 1, "language");
        psync_sql_bind_string(q, 2, psync_find_result(res, "language", PARAM_STR)->str);
        psync_sql_run(q);
        if (saveauth){
          psync_sql_bind_string(q, 1, "auth");
          psync_sql_bind_string(q, 2, psync_my_auth);
          psync_sql_run(q);
        }
        psync_sql_free_result(q);
      }
      if (psync_status_get(PSTATUS_TYPE_AUTH)!=PSTATUS_AUTH_PROVIDED){
        psync_sql_rollback_transaction();
        psync_socket_close(sock);
        psync_free(res);
        psync_wait_status(PSTATUS_TYPE_AUTH, PSTATUS_AUTH_PROVIDED);
        continue;
      }
      cres=psync_check_result(res, "account", PARAM_HASH);
      q=psync_sql_prep_statement("REPLACE INTO setting (id, value) VALUES (?, ?)");
      if (cres){
        psync_sql_bind_string(q, 1, "business");
        psync_sql_bind_uint(q, 2, 1);
        psync_sql_run(q);
        psync_sql_bind_string(q, 1, "firstname");
        psync_sql_bind_string(q, 2, psync_find_result(cres, "firstname", PARAM_STR)->str);
        psync_sql_run(q);
        psync_sql_bind_string(q, 1, "lastname");
        psync_sql_bind_string(q, 2, psync_find_result(cres, "lastname", PARAM_STR)->str);
        psync_sql_run(q);
      }
      else{
        psync_sql_bind_string(q, 1, "business");
        psync_sql_bind_uint(q, 2, 0);
        psync_sql_run(q);
      }
      psync_sql_free_result(q);
      psync_sql_commit_transaction();
      pthread_mutex_lock(&psync_my_auth_mutex);
      if (psync_my_pass){
        memset(psync_my_pass, 'X', strlen(psync_my_pass));
        q=psync_sql_prep_statement("UPDATE setting SET value=? WHERE id='pass'");
        psync_sql_bind_string(q, 1, psync_my_pass);
        psync_sql_run_free(q);
        psync_free(psync_my_pass);
        psync_my_pass=NULL;
      }
      pthread_mutex_unlock(&psync_my_auth_mutex);
      if (saveauth)
        psync_sql_statement("DELETE FROM setting WHERE id='pass'");
      else
        psync_sql_statement("DELETE FROM setting WHERE id IN ('pass', 'auth')");
      psync_free(res);
      if (cres){
        binparam params[]={P_STR("timeformat", "timestamp"), 
                           P_STR("auth", psync_my_auth)};
        res=send_command(sock, "account_info", params);
        if (unlikely_log(!res)){
          psync_socket_close(sock);
          continue;
        }
        result=psync_find_result(res, "result", PARAM_NUM)->num;
        if (likely(result==0)){
          cres=psync_check_result(res, "account", PARAM_HASH);
          q=psync_sql_prep_statement("REPLACE INTO setting (id, value) VALUES (?, ?)");
          psync_sql_bind_string(q, 1, "company");
          psync_sql_bind_string(q, 2, psync_find_result(cres, "company", PARAM_STR)->str);
          psync_sql_run_free(q);
        }
        else
          debug(D_WARNING, "account_info returned %lu, continuing without business info", (unsigned long)result);
        psync_free(res);
      }
      psync_free(auth);
      psync_free(user);
      psync_free(pass);
      psync_sql_sync();
      return sock;
    }
  }
  
  static void process_createfolder(const binresult *entry){
    static psync_sql_res *st=NULL, *st2=NULL;
    psync_sql_res *res, *stmt, *stmt2;
    const binresult *meta, *name;
    uint64_t userid, perms, mtime, flags;
    psync_uint_row row;
    psync_folderid_t parentfolderid, folderid, localfolderid;
  //  char *localname;
    psync_syncid_t syncid;
    if (!entry){
      if (st){
        psync_sql_free_result(st);
        st=NULL;
      }
      if (st2){
        psync_sql_free_result(st2);
        st2=NULL;
      }
      return;
    }
    if (!st){
      st=psync_sql_prep_statement("INSERT OR IGNORE INTO folder (id, parentfolderid, userid, permissions, name, ctime, mtime, flags, subdircnt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)");
      if (!st)
        return;
      st2=psync_sql_prep_statement("UPDATE folder SET subdircnt=subdircnt+1, mtime=? WHERE id=?");
      if (!st2)
        return;
    }
    meta=psync_find_result(entry, "metadata", PARAM_HASH);
    flags=0;
    if ((name=psync_check_result(meta, "encrypted", PARAM_BOOL)) && name->num)
      flags|=PSYNC_FOLDER_FLAG_ENCRYPTED;
    if (psync_find_result(meta, "ismine", PARAM_BOOL)->num){
      userid=psync_my_userid;
      perms=PSYNC_PERM_ALL;
    }
    else{
      userid=psync_find_result(meta, "userid", PARAM_NUM)->num;
      perms=psync_get_permissions(meta);
    }
    name=psync_find_result(meta, "name", PARAM_STR);
    folderid=psync_find_result(meta, "folderid", PARAM_NUM)->num;
    parentfolderid=psync_find_result(meta, "parentfolderid", PARAM_NUM)->num;
    mtime=psync_find_result(meta, "modified", PARAM_NUM)->num;
    psync_sql_bind_uint(st, 1, folderid);
    psync_sql_bind_uint(st, 2, parentfolderid);
    psync_sql_bind_uint(st, 3, userid);
    psync_sql_bind_uint(st, 4, perms);
    psync_sql_bind_lstring(st, 5, name->str, name->length);
    psync_sql_bind_uint(st, 6, psync_find_result(meta, "created", PARAM_NUM)->num);
    psync_sql_bind_uint(st, 7, mtime);
    psync_sql_bind_uint(st, 8, flags);
    psync_sql_run(st);
    if (!psync_sql_affected_rows()){
      res=psync_sql_prep_statement("UPDATE folder SET parentfolderid=?, userid=?, permissions=?, name=?, ctime=?, mtime=?, flags=? WHERE id=?");
      psync_sql_bind_uint(res, 1, parentfolderid);
      psync_sql_bind_uint(res, 2, userid);
      psync_sql_bind_uint(res, 3, perms);
      psync_sql_bind_lstring(res, 4, name->str, name->length);
      psync_sql_bind_uint(res, 5, psync_find_result(meta, "created", PARAM_NUM)->num);
      psync_sql_bind_uint(res, 6, mtime);
      psync_sql_bind_uint(res, 7, flags);
      psync_sql_bind_uint(res, 8, folderid);
      psync_sql_run_free(res);
    }
    psync_sql_bind_uint(st2, 1, mtime);
    psync_sql_bind_uint(st2, 2, parentfolderid);
    psync_sql_run(st2);
    if (psync_is_folder_in_downloadlist(parentfolderid) && !psync_is_name_to_ignore(name->str)){
      psync_add_folder_to_downloadlist(folderid);
      res=psync_sql_query("SELECT syncid, localfolderid, synctype FROM syncedfolder WHERE folderid=? AND "PSYNC_SQL_DOWNLOAD);
      psync_sql_bind_uint(res, 1, parentfolderid);
      stmt=psync_sql_prep_statement("INSERT OR IGNORE INTO syncedfolder (syncid, folderid, localfolderid, synctype) VALUES (?, ?, ?, ?)");
      while ((row=psync_sql_fetch_rowint(res))){
        syncid=row[0];
        debug(D_NOTICE, "creating local folder %lu/%s for folderid %lu, parentfolderid %lu", (unsigned long)row[1], name->str, (unsigned long)folderid, (unsigned long)parentfolderid);
        localfolderid=psync_create_local_folder_in_db(syncid, folderid, row[1], name->str);
        psync_sql_bind_uint(stmt, 1, syncid);
        psync_sql_bind_uint(stmt, 2, folderid);
        psync_sql_bind_uint(stmt, 3, localfolderid);
        psync_sql_bind_uint(stmt, 4, row[2]);
        psync_sql_run(stmt);
        if (psync_sql_affected_rows()==1){
          psync_task_create_local_folder(syncid, folderid, localfolderid);
          needdownload=1;
        }
        else{
          stmt2=psync_sql_prep_statement("UPDATE syncedfolder SET folderid=? WHERE syncid=? AND localfolderid=?");
          psync_sql_bind_uint(stmt2, 1, folderid);
          psync_sql_bind_uint(stmt2, 2, syncid);
          psync_sql_bind_uint(stmt2, 3, localfolderid);
          psync_sql_run_free(stmt2);
        }
      }
      psync_sql_free_result(stmt);
      psync_sql_free_result(res);
    }
  }
  
  static void group_results_by_col(psync_full_result_int *restrict r1, psync_full_result_int *restrict r2, uint32_t col){
    psync_def_var_arr(buff, uint64_t, r1->cols);
    size_t rowsize;
    uint32_t i, j, l;
    l=0;
    rowsize=sizeof(r1->data[0])*r1->cols;
    assert(r1->cols==r2->cols);
    for (i=0; i<r1->rows; i++)
      for (j=0; j<r2->rows; j++)
        if (psync_get_result_cell(r1, i, col)==psync_get_result_cell(r2, j, col)){
          if (i!=l){
            memcpy(buff, r1->data+i*r1->cols, rowsize);
            memcpy(r1->data+i*r1->cols, r1->data+l*r1->cols, rowsize);
            memcpy(r1->data+l*r1->cols, buff, rowsize);
          }
          if (j!=l){
            memcpy(buff, r2->data+j*r2->cols, rowsize);
            memcpy(r2->data+j*r2->cols, r2->data+l*r2->cols, rowsize);
            memcpy(r2->data+l*r2->cols, buff, rowsize);
          }
          l++;
        }
  }
  
  static void del_synced_folder_rec(psync_folderid_t folderid, psync_syncid_t syncid){
    psync_sql_res *res;
    psync_uint_row row;
    res=psync_sql_prep_statement("DELETE FROM syncedfolder WHERE folderid=? AND syncid=?");
    psync_sql_bind_uint(res, 1, folderid);
    psync_sql_bind_uint(res, 2, syncid);
    psync_sql_run_free(res);
    res=psync_sql_query("SELECT id FROM folder WHERE parentfolderid=?");
    psync_sql_bind_uint(res, 1, folderid);
    while ((row=psync_sql_fetch_rowint(res)))
      del_synced_folder_rec(row[0], syncid);
    psync_sql_free_result(res);
  }
  
  static void process_modifyfolder(const binresult *entry){
    static psync_sql_res *st=NULL;
    psync_sql_res *res;
    psync_full_result_int *fres1, *fres2;
    const binresult *meta, *name;
    uint64_t userid, perms, mtime, flags;
    psync_variant_row vrow;
    psync_uint_row row;
    psync_folderid_t parentfolderid, folderid, oldparentfolderid, localfolderid;
    char *oldname;
    psync_syncid_t syncid;
    uint32_t i, cnt;
    int oldsync, newsync;
    if (!entry){
      process_createfolder(NULL);
      if (st){
        psync_sql_free_result(st);
        st=NULL;
      }
      return;
    }
    if (!st){
      st=psync_sql_prep_statement("UPDATE folder SET parentfolderid=?, userid=?, permissions=?, name=?, ctime=?, mtime=?, flags=? WHERE id=?");
      if (!st)
        return;
    }
    meta=psync_find_result(entry, "metadata", PARAM_HASH);
    flags=0;
    if ((name=psync_check_result(meta, "encrypted", PARAM_BOOL)) && name->num)
      flags|=PSYNC_FOLDER_FLAG_ENCRYPTED;
    if (psync_find_result(meta, "ismine", PARAM_BOOL)->num){
      userid=psync_my_userid;
      perms=PSYNC_PERM_ALL;
    }
    else{
      userid=psync_find_result(meta, "userid", PARAM_NUM)->num;
      perms=psync_get_permissions(meta);
    }
    name=psync_find_result(meta, "name", PARAM_STR);
    folderid=psync_find_result(meta, "folderid", PARAM_NUM)->num;
    parentfolderid=psync_find_result(meta, "parentfolderid", PARAM_NUM)->num;
    res=psync_sql_query("SELECT parentfolderid, name FROM folder WHERE id=?");
    psync_sql_bind_uint(res, 1, folderid);
    vrow=psync_sql_fetch_row(res);
    if (likely(vrow)){
      oldparentfolderid=psync_get_number(vrow[0]);
      oldname=psync_dup_string(vrow[1]);
    }
    else{
      debug(D_ERROR, "got modify for non-existing folder %lu (%s), processing as create", (unsigned long)folderid, name->str);
      psync_sql_free_result(res);
      process_createfolder(entry);
      return;
    }
    psync_sql_free_result(res);
    mtime=psync_find_result(meta, "modified", PARAM_NUM)->num;
    psync_sql_bind_uint(st, 1, parentfolderid);
    psync_sql_bind_uint(st, 2, userid);
    psync_sql_bind_uint(st, 3, perms);
    psync_sql_bind_lstring(st, 4, name->str, name->length);
    psync_sql_bind_uint(st, 5, psync_find_result(meta, "created", PARAM_NUM)->num);
    psync_sql_bind_uint(st, 6, mtime);
    psync_sql_bind_uint(st, 7, flags);
    psync_sql_bind_uint(st, 8, folderid);
    psync_sql_run(st);
    if (oldparentfolderid!=parentfolderid){
      res=psync_sql_prep_statement("UPDATE folder SET subdircnt=subdircnt-1, mtime=? WHERE id=?");
      psync_sql_bind_uint(res, 1, mtime);
      psync_sql_bind_uint(res, 2, oldparentfolderid);
      psync_sql_run_free(res);
      res=psync_sql_prep_statement("UPDATE folder SET subdircnt=subdircnt+1, mtime=? WHERE id=?");
      psync_sql_bind_uint(res, 1, mtime);
      psync_sql_bind_uint(res, 2, parentfolderid);
      psync_sql_run_free(res);
    }
    /* We should check if oldparentfolderid is in downloadlist, not folderid. If parentfolderid is not in and
     * folderid is in, it means that folder that is a "root" of a syncid is modified, we do not care about that.
     */
    oldsync=psync_is_folder_in_downloadlist(oldparentfolderid);
    if (oldparentfolderid==parentfolderid)
      newsync=oldsync;
    else{
      newsync=psync_is_folder_in_downloadlist(parentfolderid);
      psync_diff_refresh_fs_add_folder(oldparentfolderid);
    }
    if ((oldsync || newsync) && (oldparentfolderid!=parentfolderid || strcmp(name->str, oldname))){
      if (!oldsync) 
        psync_add_folder_to_downloadlist(folderid);
      else if (!newsync){
        res=psync_sql_query("SELECT id FROM syncfolder WHERE folderid=? AND "PSYNC_SQL_DOWNLOAD);
        psync_sql_bind_uint(res, 1, folderid);
        if (!psync_sql_fetch_rowint(res))
          psync_del_folder_from_downloadlist(folderid);
        psync_sql_free_result(res);
      }
      res=psync_sql_query("SELECT syncid, localfolderid, synctype FROM syncedfolder WHERE folderid=? AND "PSYNC_SQL_DOWNLOAD);
      psync_sql_bind_uint(res, 1, oldparentfolderid);
      fres1=psync_sql_fetchall_int(res);
      res=psync_sql_query("SELECT syncid, localfolderid, synctype FROM syncedfolder WHERE folderid=? AND "PSYNC_SQL_DOWNLOAD);
      psync_sql_bind_uint(res, 1, parentfolderid);
      fres2=psync_sql_fetchall_int(res);
      if (psync_is_name_to_ignore(name->str))
        fres2->rows=0;
      group_results_by_col(fres1, fres2, 0);
      cnt=fres2->rows>fres1->rows?fres1->rows:fres2->rows;
      for (i=0; i<cnt; i++){
        res=psync_sql_query("SELECT localfolderid FROM syncedfolder WHERE folderid=? AND syncid=?");
        psync_sql_bind_uint(res, 1, folderid);
        psync_sql_bind_uint(res, 2, psync_get_result_cell(fres1, i, 0));
        row=psync_sql_fetch_rowint(res);
        if (unlikely(!row)){
          debug(D_ERROR, "could not find local folder of folderid %lu", (unsigned long)folderid);
          psync_sql_free_result(res);
          continue;
        }
        localfolderid=row[0];
        psync_sql_free_result(res);
        psync_task_rename_local_folder(psync_get_result_cell(fres2, i, 0), folderid, localfolderid, psync_get_result_cell(fres2, i, 1), name->str);
        needdownload=1;
        if (psync_get_result_cell(fres2, i, 0)!=psync_get_result_cell(fres1, i, 0)){
          res=psync_sql_prep_statement("UPDATE syncedfolder SET syncid=?, synctype=? WHERE syncid=? AND folderid=?");
          psync_sql_bind_uint(res, 1, psync_get_result_cell(fres2, i, 0));
          psync_sql_bind_uint(res, 2, psync_get_result_cell(fres2, i, 2));
          psync_sql_bind_uint(res, 3, psync_get_result_cell(fres1, i, 0));
          psync_sql_bind_uint(res, 4, folderid);
          psync_sql_run_free(res);
        }
      }
      for (/*i is already=cnt*/; i<fres1->rows; i++){
        syncid=psync_get_result_cell(fres1, i, 0);
        res=psync_sql_query("SELECT localfolderid FROM syncedfolder WHERE folderid=? AND syncid=?");
        psync_sql_bind_uint(res, 1, folderid);
        psync_sql_bind_uint(res, 2, syncid);
        row=psync_sql_fetch_rowint(res);
        if (unlikely(!row)){
          debug(D_ERROR, "could not find local folder of folderid %lu", (unsigned long)folderid);
          psync_sql_free_result(res);
          continue;
        }
        localfolderid=row[0];
        psync_sql_free_result(res);
        del_synced_folder_rec(folderid, syncid);
        psync_task_delete_local_folder_recursive(syncid, folderid, localfolderid);
        needdownload=1;
      }
      for (/*i is already=cnt*/; i<fres2->rows; i++){
        syncid=psync_get_result_cell(fres2, i, 0);
        debug(D_NOTICE, "creating local folder %lu/%s for folderid %lu, parentfolderid %lu", (unsigned long)psync_get_result_cell(fres2, i, 1), name->str, (unsigned long)folderid, (unsigned long)parentfolderid);
        localfolderid=psync_create_local_folder_in_db(syncid, folderid, psync_get_result_cell(fres2, i, 1), name->str);
        psync_task_create_local_folder(syncid, folderid, localfolderid);
        psync_add_folder_for_downloadsync(syncid, psync_get_result_cell(fres2, i, 2), folderid, localfolderid);
        needdownload=1;
      }
      psync_free(fres1);
      psync_free(fres2);
    }
    psync_free(oldname);
  }
  
  static void process_deletefolder(const binresult *entry){
    static psync_sql_res *st=NULL, *st2=NULL;
    const binresult *meta;
    psync_sql_res *res, *stmt;
    char *path;
    psync_folderid_t folderid;
    psync_uint_row row;
    if (!entry){
      if (st){
        psync_sql_free_result(st);
        st=NULL;
      }
      if (st2){
        psync_sql_free_result(st2);
        st2=NULL;
      }
      return;
    }
    if (!st){
      st=psync_sql_prep_statement("DELETE FROM folder WHERE id=?");
      if (!st)
        return;
      st2=psync_sql_prep_statement("UPDATE folder SET subdircnt=subdircnt-1, mtime=? WHERE id=?");
      if (!st2)
        return;
    }
    meta=psync_find_result(entry, "metadata", PARAM_HASH);
    folderid=psync_find_result(meta, "folderid", PARAM_NUM)->num;
    if (psync_is_folder_in_downloadlist(folderid)){
      psync_del_folder_from_downloadlist(folderid);
      res=psync_sql_query("SELECT syncid, localfolderid FROM syncedfolder WHERE folderid=?");
      psync_sql_bind_uint(res, 1, folderid);
      while ((row=psync_sql_fetch_rowint(res))){
        stmt=psync_sql_prep_statement("DELETE FROM syncedfolder WHERE syncid=? AND folderid=?");
        psync_sql_bind_uint(stmt, 1, row[0]);
        psync_sql_bind_uint(stmt, 2, folderid);
        psync_sql_run_free(stmt);
        if (psync_sql_affected_rows()==1){
          path=psync_get_path_by_folderid(folderid, NULL);
          psync_task_delete_local_folder(row[0], folderid, row[1], path);
          psync_free(path);
          needdownload=1;
        }
      }
      psync_sql_free_result(res);
    }
    psync_sql_bind_uint(st, 1, folderid);
    psync_sql_run(st);
    if (psync_sql_affected_rows()){
      psync_sql_bind_uint(st2, 1, psync_find_result(meta, "modified", PARAM_NUM)->num);
      psync_sql_bind_uint(st2, 2, psync_find_result(meta, "parentfolderid", PARAM_NUM)->num);
      psync_sql_run(st2);
      psync_fs_folder_deleted(folderid);
    }
  }
  
  static void check_for_deletedfileid(const binresult *meta){
    const binresult *delfileid;
    delfileid=psync_check_result(meta, "deletedfileid", PARAM_NUM);
    if (likely(!delfileid))
      return;
    else{
      psync_sql_res *res;
      res=psync_sql_prep_statement("DELETE FROM file WHERE id=?");
      psync_sql_bind_uint(res, 1, delfileid->num);
      psync_sql_run_free(res);
      psync_fs_file_deleted(delfileid->num);
    }
  }
  
  #define bind_num(s) psync_sql_bind_uint(res, off++, psync_find_result(meta, s, PARAM_NUM)->num)
  #define bind_bool(s) psync_sql_bind_uint(res, off++, psync_find_result(meta, s, PARAM_BOOL)->num)
  #define bind_str(s) do {br=psync_find_result(meta, s, PARAM_STR); psync_sql_bind_lstring(res, off++, br->str, br->length);} while (0)
  #define bind_opt_str(s) \
    do {\
      br=psync_check_result(meta, s, PARAM_STR);\
      if (br)\
        psync_sql_bind_lstring(res, off++, br->str, br->length);\
      else\
        psync_sql_bind_null(res, off++);\
    } while (0)
  #define bind_opt_num(s) \
    do {\
      br=psync_check_result(meta, s, PARAM_NUM);\
      if (br)\
        psync_sql_bind_uint(res, off++, br->num);\
      else\
        psync_sql_bind_null(res, off++);\
    } while (0)
  #define bind_opt_double(s) \
    do {\
      br=psync_check_result(meta, s, PARAM_STR);\
      if (br)\
        psync_sql_bind_double(res, off++, atof(br->str));\
      else\
        psync_sql_bind_null(res, off++);\
    } while (0)
    
  static int bind_meta(psync_sql_res *res, const binresult *meta, int off){
    const binresult *br;
    bind_num("created");
    bind_num("modified");
    bind_num("category");
    bind_bool("thumb");
    bind_str("icon");
    bind_opt_str("artist");
    bind_opt_str("album");
    bind_opt_str("title");
    bind_opt_str("genre");
    bind_opt_num("trackno");
    bind_opt_num("width");
    bind_opt_num("height");
    bind_opt_double("duration");
    bind_opt_double("fps");
    bind_opt_str("videocodec");
    bind_opt_str("audiocodec");
    bind_opt_num("videobitrate");
    bind_opt_num("audiobitrate");
    bind_opt_num("audiosamplerate");
    bind_opt_num("rotate");
    return off;
  }
  
  static void insert_revision(psync_fileid_t fileid, uint64_t hash, uint64_t ctime, uint64_t size){
    static psync_sql_res *st=NULL;
    if (!fileid){
      if (st){
        psync_sql_free_result(st);
        st=NULL;
      }
      return;
    }
    if (!st)
      st=psync_sql_prep_statement("REPLACE INTO filerevision (fileid, hash, ctime, size) VALUES (?, ?, ?, ?)");
    psync_sql_bind_uint(st, 1, fileid);
    psync_sql_bind_uint(st, 2, hash);
    psync_sql_bind_uint(st, 3, ctime);
    psync_sql_bind_uint(st, 4, size);
    psync_sql_run(st);
  }
  
  static void process_createfile(const binresult *entry){
    static psync_sql_res *st=NULL;
    const binresult *meta, *name;
    psync_sql_res *res, *res2;
    psync_folderid_t parentfolderid;
    psync_fileid_t fileid;
    uint64_t size, userid, hash;
    psync_uint_row row;
    psync_str_row row2;
    int hasit;
    if (!entry){
      if (st){
        psync_sql_free_result(st);
        st=NULL;
      }
      insert_revision(0, 0, 0, 0);
      return;
    }
    if (!st)
      st=psync_sql_prep_statement("INSERT OR IGNORE INTO file (id, parentfolderid, userid, size, hash, name, ctime, mtime, category, thumb, icon, "
                                  "artist, album, title, genre, trackno, width, height, duration, fps, videocodec, audiocodec, videobitrate, "
                                  "audiobitrate, audiosamplerate, rotate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
    meta=psync_find_result(entry, "metadata", PARAM_HASH);
    size=psync_find_result(meta, "size", PARAM_NUM)->num;
    fileid=psync_find_result(meta, "fileid", PARAM_NUM)->num;
    parentfolderid=psync_find_result(meta, "parentfolderid", PARAM_NUM)->num;
    if (psync_find_result(meta, "ismine", PARAM_BOOL)->num){
      userid=psync_my_userid;
      used_quota+=size;
    }
    else
      userid=psync_find_result(meta, "userid", PARAM_NUM)->num;
    hash=psync_find_result(meta, "hash", PARAM_NUM)->num;
    name=psync_find_result(meta, "name", PARAM_STR);
    check_for_deletedfileid(meta);
    psync_sql_bind_uint(st, 1, fileid);
    psync_sql_bind_uint(st, 2, parentfolderid);
    psync_sql_bind_uint(st, 3, userid);
    psync_sql_bind_uint(st, 4, size);
    psync_sql_bind_uint(st, 5, hash);
    psync_sql_bind_lstring(st, 6, name->str, name->length);
    bind_meta(st, meta, 7);
    psync_sql_run(st);
    if (!psync_sql_affected_rows()){
      int off;
      res=psync_sql_prep_statement("UPDATE file SET id=?, parentfolderid=?, userid=?, size=?, hash=?, name=?, ctime=?, mtime=?, category=?, thumb=?, icon=?, "
                                  "artist=?, album=?, title=?, genre=?, trackno=?, width=?, height=?, duration=?, fps=?, videocodec=?, audiocodec=?, videobitrate=?, "
                                  "audiobitrate=?, audiosamplerate=?, rotate=? WHERE id=?");
      psync_sql_bind_uint(res, 1, fileid);
      psync_sql_bind_uint(res, 2, parentfolderid);
      psync_sql_bind_uint(res, 3, userid);
      psync_sql_bind_uint(res, 4, size);
      psync_sql_bind_uint(res, 5, hash);
      psync_sql_bind_lstring(res, 6, name->str, name->length);
      off=bind_meta(res, meta, 7);
      psync_sql_bind_uint(res, off, fileid);
      psync_sql_run_free(res);
    }
    insert_revision(fileid, hash, psync_find_result(meta, "modified", PARAM_NUM)->num, size);
    if (psync_is_folder_in_downloadlist(parentfolderid) && !psync_is_name_to_ignore(name->str)){
      res=psync_sql_query("SELECT syncid, localfolderid FROM syncedfolder WHERE folderid=? AND "PSYNC_SQL_DOWNLOAD);
      psync_sql_bind_uint(res, 1, parentfolderid);
      while ((row=psync_sql_fetch_rowint(res))){
        res2=psync_sql_query("SELECT name FROM localfile WHERE syncid=? AND localparentfolderid=? AND hash=? AND fileid=?");
        psync_sql_bind_uint(res2, 1, row[0]);
        psync_sql_bind_uint(res2, 2, row[1]);
        psync_sql_bind_uint(res2, 3, hash);
        psync_sql_bind_uint(res2, 4, fileid);
        if ((row2=psync_sql_fetch_rowstr(res2)))
          hasit=1;
        else
          hasit=0;
        psync_sql_free_result(res2);
        if (!hasit){
          debug(D_NOTICE, "downloading file %s with hash %ld to local folder %lu", name->str, (long)hash, (unsigned long)row[1]);
          psync_task_download_file_silent(row[0], fileid, row[1], name->str);
          needdownload=1;
        }
        else
          debug(D_NOTICE, "file %s with hash %ld already exists in local folder %lu", name->str, (long)hash, (unsigned long)row[1]);
      }
      psync_sql_free_result(res);
    }
  }
  
  static void process_modifyfile(const binresult *entry){
    static psync_sql_res *sq=NULL, *st=NULL;
    psync_sql_res *res;
    psync_full_result_int *fres1, *fres2;
    const binresult *meta, *name;
    const char *oldname;
    size_t oldnamelen;
    psync_variant_row row;
    psync_fileid_t fileid;
    psync_folderid_t parentfolderid, oldparentfolderid;
    uint64_t size, userid, hash, oldsize;
    int oldsync, newsync, lneeddownload, needrename;
    uint32_t cnt, i;
    if (!entry){
      if (sq){
        psync_sql_free_result(sq);
        sq=NULL;
      }
      if (st){
        psync_sql_free_result(st);
        st=NULL;
      }
      process_createfile(NULL);
      return;
    }
    meta=psync_find_result(entry, "metadata", PARAM_HASH);
    fileid=psync_find_result(meta, "fileid", PARAM_NUM)->num;
    name=psync_find_result(meta, "name", PARAM_STR);
    if (sq)
      psync_sql_reset(sq);
    else
      sq=psync_sql_query("SELECT parentfolderid, userid, size, hash, name FROM file WHERE id=?");
    psync_sql_bind_uint(sq, 1, fileid);
    row=psync_sql_fetch_row(sq);
    if (!row){
      debug(D_ERROR, "got modify for non-existing file %lu (%s), processing as create", (unsigned long)fileid, name->str);
      process_createfile(entry);
      return;
    }
    oldsize=psync_get_number(row[2]);
    if (psync_get_number(row[1])==psync_my_userid)
      used_quota-=oldsize;
    if (!st)
      st=psync_sql_prep_statement("UPDATE file SET id=?, parentfolderid=?, userid=?, size=?, hash=?, name=?, ctime=?, mtime=?, category=?, thumb=?, icon=?, "
                                  "artist=?, album=?, title=?, genre=?, trackno=?, width=?, height=?, duration=?, fps=?, videocodec=?, audiocodec=?, videobitrate=?, "
                                  "audiobitrate=?, audiosamplerate=?, rotate=? WHERE id=?");
    size=psync_find_result(meta, "size", PARAM_NUM)->num;
    parentfolderid=psync_find_result(meta, "parentfolderid", PARAM_NUM)->num;
    hash=psync_find_result(meta, "hash", PARAM_NUM)->num;
    if (psync_find_result(meta, "ismine", PARAM_BOOL)->num){
      userid=psync_my_userid;
      used_quota+=size;
    }
    else
      userid=psync_find_result(meta, "userid", PARAM_NUM)->num;
    check_for_deletedfileid(meta);
    psync_sql_bind_uint(st, 1, fileid);
    psync_sql_bind_uint(st, 2, parentfolderid);
    psync_sql_bind_uint(st, 3, userid);
    psync_sql_bind_uint(st, 4, size);
    psync_sql_bind_uint(st, 5, hash);
    psync_sql_bind_lstring(st, 6, name->str, name->length);
    i=bind_meta(st, meta, 7);
    psync_sql_bind_uint(st, i, fileid);
    psync_sql_run(st);
    insert_revision(fileid, hash, psync_find_result(meta, "modified", PARAM_NUM)->num, size);
    oldparentfolderid=psync_get_number(row[0]);
    oldsync=psync_is_folder_in_downloadlist(oldparentfolderid);
    if (oldparentfolderid==parentfolderid)
      newsync=oldsync;
    else{
      newsync=psync_is_folder_in_downloadlist(parentfolderid);
      psync_diff_refresh_fs_add_folder(oldparentfolderid);
    }
    if (oldsync || newsync){
      if (psync_is_name_to_ignore(name->str)){
        char *path;
        psync_delete_download_tasks_for_file(fileid);
        path=psync_get_path_by_fileid(fileid, NULL);
        psync_task_delete_local_file(fileid, path);
        psync_free(path);
        needdownload=1;
        return;
      }
      lneeddownload=hash!=psync_get_number(row[3]) || size!=oldsize;
      oldname=psync_get_lstring(row[4], &oldnamelen);
      if (lneeddownload)
        psync_delete_download_tasks_for_file(fileid);
      needrename=oldparentfolderid!=parentfolderid || name->length!=oldnamelen || memcmp(name->str, oldname, oldnamelen);
      res=psync_sql_query("SELECT syncid, localfolderid, synctype FROM syncedfolder WHERE folderid=? AND "PSYNC_SQL_DOWNLOAD);
      psync_sql_bind_uint(res, 1, oldparentfolderid);
      fres1=psync_sql_fetchall_int(res);
      res=psync_sql_query("SELECT syncid, localfolderid, synctype FROM syncedfolder WHERE folderid=? AND "PSYNC_SQL_DOWNLOAD);
      psync_sql_bind_uint(res, 1, parentfolderid);
      fres2=psync_sql_fetchall_int(res);
      group_results_by_col(fres1, fres2, 0);
      cnt=fres2->rows>fres1->rows?fres1->rows:fres2->rows;
      for (i=0; i<cnt; i++){
        if (needrename){
          psync_task_rename_local_file(psync_get_result_cell(fres1, i, 0), psync_get_result_cell(fres2, i, 0), fileid,
                                       psync_get_result_cell(fres1, i, 1), psync_get_result_cell(fres2, i, 1),
                                       name->str);
          needdownload=1;
        }
        if (lneeddownload){
          res=psync_sql_query("SELECT 1 FROM localfile WHERE localparentfolderid=? AND fileid=? AND hash=? AND syncid=?");
          psync_sql_bind_uint(res, 1, psync_get_result_cell(fres2, i, 1));
          psync_sql_bind_uint(res, 2, fileid);
          psync_sql_bind_uint(res, 3, hash);
          psync_sql_bind_uint(res, 4, psync_get_result_cell(fres2, i, 0));
          row=psync_sql_fetch_row(res);
          psync_sql_free_result(res);
          if (row)
            debug(D_NOTICE, "ignoring update for file %s, has correct hash in the database", name->str);
          else{
            psync_task_download_file_silent(psync_get_result_cell(fres2, i, 0), fileid, psync_get_result_cell(fres2, i, 1), name->str);
            needdownload=1;
          }
        }
      }
      for (/*i is already=cnt*/; i<fres2->rows; i++){
        psync_task_download_file_silent(psync_get_result_cell(fres2, i, 0), fileid, psync_get_result_cell(fres2, i, 1), name->str);
        needdownload=1;
      }
      for (/*i is already=cnt*/; i<fres1->rows; i++){
        char *path=psync_get_path_by_fileid(fileid, NULL);
        psync_task_delete_local_file_syncid(psync_get_result_cell(fres1, i, 0), fileid, path);
        psync_free(path);
        needdownload=1;
      }
      psync_free(fres1);
      psync_free(fres2);
    }
  }
  
  static void process_deletefile(const binresult *entry){
    static psync_sql_res *st=NULL;
    const binresult *meta;
    char *path;
    psync_fileid_t fileid;
    if (!entry){
      if (st){
        psync_sql_free_result(st);
        st=NULL;
      }
      return;
    }
    if (!st){
      st=psync_sql_prep_statement("DELETE FROM file WHERE id=?");
      if (!st)
        return;
    }
    meta=psync_find_result(entry, "metadata", PARAM_HASH);
    fileid=psync_find_result(meta, "fileid", PARAM_NUM)->num;
    if (psync_is_folder_in_downloadlist(psync_find_result(meta, "parentfolderid", PARAM_NUM)->num)){
      psync_delete_download_tasks_for_file(fileid);
      path=psync_get_path_by_fileid(fileid, NULL);
      if (likely(path)){
        psync_task_delete_local_file(fileid, path);
        psync_free(path);
        needdownload=1;
      }
    }
    psync_sql_bind_uint(st, 1, fileid);
    psync_sql_run(st);
    if (psync_sql_affected_rows()){
      if (psync_find_result(meta, "ismine", PARAM_BOOL)->num)
        used_quota-=psync_find_result(meta, "size", PARAM_NUM)->num;
      psync_fs_file_deleted(fileid);
    }
  }
  
  void psync_diff_create_file(const binresult *meta){
    psync_sql_res *st;
    const binresult *name;
    uint64_t userid, fileid, hash, size;
    st=psync_sql_prep_statement("INSERT OR IGNORE INTO file (id, parentfolderid, userid, size, hash, name, ctime, mtime, category, thumb, icon, "
                                  "artist, album, title, genre, trackno, width, height, duration, fps, videocodec, audiocodec, videobitrate, "
                                  "audiobitrate, audiosamplerate, rotate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
    name=psync_find_result(meta, "name", PARAM_STR);
    check_for_deletedfileid(meta);
    if (psync_find_result(meta, "ismine", PARAM_BOOL)->num)
      userid=psync_my_userid;
    else
      userid=psync_find_result(meta, "userid", PARAM_NUM)->num;
    fileid=psync_find_result(meta, "fileid", PARAM_NUM)->num;
    size=psync_find_result(meta, "size", PARAM_NUM)->num;
    hash=psync_find_result(meta, "hash", PARAM_NUM)->num;
    psync_sql_bind_uint(st, 1, fileid);
    psync_sql_bind_uint(st, 2, psync_find_result(meta, "parentfolderid", PARAM_NUM)->num);
    psync_sql_bind_uint(st, 3, userid);
    psync_sql_bind_uint(st, 4, size);
    psync_sql_bind_uint(st, 5, hash);
    psync_sql_bind_lstring(st, 6, name->str, name->length);
    bind_meta(st, meta, 7);
    psync_sql_run_free(st);
    insert_revision(fileid, hash, psync_find_result(meta, "modified", PARAM_NUM)->num, size);
    insert_revision(0, 0, 0, 0);
  }
  
  static void start_download(){
    if (needdownload){
      psync_wake_download();
      psync_status_recalc_to_download();
      psync_send_status_update();
      needdownload=0;
    }
  }
  
  #define create_entry() \
    binresult entry;\
    hashpair pair;\
    entry.type=PARAM_HASH;\
    entry.length=1;\
    entry.hash=&pair;\
    pair.key="metadata";\
    pair.value=(binresult *)meta;
    
  void psync_diff_update_file(const binresult *meta){
    create_entry();
    process_modifyfile(&entry);
    process_modifyfile(NULL);
    start_download();
  }
  
  void psync_diff_delete_file(const binresult *meta){
    create_entry();
    process_deletefile(&entry);
    process_deletefile(NULL);
    start_download();
  }
  
  void psync_diff_update_folder(const binresult *meta){
    create_entry();
    process_modifyfolder(&entry);
    process_modifyfolder(NULL);
    start_download();
  }
  
  void psync_diff_delete_folder(const binresult *meta){
    create_entry();
    process_deletefolder(&entry);
    process_deletefolder(NULL);
    start_download();
  }
  
  static void process_modifyuserinfo(const binresult *entry){
    const binresult *res;
    psync_sql_res *q;
    uint64_t u;
    if (!entry)
      return;
    res=psync_find_result(entry, "userinfo", PARAM_HASH);
    q=psync_sql_prep_statement("REPLACE INTO setting (id, value) VALUES (?, ?)");
    psync_sql_bind_string(q, 1, "userid");
    psync_sql_bind_uint(q, 2, psync_find_result(res, "userid", PARAM_NUM)->num);
    psync_sql_run(q);
    psync_sql_bind_string(q, 1, "quota");
    current_quota=psync_find_result(res, "quota", PARAM_NUM)->num;
    psync_sql_bind_uint(q, 2, current_quota);
    psync_sql_run(q);
    u=psync_find_result(res, "premium", PARAM_BOOL)->num;
    psync_sql_bind_string(q, 1, "premium");
    psync_sql_bind_uint(q, 2, u);
    psync_sql_run(q);
    if (u)
      u=psync_find_result(res, "premiumexpires", PARAM_NUM)->num;
    else
      u=0;
    psync_sql_bind_string(q, 1, "premiumexpires");
    psync_sql_bind_uint(q, 2, u);
    psync_sql_run(q);
    psync_sql_bind_string(q, 1, "emailverified");
    psync_sql_bind_uint(q, 2, psync_find_result(res, "emailverified", PARAM_BOOL)->num);
    psync_sql_run(q);
    psync_sql_bind_string(q, 1, "username");
    psync_sql_bind_string(q, 2, psync_find_result(res, "email", PARAM_STR)->str);
    psync_sql_run(q);
    psync_sql_bind_string(q, 1, "language");
    psync_sql_bind_string(q, 2, psync_find_result(res, "language", PARAM_STR)->str);
    psync_sql_run(q);
    psync_sql_free_result(q);
    psync_send_eventid(PEVENT_USERINFO_CHANGED);
  }
  
  #define fill_str(f, s, sl)\
    do {\
      if (s && sl) {\
        memcpy(str, s, sl);\
        f=str;\
        str+=sl;\
      }\
      else\
        f="";\
    } while (0);
  
  static void send_share_notify(psync_eventtype_t eventid, const binresult *share){
    psync_share_event_t *e;
    char *str, *sharename;
    const char *email, *message;
    const binresult *br;
    uint64_t ctime;
    size_t stringslen, sharenamelen, emaillen, messagelen;
    int freesharename;
    if (initialdownload)
      return;
    stringslen=0;
    ctime=0;
    if (!(br=psync_check_result(share, "frommail", PARAM_STR)) && !(br=psync_check_result(share, "tomail", PARAM_STR))){
      debug(D_WARNING, "Neigher frommail or tomail found for eventtype %u", (unsigned)eventid);
      return;
    }
    email=br->str;
    emaillen=br->length+1;
    stringslen+=br->length+1;
    if ((br=psync_check_result(share, "message", PARAM_STR))){
      message=br->str;
      messagelen=br->length+1;
      stringslen+=br->length+1;
    }
    else{
      message=NULL;
      messagelen=0;
    }
    if ((br=psync_check_result(share, "sharename", PARAM_STR))){
      sharename=(char *)br->str;
      sharenamelen=br->length+1;
      stringslen+=br->length+1;
      freesharename=0;
    }
    else{
      psync_sql_res *res;
      psync_variant_row row;
      const char *cstr;
      if ((br=psync_check_result(share, "shareid", PARAM_NUM)))
        res=psync_sql_query("SELECT name, ctime FROM sharedfolder WHERE id=?");
      else if ((br=psync_check_result(share, "sharerequestid", PARAM_NUM)))
        res=psync_sql_query("SELECT name, ctime FROM sharerequest WHERE id=?");
      else {
        debug(D_WARNING, "Neither sharename, shareid or sharerequestid found for eventtype %u", (unsigned)eventid);
        return;
      }
      psync_sql_bind_uint(res, 1, br->num);
      if ((row=psync_sql_fetch_row(res))){
        cstr=psync_get_lstring(row[0], &sharenamelen);
        stringslen+=++sharenamelen;
        sharename=(char *)psync_malloc(sharenamelen);
        memcpy(sharename, cstr, sharenamelen);
        freesharename=1;
        ctime=psync_get_number(row[1]);
      }
      else
        sharename=NULL;
      psync_sql_free_result(res);
      if (!sharename){
        debug(D_WARNING, "Could not find sharedfolder or sharerequest in the database.");
        return;
      }
    }
    e=(psync_share_event_t *)psync_malloc(sizeof(psync_share_event_t)+stringslen);
    str=(char *)(e+1);
    memset(e, 0, sizeof(psync_share_event_t));
    e->folderid=psync_find_result(share, "folderid", PARAM_NUM)->num;
    fill_str(e->sharename, sharename, sharenamelen);
    if (freesharename)
      psync_free(sharename);
    fill_str(e->email, email, emaillen);
    fill_str(e->message, message, messagelen);
    if ((br=psync_check_result(share, "userid", PARAM_NUM)))
      e->userid=br->num;
    if ((br=psync_check_result(share, "shareid", PARAM_NUM)))
      e->shareid=br->num;
    if ((br=psync_check_result(share, "sharerequestid", PARAM_NUM)))
      e->sharerequestid=br->num;
    if ((br=psync_check_result(share, "created", PARAM_NUM)))
      ctime=br->num;
    e->created=ctime;
    e->canread=psync_find_result(share, "canread", PARAM_BOOL)->num;
    e->cancreate=psync_find_result(share, "cancreate", PARAM_BOOL)->num;
    e->canmodify=psync_find_result(share, "canmodify", PARAM_BOOL)->num;
    e->candelete=psync_find_result(share, "candelete", PARAM_BOOL)->num;
    psync_send_eventdata(eventid, e);
  }
  
  static void process_requestsharein(const binresult *entry){
    psync_sql_res *q;
    const binresult *share, *br;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_REQUESTIN, share);
    q=psync_sql_prep_statement("REPLACE INTO sharerequest (id, isincoming, folderid, ctime, etime, permissions, userid, mail, name, message) "
                                                  "VALUES (?, 1, ?, ?, ?, ?, ?, ?, ?, ?)");
    psync_sql_bind_uint(q, 1, psync_find_result(share, "sharerequestid", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 2, psync_find_result(share, "folderid", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 3, psync_find_result(share, "created", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 4, psync_find_result(share, "expires", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 5, psync_get_permissions(share));
    psync_sql_bind_uint(q, 6, psync_find_result(share, "fromuserid", PARAM_NUM)->num);
    br=psync_find_result(share, "frommail", PARAM_STR);
    psync_sql_bind_lstring(q, 7, br->str, br->length);
    br=psync_find_result(share, "sharename", PARAM_STR);
    psync_sql_bind_lstring(q, 8, br->str, br->length);
    br=psync_check_result(share, "message", PARAM_STR);
    if (br)
      psync_sql_bind_lstring(q, 9, br->str, br->length);
    else
      psync_sql_bind_null(q, 9);
    psync_sql_run_free(q);
  }
  
  static void process_requestshareout(const binresult *entry){
    psync_sql_res *q;
    const binresult *share, *br;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_REQUESTOUT, share);
    q=psync_sql_prep_statement("REPLACE INTO sharerequest (id, isincoming, folderid, ctime, etime, permissions, userid, mail, name, message) "
                                                  "VALUES (?, 0, ?, ?, ?, ?, ?, ?, ?, ?)");
    psync_sql_bind_uint(q, 1, psync_find_result(share, "sharerequestid", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 2, psync_find_result(share, "folderid", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 3, psync_find_result(share, "created", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 4, psync_find_result(share, "expires", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 5, psync_get_permissions(share));
    psync_sql_bind_null(q, 6);
    br=psync_find_result(share, "tomail", PARAM_STR);
    psync_sql_bind_lstring(q, 7, br->str, br->length);
    br=psync_find_result(share, "sharename", PARAM_STR);
    psync_sql_bind_lstring(q, 8, br->str, br->length);
    br=psync_check_result(share, "message", PARAM_STR);
    if (br)
      psync_sql_bind_lstring(q, 9, br->str, br->length);
    else
      psync_sql_bind_null(q, 9);
    psync_sql_run_free(q);
  }
  
  static void process_acceptedsharein(const binresult *entry){
    psync_sql_res *q;
    const binresult *share, *br;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_ACCEPTIN, share);
    q=psync_sql_prep_statement("DELETE FROM sharerequest WHERE id=?");
    psync_sql_bind_uint(q, 1, psync_find_result(share, "sharerequestid", PARAM_NUM)->num);
    psync_sql_run_free(q);
    q=psync_sql_prep_statement("REPLACE INTO sharedfolder (id, isincoming, folderid, ctime, permissions, userid, mail, name) "
                                                  "VALUES (?, 1, ?, ?, ?, ?, ?, ?)");
    psync_sql_bind_uint(q, 1, psync_find_result(share, "shareid", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 2, psync_find_result(share, "folderid", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 3, psync_find_result(share, "created", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 4, psync_get_permissions(share));
    psync_sql_bind_uint(q, 5, psync_find_result(share, "fromuserid", PARAM_NUM)->num);
    br=psync_find_result(share, "frommail", PARAM_STR);
    psync_sql_bind_lstring(q, 6, br->str, br->length);
    br=psync_find_result(share, "sharename", PARAM_STR);
    psync_sql_bind_lstring(q, 7, br->str, br->length);
    psync_sql_run_free(q);
  }
  
  static void process_acceptedshareout(const binresult *entry){
    psync_sql_res *q;
    const binresult *share, *br;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_ACCEPTOUT, share);
    q=psync_sql_prep_statement("DELETE FROM sharerequest WHERE id=?");
    psync_sql_bind_uint(q, 1, psync_find_result(share, "sharerequestid", PARAM_NUM)->num);
    psync_sql_run_free(q);
    q=psync_sql_prep_statement("REPLACE INTO sharedfolder (id, isincoming, folderid, ctime, permissions, userid, mail, name) "
                                                  "VALUES (?, 0, ?, ?, ?, ?, ?, ?)");
    psync_sql_bind_uint(q, 1, psync_find_result(share, "shareid", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 2, psync_find_result(share, "folderid", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 3, psync_find_result(share, "created", PARAM_NUM)->num);
    psync_sql_bind_uint(q, 4, psync_get_permissions(share));
    psync_sql_bind_uint(q, 5, psync_find_result(share, "touserid", PARAM_NUM)->num);
    br=psync_find_result(share, "tomail", PARAM_STR);
    psync_sql_bind_lstring(q, 6, br->str, br->length);
    br=psync_find_result(share, "sharename", PARAM_STR);
    psync_sql_bind_lstring(q, 7, br->str, br->length);
    psync_sql_run_free(q);
  }
  
  static void delete_share_request(const binresult *share){
    psync_sql_res *q;
    q=psync_sql_prep_statement("DELETE FROM sharerequest WHERE id=?");
    psync_sql_bind_uint(q, 1, psync_find_result(share, "sharerequestid", PARAM_NUM)->num);
    psync_sql_run_free(q);
  }
  
  static void process_declinedsharein(const binresult *entry){
    const binresult *share;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_DECLINEIN, share);
    delete_share_request(share);
  }
  
  static void process_declinedshareout(const binresult *entry){
    const binresult *share;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_DECLINEOUT, share);
    delete_share_request(share);
  }
  
  static void process_cancelledsharein(const binresult *entry){
    const binresult *share;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_CANCELIN, share);
    delete_share_request(share);
  }
  
  static void process_cancelledshareout(const binresult *entry){
    const binresult *share;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_CANCELOUT, share);
    delete_share_request(share);
  }
  
  static void delete_shared_folder(const binresult *share){
    psync_sql_res *q;
    q=psync_sql_prep_statement("DELETE FROM sharedfolder WHERE id=?");
    psync_sql_bind_uint(q, 1, psync_find_result(share, "shareid", PARAM_NUM)->num);
    psync_sql_run_free(q);
  }
  
  static void process_removedsharein(const binresult *entry){
    const binresult *share;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_REMOVEIN, share);
    delete_shared_folder(share);
  }
  
  static void process_removedshareout(const binresult *entry){
    const binresult *share;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_REMOVEOUT, share);
    delete_shared_folder(share);
  }
  
  static void modify_shared_folder(const binresult *share){
    psync_sql_res *q;
    q=psync_sql_prep_statement("UPDATE sharedfolder SET permissions=? WHERE id=?");
    psync_sql_bind_uint(q, 1, psync_get_permissions(share));
    psync_sql_bind_uint(q, 2, psync_find_result(share, "shareid", PARAM_NUM)->num);
    psync_sql_run_free(q);
  }
  
  static void process_modifiedsharein(const binresult *entry){
    const binresult *share;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_MODIFYIN, share);
    modify_shared_folder(share);
  }
  
  static void process_modifiedshareout(const binresult *entry){
    const binresult *share;
    if (!entry)
      return;
    share=psync_find_result(entry, "share", PARAM_HASH);
    send_share_notify(PEVENT_SHARE_MODIFYOUT, share);
    modify_shared_folder(share);
  }
  
  #define FN(n) {process_##n, #n, sizeof(#n)-1, 0}
  
  static struct {
    void (*process)(const binresult *);
    const char *name;
    uint32_t len;
    uint8_t used;
  } event_list[] = {
    FN(createfolder),
    FN(modifyfolder),
    FN(deletefolder),
    FN(createfile),
    FN(modifyfile),
    FN(deletefile),
    FN(modifyuserinfo),
    FN(requestsharein),
    FN(requestshareout),
    FN(acceptedsharein),
    FN(acceptedshareout),
    FN(declinedsharein),
    FN(declinedshareout),
    FN(cancelledsharein),
    FN(cancelledshareout),
    FN(removedsharein),
    FN(removedshareout),
    FN(modifiedsharein),
    FN(modifiedshareout)
  };
  
  #define event_list_size ARRAY_SIZE(event_list)
  
  void psync_diff_lock(){
    pthread_mutex_lock(&diff_mutex);
  }
  
  void psync_diff_unlock(){
    pthread_mutex_unlock(&diff_mutex);
  }
  
  static uint64_t process_entries(const binresult *entries, uint64_t newdiffid){
    const binresult *entry, *etype;
    uint64_t oused_quota;
    uint32_t i, j;
    oused_quota=used_quota;
    needdownload=0;
    psync_diff_lock();
    if (psync_status_get(PSTATUS_TYPE_AUTH)!=PSTATUS_AUTH_PROVIDED){
      psync_diff_unlock();
      return psync_sql_cellint("SELECT value FROM setting WHERE id='diffid'", 0);
    }
    psync_sql_start_transaction();
    for (i=0; i<entries->length; i++){
      entry=entries->array[i];
      etype=psync_find_result(entry, "event", PARAM_STR);
      for (j=0; j<event_list_size; j++)
        if (etype->length==event_list[j].len && !memcmp(etype->str, event_list[j].name, etype->length)){
          event_list[j].process(entry);
          event_list[j].used=1;
        }
    }
    for (j=0; j<event_list_size; j++)
      if (event_list[j].used)
        event_list[j].process(NULL);
    psync_set_uint_value("diffid", newdiffid);
    psync_set_uint_value("usedquota", used_quota);
    psync_sql_commit_transaction();
    psync_diff_unlock();
    if (needdownload){
      psync_wake_download();
      psync_status_recalc_to_download();
      psync_send_status_update();
      needdownload=0;
    }
    used_quota=psync_sql_cellint("SELECT value FROM setting WHERE id='usedquota'", 0);
    if (oused_quota!=used_quota)
      psync_send_eventid(PEVENT_USEDQUOTA_CHANGED);
    return psync_sql_cellint("SELECT value FROM setting WHERE id='diffid'", 0);
  }
  
  static void check_overquota(){
    static int lisover=0;
    int isover=(used_quota>=current_quota);
    if (isover!=lisover){
      lisover=isover;
      if (isover)
        psync_set_status(PSTATUS_TYPE_ACCFULL, PSTATUS_ACCFULL_OVERQUOTA);
      else
        psync_set_status(PSTATUS_TYPE_ACCFULL, PSTATUS_ACCFULL_QUOTAOK);
    }
  }
  
  static void diff_exception_handler(){
    debug(D_NOTICE, "got exception");
    if (likely(exceptionsockwrite!=INVALID_SOCKET))
      psync_pipe_write(exceptionsockwrite, "e", 1);
  }
  
  static psync_socket_t setup_exeptions(){
    psync_socket_t pfds[2];
    if (psync_pipe(pfds)==SOCKET_ERROR)
      return INVALID_SOCKET;
    exceptionsockwrite=pfds[1];
    psync_timer_exception_handler(diff_exception_handler);
    return pfds[0];
  }
  
  static int send_diff_command(psync_socket *sock, uint64_t diffid){
    binparam diffparams[]={P_STR("timeformat", "timestamp"), P_NUM("limit", PSYNC_DIFF_LIMIT), P_NUM("diffid", diffid), P_BOOL("block", 1)};
    return send_command_no_res(sock, "diff", diffparams)?0:-1;
  }
  
  static void handle_exception(psync_socket **sock, uint64_t *diffid, char ex){
    debug(D_NOTICE, "exception handler %c", ex);
    if (ex=='r' || 
        psync_status_get(PSTATUS_TYPE_RUN)==PSTATUS_RUN_STOP || 
        psync_status_get(PSTATUS_TYPE_AUTH)!=PSTATUS_AUTH_PROVIDED ||
        psync_setting_get_bool(_PS(usessl))!=psync_socket_isssl(*sock)){
      psync_socket_close(*sock);
      debug(D_NOTICE, "waiting for new socket");
      *sock=get_connected_socket();
      debug(D_NOTICE, "got new socket");
      psync_set_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_ONLINE);
      psync_syncer_check_delayed_syncs();
      *diffid=psync_sql_cellint("SELECT value FROM setting WHERE id='diffid'", 0);
      send_diff_command(*sock, *diffid);
    }
    else if (ex=='e'){
      binparam diffparams[]={P_STR("id", "ignore")};
      if (!send_command_no_res(*sock, "nop", diffparams) || psync_select_in(&(*sock)->sock, 1, PSYNC_SOCK_TIMEOUT_ON_EXCEPTION*1000)!=0){
        debug(D_NOTICE, "reconnecting diff");
        psync_socket_close_bad(*sock);
        *sock=get_connected_socket();
        psync_set_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_ONLINE);
        psync_syncer_check_delayed_syncs();
        send_diff_command(*sock, *diffid);
      }
      else{
        debug(D_NOTICE, "diff socket seems to be alive");
        (*sock)->pending=1;
      }
    }
  }
  
  static int cmp_folderid(const void *ptr1, const void *ptr2){
    psync_folderid_t *folderid1=(psync_folderid_t *)ptr1;
    psync_folderid_t *folderid2=(psync_folderid_t *)ptr2;
    if (folderid1<folderid2)
      return -1;
    else if (folderid1>folderid2)
      return 1;
    else
      return 0;
  }
  
  static psync_folderid_t *refresh_folders=NULL;
  static uint32_t refresh_allocated=0;
  static uint32_t refresh_last=0;
  
  static void psync_diff_refresh_fs_add_folder(psync_folderid_t folderid){
    if (psync_fs_need_per_folder_refresh()){
      if (refresh_allocated==refresh_last){
        if (refresh_allocated)
          refresh_allocated*=2;
        else
          refresh_allocated=8;
        refresh_folders=(psync_folderid_t *)psync_realloc(refresh_folders, sizeof(psync_folderid_t)*refresh_allocated);
      }
      refresh_folders[refresh_last++]=folderid;
    }
  }
  
  static void psync_diff_refresh_fs(const binresult *entries){
    if (psync_fs_need_per_folder_refresh()){
      const binresult *meta;
      psync_folderid_t folderid, lastfolderid;
      uint32_t i;
      lastfolderid=(psync_folderid_t)-1;
      for (i=0; i<entries->length; i++){
        meta=psync_check_result(entries->array[i], "metadata", PARAM_HASH);
        if (!meta)
          continue;
        meta=psync_check_result(meta, "parentfolderid", PARAM_NUM);
        if (!meta)
          continue;
        folderid=meta->num;
        if (folderid==lastfolderid)
          continue;
        psync_diff_refresh_fs_add_folder(folderid);
        lastfolderid=folderid;
      }
      if (!refresh_last)
        return;
      lastfolderid=(psync_folderid_t)-1;
      qsort(refresh_folders, refresh_last, sizeof(psync_folderid_t), cmp_folderid);
      for (i=0; i<refresh_last; i++)
        if (refresh_folders[i]!=lastfolderid){
          psync_fs_refresh_folder(refresh_folders[i]);
          lastfolderid=refresh_folders[i];
        }
      psync_free(refresh_folders);
      refresh_folders=NULL;
      refresh_allocated=0;
      refresh_last=0;
    }
  }
  
  static void psync_diff_thread(){
    psync_socket *sock;
    binresult *res;
    const binresult *entries;
    uint64_t diffid, newdiffid, result;
    psync_socket_t exceptionsock, socks[2];
    int sel;
    char ex;
    psync_set_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_CONNECTING);
    psync_send_status_update();
  restart:
    psync_set_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_CONNECTING);
    sock=get_connected_socket();
    debug(D_NOTICE, "connected");
    psync_set_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_SCANNING);
    diffid=psync_sql_cellint("SELECT value FROM setting WHERE id='diffid'", 0);
    if (diffid==0)
      initialdownload=1;
    used_quota=psync_sql_cellint("SELECT value FROM setting WHERE id='usedquota'", 0);
    do{
      binparam diffparams[]={P_STR("timeformat", "timestamp"), P_NUM("limit", PSYNC_DIFF_LIMIT), P_NUM("diffid", diffid)};
      if (!psync_do_run)
        break;
      res=send_command(sock, "diff", diffparams);
      if (!res){
        psync_socket_close(sock);
        goto restart;
      }
      result=psync_find_result(res, "result", PARAM_NUM)->num;
      if (unlikely(result)){
        debug(D_ERROR, "diff returned error %u: %s", (unsigned int)result, psync_find_result(res, "error", PARAM_STR)->str);
        psync_free(res);
        psync_socket_close(sock);
        psync_milisleep(PSYNC_SLEEP_BEFORE_RECONNECT);
        goto restart;
      }
      entries=psync_find_result(res, "entries", PARAM_ARRAY);
      if (entries->length){
        newdiffid=psync_find_result(res, "diffid", PARAM_NUM)->num;
        debug(D_NOTICE, "processing diff with %u entries", (unsigned)entries->length);
        diffid=process_entries(entries, newdiffid);
        psync_diff_refresh_fs(entries);
        debug(D_NOTICE, "got diff with %u entries, new diffid %lu", (unsigned)entries->length, (unsigned long)diffid);
      }
      result=entries->length;
      psync_free(res);
    } while (result);
    debug(D_NOTICE, "initial sync finished");
    check_overquota();
    psync_set_status(PSTATUS_TYPE_ONLINE, PSTATUS_ONLINE_ONLINE);
    initialdownload=0;
    psync_syncer_check_delayed_syncs();
    psync_sql_statement("ANALYZE");
    exceptionsock=setup_exeptions();
    if (unlikely(exceptionsock==INVALID_SOCKET)){
      debug(D_ERROR, "could not create pipe");
      psync_socket_close(sock);
      return;
    }
    socks[0]=exceptionsock;
    socks[1]=sock->sock;
    send_diff_command(sock, diffid);
    while (psync_do_run){
      if (psync_socket_pendingdata(sock))
        sel=1;
      else
        sel=psync_select_in(socks, 2, -1);
      if (sel==0){
        if (!psync_do_run)
          break;
        if (psync_pipe_read(exceptionsock, &ex, 1)!=1)
          continue;
        handle_exception(&sock, &diffid, ex);
        while (psync_select_in(socks, 1, 0)==0 && psync_pipe_read(exceptionsock, &ex, 1)==1);
        socks[1]=sock->sock;
      }
      else if (sel==1){
        sock->pending=1;
        res=get_result(sock);
        if (unlikely_log(!res)){
          psync_timer_notify_exception();
          handle_exception(&sock, &diffid, 'r');
          socks[1]=sock->sock;
          continue;
        }
        result=psync_find_result(res, "result", PARAM_NUM)->num;
        if (unlikely(result)){
          debug(D_ERROR, "diff returned error %u: %s", (unsigned int)result, psync_find_result(res, "error", PARAM_STR)->str);
          psync_free(res);
          handle_exception(&sock, &diffid, 'r');
          socks[1]=sock->sock;
          continue;
        }
        entries=psync_check_result(res, "entries", PARAM_ARRAY);
        if (entries){
          if (entries->length){
            newdiffid=psync_find_result(res, "diffid", PARAM_NUM)->num;
            diffid=process_entries(entries, newdiffid);
            check_overquota();
            psync_diff_refresh_fs(entries);
          }
          else
            debug(D_NOTICE, "diff with 0 entries, did we send a nop recently?");
          send_diff_command(sock, diffid);
        }
        else
          debug(D_NOTICE, "diff with no entries, did we send a nop recently?");
        psync_free(res);
      }
    }
    psync_socket_close(sock);
    psync_pipe_close(exceptionsock);
    psync_pipe_close(exceptionsockwrite);
  }
  
  void psync_diff_init(){
    psync_run_thread("diff", psync_diff_thread);
  }
  