[win] Add file/folder sync status for upload.

This commit is contained in:
Jiaqiang Xu 2015-04-29 14:43:15 +08:00
parent 089cbf1161
commit 4d8e21bc43
18 changed files with 1254 additions and 46 deletions

View File

@ -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:

View File

@ -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 */

View File

@ -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 \

View File

@ -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);

View File

@ -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, &params);
ret = add_dir_recursive (path, full_path, &st, &params, 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) {

View File

@ -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

View File

@ -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

View File

@ -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
View 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
View 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
View 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);
}

View File

@ -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
*/

View File

@ -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);

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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"] ],

View File

@ -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;