From 4d8e21bc43dafb3bfb3897af2126747b3f109c34 Mon Sep 17 00:00:00 2001 From: Jiaqiang Xu Date: Wed, 29 Apr 2015 14:43:15 +0800 Subject: [PATCH] [win] Add file/folder sync status for upload. --- common/index/index.c | 3 +- common/rpc-service.c | 30 +++ daemon/Makefile.am | 2 + daemon/http-tx-mgr.c | 97 +++++++-- daemon/repo-mgr.c | 317 ++++++++++++++++++++++++++-- daemon/seaf-daemon.c | 4 + daemon/sync-mgr.c | 403 ++++++++++++++++++++++++++++++++++-- daemon/sync-mgr.h | 43 ++++ daemon/sync-status-tree.c | 287 +++++++++++++++++++++++++ daemon/sync-status-tree.h | 33 +++ daemon/test-sync-tree.c | 50 +++++ daemon/wt-monitor-structs.c | 3 + daemon/wt-monitor-structs.h | 8 + daemon/wt-monitor-win32.c | 10 + include/seafile-rpc.h | 6 + lib/repo.vala | 1 + lib/rpc_table.py | 1 + lib/utils.c | 2 + 18 files changed, 1254 insertions(+), 46 deletions(-) create mode 100644 daemon/sync-status-tree.c create mode 100644 daemon/sync-status-tree.h create mode 100644 daemon/test-sync-tree.c diff --git a/common/index/index.c b/common/index/index.c index a34ca026..535ab761 100644 --- a/common/index/index.c +++ b/common/index/index.c @@ -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: diff --git a/common/rpc-service.c b/common/rpc-service.c index fde51115..48606528 100644 --- a/common/rpc-service.c +++ b/common/rpc-service.c @@ -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 */ diff --git a/daemon/Makefile.am b/daemon/Makefile.am index ac25ef97..894bd5ac 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -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 \ diff --git a/daemon/http-tx-mgr.c b/daemon/http-tx-mgr.c index 8c775c0a..a4c9a177 100644 --- a/daemon/http-tx-mgr.c +++ b/daemon/http-tx-mgr.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); diff --git a/daemon/repo-mgr.c b/daemon/repo-mgr.c index 9f27fde4..5d99b6d4 100644 --- a/daemon/repo-mgr.c +++ b/daemon/repo-mgr.c @@ -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) { diff --git a/daemon/seaf-daemon.c b/daemon/seaf-daemon.c index 8246d6bf..58eb7437 100644 --- a/daemon/seaf-daemon.c +++ b/daemon/seaf-daemon.c @@ -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 diff --git a/daemon/sync-mgr.c b/daemon/sync-mgr.c index 47d23ea4..7c11f6b0 100644 --- a/daemon/sync-mgr.c +++ b/daemon/sync-mgr.c @@ -3,6 +3,8 @@ #include "common.h" +#include + #include #include "db.h" @@ -19,6 +21,12 @@ #include "mq-mgr.h" #include "utils.h" +#include "sync-status-tree.h" + +#ifdef WIN32 +#include +#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 diff --git a/daemon/sync-mgr.h b/daemon/sync-mgr.h index 2e1cfb95..00521636 100644 --- a/daemon/sync-mgr.h +++ b/daemon/sync-mgr.h @@ -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 diff --git a/daemon/sync-status-tree.c b/daemon/sync-status-tree.c new file mode 100644 index 00000000..d71b062c --- /dev/null +++ b/daemon/sync-status-tree.c @@ -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; +} diff --git a/daemon/sync-status-tree.h b/daemon/sync-status-tree.h new file mode 100644 index 00000000..abd26d51 --- /dev/null +++ b/daemon/sync-status-tree.h @@ -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 diff --git a/daemon/test-sync-tree.c b/daemon/test-sync-tree.c new file mode 100644 index 00000000..3454107d --- /dev/null +++ b/daemon/test-sync-tree.c @@ -0,0 +1,50 @@ +#include +#include +#include + +#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); +} diff --git a/daemon/wt-monitor-structs.c b/daemon/wt-monitor-structs.c index c4d38a39..3c5be618 100644 --- a/daemon/wt-monitor-structs.c +++ b/daemon/wt-monitor-structs.c @@ -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 */ diff --git a/daemon/wt-monitor-structs.h b/daemon/wt-monitor-structs.h index 10645129..414e4e32 100644 --- a/daemon/wt-monitor-structs.h +++ b/daemon/wt-monitor-structs.h @@ -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); diff --git a/daemon/wt-monitor-win32.c b/daemon/wt-monitor-win32.c index 7732c4fd..71cfeffb 100644 --- a/daemon/wt-monitor-win32.c +++ b/daemon/wt-monitor-win32.c @@ -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 diff --git a/include/seafile-rpc.h b/include/seafile-rpc.h index b4c0addf..90a25efe 100644 --- a/include/seafile-rpc.h +++ b/include/seafile-rpc.h @@ -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. diff --git a/lib/repo.vala b/lib/repo.vala index f632b6cf..1302c921 100644 --- a/lib/repo.vala +++ b/lib/repo.vala @@ -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 diff --git a/lib/rpc_table.py b/lib/rpc_table.py index cb96620b..e396812e 100644 --- a/lib/rpc_table.py +++ b/lib/rpc_table.py @@ -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"] ], diff --git a/lib/utils.c b/lib/utils.c index 220c4b6d..ca203ea2 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -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;