mirror of
https://github.com/haiwen/seafile.git
synced 2025-01-07 03:17:13 +08:00
[win] Add file/folder sync status for upload.
This commit is contained in:
parent
089cbf1161
commit
4d8e21bc43
@ -1060,10 +1060,9 @@ int add_to_index(const char *repo_id,
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Skip index file errors. */
|
||||
if (index_cb (repo_id, version, full_path, sha1, crypt, TRUE) < 0) {
|
||||
free (ce);
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
update_index:
|
||||
|
@ -80,6 +80,7 @@ convert_repo (SeafRepo *r)
|
||||
#ifndef SEAFILE_SERVER
|
||||
g_object_set (repo, "worktree", r->worktree,
|
||||
"relay-id", r->relay_id,
|
||||
"worktree-invalid", r->worktree_invalid,
|
||||
"last-sync-time", r->last_sync_time,
|
||||
"auto-sync", r->auto_sync,
|
||||
NULL);
|
||||
@ -813,6 +814,35 @@ int seafile_is_auto_sync_enabled (GError **error)
|
||||
return seaf_sync_manager_is_auto_sync_enabled (seaf->sync_mgr);
|
||||
}
|
||||
|
||||
char *
|
||||
seafile_get_path_sync_status (const char *repo_id,
|
||||
const char *path,
|
||||
int is_dir,
|
||||
GError **error)
|
||||
{
|
||||
char *canon_path = NULL;
|
||||
int len;
|
||||
char *status;
|
||||
|
||||
if (!repo_id || !path) {
|
||||
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Argument should not be null");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (*path == '/')
|
||||
++path;
|
||||
canon_path = g_strdup(path);
|
||||
len = strlen(canon_path);
|
||||
if (canon_path[len-1] == '/')
|
||||
canon_path[len-1] = 0;
|
||||
|
||||
status = seaf_sync_manager_get_path_sync_status (seaf->sync_mgr,
|
||||
repo_id,
|
||||
canon_path,
|
||||
is_dir);
|
||||
g_free (canon_path);
|
||||
return status;
|
||||
}
|
||||
|
||||
#endif /* not define SEAFILE_SERVER */
|
||||
|
||||
|
@ -59,6 +59,7 @@ noinst_HEADERS = \
|
||||
seafile-config.h \
|
||||
client-migrate.h \
|
||||
http-tx-mgr.h \
|
||||
sync-status-tree.h \
|
||||
$(proc_headers)
|
||||
|
||||
if LINUX
|
||||
@ -97,6 +98,7 @@ common_src = \
|
||||
block-tx-client.c \
|
||||
../common/block-tx-utils.c \
|
||||
client-migrate.c \
|
||||
sync-status-tree.c \
|
||||
processors/check-tx-proc.c \
|
||||
processors/check-tx-v2-proc.c \
|
||||
processors/check-tx-v3-proc.c \
|
||||
|
@ -1443,22 +1443,33 @@ http_tx_manager_add_upload (HttpTxManager *manager,
|
||||
typedef struct {
|
||||
HttpTxTask *task;
|
||||
gint64 delta;
|
||||
GHashTable *active_paths;
|
||||
} CalcQuotaDeltaData;
|
||||
|
||||
static int
|
||||
check_quota_diff_files (int n, const char *basedir, SeafDirent *files[], void *vdata)
|
||||
check_quota_and_active_paths_diff_files (int n, const char *basedir,
|
||||
SeafDirent *files[], void *vdata)
|
||||
{
|
||||
CalcQuotaDeltaData *data = vdata;
|
||||
SeafDirent *file1 = files[0];
|
||||
SeafDirent *file2 = files[1];
|
||||
gint64 size1, size2;
|
||||
char *path;
|
||||
|
||||
if (file1 && file2) {
|
||||
size1 = file1->size;
|
||||
size2 = file2->size;
|
||||
data->delta += (size1 - size2);
|
||||
|
||||
if (strcmp(file1->id, file2->id) != 0) {
|
||||
path = g_strconcat(basedir, file1->name, NULL);
|
||||
g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFREG);
|
||||
}
|
||||
} else if (file1 && !file2) {
|
||||
data->delta += file1->size;
|
||||
|
||||
path = g_strconcat (basedir, file1->name, NULL);
|
||||
g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFREG);
|
||||
} else if (!file1 && file2) {
|
||||
data->delta -= file2->size;
|
||||
}
|
||||
@ -1467,15 +1478,28 @@ check_quota_diff_files (int n, const char *basedir, SeafDirent *files[], void *v
|
||||
}
|
||||
|
||||
static int
|
||||
check_quota_diff_dirs (int n, const char *basedir, SeafDirent *dirs[], void *data,
|
||||
gboolean *recurse)
|
||||
check_quota_and_active_paths_diff_dirs (int n, const char *basedir,
|
||||
SeafDirent *dirs[], void *vdata,
|
||||
gboolean *recurse)
|
||||
{
|
||||
/* Do nothing */
|
||||
CalcQuotaDeltaData *data = vdata;
|
||||
SeafDirent *dir1 = dirs[0];
|
||||
SeafDirent *dir2 = dirs[1];
|
||||
char *path;
|
||||
|
||||
/* When a new empty dir is created. */
|
||||
if (!dir2 && dir1 && strcmp(dir1->id, EMPTY_SHA1) == 0) {
|
||||
path = g_strconcat (basedir, dir1->name, NULL);
|
||||
g_hash_table_replace (data->active_paths, path, (void*)(long)S_IFDIR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
calculate_upload_size_delta (HttpTxTask *task, gint64 *delta)
|
||||
calculate_upload_size_delta_and_active_paths (HttpTxTask *task,
|
||||
gint64 *delta,
|
||||
GHashTable *active_paths)
|
||||
{
|
||||
int ret = 0;
|
||||
SeafBranch *local = NULL, *master = NULL;
|
||||
@ -1516,13 +1540,14 @@ calculate_upload_size_delta (HttpTxTask *task, gint64 *delta)
|
||||
CalcQuotaDeltaData data;
|
||||
memset (&data, 0, sizeof(data));
|
||||
data.task = task;
|
||||
data.active_paths = active_paths;
|
||||
|
||||
DiffOptions opts;
|
||||
memset (&opts, 0, sizeof(opts));
|
||||
memcpy (opts.store_id, task->repo_id, 36);
|
||||
opts.version = task->repo_version;
|
||||
opts.file_cb = check_quota_diff_files;
|
||||
opts.dir_cb = check_quota_diff_dirs;
|
||||
opts.file_cb = check_quota_and_active_paths_diff_files;
|
||||
opts.dir_cb = check_quota_and_active_paths_diff_dirs;
|
||||
opts.data = &data;
|
||||
|
||||
const char *trees[2];
|
||||
@ -1532,6 +1557,7 @@ calculate_upload_size_delta (HttpTxTask *task, gint64 *delta)
|
||||
seaf_warning ("Failed to diff local and master head for repo %.8s.\n",
|
||||
task->repo_id);
|
||||
ret = -1;
|
||||
g_hash_table_destroy (data.active_paths);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -1547,21 +1573,13 @@ out:
|
||||
}
|
||||
|
||||
static int
|
||||
check_quota (HttpTxTask *task, Connection *conn)
|
||||
check_quota (HttpTxTask *task, Connection *conn, gint64 delta)
|
||||
{
|
||||
CURL *curl;
|
||||
char *url;
|
||||
int status;
|
||||
gint64 delta = 0;
|
||||
int ret = 0;
|
||||
|
||||
if (calculate_upload_size_delta (task, &delta) < 0) {
|
||||
seaf_warning ("Failed to calculate upload size delta for repo %s.\n",
|
||||
task->repo_id);
|
||||
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
|
||||
return -1;
|
||||
}
|
||||
|
||||
curl = conn->curl;
|
||||
|
||||
if (!task->use_fileserver_port)
|
||||
@ -2305,6 +2323,32 @@ update_master_branch (HttpTxTask *task)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
set_path_status_syncing (gpointer key, gpointer value, gpointer user_data)
|
||||
{
|
||||
HttpTxTask *task = user_data;
|
||||
char *path = key;
|
||||
int mode = (int)(long)value;
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
task->repo_id,
|
||||
path,
|
||||
mode,
|
||||
SYNC_STATUS_SYNCING);
|
||||
}
|
||||
|
||||
static void
|
||||
set_path_status_synced (gpointer key, gpointer value, gpointer user_data)
|
||||
{
|
||||
HttpTxTask *task = user_data;
|
||||
char *path = key;
|
||||
int mode = (int)(long)value;
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
task->repo_id,
|
||||
path,
|
||||
mode,
|
||||
SYNC_STATUS_SYNCED);
|
||||
}
|
||||
|
||||
static void *
|
||||
http_upload_thread (void *vdata)
|
||||
{
|
||||
@ -2316,6 +2360,7 @@ http_upload_thread (void *vdata)
|
||||
GList *send_fs_list = NULL, *needed_fs_list = NULL;
|
||||
GList *block_list = NULL, *needed_block_list = NULL;
|
||||
GList *ptr;
|
||||
GHashTable *active_paths = NULL;
|
||||
|
||||
SeafBranch *local = seaf_branch_manager_get_branch (seaf->branch_mgr,
|
||||
task->repo_id, "local");
|
||||
@ -2346,13 +2391,25 @@ http_upload_thread (void *vdata)
|
||||
|
||||
transition_state (task, task->state, HTTP_TASK_RT_STATE_CHECK);
|
||||
|
||||
gint64 delta = 0;
|
||||
active_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
|
||||
if (calculate_upload_size_delta_and_active_paths (task, &delta, active_paths) < 0) {
|
||||
seaf_warning ("Failed to calculate upload size delta for repo %s.\n",
|
||||
task->repo_id);
|
||||
task->error = HTTP_TASK_ERR_BAD_LOCAL_DATA;
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_hash_table_foreach (active_paths, set_path_status_syncing, task);
|
||||
|
||||
if (check_permission (task, conn) < 0) {
|
||||
seaf_warning ("Upload permission denied for repo %.8s on server %s.\n",
|
||||
task->repo_id, task->host);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (check_quota (task, conn) < 0) {
|
||||
if (check_quota (task, conn, delta) < 0) {
|
||||
seaf_warning ("Not enough quota for repo %.8s on server %s.\n",
|
||||
task->repo_id, task->host);
|
||||
goto out;
|
||||
@ -2474,12 +2531,18 @@ http_upload_thread (void *vdata)
|
||||
*/
|
||||
update_master_branch (task);
|
||||
|
||||
if (active_paths != NULL)
|
||||
g_hash_table_foreach (active_paths, set_path_status_synced, task);
|
||||
|
||||
out:
|
||||
string_list_free (send_fs_list);
|
||||
string_list_free (needed_fs_list);
|
||||
string_list_free (block_list);
|
||||
string_list_free (needed_block_list);
|
||||
|
||||
if (active_paths)
|
||||
g_hash_table_destroy (active_paths);
|
||||
|
||||
g_free (url);
|
||||
|
||||
connection_pool_return_connection (pool, conn);
|
||||
|
@ -907,6 +907,7 @@ typedef struct _AddOptions {
|
||||
GList *user_perms;
|
||||
GList *group_perms;
|
||||
gboolean is_repo_ro;
|
||||
gboolean startup_scan;
|
||||
} AddOptions;
|
||||
|
||||
#ifndef WIN32
|
||||
@ -1072,6 +1073,21 @@ add_file (const char *repo_id,
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (options && options->startup_scan) {
|
||||
struct cache_entry *ce;
|
||||
SyncStatus status;
|
||||
ce = index_name_exists (istate, path, strlen(path), 0);
|
||||
if (!ce || ie_match_stat(ce, st, 0) != 0)
|
||||
status = SYNC_STATUS_SYNCING;
|
||||
else
|
||||
status = SYNC_STATUS_SYNCED;
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
repo_id,
|
||||
path,
|
||||
S_IFREG,
|
||||
status);
|
||||
}
|
||||
|
||||
if (options && options->fset) {
|
||||
LockedFile *file = locked_file_set_lookup (options->fset, path);
|
||||
if (file) {
|
||||
@ -1101,6 +1117,13 @@ add_file (const char *repo_id,
|
||||
} else
|
||||
g_queue_push_tail (*remain_files, g_strdup(path));
|
||||
|
||||
if (ret < 0)
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
repo_id,
|
||||
path,
|
||||
S_IFREG,
|
||||
SYNC_STATUS_ERROR);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1123,11 +1146,14 @@ typedef struct IterCBData {
|
||||
const char *parent;
|
||||
const char *full_parent;
|
||||
int n;
|
||||
|
||||
/* If parent dir is ignored, all children are ignored too. */
|
||||
gboolean ignored;
|
||||
} IterCBData;
|
||||
|
||||
static int
|
||||
add_dir_recursive (const char *path, const char *full_path, SeafStat *st,
|
||||
AddParams *params);
|
||||
AddParams *params, gboolean ignored);
|
||||
|
||||
static int
|
||||
iter_dir_cb (wchar_t *full_parent_w,
|
||||
@ -1137,22 +1163,35 @@ iter_dir_cb (wchar_t *full_parent_w,
|
||||
{
|
||||
IterCBData *data = user_data;
|
||||
AddParams *params = data->add_params;
|
||||
AddOptions *options = params->options;
|
||||
char *dname = NULL, *path = NULL, *full_path = NULL;
|
||||
SeafStat st;
|
||||
int ret = 0;
|
||||
|
||||
dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
|
||||
|
||||
if (should_ignore(data->full_parent, dname, params->ignore_list))
|
||||
goto out;
|
||||
|
||||
path = g_build_path ("/", data->parent, dname, NULL);
|
||||
full_path = g_build_path ("/", params->worktree, path, NULL);
|
||||
|
||||
seaf_stat_from_find_data (fdata, &st);
|
||||
|
||||
if (data->ignored ||
|
||||
should_ignore(data->full_parent, dname, params->ignore_list)) {
|
||||
if (options && options->startup_scan) {
|
||||
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
add_dir_recursive (path, full_path, &st, params, TRUE);
|
||||
else
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
params->repo_id,
|
||||
path,
|
||||
S_IFREG,
|
||||
SYNC_STATUS_IGNORED);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
ret = add_dir_recursive (path, full_path, &st, params);
|
||||
ret = add_dir_recursive (path, full_path, &st, params, FALSE);
|
||||
else
|
||||
ret = add_file (params->repo_id,
|
||||
params->version,
|
||||
@ -1178,7 +1217,7 @@ out:
|
||||
|
||||
static int
|
||||
add_dir_recursive (const char *path, const char *full_path, SeafStat *st,
|
||||
AddParams *params)
|
||||
AddParams *params, gboolean ignored)
|
||||
{
|
||||
AddOptions *options = params->options;
|
||||
IterCBData data;
|
||||
@ -1189,14 +1228,45 @@ add_dir_recursive (const char *path, const char *full_path, SeafStat *st,
|
||||
data.add_params = params;
|
||||
data.parent = path;
|
||||
data.full_parent = full_path;
|
||||
data.ignored = ignored;
|
||||
|
||||
full_path_w = win32_long_path (full_path);
|
||||
ret = traverse_directory_win32 (full_path_w, iter_dir_cb, &data);
|
||||
g_free (full_path_w);
|
||||
|
||||
/* Ignore traverse dir error. */
|
||||
if (ret < 0)
|
||||
if (ret < 0) {
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
params->repo_id,
|
||||
path,
|
||||
S_IFDIR,
|
||||
SYNC_STATUS_ERROR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Update active path status for empty dir */
|
||||
if (options && options->startup_scan && ret == 0) {
|
||||
if (ignored) {
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
params->repo_id,
|
||||
path,
|
||||
S_IFDIR,
|
||||
SYNC_STATUS_IGNORED);
|
||||
} else {
|
||||
SyncStatus status;
|
||||
struct cache_entry *ce = index_name_exists (params->istate, path,
|
||||
strlen(path), 0);
|
||||
if (!ce)
|
||||
status = SYNC_STATUS_SYNCING;
|
||||
else
|
||||
status = SYNC_STATUS_SYNCED;
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
params->repo_id,
|
||||
path,
|
||||
S_IFDIR,
|
||||
status);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.n == 0 && path[0] != 0 && !params->ignore_empty_dir &&
|
||||
(!options ||
|
||||
@ -1233,6 +1303,11 @@ add_recursive (const char *repo_id,
|
||||
if (seaf_stat (full_path, &st) < 0) {
|
||||
g_warning ("Failed to stat %s.\n", full_path);
|
||||
g_free (full_path);
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
repo_id,
|
||||
path,
|
||||
0,
|
||||
SYNC_STATUS_ERROR);
|
||||
/* Ignore error */
|
||||
return 0;
|
||||
}
|
||||
@ -1264,7 +1339,7 @@ add_recursive (const char *repo_id,
|
||||
.options = options,
|
||||
};
|
||||
|
||||
ret = add_dir_recursive (path, full_path, &st, ¶ms);
|
||||
ret = add_dir_recursive (path, full_path, &st, ¶ms, FALSE);
|
||||
}
|
||||
|
||||
g_free (full_path);
|
||||
@ -1476,6 +1551,7 @@ add_path_to_index (SeafRepo *repo, struct index_state *istate,
|
||||
options.user_perms = user_perms;
|
||||
options.group_perms = group_perms;
|
||||
options.is_repo_ro = repo->is_readonly;
|
||||
options.startup_scan = TRUE;
|
||||
|
||||
add_recursive (repo->id, repo->version, repo->email, istate,
|
||||
repo->worktree, path,
|
||||
@ -1924,6 +2000,216 @@ handle_add_files (SeafRepo *repo, struct index_state *istate,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
typedef struct _UpdatePathData {
|
||||
SeafRepo *repo;
|
||||
struct index_state *istate;
|
||||
GList *ignore_list;
|
||||
|
||||
const char *parent;
|
||||
const char *full_parent;
|
||||
gboolean ignored;
|
||||
} UpdatePathData;
|
||||
|
||||
static void
|
||||
update_active_file (const char *repo_id,
|
||||
const char *path,
|
||||
SeafStat *st,
|
||||
struct index_state *istate,
|
||||
gboolean ignored)
|
||||
{
|
||||
if (ignored) {
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
repo_id,
|
||||
path,
|
||||
S_IFREG,
|
||||
SYNC_STATUS_IGNORED);
|
||||
} else {
|
||||
SyncStatus status;
|
||||
struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);
|
||||
if (!ce || ie_match_stat(ce, st, 0) != 0)
|
||||
status = SYNC_STATUS_SYNCING;
|
||||
else
|
||||
status = SYNC_STATUS_SYNCED;
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
repo_id,
|
||||
path,
|
||||
S_IFREG,
|
||||
status);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
update_active_path_recursive (SeafRepo *repo,
|
||||
const char *path,
|
||||
struct index_state *istate,
|
||||
GList *ignore_list,
|
||||
gboolean ignored);
|
||||
|
||||
static int
|
||||
update_active_path_cb (wchar_t *full_parent_w,
|
||||
WIN32_FIND_DATAW *fdata,
|
||||
void *user_data,
|
||||
gboolean *stop)
|
||||
{
|
||||
UpdatePathData *upd_data = user_data;
|
||||
char *dname;
|
||||
char *path;
|
||||
SyncStatus status;
|
||||
gboolean ignored = FALSE;
|
||||
SeafStat st;
|
||||
|
||||
dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
|
||||
path = g_build_path ("/", upd_data->parent, dname, NULL);
|
||||
|
||||
if (upd_data->ignored || should_ignore (upd_data->full_parent, dname, upd_data->ignore_list))
|
||||
ignored = TRUE;
|
||||
|
||||
seaf_stat_from_find_data (fdata, &st);
|
||||
|
||||
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
update_active_path_recursive (upd_data->repo,
|
||||
path,
|
||||
upd_data->istate,
|
||||
upd_data->ignore_list,
|
||||
ignored);
|
||||
} else {
|
||||
update_active_file (upd_data->repo->id,
|
||||
path,
|
||||
&st,
|
||||
upd_data->istate,
|
||||
ignored);
|
||||
}
|
||||
|
||||
g_free (dname);
|
||||
g_free (path);
|
||||
}
|
||||
|
||||
static void
|
||||
update_active_path_recursive (SeafRepo *repo,
|
||||
const char *path,
|
||||
struct index_state *istate,
|
||||
GList *ignore_list,
|
||||
gboolean ignored)
|
||||
{
|
||||
char *full_path;
|
||||
wchar_t *full_path_w;
|
||||
int ret = 0;
|
||||
SyncStatus status;
|
||||
UpdatePathData upd_data;
|
||||
|
||||
full_path = g_build_filename (repo->worktree, path, NULL);
|
||||
|
||||
memset (&upd_data, 0, sizeof(upd_data));
|
||||
upd_data.repo = repo;
|
||||
upd_data.istate = istate;
|
||||
upd_data.ignore_list = ignore_list;
|
||||
upd_data.parent = path;
|
||||
upd_data.full_parent = full_path;
|
||||
upd_data.ignored = ignored;
|
||||
|
||||
full_path_w = win32_long_path (full_path);
|
||||
ret = traverse_directory_win32 (full_path_w, update_active_path_cb, &upd_data);
|
||||
g_free (full_path_w);
|
||||
g_free (full_path);
|
||||
|
||||
if (ret < 0)
|
||||
return;
|
||||
|
||||
/* traverse_directory_win32() returns number of entries in the directory. */
|
||||
if (ret == 0 && path[0] != 0) {
|
||||
if (ignored) {
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
repo->id,
|
||||
path,
|
||||
S_IFDIR,
|
||||
SYNC_STATUS_IGNORED);
|
||||
} else {
|
||||
/* There is no need to update an empty dir. */
|
||||
SyncStatus status;
|
||||
struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);
|
||||
if (!ce)
|
||||
status = SYNC_STATUS_SYNCING;
|
||||
else
|
||||
status = SYNC_STATUS_SYNCED;
|
||||
seaf_sync_manager_update_active_path (seaf->sync_mgr,
|
||||
repo->id,
|
||||
path,
|
||||
S_IFDIR,
|
||||
status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void
|
||||
update_active_file (const char *repo_id,
|
||||
const char *path,
|
||||
SeafStat *st,
|
||||
struct index_state *istate,
|
||||
gboolean ignored)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
update_active_path_recursive (SeafRepo *repo,
|
||||
const char *path,
|
||||
struct index_state *istate,
|
||||
GList *ignore_list,
|
||||
gboolean ignored)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
process_active_path (SeafRepo *repo, const char *path,
|
||||
struct index_state *istate, GList *ignore_list)
|
||||
{
|
||||
SeafStat st;
|
||||
SyncStatus status;
|
||||
gboolean ignored = FALSE;
|
||||
|
||||
char *fullpath = g_build_filename (repo->worktree, path, NULL);
|
||||
if (seaf_stat (fullpath, &st) < 0) {
|
||||
seaf_warning ("Failed to stat %s: %s.\n", fullpath, strerror(errno));
|
||||
g_free (fullpath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (check_full_path_ignore (repo->worktree, path, ignore_list))
|
||||
ignored = TRUE;
|
||||
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
update_active_file (repo->id, path, &st, istate, ignored);
|
||||
} else {
|
||||
update_active_path_recursive (repo, path, istate, ignore_list, ignored);
|
||||
}
|
||||
|
||||
g_free (fullpath);
|
||||
}
|
||||
|
||||
static void
|
||||
update_path_sync_status (SeafRepo *repo, WTStatus *status,
|
||||
struct index_state *istate, GList *ignore_list)
|
||||
{
|
||||
char *path;
|
||||
|
||||
while (1) {
|
||||
pthread_mutex_lock (&status->ap_q_lock);
|
||||
path = g_queue_pop_head (status->active_paths);
|
||||
pthread_mutex_unlock (&status->ap_q_lock);
|
||||
|
||||
if (!path)
|
||||
break;
|
||||
|
||||
process_active_path (repo, path, istate, ignore_list);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
apply_worktree_changes_to_index (SeafRepo *repo, struct index_state *istate,
|
||||
SeafileCrypt *crypt, GList *ignore_list,
|
||||
@ -1941,6 +2227,10 @@ apply_worktree_changes_to_index (SeafRepo *repo, struct index_state *istate,
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
update_path_sync_status (repo, status, istate, ignore_list);
|
||||
#endif
|
||||
|
||||
GList *scanned_dirs = NULL, *scanned_del_dirs = NULL;
|
||||
|
||||
WTEvent *last_event;
|
||||
@ -4186,6 +4476,8 @@ seaf_repo_manager_del_repo (SeafRepoManager *mgr,
|
||||
seaf_repo_manager_remove_repo_ondisk (mgr, repo->id,
|
||||
(repo->version > 0) ? TRUE : FALSE);
|
||||
|
||||
seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id);
|
||||
|
||||
if (pthread_rwlock_wrlock (&mgr->priv->lock) < 0) {
|
||||
g_warning ("[repo mgr] failed to lock repo cache.\n");
|
||||
return -1;
|
||||
@ -4889,16 +5181,15 @@ seaf_repo_manager_set_repo_property (SeafRepoManager *manager,
|
||||
|
||||
if (g_strcmp0(value, "true") == 0) {
|
||||
repo->auto_sync = 1;
|
||||
if (!repo->is_readonly)
|
||||
seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id,
|
||||
repo->worktree);
|
||||
seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id,
|
||||
repo->worktree);
|
||||
repo->last_sync_time = 0;
|
||||
} else {
|
||||
repo->auto_sync = 0;
|
||||
if (!repo->is_readonly)
|
||||
seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);
|
||||
seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);
|
||||
/* Cancel current sync task if any. */
|
||||
seaf_sync_manager_cancel_sync_task (seaf->sync_mgr, repo->id);
|
||||
seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id);
|
||||
}
|
||||
}
|
||||
if (strcmp(key, REPO_NET_BROWSABLE) == 0) {
|
||||
|
@ -272,6 +272,10 @@ start_rpc_service (CcnetClient *client)
|
||||
"seafile_get_checkout_task",
|
||||
searpc_signature_object__string());
|
||||
|
||||
searpc_server_register_function ("seafile-rpcserver",
|
||||
seafile_get_path_sync_status,
|
||||
"seafile_get_path_sync_status",
|
||||
searpc_signature_string__string_string_int());
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <ccnet.h>
|
||||
|
||||
#include "db.h"
|
||||
@ -19,6 +21,12 @@
|
||||
#include "mq-mgr.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "sync-status-tree.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <shlobj.h>
|
||||
#endif
|
||||
|
||||
#define DEBUG_FLAG SEAFILE_DEBUG_SYNC
|
||||
#include "log.h"
|
||||
|
||||
@ -63,8 +71,22 @@ struct _SeafSyncManagerPriv {
|
||||
|
||||
/* When FALSE, auto sync is globally disabled */
|
||||
gboolean auto_sync_enabled;
|
||||
|
||||
GHashTable *active_paths;
|
||||
pthread_mutex_t paths_lock;
|
||||
|
||||
#ifdef WIN32
|
||||
GAsyncQueue *refresh_paths;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct _ActivePathsInfo {
|
||||
GHashTable *paths;
|
||||
struct SyncStatusTree *syncing_tree;
|
||||
struct SyncStatusTree *synced_tree;
|
||||
};
|
||||
typedef struct _ActivePathsInfo ActivePathsInfo;
|
||||
|
||||
static void
|
||||
start_sync (SeafSyncManager *manager, SeafRepo *repo,
|
||||
gboolean need_commit, gboolean is_manual_sync,
|
||||
@ -102,6 +124,9 @@ sync_repo_v2 (SeafSyncManager *manager, SeafRepo *repo, gboolean is_manual_sync)
|
||||
static gboolean
|
||||
check_http_protocol (SeafSyncManager *mgr, SeafRepo *repo);
|
||||
|
||||
static void
|
||||
active_paths_info_free (ActivePathsInfo *info);
|
||||
|
||||
SeafSyncManager*
|
||||
seaf_sync_manager_new (SeafileSession *seaf)
|
||||
{
|
||||
@ -132,6 +157,15 @@ seaf_sync_manager_new (SeafileSession *seaf)
|
||||
if (exists)
|
||||
mgr->upload_limit = upload_limit;
|
||||
|
||||
mgr->priv->active_paths = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free,
|
||||
(GDestroyNotify)active_paths_info_free);
|
||||
pthread_mutex_init (&mgr->priv->paths_lock, NULL);
|
||||
|
||||
#ifdef WIN32
|
||||
mgr->priv->refresh_paths = g_async_queue_new ();
|
||||
#endif
|
||||
|
||||
return mgr;
|
||||
}
|
||||
|
||||
@ -351,6 +385,11 @@ update_tx_state (void *vmanager)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
static void *
|
||||
refresh_windows_explorer_thread (void *vdata);
|
||||
#endif
|
||||
|
||||
int
|
||||
seaf_sync_manager_start (SeafSyncManager *mgr)
|
||||
{
|
||||
@ -380,6 +419,13 @@ seaf_sync_manager_start (SeafSyncManager *mgr)
|
||||
g_signal_connect (seaf, "repo-http-uploaded",
|
||||
(GCallback)on_repo_http_uploaded, mgr);
|
||||
|
||||
#ifdef WIN32
|
||||
ccnet_job_manager_schedule_job (seaf->job_mgr,
|
||||
refresh_windows_explorer_thread,
|
||||
NULL,
|
||||
mgr->priv->refresh_paths);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2454,6 +2500,18 @@ check_folder_permissions (SeafSyncManager *mgr, GList *repos)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_active_paths (SeafSyncManager *mgr)
|
||||
{
|
||||
int n = seaf_sync_manager_active_paths_number(mgr);
|
||||
seaf_message ("%d active paths\n\n", n);
|
||||
if (n < 10) {
|
||||
char *paths_json = seaf_sync_manager_list_active_paths_json (mgr);
|
||||
seaf_message ("%s\n", paths_json);
|
||||
g_free (paths_json);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
auto_sync_pulse (void *vmanager)
|
||||
{
|
||||
@ -2462,6 +2520,8 @@ auto_sync_pulse (void *vmanager)
|
||||
SeafRepo *repo;
|
||||
gint64 now;
|
||||
|
||||
/* print_active_paths (manager); */
|
||||
|
||||
repos = seaf_repo_manager_get_repo_list (manager->seaf->repo_mgr, -1, -1);
|
||||
|
||||
check_folder_permissions (manager, repos);
|
||||
@ -2778,7 +2838,7 @@ sync_state_to_str (int state)
|
||||
}
|
||||
|
||||
static void
|
||||
cancel_all_sync_tasks (SeafSyncManager *mgr)
|
||||
disable_auto_sync_for_repos (SeafSyncManager *mgr)
|
||||
{
|
||||
GList *repos;
|
||||
GList *ptr;
|
||||
@ -2787,7 +2847,9 @@ cancel_all_sync_tasks (SeafSyncManager *mgr)
|
||||
repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1);
|
||||
for (ptr = repos; ptr; ptr = ptr->next) {
|
||||
repo = ptr->data;
|
||||
seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);
|
||||
seaf_sync_manager_cancel_sync_task (mgr, repo->id);
|
||||
seaf_sync_manager_remove_active_path_info (mgr, repo->id);
|
||||
}
|
||||
|
||||
g_list_free (repos);
|
||||
@ -2801,34 +2863,28 @@ seaf_sync_manager_disable_auto_sync (SeafSyncManager *mgr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
cancel_all_sync_tasks (mgr);
|
||||
disable_auto_sync_for_repos (mgr);
|
||||
|
||||
mgr->priv->auto_sync_enabled = FALSE;
|
||||
g_debug ("[sync mgr] auto sync is disabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void
|
||||
add_sync_tasks_for_all (SeafSyncManager *mgr)
|
||||
enable_auto_sync_for_repos (SeafSyncManager *mgr)
|
||||
{
|
||||
GList *repos, *ptr;
|
||||
GList *repos;
|
||||
GList *ptr;
|
||||
SeafRepo *repo;
|
||||
|
||||
repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1);
|
||||
for (ptr = repos; ptr; ptr = ptr->next) {
|
||||
repo = ptr->data;
|
||||
if (!repo->auto_sync)
|
||||
continue;
|
||||
|
||||
if (repo->worktree_invalid)
|
||||
continue;
|
||||
|
||||
start_sync (mgr, repo, TRUE, FALSE, TRUE);
|
||||
seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree);
|
||||
}
|
||||
|
||||
g_list_free (repos);
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
seaf_sync_manager_enable_auto_sync (SeafSyncManager *mgr)
|
||||
@ -2838,7 +2894,8 @@ seaf_sync_manager_enable_auto_sync (SeafSyncManager *mgr)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* add_sync_tasks_for_all (mgr); */
|
||||
enable_auto_sync_for_repos (mgr);
|
||||
|
||||
mgr->priv->auto_sync_enabled = TRUE;
|
||||
g_debug ("[sync mgr] auto sync is enabled\n");
|
||||
return 0;
|
||||
@ -2852,3 +2909,321 @@ seaf_sync_manager_is_auto_sync_enabled (SeafSyncManager *mgr)
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ActivePathsInfo *
|
||||
active_paths_info_new (SeafRepo *repo)
|
||||
{
|
||||
ActivePathsInfo *info = g_new0 (ActivePathsInfo, 1);
|
||||
|
||||
info->paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
info->syncing_tree = sync_status_tree_new (repo->worktree);
|
||||
info->synced_tree = sync_status_tree_new (repo->worktree);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static void
|
||||
active_paths_info_free (ActivePathsInfo *info)
|
||||
{
|
||||
if (!info)
|
||||
return;
|
||||
g_hash_table_destroy (info->paths);
|
||||
sync_status_tree_free (info->syncing_tree);
|
||||
sync_status_tree_free (info->synced_tree);
|
||||
g_free (info);
|
||||
}
|
||||
|
||||
void
|
||||
seaf_sync_manager_update_active_path (SeafSyncManager *mgr,
|
||||
const char *repo_id,
|
||||
const char *path,
|
||||
int mode,
|
||||
SyncStatus status)
|
||||
{
|
||||
ActivePathsInfo *info;
|
||||
SeafRepo *repo;
|
||||
|
||||
if (!repo_id || !path) {
|
||||
seaf_warning ("BUG: empty repo_id or path.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status <= SYNC_STATUS_NONE || status >= N_SYNC_STATUS) {
|
||||
seaf_warning ("BUG: invalid sync status %d.\n", status);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock (&mgr->priv->paths_lock);
|
||||
|
||||
info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);
|
||||
if (!info) {
|
||||
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
|
||||
if (!repo) {
|
||||
seaf_warning ("Failed to find repo %s\n", repo_id);
|
||||
pthread_mutex_unlock (&mgr->priv->paths_lock);
|
||||
return;
|
||||
}
|
||||
info = active_paths_info_new (repo);
|
||||
g_hash_table_insert (mgr->priv->active_paths, g_strdup(repo_id), info);
|
||||
}
|
||||
|
||||
SyncStatus existing = (SyncStatus) g_hash_table_lookup (info->paths, path);
|
||||
if (!existing) {
|
||||
g_hash_table_insert (info->paths, g_strdup(path), (void*)status);
|
||||
if (status == SYNC_STATUS_SYNCING)
|
||||
sync_status_tree_add (info->syncing_tree, path, mode);
|
||||
else if (status == SYNC_STATUS_SYNCED)
|
||||
sync_status_tree_add (info->synced_tree, path, mode);
|
||||
else {
|
||||
#ifdef WIN32
|
||||
seaf_sync_manager_add_refresh_path (mgr, path);
|
||||
#endif
|
||||
}
|
||||
} else if (existing != status) {
|
||||
g_hash_table_replace (info->paths, g_strdup(path), (void*)status);
|
||||
|
||||
if (existing == SYNC_STATUS_SYNCING)
|
||||
sync_status_tree_del (info->syncing_tree, path);
|
||||
else if (existing == SYNC_STATUS_SYNCED)
|
||||
sync_status_tree_del (info->synced_tree, path);
|
||||
|
||||
if (status == SYNC_STATUS_SYNCING)
|
||||
sync_status_tree_add (info->syncing_tree, path, mode);
|
||||
else if (status == SYNC_STATUS_SYNCED)
|
||||
sync_status_tree_add (info->synced_tree, path, mode);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock (&mgr->priv->paths_lock);
|
||||
}
|
||||
|
||||
void
|
||||
seaf_sync_manager_delete_active_path (SeafSyncManager *mgr,
|
||||
const char *repo_id,
|
||||
const char *path)
|
||||
{
|
||||
ActivePathsInfo *info;
|
||||
|
||||
if (!repo_id || !path) {
|
||||
seaf_warning ("BUG: empty repo_id or path.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock (&mgr->priv->paths_lock);
|
||||
|
||||
info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);
|
||||
if (!info) {
|
||||
pthread_mutex_unlock (&mgr->priv->paths_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_remove (info->paths, path);
|
||||
sync_status_tree_del (info->syncing_tree, path);
|
||||
sync_status_tree_del (info->synced_tree, path);
|
||||
|
||||
pthread_mutex_unlock (&mgr->priv->paths_lock);
|
||||
}
|
||||
|
||||
static char *path_status_tbl[] = {
|
||||
"none",
|
||||
"syncing",
|
||||
"error",
|
||||
"ignored",
|
||||
"synced",
|
||||
NULL,
|
||||
};
|
||||
|
||||
char *
|
||||
seaf_sync_manager_get_path_sync_status (SeafSyncManager *mgr,
|
||||
const char *repo_id,
|
||||
const char *path,
|
||||
gboolean is_dir)
|
||||
{
|
||||
ActivePathsInfo *info;
|
||||
SyncStatus ret = SYNC_STATUS_NONE;
|
||||
|
||||
if (!repo_id || !path) {
|
||||
seaf_warning ("BUG: empty repo_id or path.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
seaf_message ("get_path_sync_status for %s\n", path);
|
||||
|
||||
pthread_mutex_lock (&mgr->priv->paths_lock);
|
||||
|
||||
info = g_hash_table_lookup (mgr->priv->active_paths, repo_id);
|
||||
if (!info) {
|
||||
pthread_mutex_unlock (&mgr->priv->paths_lock);
|
||||
ret = SYNC_STATUS_NONE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = (SyncStatus) g_hash_table_lookup (info->paths, path);
|
||||
if (is_dir && (ret == SYNC_STATUS_NONE)) {
|
||||
/* If a dir is not in the syncing tree but in the synced tree,
|
||||
* it's synced. Otherwise if it's in the syncing tree, some files
|
||||
* under it must be syncing, so it should be in syncing status too.
|
||||
*/
|
||||
if (sync_status_tree_exists (info->syncing_tree, path))
|
||||
ret = SYNC_STATUS_SYNCING;
|
||||
else if (sync_status_tree_exists (info->synced_tree, path))
|
||||
ret = SYNC_STATUS_SYNCED;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock (&mgr->priv->paths_lock);
|
||||
|
||||
out:
|
||||
return g_strdup(path_status_tbl[ret]);
|
||||
}
|
||||
|
||||
static json_t *
|
||||
active_paths_to_json (GHashTable *paths)
|
||||
{
|
||||
json_t *array = NULL, *obj = NULL;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
char *path;
|
||||
SyncStatus status;
|
||||
|
||||
array = json_array ();
|
||||
|
||||
g_hash_table_iter_init (&iter, paths);
|
||||
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
||||
path = key;
|
||||
status = (SyncStatus)value;
|
||||
|
||||
obj = json_object ();
|
||||
json_object_set (obj, "path", json_string(path));
|
||||
json_object_set (obj, "status", json_string(path_status_tbl[status]));
|
||||
|
||||
json_array_append (array, obj);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
char *
|
||||
seaf_sync_manager_list_active_paths_json (SeafSyncManager *mgr)
|
||||
{
|
||||
json_t *array = NULL, *obj = NULL, *path_array = NULL;
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
char *repo_id;
|
||||
ActivePathsInfo *info;
|
||||
char *ret = NULL;
|
||||
|
||||
pthread_mutex_lock (&mgr->priv->paths_lock);
|
||||
|
||||
array = json_array ();
|
||||
|
||||
g_hash_table_iter_init (&iter, mgr->priv->active_paths);
|
||||
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
||||
repo_id = key;
|
||||
info = value;
|
||||
|
||||
obj = json_object();
|
||||
path_array = active_paths_to_json (info->paths);
|
||||
json_object_set (obj, "repo_id", json_string(repo_id));
|
||||
json_object_set (obj, "paths", path_array);
|
||||
|
||||
json_array_append (array, obj);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock (&mgr->priv->paths_lock);
|
||||
|
||||
ret = json_dumps (array, JSON_INDENT(4));
|
||||
if (!ret) {
|
||||
seaf_warning ("Failed to convert active paths to json\n");
|
||||
}
|
||||
|
||||
json_decref (array);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
seaf_sync_manager_active_paths_number (SeafSyncManager *mgr)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
ActivePathsInfo *info;
|
||||
int ret = 0;
|
||||
|
||||
g_hash_table_iter_init (&iter, mgr->priv->active_paths);
|
||||
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
||||
info = value;
|
||||
ret += g_hash_table_size(info->paths);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
seaf_sync_manager_remove_active_path_info (SeafSyncManager *mgr, const char *repo_id)
|
||||
{
|
||||
ActivePathsInfo *info;
|
||||
|
||||
pthread_mutex_lock (&mgr->priv->paths_lock);
|
||||
|
||||
g_hash_table_remove (mgr->priv->active_paths, repo_id);
|
||||
|
||||
pthread_mutex_unlock (&mgr->priv->paths_lock);
|
||||
|
||||
#ifdef WIN32
|
||||
seaf_message ("Refresh windows.\n");
|
||||
/* This is a hack to tell Windows Explorer to refresh all open windows. */
|
||||
SHChangeNotify (SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
static wchar_t *
|
||||
win_path (const char *path)
|
||||
{
|
||||
char *ret = g_strdup(path);
|
||||
wchar_t *ret_w;
|
||||
char *p;
|
||||
|
||||
for (p = ret; *p != 0; ++p)
|
||||
if (*p == '/')
|
||||
*p = '\\';
|
||||
|
||||
ret_w = g_utf8_to_utf16 (ret, -1, NULL, NULL, NULL);
|
||||
|
||||
g_free (ret);
|
||||
return ret_w;
|
||||
}
|
||||
|
||||
static void *
|
||||
refresh_windows_explorer_thread (void *vdata)
|
||||
{
|
||||
GAsyncQueue *q = vdata;
|
||||
char *path;
|
||||
wchar_t *wpath;
|
||||
int count = 0;
|
||||
|
||||
while (1) {
|
||||
path = g_async_queue_pop (q);
|
||||
wpath = win_path (path);
|
||||
|
||||
SHChangeNotify (SHCNE_ATTRIBUTES, SHCNF_PATHW, wpath, NULL);
|
||||
|
||||
seaf_debug ("Refresh %s\n", path);
|
||||
|
||||
g_free (path);
|
||||
g_free (wpath);
|
||||
|
||||
if (++count >= 100) {
|
||||
g_usleep (G_USEC_PER_SEC);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
seaf_sync_manager_add_refresh_path (SeafSyncManager *mgr, const char *path)
|
||||
{
|
||||
g_async_queue_push (mgr->priv->refresh_paths, g_strdup(path));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -88,6 +88,16 @@ struct _SyncTask {
|
||||
SeafRepo *repo; /* for convenience, only valid when in_sync. */
|
||||
};
|
||||
|
||||
enum _SyncStatus {
|
||||
SYNC_STATUS_NONE = 0,
|
||||
SYNC_STATUS_SYNCING,
|
||||
SYNC_STATUS_ERROR,
|
||||
SYNC_STATUS_IGNORED,
|
||||
SYNC_STATUS_SYNCED,
|
||||
N_SYNC_STATUS,
|
||||
};
|
||||
typedef enum _SyncStatus SyncStatus;
|
||||
|
||||
struct _SeafileSession;
|
||||
|
||||
struct _SeafSyncManager {
|
||||
@ -149,4 +159,37 @@ sync_error_to_str (int error);
|
||||
|
||||
const char *
|
||||
sync_state_to_str (int state);
|
||||
|
||||
void
|
||||
seaf_sync_manager_update_active_path (SeafSyncManager *mgr,
|
||||
const char *repo_id,
|
||||
const char *path,
|
||||
int mode,
|
||||
SyncStatus status);
|
||||
|
||||
void
|
||||
seaf_sync_manager_delete_active_path (SeafSyncManager *mgr,
|
||||
const char *repo_id,
|
||||
const char *path);
|
||||
|
||||
char *
|
||||
seaf_sync_manager_get_path_sync_status (SeafSyncManager *mgr,
|
||||
const char *repo_id,
|
||||
const char *path,
|
||||
gboolean is_dir);
|
||||
|
||||
char *
|
||||
seaf_sync_manager_list_active_paths_json (SeafSyncManager *mgr);
|
||||
|
||||
int
|
||||
seaf_sync_manager_active_paths_number (SeafSyncManager *mgr);
|
||||
|
||||
void
|
||||
seaf_sync_manager_remove_active_path_info (SeafSyncManager *mgr, const char *repo_id);
|
||||
|
||||
#ifdef WIN32
|
||||
void
|
||||
seaf_sync_manager_add_refresh_path (SeafSyncManager *mgr, const char *path);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
287
daemon/sync-status-tree.c
Normal file
287
daemon/sync-status-tree.c
Normal file
@ -0,0 +1,287 @@
|
||||
#include "common.h"
|
||||
|
||||
#include "seafile-session.h"
|
||||
|
||||
#include "sync-status-tree.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
struct _SyncStatusDir {
|
||||
GHashTable *dirents; /* name -> dirent. */
|
||||
};
|
||||
typedef struct _SyncStatusDir SyncStatusDir;
|
||||
|
||||
struct _SyncStatusDirent {
|
||||
char *name;
|
||||
int mode;
|
||||
/* Only used for directories. */
|
||||
SyncStatusDir *subdir;
|
||||
};
|
||||
typedef struct _SyncStatusDirent SyncStatusDirent;
|
||||
|
||||
struct SyncStatusTree {
|
||||
SyncStatusDir *root;
|
||||
char *worktree;
|
||||
};
|
||||
typedef struct SyncStatusTree SyncStatusTree;
|
||||
|
||||
static void
|
||||
sync_status_dirent_free (SyncStatusDirent *dirent);
|
||||
|
||||
static SyncStatusDir *
|
||||
sync_status_dir_new ()
|
||||
{
|
||||
SyncStatusDir *dir = g_new0 (SyncStatusDir, 1);
|
||||
dir->dirents = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free,
|
||||
(GDestroyNotify)sync_status_dirent_free);
|
||||
return dir;
|
||||
}
|
||||
|
||||
static void
|
||||
sync_status_dir_free (SyncStatusDir *dir)
|
||||
{
|
||||
if (!dir)
|
||||
return;
|
||||
g_hash_table_destroy (dir->dirents);
|
||||
g_free (dir);
|
||||
}
|
||||
|
||||
static SyncStatusDirent *
|
||||
sync_status_dirent_new (const char *name, int mode)
|
||||
{
|
||||
SyncStatusDirent *dirent = g_new0(SyncStatusDirent, 1);
|
||||
dirent->name = g_strdup(name);
|
||||
dirent->mode = mode;
|
||||
|
||||
if (S_ISDIR(mode))
|
||||
dirent->subdir = sync_status_dir_new ();
|
||||
|
||||
return dirent;
|
||||
}
|
||||
|
||||
static void
|
||||
sync_status_dirent_free (SyncStatusDirent *dirent)
|
||||
{
|
||||
if (!dirent)
|
||||
return;
|
||||
g_free (dirent->name);
|
||||
sync_status_dir_free (dirent->subdir);
|
||||
g_free (dirent);
|
||||
}
|
||||
|
||||
SyncStatusTree *
|
||||
sync_status_tree_new (const char *worktree)
|
||||
{
|
||||
SyncStatusTree *tree = g_new0(SyncStatusTree, 1);
|
||||
tree->root = sync_status_dir_new ();
|
||||
tree->worktree = g_strdup(worktree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
static void
|
||||
refresh_recursive (const char *basedir, SyncStatusDir *dir)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer key, value;
|
||||
char *dname, *path;
|
||||
SyncStatusDirent *dirent;
|
||||
|
||||
g_hash_table_iter_init (&iter, dir->dirents);
|
||||
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
||||
dname = key;
|
||||
dirent = value;
|
||||
|
||||
path = g_strconcat(basedir, "/", dname, NULL);
|
||||
seaf_sync_manager_add_refresh_path (seaf->sync_mgr, path);
|
||||
|
||||
if (S_ISDIR(dirent->mode))
|
||||
refresh_recursive (path, dirent->subdir);
|
||||
|
||||
g_free (path);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
sync_status_tree_free (struct SyncStatusTree *tree)
|
||||
{
|
||||
if (!tree)
|
||||
return;
|
||||
|
||||
#ifdef WIN32
|
||||
/* refresh_recursive (tree->worktree, tree->root); */
|
||||
#endif
|
||||
/* Free the tree recursively. */
|
||||
sync_status_dir_free (tree->root);
|
||||
|
||||
g_free (tree->worktree);
|
||||
}
|
||||
|
||||
void
|
||||
sync_status_tree_add (SyncStatusTree *tree,
|
||||
const char *path,
|
||||
int mode)
|
||||
{
|
||||
char **dnames = NULL;
|
||||
guint n, i;
|
||||
char *dname;
|
||||
SyncStatusDir *dir = tree->root;
|
||||
SyncStatusDirent *dirent;
|
||||
GString *buf;
|
||||
|
||||
dnames = g_strsplit (path, "/", 0);
|
||||
if (!dnames)
|
||||
return;
|
||||
n = g_strv_length (dnames);
|
||||
|
||||
buf = g_string_new ("");
|
||||
g_string_append (buf, tree->worktree);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
dname = dnames[i];
|
||||
dirent = g_hash_table_lookup (dir->dirents, dname);
|
||||
g_string_append (buf, "/");
|
||||
g_string_append (buf, dname);
|
||||
if (dirent) {
|
||||
if (S_ISDIR(dirent->mode)) {
|
||||
if (i == (n-1)) {
|
||||
goto out;
|
||||
} else {
|
||||
dir = dirent->subdir;
|
||||
}
|
||||
} else {
|
||||
goto out;
|
||||
}
|
||||
} else {
|
||||
if (i == (n-1)) {
|
||||
dirent = sync_status_dirent_new (dname, mode);
|
||||
g_hash_table_insert (dir->dirents, g_strdup(dname), dirent);
|
||||
} else {
|
||||
dirent = sync_status_dirent_new (dname, S_IFDIR);
|
||||
g_hash_table_insert (dir->dirents, g_strdup(dname), dirent);
|
||||
dir = dirent->subdir;
|
||||
}
|
||||
#ifdef WIN32
|
||||
seaf_sync_manager_add_refresh_path (seaf->sync_mgr, buf->str);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
g_string_free (buf, TRUE);
|
||||
g_strfreev (dnames);
|
||||
}
|
||||
|
||||
inline static gboolean
|
||||
is_empty_dir (SyncStatusDirent *dirent)
|
||||
{
|
||||
return (g_hash_table_size(dirent->subdir->dirents) == 0);
|
||||
}
|
||||
|
||||
static void
|
||||
remove_item (SyncStatusDir *dir, const char *dname, const char *fullpath)
|
||||
{
|
||||
g_hash_table_remove (dir->dirents, dname);
|
||||
#ifdef WIN32
|
||||
seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
delete_recursive (SyncStatusDir *dir, char **dnames, guint n, guint i,
|
||||
const char *base)
|
||||
{
|
||||
char *dname;
|
||||
SyncStatusDirent *dirent;
|
||||
char *fullpath = NULL;
|
||||
|
||||
dname = dnames[i];
|
||||
fullpath = g_strconcat (base, "/", dname, NULL);
|
||||
|
||||
dirent = g_hash_table_lookup (dir->dirents, dname);
|
||||
if (dirent) {
|
||||
if (S_ISDIR(dirent->mode)) {
|
||||
if (i == (n-1)) {
|
||||
if (is_empty_dir(dirent))
|
||||
remove_item (dir, dname, fullpath);
|
||||
} else {
|
||||
delete_recursive (dirent->subdir, dnames, n, ++i, fullpath);
|
||||
/* If this dir becomes empty after deleting the entry below,
|
||||
* remove the dir itself too.
|
||||
*/
|
||||
if (is_empty_dir(dirent))
|
||||
remove_item (dir, dname, fullpath);
|
||||
}
|
||||
} else if (i == (n-1)) {
|
||||
remove_item (dir, dname, fullpath);
|
||||
}
|
||||
}
|
||||
|
||||
g_free (fullpath);
|
||||
}
|
||||
|
||||
void
|
||||
sync_status_tree_del (SyncStatusTree *tree,
|
||||
const char *path)
|
||||
{
|
||||
char **dnames = NULL;
|
||||
guint n;
|
||||
SyncStatusDir *dir = tree->root;
|
||||
|
||||
dnames = g_strsplit (path, "/", 0);
|
||||
if (!dnames)
|
||||
return;
|
||||
n = g_strv_length (dnames);
|
||||
|
||||
delete_recursive (dir, dnames, n, 0, tree->worktree);
|
||||
|
||||
out:
|
||||
g_strfreev (dnames);
|
||||
}
|
||||
|
||||
int
|
||||
sync_status_tree_exists (SyncStatusTree *tree,
|
||||
const char *path)
|
||||
{
|
||||
char **dnames = NULL;
|
||||
guint n, i;
|
||||
char *dname;
|
||||
SyncStatusDir *dir = tree->root;
|
||||
SyncStatusDirent *dirent;
|
||||
int ret = 0;
|
||||
|
||||
dnames = g_strsplit (path, "/", 0);
|
||||
if (!dnames)
|
||||
return ret;
|
||||
n = g_strv_length (dnames);
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
dname = dnames[i];
|
||||
dirent = g_hash_table_lookup (dir->dirents, dname);
|
||||
if (dirent) {
|
||||
if (S_ISDIR(dirent->mode)) {
|
||||
if (i == (n-1)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
} else {
|
||||
dir = dirent->subdir;
|
||||
}
|
||||
} else {
|
||||
if (i == (n-1)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
} else {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
g_strfreev (dnames);
|
||||
return ret;
|
||||
}
|
33
daemon/sync-status-tree.h
Normal file
33
daemon/sync-status-tree.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef SYNC_STATUS_TREE_H
|
||||
#define SYNC_STATUS_TREE_H
|
||||
|
||||
struct SyncStatusTree;
|
||||
|
||||
struct SyncStatusTree *
|
||||
sync_status_tree_new (const char *worktree);
|
||||
|
||||
void
|
||||
sync_status_tree_free (struct SyncStatusTree *tree);
|
||||
|
||||
/*
|
||||
* Add a @path into the @tree. If any directory along the path is missing,
|
||||
* it will be created. If the path already exists, it won't be overwritten.
|
||||
*/
|
||||
void
|
||||
sync_status_tree_add (struct SyncStatusTree *tree,
|
||||
const char *path,
|
||||
int mode);
|
||||
|
||||
/*
|
||||
* Delete a path from the tree. If directory becomes empty after the deletion,
|
||||
* it will be deleted too. All empty direcotries along the path will be deleted.
|
||||
*/
|
||||
void
|
||||
sync_status_tree_del (struct SyncStatusTree *tree,
|
||||
const char *path);
|
||||
|
||||
int
|
||||
sync_status_tree_exists (struct SyncStatusTree *tree,
|
||||
const char *path);
|
||||
|
||||
#endif
|
50
daemon/test-sync-tree.c
Normal file
50
daemon/test-sync-tree.c
Normal file
@ -0,0 +1,50 @@
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "sync-status-tree.h"
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
struct SyncStatusTree *tree;
|
||||
int val;
|
||||
|
||||
tree = sync_status_tree_new ();
|
||||
|
||||
sync_status_tree_add (tree, "a/b/c.txt", S_IFREG);
|
||||
sync_status_tree_add (tree, "a/b/c/d", S_IFDIR);
|
||||
sync_status_tree_add (tree, "a/xxx.txt", S_IFREG);
|
||||
|
||||
printf ("test after add\n");
|
||||
|
||||
val = sync_status_tree_exists (tree, "a/b/c.txt");
|
||||
printf ("a/b/c.txt: %d\n", val);
|
||||
|
||||
val = sync_status_tree_exists (tree, "a/b/c/d");
|
||||
printf ("a/b/c/d: %d\n", val);
|
||||
|
||||
val = sync_status_tree_exists (tree, "a/d/f.foo");
|
||||
printf ("a/d/f.foo: %d\n", val);
|
||||
|
||||
val = sync_status_tree_exists (tree, "a/b");
|
||||
printf ("a/b: %d\n", val);
|
||||
|
||||
sync_status_tree_del (tree, "a/b/c.txt");
|
||||
sync_status_tree_del (tree, "a/b/c/d");
|
||||
sync_status_tree_del (tree, "a/xxx.txt");
|
||||
sync_status_tree_del (tree, "a/c.pdf");
|
||||
|
||||
printf ("test after del\n");
|
||||
|
||||
val = sync_status_tree_exists (tree, "a/b/c.txt");
|
||||
printf ("a/b/c.txt: %d\n", val);
|
||||
|
||||
val = sync_status_tree_exists (tree, "a/b/c/d");
|
||||
printf ("a/b/c/d: %d\n", val);
|
||||
|
||||
val = sync_status_tree_exists (tree, "a/b");
|
||||
printf ("a/b: %d\n", val);
|
||||
|
||||
val = sync_status_tree_exists (tree, "a");
|
||||
printf ("a: %d\n", val);
|
||||
}
|
@ -43,6 +43,9 @@ WTStatus *create_wt_status (const char *repo_id)
|
||||
status->event_q = g_queue_new ();
|
||||
pthread_mutex_init (&status->q_lock, NULL);
|
||||
|
||||
status->active_paths = g_queue_new ();
|
||||
pthread_mutex_init (&status->ap_q_lock, NULL);
|
||||
|
||||
/* The monitor thread always holds a reference to this status
|
||||
* until it's unwatched
|
||||
*/
|
||||
|
@ -43,6 +43,14 @@ typedef struct WTStatus {
|
||||
|
||||
pthread_mutex_t q_lock;
|
||||
GQueue *event_q;
|
||||
|
||||
/* Paths that're updated. They corresponds to CREATE_OR_UPDATE events.
|
||||
* Use a separate queue since we need to process them simultaneously with
|
||||
* the event queue. And this queue is usually shorter and consumed faster,
|
||||
* because we don't need to process them in multiple batches.
|
||||
*/
|
||||
pthread_mutex_t ap_q_lock;
|
||||
GQueue *active_paths;
|
||||
} WTStatus;
|
||||
|
||||
WTStatus *create_wt_status (const char *repo_id);
|
||||
|
@ -167,6 +167,16 @@ add_event_to_queue (WTStatus *status,
|
||||
pthread_mutex_lock (&status->q_lock);
|
||||
g_queue_push_tail (status->event_q, event);
|
||||
pthread_mutex_unlock (&status->q_lock);
|
||||
|
||||
if (type == WT_EVENT_CREATE_OR_UPDATE) {
|
||||
pthread_mutex_lock (&status->ap_q_lock);
|
||||
|
||||
char *last = g_queue_peek_tail (status->active_paths);
|
||||
if (!last || strcmp(last, path) != 0)
|
||||
g_queue_push_tail (status->active_paths, g_strdup(path));
|
||||
|
||||
pthread_mutex_unlock (&status->ap_q_lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* Every time after a read event is processed, we should call
|
||||
|
@ -208,6 +208,12 @@ int seafile_enable_auto_sync (GError **error);
|
||||
|
||||
int seafile_is_auto_sync_enabled (GError **error);
|
||||
|
||||
char *
|
||||
seafile_get_path_sync_status (const char *repo_id,
|
||||
const char *path,
|
||||
int is_dir,
|
||||
GError **error);
|
||||
|
||||
/**
|
||||
* seafile_list_dir:
|
||||
* List a directory.
|
||||
|
@ -64,6 +64,7 @@ public class Repo : Object {
|
||||
}
|
||||
public int last_sync_time { get; set; }
|
||||
public bool auto_sync { get; set; }
|
||||
public bool worktree_invalid { get; set; }
|
||||
|
||||
// Section 4: Server only information
|
||||
// Should be set for all server repo objects
|
||||
|
@ -42,6 +42,7 @@ func_table = [
|
||||
[ "string", ["string", "int"] ],
|
||||
[ "string", ["string", "int", "int"] ],
|
||||
[ "string", ["string", "string"] ],
|
||||
[ "string", ["string", "string", "int"] ],
|
||||
[ "string", ["string", "string", "int", "int"] ],
|
||||
[ "string", ["string", "string", "string"] ],
|
||||
[ "string", ["string", "string", "string", "string"] ],
|
||||
|
@ -688,6 +688,8 @@ traverse_directory_win32 (wchar_t *path_w,
|
||||
wcscmp (fdata.cFileName, L"..") == 0)
|
||||
continue;
|
||||
|
||||
++ret;
|
||||
|
||||
stop = FALSE;
|
||||
if (callback (path_w, &fdata, user_data, &stop) < 0) {
|
||||
ret = -1;
|
||||
|
Loading…
Reference in New Issue
Block a user