pcloudcc-lneely/pCloudCC/lib/pclsync/psyncer.c

481 lines
17 KiB
C

/* 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 "psyncer.h"
#include "plibs.h"
#include "psettings.h"
#include "ptasks.h"
#include "plocalscan.h"
#include "plocalnotify.h"
#include "pfolder.h"
#include "pdownload.h"
#include "pstatus.h"
#include "pcallbacks.h"
#include "ptree.h"
#include "ppathstatus.h"
#include <string.h>
typedef struct {
psync_tree tree;
psync_folderid_t folderid;
} synced_down_folder;
static psync_tree *synced_down_folders=PSYNC_TREE_EMPTY;
static pthread_mutex_t sync_down_mutex=PTHREAD_MUTEX_INITIALIZER;
static psync_tree *psync_new_sd_folder(psync_folderid_t folderid){
synced_down_folder *f=psync_new(synced_down_folder);
f->folderid=folderid;
return &f->tree;
}
static void psync_add_folder_to_downloadlist_locked(psync_folderid_t folderid){
synced_down_folder *f;
if (!synced_down_folders){
psync_tree_add_after(&synced_down_folders, NULL, psync_new_sd_folder(folderid));
return;
}
f=psync_tree_element(synced_down_folders, synced_down_folder, tree);
while (1){
if (folderid<f->folderid){
if (f->tree.left)
f=psync_tree_element(f->tree.left, synced_down_folder, tree);
else{
f->tree.left=psync_new_sd_folder(folderid);
psync_tree_added_at(&synced_down_folders, &f->tree, f->tree.left);
break;
}
}
else if (folderid>f->folderid){
if (f->tree.right)
f=psync_tree_element(f->tree.right, synced_down_folder, tree);
else{
f->tree.right=psync_new_sd_folder(folderid);
psync_tree_added_at(&synced_down_folders, &f->tree, f->tree.right);
break;
}
}
else{
debug(D_NOTICE, "not adding folderid %llu to downloadlist as it is already there", (unsigned long long)folderid);
break;
}
}
}
void psync_add_folder_to_downloadlist(psync_folderid_t folderid){
pthread_mutex_lock(&sync_down_mutex);
psync_add_folder_to_downloadlist_locked(folderid);
pthread_mutex_unlock(&sync_down_mutex);
}
void psync_del_folder_from_downloadlist(psync_folderid_t folderid){
synced_down_folder *f;
pthread_mutex_lock(&sync_down_mutex);
f=psync_tree_element(synced_down_folders, synced_down_folder, tree);
while (f){
if (folderid<f->folderid)
f=psync_tree_element(f->tree.left, synced_down_folder, tree);
else if (folderid>f->folderid)
f=psync_tree_element(f->tree.right, synced_down_folder, tree);
else{
psync_tree_del(&synced_down_folders, &f->tree);
psync_free(f);
break;
}
}
pthread_mutex_unlock(&sync_down_mutex);
}
void psync_clear_downloadlist() {
pthread_mutex_lock(&sync_down_mutex);
psync_tree_for_each_element_call_safe(synced_down_folders, synced_down_folder, tree, psync_free);
synced_down_folders=PSYNC_TREE_EMPTY;
pthread_mutex_unlock(&sync_down_mutex);
}
int psync_is_folder_in_downloadlist(psync_folderid_t folderid){
synced_down_folder *f;
pthread_mutex_lock(&sync_down_mutex);
f=psync_tree_element(synced_down_folders, synced_down_folder, tree);
while (f){
if (folderid<f->folderid)
f=psync_tree_element(f->tree.left, synced_down_folder, tree);
else if (folderid>f->folderid)
f=psync_tree_element(f->tree.right, synced_down_folder, tree);
else
break;
}
pthread_mutex_unlock(&sync_down_mutex);
return f!=NULL;
}
void psync_increase_local_folder_taskcnt(psync_folderid_t lfolderid){
psync_sql_res *res;
res=psync_sql_prep_statement("UPDATE localfolder SET taskcnt=taskcnt+1 WHERE id=?");
psync_sql_bind_uint(res, 1, lfolderid);
psync_sql_run_free(res);
assertw(psync_sql_affected_rows()==1);
}
void psync_decrease_local_folder_taskcnt(psync_folderid_t lfolderid){
psync_sql_res *res;
res=psync_sql_prep_statement("UPDATE localfolder SET taskcnt=taskcnt+1 WHERE id=?");
psync_sql_bind_uint(res, 1, lfolderid);
psync_sql_run_free(res);
assertw(psync_sql_affected_rows()==1);
}
psync_folderid_t psync_create_local_folder_in_db(psync_syncid_t syncid, psync_folderid_t folderid, psync_folderid_t localparentfolderid, const char *name){
psync_sql_res *res;
psync_uint_row row;
psync_folderid_t lfolderid, dbfolderid;
const char *ptr;
char *vname;
debug(D_NOTICE, "creating local folder in db as %lu/%s for folderid %lu", (unsigned long)localparentfolderid, name, (unsigned long)folderid);
res=psync_sql_query("SELECT id FROM localfolder WHERE syncid=? AND folderid=?");
psync_sql_bind_uint(res, 1, syncid);
psync_sql_bind_uint(res, 2, folderid);
row=psync_sql_fetch_rowint(res);
if (row)
lfolderid=row[0];
else
lfolderid=0;
psync_sql_free_result(res);
if (lfolderid)
return lfolderid;
vname=NULL;
if (name)
for (ptr=name; *ptr; ptr++)
if (psync_invalid_filename_chars[(unsigned char)*ptr]){
if (!vname)
vname=psync_strdup(name);
vname[ptr-name]='_';
}
if (vname)
name=vname;
res=psync_sql_prep_statement("INSERT OR IGNORE INTO localfolder (localparentfolderid, folderid, syncid, flags, taskcnt, name) VALUES (?, ?, ?, 0, 1, ?)");
psync_sql_bind_uint(res, 1, localparentfolderid);
psync_sql_bind_uint(res, 2, folderid);
psync_sql_bind_uint(res, 3, syncid);
psync_sql_bind_string(res, 4, name);
psync_sql_run(res);
if (psync_sql_affected_rows()>0){
lfolderid=psync_sql_insertid();
psync_sql_free_result(res);
psync_free(vname);
return lfolderid;
}
psync_sql_free_result(res);
res=psync_sql_query("SELECT id, folderid FROM localfolder WHERE localparentfolderid=? AND syncid=? AND name=?");
psync_sql_bind_uint(res, 1, localparentfolderid);
psync_sql_bind_uint(res, 2, syncid);
psync_sql_bind_string(res, 3, name);
row=psync_sql_fetch_rowint(res);
if (row){
lfolderid=row[0];
dbfolderid=row[1];
}
else{
lfolderid=0;
dbfolderid=0;
debug(D_ERROR, "local folder %s not found in the database", name);
}
psync_sql_free_result(res);
if (lfolderid && dbfolderid!=folderid){
debug(D_NOTICE, "local folder %lu does not have folderid associated, setting to %lu", (unsigned long)lfolderid, (unsigned long)folderid);
res=psync_sql_prep_statement("UPDATE localfolder SET folderid=? WHERE id=?");
psync_sql_bind_uint(res, 1, lfolderid);
psync_sql_bind_uint(res, 2, folderid);
psync_sql_run_free(res);
}
psync_increase_local_folder_taskcnt(lfolderid);
psync_free(vname);
return lfolderid;
}
void psync_add_folder_for_downloadsync(psync_syncid_t syncid, psync_synctype_t synctype, psync_folderid_t folderid, psync_folderid_t lfoiderid){
psync_sql_res *res;
psync_variant_row row;
const char *name;
psync_folderid_t cfolderid, clfolderid;
res=psync_sql_prep_statement("REPLACE INTO syncedfolder (syncid, folderid, localfolderid, synctype) VALUES (?, ?, ?, ?)");
psync_sql_bind_uint(res, 1, syncid);
psync_sql_bind_uint(res, 2, folderid);
psync_sql_bind_uint(res, 3, lfoiderid);
psync_sql_bind_uint(res, 4, synctype);
psync_sql_run_free(res);
psync_add_folder_to_downloadlist(folderid);
res=psync_sql_query("SELECT id, permissions, name FROM folder WHERE parentfolderid=?");
psync_sql_bind_uint(res, 1, folderid);
while ((row=psync_sql_fetch_row(res))){
if (psync_get_number(row[1])&PSYNC_PERM_READ){
name=psync_get_string(row[2]);
if (psync_is_name_to_ignore(name))
continue;
cfolderid=psync_get_number(row[0]);
clfolderid=psync_create_local_folder_in_db(syncid, cfolderid, lfoiderid, name);
psync_task_create_local_folder(syncid, cfolderid, clfolderid);
psync_add_folder_for_downloadsync(syncid, synctype, cfolderid, clfolderid/*, path*/);
}
}
psync_sql_free_result(res);
res=psync_sql_query("SELECT id, name FROM file WHERE parentfolderid=?");
psync_sql_bind_uint(res, 1, folderid);
while ((row=psync_sql_fetch_row(res))){
name=psync_get_string(row[1]);
if (psync_is_name_to_ignore(name))
continue;
psync_task_download_file_silent(syncid, psync_get_number(row[0]), lfoiderid, name);
}
psync_sql_free_result(res);
}
static void psync_sync_newsyncedfolder(psync_syncid_t syncid){
psync_sql_res *res;
psync_variant_row row;
uint64_t folderid;
psync_synctype_t synctype;
char *localpath;
psync_sql_start_transaction();
res=psync_sql_query("SELECT folderid, synctype, localpath FROM syncfolder WHERE id=? AND flags=0");
psync_sql_bind_uint(res, 1, syncid);
row=psync_sql_fetch_row(res);
if (unlikely_log(!row)){
psync_sql_free_result(res);
psync_sql_rollback_transaction();
return;
}
folderid=psync_get_number(row[0]);
synctype=psync_get_number(row[1]);
localpath=psync_strndup(psync_get_string(row[2]), strlen(psync_get_string(row[2])));
psync_sql_free_result(res);
if (synctype&PSYNC_DOWNLOAD_ONLY){
psync_add_folder_for_downloadsync(syncid, synctype, folderid, 0);
}
else {
res=psync_sql_prep_statement("REPLACE INTO syncedfolder (syncid, folderid, localfolderid, synctype) VALUES (?, ?, 0, ?)");
psync_sql_bind_uint(res, 1, syncid);
psync_sql_bind_uint(res, 2, folderid);
psync_sql_bind_uint(res, 3, synctype);
psync_sql_run_free(res);
}
res=psync_sql_prep_statement("UPDATE syncfolder SET flags=1 WHERE flags=0 AND id=?");
psync_sql_bind_uint(res, 1, syncid);
psync_sql_run_free(res);
if (likely_log(psync_sql_affected_rows())){
if (!psync_sql_commit_transaction()){
if (synctype&PSYNC_UPLOAD_ONLY)
psync_wake_localscan();
if (synctype&PSYNC_DOWNLOAD_ONLY){
psync_status_recalc_to_download();
psync_send_status_update();
psync_wake_download();
}
psync_localnotify_add_sync(syncid);
psync_restat_sync_folders_add(syncid, localpath);
}
}
else
psync_sql_rollback_transaction();
psync_free(localpath);
}
static void psync_do_sync_thread(void *ptr){
psync_sync_newsyncedfolder(*((psync_syncid_t *)ptr));
psync_free(ptr);
}
void psync_syncer_new(psync_syncid_t syncid){
psync_syncid_t *psid=psync_new(psync_syncid_t);
*psid=syncid;
psync_run_thread1("syncer", psync_do_sync_thread, psid);
}
static void psync_syncer_thread(){
int64_t syncid;
psync_sql_lock();
if (psync_sql_cellint("SELECT COUNT(*) FROM task", -1)==0)
psync_sql_statement("DELETE FROM syncfolder WHERE folderid IS NULL");
while ((syncid=psync_sql_cellint("SELECT id FROM syncfolder WHERE flags=0", -1))!=-1)
psync_sync_newsyncedfolder(syncid);
psync_sql_unlock();
}
static void delete_delayed_sync(uint64_t id){
psync_sql_res *res;
res=psync_sql_prep_statement("DELETE FROM syncfolderdelayed WHERE id=?");
psync_sql_bind_uint(res, 1, id);
psync_sql_run_free(res);
}
/*********************************************************************************************/
int psync_str_is_prefix(const char *str1, const char *str2){
size_t len1, len2;
len1=strlen(str1);
len2=strlen(str2);
while (len1 > 1 && (str1[len1 - 1] == '/' || str1[len1 - 1] == PSYNC_DIRECTORY_SEPARATORC))
len1--;
if (len2<len1){
if (str1[len2]!='/' && str1[len2]!=PSYNC_DIRECTORY_SEPARATORC)
return 0;
len1=len2;
}
else{
if (str2[len1]!='/' && str2[len1]!=PSYNC_DIRECTORY_SEPARATORC)
return 0;
}
return !psync_filename_cmpn(str1, str2, len1);
}
/*********************************************************************************************/
int psync_left_str_is_prefix(const char* str1, const char* str2) {
size_t len1, len2;
len1 = strlen(str1);
len2 = strlen(str2);
while (len1 > 1 && (str1[len1 - 1] == '/' || str1[len1 - 1] == PSYNC_DIRECTORY_SEPARATORC))
len1--;
while (len2 > 1 && (str2[len2 - 1] == '/' || str1[len2 - 1] == PSYNC_DIRECTORY_SEPARATORC))
len2--;
if (len2 < len1) {
return 0;
}
return !psync_filename_cmpn(str1, str2, len1);
}
/*********************************************************************************************/
void psync_syncer_check_delayed_syncs(){
psync_stat_t st;
psync_sql_res *res, *res2, *stmt;
psync_variant_row row;
psync_uint_row urow;
psync_str_row srow;
char *localpath, *remotepath;
uint64_t id, synctype;
int64_t syncid;
psync_folderid_t folderid;
int unsigned md;
re:
res=psync_sql_query("SELECT id, localpath, remotepath, synctype FROM syncfolderdelayed");
while ((row=psync_sql_fetch_row(res))){
id=psync_get_number(row[0]);
localpath=(char *)psync_get_string(row[1]);
remotepath=(char *)psync_get_string(row[2]);
synctype=psync_get_number(row[3]);
if (synctype&PSYNC_DOWNLOAD_ONLY)
md=7;
else
md=5;
if (unlikely_log(psync_stat(localpath, &st)) || unlikely_log(!psync_stat_isfolder(&st)) || unlikely_log(!psync_stat_mode_ok(&st, md))){
debug(D_WARNING, "ignoring delayed sync id %"P_PRI_U64" for local path %s", id, localpath);
delete_delayed_sync(id);
continue;
}
md=0;
res2=psync_sql_query("SELECT localpath FROM syncfolder");
while ((srow=psync_sql_fetch_rowstr(res2)))
if (psync_str_is_prefix(srow[0], localpath)){
debug(D_WARNING, "skipping localfolder %s, remote %s, because of same parent to %s", localpath, remotepath, srow[0]);
md=1;
}
else if (!psync_filename_cmp(srow[0], localpath)){
debug(D_WARNING, "skipping localfolder %s, remote %s, because of same dir to %s", localpath, remotepath, srow[0]);
md=1;
}
psync_sql_free_result(res2);
if (md){
delete_delayed_sync(id);
continue;
}
localpath=psync_strdup(localpath);
remotepath=psync_strdup(remotepath);
psync_sql_free_result(res);
folderid=psync_get_folderid_by_path_or_create(remotepath);
if (unlikely(folderid==PSYNC_INVALID_FOLDERID)){
debug(D_WARNING, "could not get folderid/create folder %s", remotepath);
psync_free(localpath);
psync_free(remotepath);
if (psync_error!=PERROR_OFFLINE){
delete_delayed_sync(id);
goto re;
}
else
return;
}
psync_sql_start_transaction();
delete_delayed_sync(id);
stmt=psync_sql_query_nolock("SELECT id FROM folder WHERE id=?");
psync_sql_bind_uint(stmt, 1, folderid);
urow=psync_sql_fetch_rowint(stmt);
psync_sql_free_result(stmt);
if (!urow){
psync_sql_commit_transaction();
psync_free(localpath);
psync_free(remotepath);
goto re;
}
stmt=psync_sql_prep_statement("INSERT OR IGNORE INTO syncfolder (folderid, localpath, synctype, flags, inode, deviceid) VALUES (?, ?, ?, 0, ?, ?)");
psync_sql_bind_uint(stmt, 1, folderid);
psync_sql_bind_string(stmt, 2, localpath);
psync_sql_bind_uint(stmt, 3, synctype);
psync_sql_bind_uint(stmt, 4, psync_stat_inode(&st));
psync_sql_bind_uint(stmt, 5, psync_stat_device(&st));
psync_sql_run(stmt);
if (likely_log(psync_sql_affected_rows()))
syncid=psync_sql_insertid();
else
syncid=-1;
psync_sql_free_result(stmt);
psync_free(localpath);
psync_free(remotepath);
if (!psync_sql_commit_transaction() && syncid!=-1) {
psync_path_status_reload_syncs();
psync_syncer_new(syncid);
goto re;
}
return;
}
psync_sql_free_result(res);
}
void psync_syncer_init(){
psync_sql_res *res;
psync_uint_row row;
res=psync_sql_query("SELECT folderid FROM syncedfolder WHERE synctype&"NTO_STR(PSYNC_DOWNLOAD_ONLY)"="NTO_STR(PSYNC_DOWNLOAD_ONLY));
pthread_mutex_lock(&sync_down_mutex);
while ((row=psync_sql_fetch_rowint(res)))
psync_add_folder_to_downloadlist_locked(row[0]);
pthread_mutex_unlock(&sync_down_mutex);
psync_sql_free_result(res);
psync_run_thread("syncer", psync_syncer_thread);
}