460 lines
14 KiB
C
460 lines
14 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 <string.h>
|
|
|
|
#include "pfoldersync.h"
|
|
#include "plibs.h"
|
|
#include "pmem.h"
|
|
#include "plist.h"
|
|
#include "pqevent.h"
|
|
#include "prun.h"
|
|
#include "psys.h"
|
|
|
|
#define MAX_STATUS_STR_LEN 64
|
|
#define DONT_SHOW_TIME_IF_SEC_OVER (2 * 86400)
|
|
#define DONT_SHOW_TIME_IF_SPEED_BELOW (4 * 1024)
|
|
|
|
#define cat_const(src, app) cat_lstr(src, app, sizeof(app) - 1)
|
|
|
|
static pthread_mutex_t statusmutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t statuscond = PTHREAD_COND_INITIALIZER;
|
|
static int statuschanges = 0;
|
|
static int statusthreadrunning = 0;
|
|
|
|
static pthread_mutex_t eventmutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t eventcond = PTHREAD_COND_INITIALIZER;
|
|
static psync_list eventlist;
|
|
static int eventthreadrunning = 0;
|
|
static pstatus_t status_old = {0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
typedef struct {
|
|
psync_list list;
|
|
psync_eventdata_t data;
|
|
psync_eventtype_t event;
|
|
int freedata;
|
|
} event_list_t;
|
|
|
|
static char *cat_lstr(char *src, const char *app, size_t len) {
|
|
return (char *)memcpy(src, app, len) + len;
|
|
}
|
|
|
|
static char *cat_str(char *src, const char *app) {
|
|
return cat_lstr(src, app, strlen(app));
|
|
}
|
|
|
|
PSYNC_NOINLINE static char *cat_uint32l(char *src, uint32_t num) {
|
|
char str[16];
|
|
uint32_t rem;
|
|
int unsigned len;
|
|
len = 0;
|
|
do {
|
|
rem = num % 10;
|
|
str[sizeof(str) - ++len] = '0' + rem;
|
|
} while (num /= 10);
|
|
memcpy(src, str + sizeof(str) - len, len);
|
|
return src + len;
|
|
}
|
|
|
|
static char *cat_uint32(char *src, uint32_t num) {
|
|
if (num >= 1000)
|
|
return cat_uint32l(src, num);
|
|
if (num < 100) {
|
|
if (num < 10)
|
|
goto format10;
|
|
else
|
|
goto format100;
|
|
}
|
|
*src++ = '0' + num / 100;
|
|
num %= 100;
|
|
format100:
|
|
*src++ = '0' + num / 10;
|
|
num %= 10;
|
|
format10:
|
|
*src++ = '0' + num;
|
|
return src;
|
|
}
|
|
|
|
static char *fill_formatted_bytes(char *str, uint64_t bytes) {
|
|
const static char *sizes[] = {"Bytes", "KB", "MB", "GB", "TB"};
|
|
uint32_t rem, sz;
|
|
rem = 0;
|
|
sz = 0;
|
|
while (bytes >= 1024 && sz < ARRAY_SIZE(sizes) - 1) {
|
|
rem = bytes % 1024;
|
|
bytes /= 1024;
|
|
sz++;
|
|
}
|
|
str = cat_uint32(str, (uint32_t)bytes);
|
|
if (sz && bytes < 100) {
|
|
rem = rem * 1000 / 1024;
|
|
pdbg_assert(rem <= 999);
|
|
*str++ = '.';
|
|
if (bytes < 10) {
|
|
rem /= 10;
|
|
*str++ = '0' + rem / 10;
|
|
*str++ = '0' + rem % 10;
|
|
} else
|
|
*str++ = '0' + rem / 100;
|
|
}
|
|
return cat_str(str, sizes[sz]);
|
|
}
|
|
|
|
static char *fill_remaining(char *str, uint32_t files, uint64_t bytes) {
|
|
str = cat_const(str, "Remaining: ");
|
|
str = cat_uint32(str, files);
|
|
str = cat_const(str, " files, ");
|
|
return fill_formatted_bytes(str, bytes);
|
|
}
|
|
|
|
static char *fill_formatted_time(char *str, uint64_t totalsec) {
|
|
uint32_t d, h, m, s;
|
|
s = totalsec % 60;
|
|
totalsec /= 60;
|
|
m = totalsec % 60;
|
|
totalsec /= 60;
|
|
h = totalsec % 24;
|
|
d = totalsec / 24;
|
|
if (d) {
|
|
str = cat_uint32(str, d);
|
|
*str++ = 'd';
|
|
*str++ = ' ';
|
|
if (m >= 30)
|
|
h++;
|
|
str = cat_uint32(str, h);
|
|
*str++ = 'h';
|
|
} else if (h) {
|
|
str = cat_uint32(str, h);
|
|
*str++ = 'h';
|
|
*str++ = ' ';
|
|
if (s >= 30)
|
|
m++;
|
|
str = cat_uint32(str, m);
|
|
*str++ = 'm';
|
|
} else if (m) {
|
|
str = cat_uint32(str, m);
|
|
*str++ = 'm';
|
|
*str++ = ' ';
|
|
str = cat_uint32(str, s);
|
|
*str++ = 's';
|
|
} else {
|
|
if (!s)
|
|
s = 1;
|
|
str = cat_uint32(str, s);
|
|
*str++ = 's';
|
|
}
|
|
return str;
|
|
}
|
|
|
|
static uint64_t sub_no_underf(uint64_t a, uint64_t b) {
|
|
if (likely(b < a))
|
|
return a - b;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static void status_fill_formatted_str(pstatus_t *status, char *downloadstr,
|
|
char *uploadstr) {
|
|
char *up, *dw;
|
|
uint64_t remsec;
|
|
uint32_t speed;
|
|
dw = downloadstr;
|
|
up = uploadstr;
|
|
|
|
if (status->filestodownload) {
|
|
speed = status->downloadspeed;
|
|
if (status->status == PSTATUS_PAUSED || status->status == PSTATUS_STOPPED ||
|
|
status->localisfull || speed == 0) {
|
|
if (status->status == PSTATUS_PAUSED)
|
|
dw = cat_const(dw, "Paused. ");
|
|
else if (status->status == PSTATUS_STOPPED)
|
|
dw = cat_const(dw, "Stopped. ");
|
|
else if (status->localisfull)
|
|
dw = cat_const(dw, "Disk full. ");
|
|
dw = fill_remaining(
|
|
dw, status->filestodownload,
|
|
sub_no_underf(status->bytestodownload, status->bytesdownloaded));
|
|
} else {
|
|
dw = fill_formatted_bytes(dw, speed);
|
|
dw = cat_const(dw, "/sec, ");
|
|
dw = fill_remaining(
|
|
dw, status->filestodownload,
|
|
sub_no_underf(status->bytestodownload, status->bytesdownloaded));
|
|
remsec = (status->bytestodownload - status->bytesdownloaded) / speed;
|
|
if (remsec < DONT_SHOW_TIME_IF_SEC_OVER ||
|
|
speed >= DONT_SHOW_TIME_IF_SPEED_BELOW) {
|
|
*dw++ = ' ';
|
|
dw = fill_formatted_time(dw, remsec);
|
|
}
|
|
}
|
|
} else
|
|
dw = cat_const(dw, "Everything Downloaded");
|
|
|
|
if (status->filestoupload) {
|
|
speed = status->uploadspeed;
|
|
if (status->status == PSTATUS_PAUSED || status->status == PSTATUS_STOPPED ||
|
|
status->remoteisfull || speed == 0) {
|
|
if (status->status == PSTATUS_PAUSED)
|
|
up = cat_const(up, "Paused. ");
|
|
else if (status->status == PSTATUS_STOPPED)
|
|
up = cat_const(up, "Stopped. ");
|
|
else if (status->remoteisfull)
|
|
up = cat_const(up, "Account full. ");
|
|
up = fill_remaining(
|
|
up, status->filestoupload,
|
|
sub_no_underf(status->bytestoupload, status->bytesuploaded));
|
|
} else {
|
|
up = fill_formatted_bytes(up, speed);
|
|
up = cat_const(up, "/sec, ");
|
|
up = fill_remaining(
|
|
up, status->filestoupload,
|
|
sub_no_underf(status->bytestoupload, status->bytesuploaded));
|
|
remsec = (status->bytestoupload - status->bytesuploaded) / speed;
|
|
if (remsec < DONT_SHOW_TIME_IF_SEC_OVER ||
|
|
speed >= DONT_SHOW_TIME_IF_SPEED_BELOW) {
|
|
*up++ = ' ';
|
|
up = fill_formatted_time(up, remsec);
|
|
}
|
|
}
|
|
} else
|
|
up = cat_const(up, "Everything Uploaded");
|
|
|
|
pdbg_assert(dw < downloadstr + MAX_STATUS_STR_LEN);
|
|
pdbg_assert(up < uploadstr + MAX_STATUS_STR_LEN);
|
|
*dw = 0;
|
|
*up = 0;
|
|
status->downloadstr = downloadstr;
|
|
status->uploadstr = uploadstr;
|
|
}
|
|
|
|
static void status_change_thread(void *ptr) {
|
|
char downloadstr[MAX_STATUS_STR_LEN], uploadstr[MAX_STATUS_STR_LEN];
|
|
pstatus_change_callback_t callback = (pstatus_change_callback_t)ptr;
|
|
while (1) {
|
|
// Maximum 2 updates/sec
|
|
psys_sleep_milliseconds(500);
|
|
pthread_mutex_lock(&statusmutex);
|
|
while (statuschanges <= 0) {
|
|
statuschanges = -1;
|
|
pthread_cond_wait(&statuscond, &statusmutex);
|
|
}
|
|
statuschanges = 0;
|
|
uint32_t cur_status = __atomic_load_n(&psync_status.status, __ATOMIC_RELAXED);
|
|
uint32_t cur_filestodownload = __atomic_load_n(&psync_status.filestodownload, __ATOMIC_RELAXED);
|
|
uint32_t cur_filestoupload = __atomic_load_n(&psync_status.filestoupload, __ATOMIC_RELAXED);
|
|
uint8_t cur_localisfull = __atomic_load_n(&psync_status.localisfull, __ATOMIC_RELAXED);
|
|
uint8_t cur_remoteisfull = __atomic_load_n(&psync_status.remoteisfull, __ATOMIC_RELAXED);
|
|
if (((status_old.filestodownload > 0) &&
|
|
(cur_filestodownload == 0)) ||
|
|
((cur_filestodownload > 0) &&
|
|
(status_old.filestodownload == 0)) ||
|
|
((status_old.filestoupload > 0) && (cur_filestoupload == 0)) ||
|
|
((cur_filestoupload > 0) && (status_old.filestoupload == 0)) ||
|
|
((cur_localisfull != status_old.localisfull)) ||
|
|
((cur_remoteisfull != status_old.remoteisfull)) ||
|
|
((cur_status != status_old.status) &&
|
|
((cur_status == PSTATUS_STOPPED) ||
|
|
(cur_status == PSTATUS_PAUSED) ||
|
|
(cur_status == PSTATUS_OFFLINE) ||
|
|
(status_old.status == PSTATUS_STOPPED) ||
|
|
(status_old.status == PSTATUS_PAUSED) ||
|
|
(status_old.status == PSTATUS_OFFLINE)))) {
|
|
status_old = psync_status;
|
|
status_old.status = cur_status;
|
|
status_old.filestodownload = cur_filestodownload;
|
|
status_old.filestoupload = cur_filestoupload;
|
|
status_old.localisfull = cur_localisfull;
|
|
status_old.remoteisfull = cur_remoteisfull;
|
|
}
|
|
pthread_mutex_unlock(&statusmutex);
|
|
if (!psync_do_run)
|
|
break;
|
|
status_fill_formatted_str(&psync_status, downloadstr, uploadstr);
|
|
pdbg_logf(D_NOTICE, "sending status update, dwlstr: %s, uplstr: %s",
|
|
psync_status.downloadstr, psync_status.uploadstr);
|
|
callback(&psync_status);
|
|
}
|
|
}
|
|
|
|
static void proc_send_event(void *ptr) {
|
|
pevent_callback_t callback = (pevent_callback_t)ptr;
|
|
event_list_t *event;
|
|
|
|
while (1) {
|
|
pthread_mutex_lock(&eventmutex);
|
|
|
|
while (psync_list_isempty(&eventlist))
|
|
pthread_cond_wait(&eventcond, &eventmutex);
|
|
|
|
event = psync_list_remove_head_element(&eventlist, event_list_t, list);
|
|
|
|
pthread_mutex_unlock(&eventmutex);
|
|
|
|
if (!psync_do_run)
|
|
break;
|
|
|
|
callback(event->event, event->data);
|
|
|
|
if (event->freedata)
|
|
pmem_free(PMEM_SUBSYS_OTHER, event->data.ptr);
|
|
|
|
pmem_free(PMEM_SUBSYS_OTHER, event);
|
|
}
|
|
}
|
|
|
|
void pstatus_get_cb(pstatus_t *status) {
|
|
static char downloadstr[MAX_STATUS_STR_LEN], uploadstr[MAX_STATUS_STR_LEN];
|
|
memcpy(status, &psync_status, sizeof(pstatus_t));
|
|
status_fill_formatted_str(status, downloadstr, uploadstr);
|
|
}
|
|
|
|
void pstatus_set_cb(pstatus_change_callback_t callback) {
|
|
pthread_mutex_lock(&statusmutex);
|
|
statusthreadrunning = 1;
|
|
pthread_mutex_unlock(&statusmutex);
|
|
prun_thread1("status change", status_change_thread, callback);
|
|
}
|
|
|
|
void pstatus_send_status_update() {
|
|
if (statusthreadrunning) {
|
|
pthread_mutex_lock(&statusmutex);
|
|
if (++statuschanges == 0) {
|
|
statuschanges++;
|
|
pthread_cond_signal(&statuscond);
|
|
}
|
|
pthread_mutex_unlock(&statusmutex);
|
|
}
|
|
}
|
|
|
|
void pqevent_process(pevent_callback_t callback) {
|
|
pthread_mutex_lock(&statusmutex);
|
|
eventthreadrunning = 1;
|
|
pthread_mutex_unlock(&statusmutex);
|
|
psync_list_init(&eventlist);
|
|
prun_thread1("event", proc_send_event, callback);
|
|
}
|
|
|
|
void pqevent_queue_sync_event_id(psync_eventtype_t eventid, psync_syncid_t syncid,
|
|
const char *localpath,
|
|
psync_fileorfolderid_t remoteid) {
|
|
if (eventthreadrunning) {
|
|
char *remotepath;
|
|
if (eventid & PEVENT_TYPE_FOLDER)
|
|
remotepath = pfolder_path(remoteid, NULL);
|
|
else
|
|
remotepath = pfolder_file_path(remoteid, NULL);
|
|
if (pdbg_unlikely(!remotepath))
|
|
return;
|
|
pqevent_queue_sync_event_path(eventid, syncid, localpath, remoteid, remotepath);
|
|
pmem_free(PMEM_SUBSYS_OTHER, remotepath);
|
|
}
|
|
}
|
|
|
|
void pqevent_queue_sync_event_path(psync_eventtype_t eventid, psync_syncid_t syncid,
|
|
const char *localpath,
|
|
psync_fileorfolderid_t remoteid,
|
|
const char *remotepath) {
|
|
if (eventthreadrunning) {
|
|
event_list_t *event;
|
|
size_t llen, rlen, slen;
|
|
char *lcopy, *rcopy, *strct, *name;
|
|
llen = strlen(localpath) + 1;
|
|
rlen = strlen(remotepath) + 1;
|
|
if (eventid & PEVENT_TYPE_FOLDER)
|
|
slen = sizeof(psync_folder_event_t);
|
|
else
|
|
slen = sizeof(psync_file_event_t);
|
|
event =
|
|
(event_list_t *)pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(event_list_t) + slen + llen + rlen);
|
|
strct = (char *)(event + 1);
|
|
lcopy = strct + slen;
|
|
rcopy = lcopy + llen;
|
|
memcpy(lcopy, localpath, llen);
|
|
memcpy(rcopy, remotepath, rlen);
|
|
name = strrchr(rcopy, '/') + 1;
|
|
if (eventid & PEVENT_TYPE_FOLDER) {
|
|
psync_folder_event_t *f = (psync_folder_event_t *)strct;
|
|
f->folderid = remoteid;
|
|
f->name = name;
|
|
f->localpath = lcopy;
|
|
f->remotepath = rcopy;
|
|
f->syncid = syncid;
|
|
event->data.folder = f;
|
|
} else {
|
|
psync_file_event_t *f = (psync_file_event_t *)strct;
|
|
f->fileid = remoteid;
|
|
f->name = name;
|
|
f->localpath = lcopy;
|
|
f->remotepath = rcopy;
|
|
f->syncid = syncid;
|
|
event->data.file = f;
|
|
}
|
|
event->event = eventid;
|
|
event->freedata = 0;
|
|
pthread_mutex_lock(&eventmutex);
|
|
psync_list_add_tail(&eventlist, &event->list);
|
|
pthread_cond_signal(&eventcond);
|
|
pthread_mutex_unlock(&eventmutex);
|
|
}
|
|
}
|
|
|
|
void pqevent_queue_eventid(psync_eventtype_t eventid) {
|
|
if (eventthreadrunning) {
|
|
event_list_t *event;
|
|
|
|
event = pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(event_list_t));
|
|
event->data.ptr = NULL;
|
|
event->event = eventid;
|
|
event->freedata = 0;
|
|
|
|
pthread_mutex_lock(&eventmutex);
|
|
psync_list_add_tail(&eventlist, &event->list);
|
|
pthread_cond_signal(&eventcond);
|
|
pthread_mutex_unlock(&eventmutex);
|
|
}
|
|
}
|
|
|
|
void pqevent_queue_event(psync_eventtype_t eventid, void *eventdata) {
|
|
if (eventthreadrunning) {
|
|
event_list_t *event;
|
|
|
|
event = pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(event_list_t));
|
|
event->data.ptr = eventdata;
|
|
event->event = eventid;
|
|
event->freedata = 1;
|
|
|
|
pthread_mutex_lock(&eventmutex);
|
|
psync_list_add_tail(&eventlist, &event->list);
|
|
pthread_cond_signal(&eventcond);
|
|
pthread_mutex_unlock(&eventmutex);
|
|
} else
|
|
pmem_free(PMEM_SUBSYS_OTHER, eventdata);
|
|
}
|