mirror of
https://github.com/haiwen/seafile-server.git
synced 2025-04-28 19:35:10 +00:00
6573 lines
213 KiB
C
6573 lines
213 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
#include "common.h"
|
|
|
|
#include <glib/gstdio.h>
|
|
|
|
#include <jansson.h>
|
|
#include <openssl/sha.h>
|
|
|
|
#include "utils.h"
|
|
#define DEBUG_FLAG SEAFILE_DEBUG_OTHER
|
|
#include "log.h"
|
|
#include "seafile-object.h"
|
|
|
|
#include "seafile-session.h"
|
|
#include "commit-mgr.h"
|
|
#include "branch-mgr.h"
|
|
#include "repo-mgr.h"
|
|
#include "fs-mgr.h"
|
|
#include "seafile-error.h"
|
|
#include "seafile-crypt.h"
|
|
#include "diff-simple.h"
|
|
#include "merge-new.h"
|
|
|
|
#include "seaf-db.h"
|
|
|
|
#define INDEX_DIR "index"
|
|
|
|
#define PREFIX_DEL_FILE "Deleted \""
|
|
#define PREFIX_DEL_DIR "Removed directory \""
|
|
#define PREFIX_DEL_DIRS "Removed \""
|
|
|
|
gboolean
|
|
should_ignore_file(const char *filename, void *data);
|
|
|
|
static gboolean
|
|
is_virtual_repo_and_origin (SeafRepo *repo1, SeafRepo *repo2);
|
|
|
|
static gboolean
|
|
check_file_count_and_size (SeafRepo *repo, SeafDirent *dent, gint64 total_files,
|
|
gint64 *total_size_all, char **err_str);
|
|
|
|
int
|
|
post_files_and_gen_commit (GList *filenames,
|
|
SeafRepo *repo,
|
|
const char *user,
|
|
char **ret_json,
|
|
int replace_existed,
|
|
const char *canon_path,
|
|
GList *id_list,
|
|
GList *size_list,
|
|
GError **error);
|
|
|
|
/*
|
|
* Repo operations.
|
|
*/
|
|
|
|
static gint
|
|
compare_dirents (gconstpointer a, gconstpointer b)
|
|
{
|
|
const SeafDirent *ent_a = a, *ent_b = b;
|
|
|
|
return strcmp (ent_b->name, ent_a->name);
|
|
}
|
|
|
|
static inline GList *
|
|
dup_seafdir_entries (const GList *entries)
|
|
{
|
|
const GList *p;
|
|
GList *newentries = NULL;
|
|
SeafDirent *dent;
|
|
|
|
for (p = entries; p; p = p->next) {
|
|
dent = p->data;
|
|
newentries = g_list_prepend (newentries, seaf_dirent_dup(dent));
|
|
}
|
|
|
|
return g_list_reverse(newentries);
|
|
}
|
|
|
|
static gboolean
|
|
filename_exists (GList *entries, const char *filename)
|
|
{
|
|
GList *ptr;
|
|
SeafDirent *dent;
|
|
|
|
for (ptr = entries; ptr != NULL; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
if (strcmp (dent->name, filename) == 0)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
split_filename (const char *filename, char **name, char **ext)
|
|
{
|
|
char *dot;
|
|
|
|
dot = strrchr (filename, '.');
|
|
if (dot) {
|
|
*ext = g_strdup (dot + 1);
|
|
*name = g_strndup (filename, dot - filename);
|
|
} else {
|
|
*name = g_strdup (filename);
|
|
*ext = NULL;
|
|
}
|
|
}
|
|
|
|
static char *
|
|
generate_unique_filename (const char *file, GList *entries)
|
|
{
|
|
int i = 1;
|
|
char *name, *ext, *unique_name;
|
|
|
|
unique_name = g_strdup(file);
|
|
split_filename (unique_name, &name, &ext);
|
|
while (filename_exists (entries, unique_name) && i <= 100) {
|
|
g_free (unique_name);
|
|
if (ext)
|
|
unique_name = g_strdup_printf ("%s (%d).%s", name, i, ext);
|
|
else
|
|
unique_name = g_strdup_printf ("%s (%d)", name, i);
|
|
i++;
|
|
}
|
|
|
|
g_free (name);
|
|
g_free (ext);
|
|
|
|
if (i <= 100)
|
|
return unique_name;
|
|
else {
|
|
g_free (unique_name);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* We need to call this function recursively because every dirs in canon_path
|
|
* need to be updated.
|
|
*/
|
|
static char *
|
|
post_file_recursive (SeafRepo *repo,
|
|
const char *dir_id,
|
|
const char *to_path,
|
|
int replace_existed,
|
|
SeafDirent *newdent)
|
|
{
|
|
SeafDir *olddir, *newdir;
|
|
SeafDirent *dent;
|
|
GList *ptr;
|
|
char *slash;
|
|
char *to_path_dup = NULL;
|
|
char *remain = NULL;
|
|
char *id = NULL;
|
|
char *ret = NULL;
|
|
|
|
olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
dir_id);
|
|
if (!olddir)
|
|
return NULL;
|
|
|
|
/* we reach the target dir. new dir entry is added */
|
|
if (*to_path == '\0') {
|
|
GList *newentries = NULL;
|
|
char *unique_name;
|
|
SeafDirent *dent_dup;
|
|
if (replace_existed && filename_exists(olddir->entries, newdent->name)) {
|
|
GList *p;
|
|
SeafDirent *dent;
|
|
|
|
for (p = olddir->entries; p; p = p->next) {
|
|
dent = p->data;
|
|
if (strcmp(dent->name, newdent->name) == 0) {
|
|
newentries = g_list_prepend (newentries, seaf_dirent_dup(newdent));
|
|
} else {
|
|
newentries = g_list_prepend (newentries, seaf_dirent_dup(dent));
|
|
}
|
|
}
|
|
newentries = g_list_reverse (newentries);
|
|
newdir = seaf_dir_new (NULL, newentries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0) {
|
|
ret = g_strdup (newdir->dir_id);
|
|
}
|
|
seaf_dir_free (newdir);
|
|
goto out;
|
|
}
|
|
|
|
unique_name = generate_unique_filename (newdent->name, olddir->entries);
|
|
if (!unique_name)
|
|
goto out;
|
|
dent_dup = seaf_dirent_new (newdent->version,
|
|
newdent->id, newdent->mode, unique_name,
|
|
newdent->mtime, newdent->modifier, newdent->size);
|
|
g_free (unique_name);
|
|
|
|
newentries = dup_seafdir_entries (olddir->entries);
|
|
|
|
newentries = g_list_insert_sorted (newentries,
|
|
dent_dup,
|
|
compare_dirents);
|
|
|
|
newdir = seaf_dir_new (NULL, newentries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strdup (newdir->dir_id);
|
|
seaf_dir_free (newdir);
|
|
|
|
goto out;
|
|
}
|
|
|
|
to_path_dup = g_strdup (to_path);
|
|
slash = strchr (to_path_dup, '/');
|
|
|
|
if (!slash) {
|
|
remain = to_path_dup + strlen(to_path_dup);
|
|
} else {
|
|
*slash = '\0';
|
|
remain = slash + 1;
|
|
}
|
|
|
|
for (ptr = olddir->entries; ptr; ptr = ptr->next) {
|
|
dent = (SeafDirent *)ptr->data;
|
|
|
|
if (strcmp(dent->name, to_path_dup) != 0)
|
|
continue;
|
|
|
|
id = post_file_recursive (repo, dent->id, remain, replace_existed, newdent);
|
|
if (id != NULL) {
|
|
memcpy(dent->id, id, 40);
|
|
dent->id[40] = '\0';
|
|
if (repo->version > 0)
|
|
dent->mtime = (guint64)time(NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (id != NULL) {
|
|
/* Create a new SeafDir. */
|
|
GList *new_entries;
|
|
|
|
new_entries = dup_seafdir_entries (olddir->entries);
|
|
newdir = seaf_dir_new (NULL, new_entries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strndup (newdir->dir_id, 40);
|
|
seaf_dir_free (newdir);
|
|
}
|
|
|
|
out:
|
|
g_free (to_path_dup);
|
|
g_free (id);
|
|
seaf_dir_free(olddir);
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
do_post_file_replace (SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *parent_dir,
|
|
int replace_existed,
|
|
SeafDirent *dent)
|
|
{
|
|
/* if parent_dir is a absolutely path, we will remove the first '/' */
|
|
if (*parent_dir == '/')
|
|
parent_dir = parent_dir + 1;
|
|
|
|
return post_file_recursive(repo, root_id, parent_dir, replace_existed, dent);
|
|
}
|
|
|
|
static char *
|
|
do_post_file (SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *parent_dir,
|
|
SeafDirent *dent)
|
|
{
|
|
return do_post_file_replace(repo, root_id, parent_dir, 0, dent);
|
|
}
|
|
|
|
static char *
|
|
get_canonical_path (const char *path)
|
|
{
|
|
char *ret = g_strdup (path);
|
|
char *p;
|
|
|
|
for (p = ret; *p != 0; ++p) {
|
|
if (*p == '\\')
|
|
*p = '/';
|
|
}
|
|
|
|
/* Remove trailing slashes from dir path. */
|
|
int len = strlen(ret);
|
|
int i = len - 1;
|
|
while (i >= 0 && ret[i] == '/')
|
|
ret[i--] = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Return TRUE if @filename already existing in @parent_dir. If exists, and
|
|
@mode is not NULL, set its value to the mode of the dirent.
|
|
*/
|
|
static gboolean
|
|
check_file_exists (const char *store_id,
|
|
int repo_version,
|
|
const char *root_id,
|
|
const char *parent_dir,
|
|
const char *filename,
|
|
int *mode)
|
|
{
|
|
SeafDir *dir;
|
|
GList *p;
|
|
SeafDirent *dent;
|
|
int ret = FALSE;
|
|
|
|
dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
|
|
store_id, repo_version,
|
|
root_id,
|
|
parent_dir, NULL);
|
|
if (!dir) {
|
|
seaf_warning ("parent_dir %s doesn't exist in repo %s.\n",
|
|
parent_dir, store_id);
|
|
return FALSE;
|
|
}
|
|
|
|
for (p = dir->entries; p != NULL; p = p->next) {
|
|
dent = p->data;
|
|
int r = strcmp (dent->name, filename);
|
|
if (r == 0) {
|
|
ret = TRUE;
|
|
if (mode) {
|
|
*mode = dent->mode;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
seaf_dir_free (dir);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
Various online file/directory operations:
|
|
|
|
Put a file:
|
|
1. find parent seafdir
|
|
2. add a new dirent to parent seafdir
|
|
2. recursively update all seafdir in the path, in a bottom-up manner
|
|
3. commit it
|
|
|
|
Del a file/dir:
|
|
basically the same as put a file
|
|
|
|
copy a file/dir:
|
|
1. get src dirent from src repo
|
|
2. duplicate src dirent with the new file name
|
|
3. put the new dirent to dst repo and commit it.
|
|
|
|
Move a file/dir:
|
|
basically the same as a copy operation. Just one more step:
|
|
4. remove src dirent from src repo and commit it
|
|
|
|
Rename a file/dir:
|
|
1. find parent seafdir
|
|
2. update this seafdir with the old dirent replaced by a new dirent.
|
|
3. recursively update all seafdir in the path
|
|
|
|
NOTE:
|
|
|
|
All operations which add a new dirent would check if a dirent with the same
|
|
name already exists. If found, they would raise errors.
|
|
|
|
All operations which remove a dirent would check if the dirent to be removed
|
|
already exists. If not, they would do nothing and just return OK.
|
|
|
|
*/
|
|
|
|
#define GET_REPO_OR_FAIL(repo_var,repo_id) \
|
|
do { \
|
|
repo_var = seaf_repo_manager_get_repo (seaf->repo_mgr, (repo_id)); \
|
|
if (!(repo_var)) { \
|
|
seaf_warning ("Repo %s doesn't exist.\n", (repo_id)); \
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo"); \
|
|
ret = -1; \
|
|
goto out; \
|
|
} \
|
|
} while (0);
|
|
|
|
#define GET_COMMIT_OR_FAIL(commit_var,repo_id,repo_version,commit_id) \
|
|
do { \
|
|
commit_var = seaf_commit_manager_get_commit(seaf->commit_mgr, (repo_id), (repo_version), (commit_id)); \
|
|
if (!(commit_var)) { \
|
|
seaf_warning ("commit %s:%s doesn't exist.\n", (repo_id), (commit_id)); \
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid commit"); \
|
|
ret = -1; \
|
|
goto out; \
|
|
} \
|
|
} while (0);
|
|
|
|
#define FAIL_IF_FILE_EXISTS(store_id,repo_version,root_id,parent_dir,filename,mode) \
|
|
do { \
|
|
if (check_file_exists ((store_id), (repo_version), (root_id), (parent_dir), (filename), (mode))) { \
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \
|
|
"file already exists"); \
|
|
ret = -1; \
|
|
goto out; \
|
|
} \
|
|
} while (0);
|
|
|
|
#define FAIL_IF_FILE_NOT_EXISTS(store_id,repo_version,root_id,parent_dir,filename,mode) \
|
|
do { \
|
|
if (!check_file_exists ((store_id), (repo_version), (root_id), (parent_dir), (filename), (mode))) { \
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, \
|
|
"file does not exist"); \
|
|
ret = -1; \
|
|
goto out; \
|
|
} \
|
|
} while (0);
|
|
|
|
#define STD_FILE_MODE (S_IFREG | 0644)
|
|
|
|
static char *
|
|
gen_merge_description (SeafRepo *repo,
|
|
const char *merged_root,
|
|
const char *p1_root,
|
|
const char *p2_root)
|
|
{
|
|
GList *p;
|
|
GList *results = NULL;
|
|
char *desc;
|
|
|
|
diff_merge_roots (repo->store_id, repo->version,
|
|
merged_root, p1_root, p2_root, &results, TRUE);
|
|
|
|
desc = diff_results_to_description (results);
|
|
|
|
for (p = results; p; p = p->next) {
|
|
DiffEntry *de = p->data;
|
|
diff_entry_free (de);
|
|
}
|
|
g_list_free (results);
|
|
|
|
return desc;
|
|
}
|
|
|
|
static int
|
|
gen_new_commit (const char *repo_id,
|
|
SeafCommit *base,
|
|
const char *new_root,
|
|
const char *user,
|
|
const char *desc,
|
|
char *new_commit_id,
|
|
GError **error)
|
|
{
|
|
#define MAX_RETRY_COUNT 3
|
|
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *new_commit = NULL, *current_head = NULL, *merged_commit = NULL;
|
|
int retry_cnt = 0;
|
|
int ret = 0;
|
|
|
|
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
|
|
if (!repo) {
|
|
seaf_warning ("Repo %s doesn't exist.\n", repo_id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Invalid repo");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Create a new commit pointing to new_root. */
|
|
new_commit = seaf_commit_new(NULL, repo->id, new_root,
|
|
user, EMPTY_SHA1,
|
|
desc, 0);
|
|
new_commit->parent_id = g_strdup (base->commit_id);
|
|
seaf_repo_to_commit (repo, new_commit);
|
|
|
|
if (seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit) < 0) {
|
|
seaf_warning ("Failed to add commit.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to add commit");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
retry:
|
|
current_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
repo->head->commit_id);
|
|
if (!current_head) {
|
|
seaf_warning ("Failed to find head commit %s of %s.\n",
|
|
repo->head->commit_id, repo_id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Invalid repo");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Merge if base and head are not the same. */
|
|
if (strcmp (base->commit_id, current_head->commit_id) != 0) {
|
|
MergeOptions opt;
|
|
const char *roots[3];
|
|
char *desc = NULL;
|
|
|
|
memset (&opt, 0, sizeof(opt));
|
|
opt.n_ways = 3;
|
|
memcpy (opt.remote_repo_id, repo_id, 36);
|
|
memcpy (opt.remote_head, new_commit->commit_id, 40);
|
|
opt.do_merge = TRUE;
|
|
|
|
roots[0] = base->root_id; /* base */
|
|
roots[1] = current_head->root_id; /* head */
|
|
roots[2] = new_root; /* remote */
|
|
|
|
if (seaf_merge_trees (repo->store_id, repo->version, 3, roots, &opt) < 0) {
|
|
seaf_warning ("Failed to merge.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Internal error");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_debug ("Number of dirs visted in merge %.8s: %d.\n",
|
|
repo_id, opt.visit_dirs);
|
|
|
|
if (!opt.conflict)
|
|
desc = g_strdup("Auto merge by system");
|
|
else {
|
|
desc = gen_merge_description (repo,
|
|
opt.merged_tree_root,
|
|
current_head->root_id,
|
|
new_root);
|
|
if (!desc)
|
|
desc = g_strdup("Auto merge by system");
|
|
}
|
|
|
|
merged_commit = seaf_commit_new(NULL, repo->id, opt.merged_tree_root,
|
|
user, EMPTY_SHA1,
|
|
desc,
|
|
0);
|
|
g_free (desc);
|
|
|
|
merged_commit->parent_id = g_strdup (current_head->commit_id);
|
|
merged_commit->second_parent_id = g_strdup (new_commit->commit_id);
|
|
merged_commit->new_merge = TRUE;
|
|
if (opt.conflict)
|
|
merged_commit->conflict = TRUE;
|
|
seaf_repo_to_commit (repo, merged_commit);
|
|
|
|
if (seaf_commit_manager_add_commit (seaf->commit_mgr, merged_commit) < 0) {
|
|
seaf_warning ("Failed to add commit.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to add commit");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
} else {
|
|
seaf_commit_ref (new_commit);
|
|
merged_commit = new_commit;
|
|
}
|
|
|
|
seaf_branch_set_commit(repo->head, merged_commit->commit_id);
|
|
|
|
if (seaf_branch_manager_test_and_update_branch(seaf->branch_mgr,
|
|
repo->head,
|
|
current_head->commit_id) < 0)
|
|
{
|
|
seaf_repo_unref (repo);
|
|
repo = NULL;
|
|
seaf_commit_unref (current_head);
|
|
current_head = NULL;
|
|
seaf_commit_unref (merged_commit);
|
|
merged_commit = NULL;
|
|
|
|
if (++retry_cnt <= MAX_RETRY_COUNT) {
|
|
/* Sleep random time between 100 and 1000 millisecs. */
|
|
usleep (g_random_int_range(1, 11) * 100 * 1000);
|
|
|
|
repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
|
|
if (!repo) {
|
|
seaf_warning ("Repo %s doesn't exist.\n", repo_id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Invalid repo");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
goto retry;
|
|
} else {
|
|
seaf_warning ("Stop updating repo %s after %d retries.\n", repo_id, MAX_RETRY_COUNT);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Concurrent update");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (new_commit_id)
|
|
memcpy (new_commit_id, merged_commit->commit_id, 41);
|
|
|
|
out:
|
|
seaf_commit_unref (new_commit);
|
|
seaf_commit_unref (current_head);
|
|
seaf_commit_unref (merged_commit);
|
|
seaf_repo_unref (repo);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
update_repo_size(const char *repo_id)
|
|
{
|
|
schedule_repo_size_computation (seaf->size_sched, repo_id);
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_post_file (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *temp_file_path,
|
|
const char *parent_dir,
|
|
const char *file_name,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
char *canon_path = NULL;
|
|
unsigned char sha1[20];
|
|
char buf[SEAF_PATH_MAX];
|
|
char *root_id = NULL;
|
|
SeafileCrypt *crypt = NULL;
|
|
SeafDirent *new_dent = NULL;
|
|
char hex[41];
|
|
int ret = 0;
|
|
|
|
if (g_access (temp_file_path, R_OK) != 0) {
|
|
seaf_warning ("[post file] File %s doesn't exist or not readable.\n",
|
|
temp_file_path);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid input file");
|
|
return -1;
|
|
}
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
if (!canon_path)
|
|
canon_path = get_canonical_path (parent_dir);
|
|
|
|
if (should_ignore_file (file_name, NULL)) {
|
|
seaf_debug ("[post file] Invalid filename %s.\n", file_name);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid filename");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (strstr (parent_dir, "//") != NULL) {
|
|
seaf_debug ("[post file] parent_dir cantains // sequence.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid parent dir");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Write blocks. */
|
|
if (repo->encrypted) {
|
|
unsigned char key[32], iv[16];
|
|
if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
|
|
repo_id, user,
|
|
key, iv) < 0) {
|
|
seaf_debug ("Passwd for repo %s is not set.\n", repo_id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Passwd is not set");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
crypt = seafile_crypt_new (repo->enc_version, key, iv);
|
|
}
|
|
|
|
gint64 size;
|
|
if (seaf_fs_manager_index_blocks (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
temp_file_path,
|
|
sha1, &size, crypt, TRUE, FALSE, NULL) < 0) {
|
|
seaf_warning ("failed to index blocks");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to index blocks");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
rawdata_to_hex(sha1, hex, 20);
|
|
new_dent = seaf_dirent_new (dir_version_from_repo_version (repo->version),
|
|
hex, STD_FILE_MODE, file_name,
|
|
(gint64)time(NULL), user, size);
|
|
|
|
root_id = do_post_file (repo,
|
|
head_commit->root_id, canon_path, new_dent);
|
|
if (!root_id) {
|
|
seaf_warning ("[post file] Failed to post file %s to %s in repo %s.\n",
|
|
file_name, canon_path, repo->id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to post file");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
snprintf(buf, SEAF_PATH_MAX, "Added \"%s\"", file_name);
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref(head_commit);
|
|
seaf_dirent_free (new_dent);
|
|
g_free (root_id);
|
|
g_free (canon_path);
|
|
g_free (crypt);
|
|
|
|
if (ret == 0)
|
|
update_repo_size(repo_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
add_new_entries (SeafRepo *repo, const char *user, GList **entries,
|
|
GList *dents, int replace_existed, GList **name_list)
|
|
{
|
|
GList *ptr;
|
|
SeafDirent *dent;
|
|
|
|
for (ptr = dents; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
|
|
char *unique_name;
|
|
SeafDirent *newdent;
|
|
gboolean replace = FALSE;
|
|
|
|
if (replace_existed) {
|
|
GList *p;
|
|
SeafDirent *tmp_dent;
|
|
for (p = *entries; p; p = p->next) {
|
|
tmp_dent = p->data;
|
|
if (strcmp(tmp_dent->name, dent->name) == 0) {
|
|
replace = TRUE;
|
|
*entries = g_list_delete_link (*entries, p);
|
|
seaf_dirent_free (tmp_dent);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (replace)
|
|
unique_name = g_strdup (dent->name);
|
|
else
|
|
unique_name = generate_unique_filename (dent->name, *entries);
|
|
|
|
if (unique_name != NULL) {
|
|
newdent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
|
|
dent->id, dent->mode, unique_name,
|
|
dent->mtime, user, dent->size);
|
|
*entries = g_list_insert_sorted (*entries, newdent, compare_dirents);
|
|
*name_list = g_list_append (*name_list, unique_name);
|
|
/* No need to free unique_name */
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *
|
|
post_multi_files_recursive (SeafRepo *repo,
|
|
const char *dir_id,
|
|
const char *to_path,
|
|
GList *dents,
|
|
const char *user,
|
|
int replace_existed,
|
|
GList **name_list)
|
|
{
|
|
SeafDir *olddir, *newdir;
|
|
SeafDirent *dent;
|
|
GList *ptr;
|
|
char *slash;
|
|
char *to_path_dup = NULL;
|
|
char *remain = NULL;
|
|
char *id = NULL;
|
|
char *ret = NULL;
|
|
|
|
olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
dir_id);
|
|
if (!olddir)
|
|
return NULL;
|
|
|
|
/* we reach the target dir. new dir entry is added */
|
|
if (*to_path == '\0') {
|
|
GList *newentries;
|
|
|
|
newentries = dup_seafdir_entries (olddir->entries);
|
|
|
|
if (add_new_entries (repo, user,
|
|
&newentries, dents, replace_existed, name_list) < 0)
|
|
goto out;
|
|
|
|
newdir = seaf_dir_new (NULL, newentries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strdup (newdir->dir_id);
|
|
seaf_dir_free (newdir);
|
|
|
|
goto out;
|
|
}
|
|
|
|
to_path_dup = g_strdup (to_path);
|
|
slash = strchr (to_path_dup, '/');
|
|
|
|
if (!slash) {
|
|
remain = to_path_dup + strlen(to_path_dup);
|
|
} else {
|
|
*slash = '\0';
|
|
remain = slash + 1;
|
|
}
|
|
|
|
for (ptr = olddir->entries; ptr; ptr = ptr->next) {
|
|
dent = (SeafDirent *)ptr->data;
|
|
|
|
if (strcmp(dent->name, to_path_dup) != 0)
|
|
continue;
|
|
|
|
id = post_multi_files_recursive (repo, dent->id, remain, dents, user,
|
|
replace_existed, name_list);
|
|
if (id != NULL) {
|
|
memcpy(dent->id, id, 40);
|
|
dent->id[40] = '\0';
|
|
if (repo->version > 0)
|
|
dent->mtime = (guint64)time(NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (id != NULL) {
|
|
/* Create a new SeafDir. */
|
|
GList *new_entries;
|
|
|
|
new_entries = dup_seafdir_entries (olddir->entries);
|
|
newdir = seaf_dir_new (NULL, new_entries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strdup (newdir->dir_id);
|
|
seaf_dir_free (newdir);
|
|
}
|
|
|
|
out:
|
|
g_free (to_path_dup);
|
|
g_free (id);
|
|
seaf_dir_free(olddir);
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
do_post_multi_files (SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *parent_dir,
|
|
GList *filenames,
|
|
GList *id_list,
|
|
GList *size_list,
|
|
const char *user,
|
|
int replace_existed,
|
|
GList **name_list)
|
|
{
|
|
SeafDirent *dent;
|
|
GList *dents = NULL;
|
|
GList *ptr1, *ptr2, *ptr3;
|
|
char *ret;
|
|
|
|
for (ptr1 = filenames, ptr2 = id_list, ptr3 = size_list;
|
|
ptr1 && ptr2 && ptr3;
|
|
ptr1 = ptr1->next, ptr2 = ptr2->next, ptr3 = ptr3->next) {
|
|
|
|
char *name = ptr1->data;
|
|
char *id = ptr2->data;
|
|
gint64 *size = ptr3->data;
|
|
|
|
dent = g_new0 (SeafDirent, 1);
|
|
dent->name = name;
|
|
memcpy(dent->id, id, 40);
|
|
dent->id[40] = '\0';
|
|
dent->size = *size;
|
|
dent->mode = STD_FILE_MODE;
|
|
dent->mtime = (gint64)time(NULL);
|
|
|
|
dents = g_list_append (dents, dent);
|
|
}
|
|
/* if parent_dir is a absolutely path, we will remove the first '/' */
|
|
if (*parent_dir == '/')
|
|
parent_dir = parent_dir + 1;
|
|
|
|
ret = post_multi_files_recursive(repo, root_id, parent_dir,
|
|
dents, user, replace_existed, name_list);
|
|
g_list_free_full (dents, g_free);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GList *
|
|
json_to_file_list (const char *files_json)
|
|
{
|
|
json_t *array;
|
|
GList *files = NULL;
|
|
json_error_t jerror;
|
|
size_t index;
|
|
json_t *value;
|
|
const char *file;
|
|
char *norm_file;
|
|
|
|
array = json_loadb (files_json, strlen(files_json), 0, &jerror);
|
|
if (!array) {
|
|
seaf_warning ("Failed to load json file list: %s.\n", jerror.text);
|
|
return NULL;
|
|
}
|
|
|
|
size_t n = json_array_size (array);
|
|
for (index = 0; index < n; index++) {
|
|
value = json_array_get (array, index);
|
|
file = json_string_value (value);
|
|
if (!file) {
|
|
g_list_free_full (files, g_free);
|
|
files = NULL;
|
|
break;
|
|
}
|
|
|
|
norm_file = normalize_utf8_path (file);
|
|
if (!norm_file) {
|
|
g_list_free_full (files, g_free);
|
|
files = NULL;
|
|
break;
|
|
}
|
|
|
|
files = g_list_prepend (files, norm_file);
|
|
}
|
|
|
|
json_decref (array);
|
|
return g_list_reverse(files);
|
|
}
|
|
|
|
/*
|
|
* Return [{'name': 'file1', 'id': 'id1', 'size': num1}, {'name': 'file2', 'id': 'id2', 'size': num2}]
|
|
*/
|
|
static char *
|
|
format_json_ret (GList *name_list, GList *id_list, GList *size_list)
|
|
{
|
|
json_t *array, *obj;
|
|
GList *ptr, *ptr2;
|
|
GList *sptr;
|
|
char *filename, *id;
|
|
gint64 *size;
|
|
char *json_data;
|
|
char *ret;
|
|
|
|
array = json_array ();
|
|
|
|
for (ptr = name_list, ptr2 = id_list, sptr = size_list;
|
|
ptr && ptr2 && sptr;
|
|
ptr = ptr->next, ptr2 = ptr2->next, sptr = sptr->next) {
|
|
filename = ptr->data;
|
|
id = ptr2->data;
|
|
size = sptr->data;
|
|
obj = json_object ();
|
|
json_object_set_string_member (obj, "name", filename);
|
|
json_object_set_string_member (obj, "id", id);
|
|
json_object_set_int_member (obj, "size", *size);
|
|
json_array_append_new (array, obj);
|
|
}
|
|
|
|
json_data = json_dumps (array, 0);
|
|
json_decref (array);
|
|
|
|
ret = g_strdup (json_data);
|
|
free (json_data);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_post_multi_files (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *parent_dir,
|
|
const char *filenames_json,
|
|
const char *paths_json,
|
|
const char *user,
|
|
int replace_existed,
|
|
char **ret_json,
|
|
char **task_id,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
char *canon_path = NULL;
|
|
GList *filenames = NULL, *paths = NULL, *id_list = NULL, *size_list = NULL, *ptr;
|
|
char *filename, *path;
|
|
unsigned char sha1[20];
|
|
SeafileCrypt *crypt = NULL;
|
|
char hex[41];
|
|
int ret = 0;
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
|
|
canon_path = get_canonical_path (parent_dir);
|
|
|
|
/* Decode file name and tmp file paths from json. */
|
|
filenames = json_to_file_list (filenames_json);
|
|
paths = json_to_file_list (paths_json);
|
|
if (!filenames || !paths) {
|
|
seaf_debug ("[post files] Invalid filenames or paths.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid files");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Check inputs. */
|
|
for (ptr = filenames; ptr; ptr = ptr->next) {
|
|
filename = ptr->data;
|
|
if (should_ignore_file (filename, NULL)) {
|
|
seaf_debug ("[post files] Invalid filename %s.\n", filename);
|
|
g_set_error (error, SEAFILE_DOMAIN, POST_FILE_ERR_FILENAME,
|
|
"%s", filename);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (strstr (parent_dir, "//") != NULL) {
|
|
seaf_debug ("[post file] parent_dir cantains // sequence.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid parent dir");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Index tmp files and get file id list. */
|
|
if (repo->encrypted) {
|
|
unsigned char key[32], iv[16];
|
|
if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
|
|
repo_id, user,
|
|
key, iv) < 0) {
|
|
seaf_debug ("Passwd for repo %s is not set.\n", repo_id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Passwd is not set");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
crypt = seafile_crypt_new (repo->enc_version, key, iv);
|
|
}
|
|
|
|
if (!task_id) {
|
|
gint64 *size;
|
|
for (ptr = paths; ptr; ptr = ptr->next) {
|
|
path = ptr->data;
|
|
|
|
size = g_new (gint64, 1);
|
|
if (seaf_fs_manager_index_blocks (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
path, sha1, size, crypt, TRUE, FALSE, NULL) < 0) {
|
|
seaf_warning ("failed to index blocks");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to index blocks");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
rawdata_to_hex(sha1, hex, 20);
|
|
id_list = g_list_prepend (id_list, g_strdup(hex));
|
|
size_list = g_list_prepend (size_list, size);
|
|
}
|
|
id_list = g_list_reverse (id_list);
|
|
size_list = g_list_reverse (size_list);
|
|
|
|
ret = post_files_and_gen_commit (filenames,
|
|
repo,
|
|
user,
|
|
ret_json,
|
|
replace_existed,
|
|
canon_path,
|
|
id_list,
|
|
size_list,
|
|
error);
|
|
} else {
|
|
ret = index_blocks_mgr_start_index (seaf->index_blocks_mgr,
|
|
filenames,
|
|
paths,
|
|
repo_id,
|
|
user,
|
|
replace_existed,
|
|
ret_json == NULL ? FALSE : TRUE,
|
|
canon_path,
|
|
crypt,
|
|
task_id);
|
|
}
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
string_list_free (filenames);
|
|
string_list_free (paths);
|
|
string_list_free (id_list);
|
|
for (ptr = size_list; ptr; ptr = ptr->next)
|
|
g_free (ptr->data);
|
|
g_list_free (size_list);
|
|
g_free (canon_path);
|
|
g_free (crypt);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
post_files_and_gen_commit (GList *filenames,
|
|
SeafRepo *repo,
|
|
const char *user,
|
|
char **ret_json,
|
|
int replace_existed,
|
|
const char *canon_path,
|
|
GList *id_list,
|
|
GList *size_list,
|
|
GError **error)
|
|
{
|
|
GList *name_list = NULL;
|
|
GString *buf = g_string_new (NULL);
|
|
SeafCommit *head_commit = NULL;
|
|
char *root_id = NULL;
|
|
int ret = 0;
|
|
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
/* Add the files to parent dir and commit. */
|
|
root_id = do_post_multi_files (repo, head_commit->root_id, canon_path,
|
|
filenames, id_list, size_list, user,
|
|
replace_existed, &name_list);
|
|
if (!root_id) {
|
|
seaf_warning ("[post multi-file] Failed to post files to %s in repo %s.\n",
|
|
canon_path, repo->id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,
|
|
"Failed to put file");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
guint len = g_list_length (filenames);
|
|
if (len > 1)
|
|
g_string_printf (buf, "Added \"%s\" and %u more files.",
|
|
(char *)(filenames->data), len - 1);
|
|
else
|
|
g_string_printf (buf, "Added \"%s\".", (char *)(filenames->data));
|
|
|
|
if (gen_new_commit (repo->id, head_commit, root_id,
|
|
user, buf->str, NULL, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (seaf->repo_mgr, repo->id, NULL);
|
|
|
|
if (ret_json)
|
|
*ret_json = format_json_ret (name_list, id_list, size_list);
|
|
|
|
update_repo_size(repo->id);
|
|
|
|
out:
|
|
if (head_commit)
|
|
seaf_commit_unref(head_commit);
|
|
string_list_free (name_list);
|
|
g_string_free (buf, TRUE);
|
|
g_free (root_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* int */
|
|
/* seaf_repo_manager_post_file_blocks (SeafRepoManager *mgr, */
|
|
/* const char *repo_id, */
|
|
/* const char *parent_dir, */
|
|
/* const char *file_name, */
|
|
/* const char *blockids_json, */
|
|
/* const char *paths_json, */
|
|
/* const char *user, */
|
|
/* gint64 file_size, */
|
|
/* int replace_existed, */
|
|
/* char **new_id, */
|
|
/* GError **error) */
|
|
/* { */
|
|
/* SeafRepo *repo = NULL; */
|
|
/* SeafCommit *head_commit = NULL; */
|
|
/* char *canon_path = NULL; */
|
|
/* unsigned char sha1[20]; */
|
|
/* char buf[SEAF_PATH_MAX]; */
|
|
/* char *root_id = NULL; */
|
|
/* SeafDirent *new_dent = NULL; */
|
|
/* GList *blockids = NULL, *paths = NULL, *ptr; */
|
|
/* char hex[41]; */
|
|
/* int ret = 0; */
|
|
|
|
/* blockids = json_to_file_list (blockids_json); */
|
|
/* paths = json_to_file_list (paths_json); */
|
|
/* if (g_list_length(blockids) != g_list_length(paths)) { */
|
|
/* seaf_debug ("[post-blks] Invalid blockids or paths.\n"); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid files"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* for (ptr = paths; ptr; ptr = ptr->next) { */
|
|
/* char *temp_file_path = ptr->data; */
|
|
/* if (g_access (temp_file_path, R_OK) != 0) { */
|
|
/* seaf_warning ("[post-blks] File block %s doesn't exist or not readable.\n", */
|
|
/* temp_file_path); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
|
|
/* "Invalid input file"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
/* } */
|
|
|
|
/* GET_REPO_OR_FAIL(repo, repo_id); */
|
|
/* GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id); */
|
|
|
|
/* if (!canon_path) */
|
|
/* canon_path = get_canonical_path (parent_dir); */
|
|
|
|
/* if (should_ignore_file (file_name, NULL)) { */
|
|
/* seaf_debug ("[post-blks] Invalid filename %s.\n", file_name); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
|
|
/* "Invalid filename"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* if (strstr (parent_dir, "//") != NULL) { */
|
|
/* seaf_debug ("[post-blks] parent_dir cantains // sequence.\n"); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
|
|
/* "Invalid parent dir"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* /\* Write blocks. *\/ */
|
|
/* if (seaf_fs_manager_index_file_blocks (seaf->fs_mgr, */
|
|
/* repo->store_id, repo->version, */
|
|
/* paths, */
|
|
/* blockids, sha1, file_size) < 0) { */
|
|
/* seaf_warning ("Failed to index file blocks"); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, */
|
|
/* "Failed to index blocks"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* rawdata_to_hex(sha1, hex, 20); */
|
|
/* new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version), */
|
|
/* hex, STD_FILE_MODE, file_name, */
|
|
/* (gint64)time(NULL), user, file_size); */
|
|
|
|
/* root_id = do_post_file_replace (repo, head_commit->root_id, */
|
|
/* canon_path, replace_existed, new_dent); */
|
|
/* if (!root_id) { */
|
|
/* seaf_warning ("[post-blks] Failed to post file to %s in repo %s.\n", */
|
|
/* canon_path, repo->id); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, */
|
|
/* "Failed to put file"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* *new_id = g_strdup(hex); */
|
|
/* snprintf(buf, SEAF_PATH_MAX, "Added \"%s\"", file_name); */
|
|
/* if (gen_new_commit (repo_id, head_commit, root_id, */
|
|
/* user, buf, NULL, error) < 0) */
|
|
/* ret = -1; */
|
|
|
|
/* out: */
|
|
/* if (repo) */
|
|
/* seaf_repo_unref (repo); */
|
|
/* if (head_commit) */
|
|
/* seaf_commit_unref(head_commit); */
|
|
/* string_list_free (blockids); */
|
|
/* string_list_free (paths); */
|
|
/* seaf_dirent_free (new_dent); */
|
|
/* g_free (root_id); */
|
|
/* g_free (canon_path); */
|
|
|
|
/* if (ret == 0) */
|
|
/* update_repo_size(repo_id); */
|
|
|
|
/* return ret; */
|
|
/* } */
|
|
|
|
int
|
|
seaf_repo_manager_post_blocks (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *blockids_json,
|
|
const char *paths_json,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
GList *blockids = NULL, *paths = NULL, *ptr;
|
|
int ret = 0;
|
|
|
|
blockids = json_to_file_list (blockids_json);
|
|
paths = json_to_file_list (paths_json);
|
|
if (g_list_length(blockids) != g_list_length(paths)) {
|
|
seaf_warning ("[post-blks] Invalid blockids or paths.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid files");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
for (ptr = paths; ptr; ptr = ptr->next) {
|
|
char *temp_file_path = ptr->data;
|
|
if (g_access (temp_file_path, R_OK) != 0) {
|
|
seaf_warning ("[post-blks] File block %s doesn't exist or not readable.\n",
|
|
temp_file_path);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid input file");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
|
|
/* Write blocks. */
|
|
if (seaf_fs_manager_index_raw_blocks (seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
paths,
|
|
blockids) < 0) {
|
|
seaf_warning ("Failed to index file blocks.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to index blocks");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
string_list_free (blockids);
|
|
string_list_free (paths);
|
|
|
|
if (ret == 0)
|
|
update_repo_size(repo_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
check_quota_before_commit_blocks (const char *store_id,
|
|
int version,
|
|
GList *blockids)
|
|
{
|
|
GList *ptr;
|
|
char *blockid;
|
|
gint64 total_size = 0;
|
|
BlockMetadata *bmd;
|
|
|
|
for (ptr = blockids; ptr; ptr = ptr->next) {
|
|
blockid = ptr->data;
|
|
bmd = seaf_block_manager_stat_block (seaf->block_mgr, store_id, version, blockid);
|
|
if (!bmd) {
|
|
seaf_warning ("Failed to stat block %s in store %s.\n",
|
|
blockid, store_id);
|
|
return -1;
|
|
}
|
|
|
|
total_size += (gint64)bmd->size;
|
|
g_free (bmd);
|
|
}
|
|
|
|
return seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, store_id, total_size);
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_commit_file_blocks (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *parent_dir,
|
|
const char *file_name,
|
|
const char *blockids_json,
|
|
const char *user,
|
|
gint64 file_size,
|
|
int replace_existed,
|
|
char **new_id,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
char *canon_path = NULL;
|
|
unsigned char sha1[20];
|
|
char buf[SEAF_PATH_MAX];
|
|
char *root_id = NULL;
|
|
SeafDirent *new_dent = NULL;
|
|
GList *blockids = NULL;
|
|
char hex[41];
|
|
int ret = 0;
|
|
|
|
blockids = json_to_file_list (blockids_json);
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
if (!canon_path)
|
|
canon_path = get_canonical_path (parent_dir);
|
|
|
|
if (should_ignore_file (file_name, NULL)) {
|
|
seaf_warning ("[post-blks] Invalid filename %s.\n", file_name);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid filename");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (strstr (parent_dir, "//") != NULL) {
|
|
seaf_warning ("[post-blks] parent_dir cantains // sequence.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid parent dir");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
int rc = check_quota_before_commit_blocks (repo->store_id, repo->version, blockids);
|
|
if (rc != 0) {
|
|
g_set_error (error, SEAFILE_DOMAIN, POST_FILE_ERR_QUOTA_FULL,
|
|
"Quota full");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Write blocks. */
|
|
if (seaf_fs_manager_index_existed_file_blocks (
|
|
seaf->fs_mgr, repo->store_id, repo->version,
|
|
blockids, sha1, file_size) < 0) {
|
|
seaf_warning ("Failed to index existed file blocks.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, POST_FILE_ERR_BLOCK_MISSING,
|
|
"Failed to index file blocks");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
rawdata_to_hex(sha1, hex, 20);
|
|
new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
|
|
hex, STD_FILE_MODE, file_name,
|
|
(gint64)time(NULL), user, file_size);
|
|
|
|
root_id = do_post_file_replace (repo, head_commit->root_id,
|
|
canon_path, replace_existed, new_dent);
|
|
if (!root_id) {
|
|
seaf_warning ("[post-blks] Failed to post file to %s in repo %s.\n",
|
|
canon_path, repo->id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to put file");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
*new_id = g_strdup(hex);
|
|
snprintf(buf, SEAF_PATH_MAX, "Added \"%s\"", file_name);
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0)
|
|
ret = -1;
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref(head_commit);
|
|
string_list_free (blockids);
|
|
seaf_dirent_free (new_dent);
|
|
g_free (root_id);
|
|
g_free (canon_path);
|
|
|
|
if (ret == 0)
|
|
update_repo_size(repo_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
del_file_recursive(SeafRepo *repo,
|
|
const char *dir_id,
|
|
const char *to_path,
|
|
const char *filename,
|
|
int *mode, int *p_deleted_num, char **desc_file)
|
|
{
|
|
SeafDir *olddir, *newdir;
|
|
SeafDirent *dent;
|
|
GList *ptr;
|
|
char *to_path_dup = NULL;
|
|
char *remain = NULL;
|
|
char *slash;
|
|
char *id = NULL;
|
|
char *ret = NULL;
|
|
int deleted_num = 0;
|
|
|
|
olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
dir_id);
|
|
if (!olddir)
|
|
return NULL;
|
|
|
|
/* we reach the target dir. Remove the given entry from it. */
|
|
if (*to_path == '\0') {
|
|
SeafDirent *old, *new;
|
|
GList *newentries = NULL, *p;
|
|
|
|
if (strchr(filename, '\t')) {
|
|
char **file_names = g_strsplit (filename, "\t", -1);
|
|
int file_num = g_strv_length (file_names);
|
|
int i, found_flag;
|
|
|
|
for (p = olddir->entries; p != NULL; p = p->next) {
|
|
found_flag = 0;
|
|
old = p->data;
|
|
for (i = 0; i < file_num; i++) {
|
|
if (strcmp(old->name, file_names[i]) == 0) {
|
|
found_flag = 1;
|
|
deleted_num++;
|
|
if (mode)
|
|
*mode = old->mode;
|
|
if (desc_file && *desc_file==NULL)
|
|
*desc_file = g_strdup(old->name);
|
|
break;
|
|
}
|
|
}
|
|
if (!found_flag) {
|
|
new = seaf_dirent_dup (old);
|
|
newentries = g_list_prepend (newentries, new);
|
|
}
|
|
}
|
|
g_strfreev (file_names);
|
|
} else {
|
|
for (p = olddir->entries; p != NULL; p = p->next) {
|
|
old = p->data;
|
|
if (strcmp(old->name, filename) != 0) {
|
|
new = seaf_dirent_dup (old);
|
|
newentries = g_list_prepend (newentries, new);
|
|
} else {
|
|
deleted_num++;
|
|
if (mode)
|
|
*mode = old->mode;
|
|
if (desc_file && *desc_file==NULL)
|
|
*desc_file = g_strdup(old->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deleted_num == 0) {
|
|
ret = g_strdup(olddir->dir_id);
|
|
if (newentries)
|
|
g_list_free_full (newentries, (GDestroyNotify)seaf_dirent_free);
|
|
goto out;
|
|
}
|
|
|
|
newentries = g_list_reverse (newentries);
|
|
|
|
newdir = seaf_dir_new(NULL, newentries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save(seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strdup(newdir->dir_id);
|
|
seaf_dir_free(newdir);
|
|
goto out;
|
|
}
|
|
|
|
to_path_dup = g_strdup (to_path);
|
|
slash = strchr (to_path_dup, '/');
|
|
|
|
if (!slash) {
|
|
remain = to_path_dup + strlen(to_path_dup);
|
|
} else {
|
|
*slash = '\0';
|
|
remain = slash + 1;
|
|
}
|
|
|
|
for (ptr = olddir->entries; ptr; ptr = ptr->next) {
|
|
dent = (SeafDirent *)ptr->data;
|
|
|
|
if (strcmp(dent->name, to_path_dup) != 0)
|
|
continue;
|
|
|
|
id = del_file_recursive(repo, dent->id, remain, filename,
|
|
mode, &deleted_num, desc_file);
|
|
if (id != NULL && deleted_num > 0) {
|
|
memcpy(dent->id, id, 40);
|
|
dent->id[40] = '\0';
|
|
if (repo->version > 0)
|
|
dent->mtime = (guint64)time(NULL);
|
|
}
|
|
break;
|
|
}
|
|
if (id != NULL) {
|
|
if (deleted_num == 0) {
|
|
ret = g_strdup(olddir->dir_id);
|
|
} else {
|
|
/* Create a new SeafDir. */
|
|
GList *new_entries;
|
|
|
|
new_entries = dup_seafdir_entries (olddir->entries);
|
|
newdir = seaf_dir_new (NULL, new_entries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strdup (newdir->dir_id);
|
|
seaf_dir_free (newdir);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (p_deleted_num)
|
|
*p_deleted_num = deleted_num;
|
|
|
|
g_free (to_path_dup);
|
|
g_free (id);
|
|
seaf_dir_free(olddir);
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
do_del_file(SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *parent_dir,
|
|
const char *file_name,
|
|
int *mode, int *deleted_num, char **desc_file)
|
|
{
|
|
/* if parent_dir is a absolutely path, we will remove the first '/' */
|
|
if (*parent_dir == '/')
|
|
parent_dir = parent_dir + 1;
|
|
|
|
return del_file_recursive(repo, root_id, parent_dir, file_name,
|
|
mode, deleted_num, desc_file);
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_del_file (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *parent_dir,
|
|
const char *file_name,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
SeafDir *dir = NULL;
|
|
char *canon_path = NULL;
|
|
char buf[SEAF_PATH_MAX];
|
|
char *root_id = NULL;
|
|
char *desc_file = NULL;
|
|
int mode = 0;
|
|
int ret = 0;
|
|
int deleted_num = 0;
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
if (!canon_path)
|
|
canon_path = get_canonical_path (parent_dir);
|
|
|
|
dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
head_commit->root_id, canon_path, NULL);
|
|
if (!dir) {
|
|
seaf_warning ("parent_dir %s doesn't exist in repo %s.\n",
|
|
canon_path, repo->store_id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
root_id = do_del_file (repo,
|
|
head_commit->root_id, canon_path, file_name, &mode,
|
|
&deleted_num, &desc_file);
|
|
if (!root_id) {
|
|
seaf_warning ("[del file] Failed to del file from %s in repo %s.\n",
|
|
canon_path, repo->id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to del file");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
if (deleted_num == 0) {
|
|
goto out;
|
|
}
|
|
|
|
/* Commit. */
|
|
if (deleted_num > 1) {
|
|
snprintf(buf, SEAF_PATH_MAX, "Deleted \"%s\" and %d more files",
|
|
desc_file, deleted_num - 1);
|
|
} else if (S_ISDIR(mode)) {
|
|
snprintf(buf, SEAF_PATH_MAX, "Removed directory \"%s\"", desc_file);
|
|
} else {
|
|
snprintf(buf, SEAF_PATH_MAX, "Deleted \"%s\"", desc_file);
|
|
}
|
|
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref(head_commit);
|
|
if (dir)
|
|
seaf_dir_free (dir);
|
|
g_free (root_id);
|
|
g_free (canon_path);
|
|
g_free (desc_file);
|
|
|
|
if (ret == 0) {
|
|
update_repo_size (repo_id);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static SeafDirent *
|
|
get_dirent_by_path (SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *path,
|
|
const char *file_name,
|
|
GError **error)
|
|
{
|
|
SeafCommit *head_commit = NULL;
|
|
SeafDirent *dent = NULL;
|
|
SeafDir *dir = NULL;
|
|
|
|
if (!root_id) {
|
|
head_commit = seaf_commit_manager_get_commit(seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
repo->head->commit_id);
|
|
if (!head_commit) {
|
|
seaf_warning ("commit %s:%s doesn't exist.\n",
|
|
repo->id, repo->head->commit_id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid commit");
|
|
goto out;
|
|
}
|
|
root_id = head_commit->root_id;
|
|
}
|
|
|
|
dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
root_id,
|
|
path, NULL);
|
|
if (!dir) {
|
|
seaf_warning ("dir %s doesn't exist in repo %s.\n", path, repo->id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid dir");
|
|
goto out;
|
|
}
|
|
|
|
GList *p;
|
|
for (p = dir->entries; p; p = p->next) {
|
|
SeafDirent *d = p->data;
|
|
int r = strcmp (d->name, file_name);
|
|
if (r == 0) {
|
|
dent = seaf_dirent_dup(d);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!dent && error && !(*error)) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"failed to get dirent");
|
|
}
|
|
|
|
out:
|
|
if (head_commit)
|
|
seaf_commit_unref (head_commit);
|
|
if (dir)
|
|
seaf_dir_free (dir);
|
|
|
|
return dent;
|
|
}
|
|
|
|
static int
|
|
put_dirent_and_commit (SeafRepo *repo,
|
|
const char *path,
|
|
SeafDirent *dents[],
|
|
int n_dents,
|
|
int replace,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafCommit *head_commit = NULL;
|
|
char *root_id = NULL;
|
|
char buf[SEAF_PATH_MAX];
|
|
int ret = 0, i = 0;
|
|
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
root_id = head_commit->root_id;
|
|
|
|
GList *dent_list = NULL;
|
|
GList *name_list = NULL;
|
|
for (i = 0; i < n_dents; i++)
|
|
dent_list = g_list_append (dent_list, dents[i]);
|
|
|
|
if (*path == '/')
|
|
path = path + 1;
|
|
root_id = post_multi_files_recursive (repo, root_id, path, dent_list, user,
|
|
replace, &name_list);
|
|
g_list_free (dent_list);
|
|
g_list_free_full (name_list, (GDestroyNotify)g_free);
|
|
|
|
if (!root_id) {
|
|
if (n_dents > 1)
|
|
seaf_warning ("[cp file] Failed to cp %s and other %d files to %s in repo %s.\n",
|
|
dents[0]->name, n_dents - 1, path, repo->id);
|
|
else
|
|
seaf_warning ("[cp file] Failed to cp %s to %s in repo %s.\n",
|
|
dents[0]->name, path, repo->id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to cp file");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Commit. */
|
|
if (n_dents > 1) {
|
|
snprintf(buf, sizeof(buf), "Added \"%s\" and %d more files",
|
|
dents[0]->name, n_dents - 1);
|
|
} else if (S_ISDIR(dents[0]->mode)) {
|
|
snprintf(buf, sizeof(buf), "Added directory \"%s\"", dents[0]->name);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "Added \"%s\"", dents[0]->name);
|
|
}
|
|
|
|
if (gen_new_commit (repo->id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0)
|
|
ret = -1;
|
|
|
|
out:
|
|
if (head_commit)
|
|
seaf_commit_unref (head_commit);
|
|
if (root_id)
|
|
g_free (root_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
copy_seafile (SeafRepo *src_repo, SeafRepo *dst_repo, const char *file_id,
|
|
CopyTask *task, guint64 *size)
|
|
{
|
|
Seafile *file;
|
|
|
|
file = seaf_fs_manager_get_seafile (seaf->fs_mgr,
|
|
src_repo->store_id, src_repo->version,
|
|
file_id);
|
|
if (!file) {
|
|
seaf_warning ("Failed to get file object %s from repo %s.\n",
|
|
file_id, src_repo->id);
|
|
return NULL;
|
|
}
|
|
|
|
/* We may be copying from v0 repo to v1 repo or vise versa. */
|
|
file->version = seafile_version_from_repo_version(dst_repo->version);
|
|
|
|
if (seafile_save (seaf->fs_mgr,
|
|
dst_repo->store_id,
|
|
dst_repo->version,
|
|
file) < 0) {
|
|
seaf_warning ("Failed to copy file object %s from repo %s to %s.\n",
|
|
file_id, src_repo->id, dst_repo->id);
|
|
seafile_unref (file);
|
|
return NULL;
|
|
}
|
|
|
|
int i;
|
|
char *block_id;
|
|
for (i = 0; i < file->n_blocks; ++i) {
|
|
/* Check cancel before copying a block. */
|
|
if (task && g_atomic_int_get (&task->canceled)) {
|
|
seafile_unref (file);
|
|
return NULL;
|
|
}
|
|
|
|
block_id = file->blk_sha1s[i];
|
|
if (seaf_block_manager_copy_block (seaf->block_mgr,
|
|
src_repo->store_id, src_repo->version,
|
|
dst_repo->store_id, dst_repo->version,
|
|
block_id) < 0) {
|
|
seaf_warning ("Failed to copy block %s from repo %s to %s.\n",
|
|
block_id, src_repo->id, dst_repo->id);
|
|
seafile_unref (file);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (task)
|
|
++(task->done);
|
|
|
|
*size = file->file_size;
|
|
char *ret = g_strdup(file->file_id);
|
|
|
|
seafile_unref (file);
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
copy_recursive (SeafRepo *src_repo, SeafRepo *dst_repo,
|
|
const char *obj_id, guint32 mode, const char *modifier,
|
|
CopyTask *task, guint64 *size)
|
|
{
|
|
if (S_ISREG(mode)) {
|
|
return copy_seafile (src_repo, dst_repo, obj_id, task, size);
|
|
} else if (S_ISDIR(mode)) {
|
|
SeafDir *src_dir = NULL, *dst_dir = NULL;
|
|
GList *dst_ents = NULL, *ptr;
|
|
char *new_id = NULL;
|
|
SeafDirent *dent, *new_dent = NULL;
|
|
|
|
src_dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr,
|
|
src_repo->store_id,
|
|
src_repo->version,
|
|
obj_id);
|
|
if (!src_dir) {
|
|
seaf_warning ("Seafdir %s doesn't exist in repo %s.\n",
|
|
obj_id, src_repo->id);
|
|
return NULL;
|
|
}
|
|
|
|
for (ptr = src_dir->entries; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
|
|
guint64 new_size = 0;
|
|
new_id = copy_recursive (src_repo, dst_repo,
|
|
dent->id, dent->mode, modifier, task, &new_size);
|
|
if (!new_id) {
|
|
seaf_dir_free (src_dir);
|
|
return NULL;
|
|
}
|
|
|
|
new_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
|
|
new_id, dent->mode, dent->name,
|
|
dent->mtime, modifier, new_size);
|
|
dst_ents = g_list_prepend (dst_ents, new_dent);
|
|
g_free (new_id);
|
|
}
|
|
dst_ents = g_list_reverse (dst_ents);
|
|
|
|
seaf_dir_free (src_dir);
|
|
|
|
dst_dir = seaf_dir_new (NULL, dst_ents,
|
|
dir_version_from_repo_version(dst_repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr,
|
|
dst_repo->store_id, dst_repo->version,
|
|
dst_dir) < 0) {
|
|
seaf_warning ("Failed to save new dir.\n");
|
|
seaf_dir_free (dst_dir);
|
|
return NULL;
|
|
}
|
|
|
|
char *ret = g_strdup(dst_dir->dir_id);
|
|
*size = 0;
|
|
seaf_dir_free (dst_dir);
|
|
return ret;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GHashTable *
|
|
get_sub_dirents_hash_map(SeafRepo *repo, const char *parent_dir)
|
|
{
|
|
GError *error;
|
|
GList *p;
|
|
SeafDir *dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr, repo->store_id,
|
|
repo->version, repo->root_id, parent_dir, &error);
|
|
if (!dir) {
|
|
if (error) {
|
|
seaf_warning ("Failed to get dir %s repo %.8s: %s.\n",
|
|
parent_dir, repo->store_id, error->message);
|
|
g_clear_error(&error);
|
|
} else {
|
|
seaf_warning ("dir %s doesn't exist in repo %.8s.\n",
|
|
parent_dir, repo->store_id);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
GHashTable *dirent_hash = g_hash_table_new_full(g_str_hash,
|
|
g_str_equal,
|
|
g_free,
|
|
(GDestroyNotify)seaf_dirent_free);
|
|
for (p = dir->entries; p; p = p->next) {
|
|
SeafDirent *d = p->data;
|
|
g_hash_table_insert(dirent_hash, g_strdup(d->name), d);
|
|
}
|
|
|
|
g_list_free (dir->entries);
|
|
g_free (dir->ondisk);
|
|
g_free(dir);
|
|
|
|
return dirent_hash;
|
|
}
|
|
|
|
static void
|
|
set_failed_reason (char **failed_reason, char *err_str)
|
|
{
|
|
*failed_reason = g_strdup (err_str);
|
|
}
|
|
|
|
static int
|
|
cross_repo_copy (const char *src_repo_id,
|
|
const char *src_path,
|
|
const char *src_filename,
|
|
const char *dst_repo_id,
|
|
const char *dst_path,
|
|
const char *dst_filename,
|
|
int replace,
|
|
const char *modifier,
|
|
CopyTask *task)
|
|
{
|
|
SeafRepo *src_repo = NULL, *dst_repo = NULL;
|
|
SeafDirent *src_dent = NULL, *dst_dent = NULL;
|
|
SeafDirent **src_dents = NULL, **dst_dents = NULL;
|
|
char **src_names = NULL, **dst_names = NULL;
|
|
char *new_id = NULL;
|
|
guint64 new_size = 0;
|
|
int ret = 0, i = 0;
|
|
int file_num = 1;
|
|
GHashTable *dirent_hash = NULL;
|
|
gint64 total_size_all = 0;
|
|
char *err_str = COPY_ERR_INTERNAL;
|
|
int check_quota_ret;
|
|
|
|
src_repo = seaf_repo_manager_get_repo (seaf->repo_mgr, src_repo_id);
|
|
if (!src_repo) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
seaf_warning ("Failed to get source repo.\n");
|
|
goto out;
|
|
}
|
|
|
|
dst_repo = seaf_repo_manager_get_repo (seaf->repo_mgr, dst_repo_id);
|
|
if (!dst_repo) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
seaf_warning ("Failed to get destination repo.\n");
|
|
goto out;
|
|
}
|
|
|
|
/* get src dirents */
|
|
if (strchr(src_filename, '\t') && strchr(dst_filename, '\t')) {
|
|
src_names = g_strsplit (src_filename, "\t", -1);
|
|
dst_names = g_strsplit (dst_filename, "\t", -1);
|
|
file_num = g_strv_length (src_names);
|
|
|
|
src_dents = g_new0 (SeafDirent *, file_num);
|
|
dst_dents = g_new0 (SeafDirent *, file_num);
|
|
|
|
dirent_hash = get_sub_dirents_hash_map (src_repo, src_path);
|
|
if (!dirent_hash) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
gint64 total_files = -1;
|
|
gint64 total_files_all = 0;
|
|
/* check filename, size and file count */
|
|
for (i = 0; i < file_num; i++) {
|
|
if (strcmp(src_names[i], "") == 0) {
|
|
err_str = COPY_ERR_BAD_ARG;
|
|
ret = -1;
|
|
seaf_warning ("[copy files] Bad args: Empty src_filename.\n");
|
|
goto out;
|
|
}
|
|
src_dents[i] = g_hash_table_lookup (dirent_hash, src_names[i]);
|
|
if (!src_dents[i]) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
seaf_warning ("[copy files] File %s not Found.\n", src_names[i]);
|
|
goto out;
|
|
}
|
|
if (S_ISDIR(src_dents[i]->mode))
|
|
total_files = seaf_fs_manager_count_fs_files (seaf->fs_mgr,
|
|
src_repo->store_id,
|
|
src_repo->version,
|
|
src_dents[i]->id);
|
|
else
|
|
total_files = 1;
|
|
if (total_files < 0) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("Failed to get file count.\n");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
total_files_all += total_files;
|
|
if (!check_file_count_and_size (src_repo, src_dents[i], total_files_all,
|
|
&total_size_all, &err_str)) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
check_quota_ret = seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, dst_repo_id, total_size_all);
|
|
if (check_quota_ret != 0) {
|
|
if (check_quota_ret == -1) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("Failed to check quota.\n");
|
|
} else {
|
|
err_str = COPY_ERR_QUOTA_IS_FULL;
|
|
}
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (task)
|
|
task->total = total_files_all;
|
|
|
|
/* do copy */
|
|
for (i = 0; i < file_num; i++) {
|
|
new_id = copy_recursive (src_repo, dst_repo,
|
|
src_dents[i]->id, src_dents[i]->mode, modifier, task,
|
|
&new_size);
|
|
if (!new_id) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
seaf_warning ("[copy files] Failed to copy file %s.\n", src_dents[i]->name);
|
|
goto out;
|
|
}
|
|
dst_dents[i] = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
|
|
new_id, src_dents[i]->mode, dst_names[i],
|
|
src_dents[i]->mtime, modifier, new_size);
|
|
g_free (new_id);
|
|
}
|
|
|
|
} else {
|
|
src_dent = get_dirent_by_path (src_repo, NULL,
|
|
src_path, src_filename, NULL);
|
|
if (!src_dent) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("[move file] File %s not Found.\n", src_filename);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
gint64 total_files = -1;
|
|
if (S_ISDIR(src_dent->mode))
|
|
total_files = seaf_fs_manager_count_fs_files (seaf->fs_mgr,
|
|
src_repo->store_id,
|
|
src_repo->version,
|
|
src_dent->id);
|
|
else
|
|
total_files = 1;
|
|
if (total_files < 0) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("Failed to get file count.\n");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!check_file_count_and_size (src_repo, src_dent, total_files, &total_size_all, &err_str)) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
check_quota_ret = seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, dst_repo_id, total_size_all);
|
|
if (check_quota_ret != 0) {
|
|
if (check_quota_ret == -1) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("Failed to check quota.\n");
|
|
} else {
|
|
err_str = COPY_ERR_QUOTA_IS_FULL;
|
|
}
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (task)
|
|
task->total = total_files;
|
|
|
|
new_id = copy_recursive (src_repo, dst_repo,
|
|
src_dent->id, src_dent->mode, modifier, task,
|
|
&new_size);
|
|
if (!new_id) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
dst_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
|
|
new_id, src_dent->mode, dst_filename,
|
|
src_dent->mtime, modifier, new_size);
|
|
g_free (new_id);
|
|
|
|
}
|
|
if (put_dirent_and_commit (dst_repo,
|
|
dst_path,
|
|
file_num > 1 ? dst_dents : &dst_dent,
|
|
file_num,
|
|
replace,
|
|
modifier,
|
|
NULL) < 0) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (task)
|
|
task->successful = TRUE;
|
|
|
|
seaf_repo_manager_merge_virtual_repo (seaf->repo_mgr, dst_repo_id, NULL);
|
|
|
|
out:
|
|
if (src_repo)
|
|
seaf_repo_unref (src_repo);
|
|
if (dst_repo)
|
|
seaf_repo_unref (dst_repo);
|
|
if (src_dent)
|
|
seaf_dirent_free(src_dent);
|
|
if (dst_dent)
|
|
seaf_dirent_free(dst_dent);
|
|
if (dirent_hash)
|
|
g_hash_table_unref(dirent_hash);
|
|
if (file_num > 1) {
|
|
g_free(src_dents);
|
|
for (i = 0; i < file_num; i++)
|
|
seaf_dirent_free (dst_dents[i]);
|
|
g_free (dst_dents);
|
|
g_strfreev(src_names);
|
|
g_strfreev(dst_names);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
update_repo_size (dst_repo_id);
|
|
} else {
|
|
if (task && !task->canceled) {
|
|
task->failed = TRUE;
|
|
set_failed_reason (&(task->failed_reason), err_str);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
is_virtual_repo_and_origin (SeafRepo *repo1, SeafRepo *repo2)
|
|
{
|
|
if (repo1->virtual_info &&
|
|
strcmp (repo1->virtual_info->origin_repo_id, repo2->id) == 0)
|
|
return TRUE;
|
|
if (repo2->virtual_info &&
|
|
strcmp (repo2->virtual_info->origin_repo_id, repo1->id) == 0)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
check_file_count_and_size (SeafRepo *repo, SeafDirent *dent, gint64 total_files,
|
|
gint64 *total_size_all, char **err_str)
|
|
{
|
|
gint64 total_file_size = 0;
|
|
gint64 size = -1;
|
|
|
|
if (seaf->copy_mgr->max_files > 0 &&
|
|
total_files > seaf->copy_mgr->max_files) {
|
|
*err_str = COPY_ERR_TOO_MANY_FILES;
|
|
seaf_warning("Failed to copy/move file from repo %.8s: Too many files\n", repo->id);
|
|
return FALSE;
|
|
}
|
|
|
|
if (S_ISREG(dent->mode)) {
|
|
if (repo->version > 0)
|
|
size = dent->size;
|
|
else
|
|
size = seaf_fs_manager_get_file_size (seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
dent->id);
|
|
} else {
|
|
size = seaf_fs_manager_get_fs_size (seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
dent->id);
|
|
}
|
|
|
|
if (size < 0) {
|
|
*err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("Failed to get dir size of %s:%s.\n",
|
|
repo->store_id, dent->id);
|
|
return FALSE;
|
|
}
|
|
|
|
if (total_size_all) {
|
|
*total_size_all += size;
|
|
total_file_size = *total_size_all;
|
|
}
|
|
|
|
if (seaf->copy_mgr->max_size > 0) {
|
|
if (total_file_size > seaf->copy_mgr->max_size) {
|
|
*err_str = COPY_ERR_SIZE_TOO_LARGE;
|
|
seaf_warning("Failed to copy/move file from repo %.8s: "
|
|
"Folder or file size is too large.\n", repo->id);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Copy a SeafDirent from a SeafDir to another.
|
|
*
|
|
* 1. When @src_repo and @dst_repo are not the same repo, neither of them
|
|
* should be encrypted.
|
|
*
|
|
* 2. the file being copied must not exist in the dst path of the dst repo.
|
|
*/
|
|
SeafileCopyResult *
|
|
seaf_repo_manager_copy_file (SeafRepoManager *mgr,
|
|
const char *src_repo_id,
|
|
const char *src_path,
|
|
const char *src_filename,
|
|
const char *dst_repo_id,
|
|
const char *dst_path,
|
|
const char *dst_filename,
|
|
const char *user,
|
|
int need_progress,
|
|
int synchronous,
|
|
GError **error)
|
|
{
|
|
SeafRepo *src_repo = NULL, *dst_repo = NULL;
|
|
SeafDirent *src_dent = NULL, *dst_dent = NULL;
|
|
char *src_canon_path = NULL, *dst_canon_path = NULL;
|
|
SeafCommit *dst_head_commit = NULL;
|
|
int ret = 0;
|
|
gboolean background = FALSE;
|
|
char *task_id = NULL;
|
|
SeafileCopyResult *res= NULL;
|
|
|
|
GET_REPO_OR_FAIL(src_repo, src_repo_id);
|
|
|
|
if (strcmp(src_repo_id, dst_repo_id) != 0) {
|
|
GET_REPO_OR_FAIL(dst_repo, dst_repo_id);
|
|
|
|
if (src_repo->encrypted || dst_repo->encrypted) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Can't copy files between encrypted repo(s)");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
} else {
|
|
seaf_repo_ref (src_repo);
|
|
dst_repo = src_repo;
|
|
}
|
|
|
|
src_canon_path = get_canonical_path (src_path);
|
|
dst_canon_path = get_canonical_path (dst_path);
|
|
|
|
GET_COMMIT_OR_FAIL(dst_head_commit,
|
|
dst_repo->id, dst_repo->version,
|
|
dst_repo->head->commit_id);
|
|
|
|
/* FAIL_IF_FILE_EXISTS(dst_repo->store_id, dst_repo->version,
|
|
dst_head_commit->root_id, dst_canon_path, dst_filename, NULL); */
|
|
|
|
if (strcmp (src_repo_id, dst_repo_id) == 0 ||
|
|
is_virtual_repo_and_origin (src_repo, dst_repo)) {
|
|
|
|
/* get src dirent */
|
|
src_dent = get_dirent_by_path (src_repo, NULL,
|
|
src_canon_path, src_filename, error);
|
|
if (!src_dent) {
|
|
seaf_warning("[copy file] file %s/%s doesn't exist.\n", src_canon_path, src_filename);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
gint64 file_size = (src_dent->version > 0) ? src_dent->size : -1;
|
|
|
|
/* duplicate src dirent with new name */
|
|
dst_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
|
|
src_dent->id, src_dent->mode, dst_filename,
|
|
src_dent->mtime, user, file_size);
|
|
|
|
if (put_dirent_and_commit (dst_repo,
|
|
dst_canon_path,
|
|
&dst_dent,
|
|
1,
|
|
0,
|
|
user,
|
|
error) < 0) {
|
|
if (!error)
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"failed to put dirent");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, dst_repo_id, NULL);
|
|
|
|
update_repo_size (dst_repo_id);
|
|
} else if (!synchronous) {
|
|
background = TRUE;
|
|
task_id = seaf_copy_manager_add_task (seaf->copy_mgr,
|
|
src_repo_id,
|
|
src_canon_path,
|
|
src_filename,
|
|
dst_repo_id,
|
|
dst_canon_path,
|
|
dst_filename,
|
|
0,
|
|
user,
|
|
cross_repo_copy,
|
|
need_progress);
|
|
if (need_progress && !task_id) {
|
|
seaf_warning ("Failed to start copy task.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"failed to start copy task");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* Synchronous for cross-repo copy */
|
|
if (cross_repo_copy (src_repo_id,
|
|
src_canon_path,
|
|
src_filename,
|
|
dst_repo_id,
|
|
dst_canon_path,
|
|
dst_filename,
|
|
0,
|
|
user,
|
|
NULL) < 0) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to move");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (src_repo)
|
|
seaf_repo_unref (src_repo);
|
|
if (dst_repo)
|
|
seaf_repo_unref (dst_repo);
|
|
if (dst_head_commit)
|
|
seaf_commit_unref(dst_head_commit);
|
|
if (src_canon_path)
|
|
g_free (src_canon_path);
|
|
if (dst_canon_path)
|
|
g_free (dst_canon_path);
|
|
if (src_dent)
|
|
seaf_dirent_free(src_dent);
|
|
if (dst_dent)
|
|
seaf_dirent_free(dst_dent);
|
|
|
|
if (ret == 0) {
|
|
res = seafile_copy_result_new ();
|
|
g_object_set (res, "background", background, "task_id", task_id, NULL);
|
|
g_free (task_id);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
SeafileCopyResult *
|
|
seaf_repo_manager_copy_multiple_files (SeafRepoManager *mgr,
|
|
const char *src_repo_id,
|
|
const char *src_path,
|
|
const char *src_filenames,
|
|
const char *dst_repo_id,
|
|
const char *dst_path,
|
|
const char *dst_filenames,
|
|
const char *user,
|
|
int need_progress,
|
|
int synchronous,
|
|
GError **error)
|
|
{
|
|
SeafRepo *src_repo = NULL, *dst_repo = NULL;
|
|
SeafDirent **src_dents = NULL, **dst_dents = NULL;
|
|
char *src_canon_path = NULL, *dst_canon_path = NULL;
|
|
SeafCommit *dst_head_commit = NULL;
|
|
int i = 0, ret = 0;
|
|
int file_num = 1;
|
|
gint64 *file_sizes = NULL;
|
|
gboolean background = FALSE;
|
|
char *task_id = NULL;
|
|
char **src_names = NULL, **dst_names = NULL;
|
|
SeafileCopyResult *res = NULL;
|
|
GHashTable *dirent_hash = NULL;
|
|
|
|
GET_REPO_OR_FAIL(src_repo, src_repo_id);
|
|
|
|
if (strcmp(src_repo_id, dst_repo_id) != 0) {
|
|
GET_REPO_OR_FAIL(dst_repo, dst_repo_id);
|
|
|
|
if (src_repo->encrypted || dst_repo->encrypted) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Can't copy files between encrypted repo(s)");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
} else {
|
|
seaf_repo_ref (src_repo);
|
|
dst_repo = src_repo;
|
|
}
|
|
|
|
src_canon_path = get_canonical_path (src_path);
|
|
dst_canon_path = get_canonical_path (dst_path);
|
|
|
|
GET_COMMIT_OR_FAIL(dst_head_commit,
|
|
dst_repo->id, dst_repo->version,
|
|
dst_repo->head->commit_id);
|
|
/*FAIL_IF_FILE_EXISTS(dst_repo->store_id, dst_repo->version,
|
|
dst_head_commit->root_id, dst_canon_path, dst_filename, NULL);*/
|
|
|
|
if (!strchr(src_filenames, '\t') || !strchr(dst_filenames, '\t')) {
|
|
ret = -1;
|
|
seaf_warning ("[copy files] Bad args: Split filenames with '\\t'.\n");
|
|
goto out;
|
|
}
|
|
src_names = g_strsplit (src_filenames, "\t", -1);
|
|
dst_names = g_strsplit (dst_filenames, "\t", -1);
|
|
file_num = g_strv_length (src_names);
|
|
int dst_file_num = g_strv_length (dst_names);
|
|
if (dst_file_num != file_num) {
|
|
ret = -1;
|
|
seaf_warning ("[copy files] Bad args.\n");
|
|
goto out;
|
|
}
|
|
|
|
/* copy file within the same repo */
|
|
if (src_repo == dst_repo ||
|
|
is_virtual_repo_and_origin (src_repo, dst_repo)) {
|
|
|
|
/* get src dirents */
|
|
src_dents = g_new0 (SeafDirent *, file_num);
|
|
file_sizes = g_new0 (gint64, file_num);
|
|
|
|
dirent_hash = get_sub_dirents_hash_map (src_repo, src_path);
|
|
if (!dirent_hash) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < file_num; i++) {
|
|
if (strcmp(src_names[i], "") == 0) {
|
|
ret = -1;
|
|
seaf_warning ("[copy files] Bad args: Empty src_filenames.\n");
|
|
goto out;
|
|
}
|
|
src_dents[i] = g_hash_table_lookup(dirent_hash, src_names[i]);
|
|
if (!src_dents[i]) {
|
|
ret = -1;
|
|
seaf_warning ("[copy files] File %s not Found.\n", src_names[i]);
|
|
goto out;
|
|
}
|
|
file_sizes[i] = (src_dents[i]->version > 0) ? src_dents[i]->size : -1;
|
|
}
|
|
|
|
dst_dents = g_new0 (SeafDirent *, file_num);
|
|
for (i = 0; i < file_num; i++) {
|
|
if (strcmp(dst_names[i], "") == 0) {
|
|
ret = -1;
|
|
seaf_warning ("[copy files] Bad args: Empty dst_filenames.\n");
|
|
goto out;
|
|
}
|
|
/* duplicate src dirents with new names */
|
|
dst_dents[i] = seaf_dirent_new (dir_version_from_repo_version (dst_repo->version),
|
|
src_dents[i]->id, src_dents[i]->mode, dst_names[i],
|
|
src_dents[i]->mtime, user, file_sizes[i]);
|
|
}
|
|
if (put_dirent_and_commit (dst_repo,
|
|
dst_canon_path,
|
|
dst_dents,
|
|
file_num,
|
|
0,
|
|
user,
|
|
error) < 0) {
|
|
if (!error)
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"failed to put dirents");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, src_repo_id, NULL);
|
|
|
|
update_repo_size (dst_repo_id);
|
|
} else {
|
|
/* copy between different repos */
|
|
if (!synchronous) {
|
|
background = TRUE;
|
|
|
|
task_id = seaf_copy_manager_add_task (seaf->copy_mgr,
|
|
src_repo_id,
|
|
src_canon_path,
|
|
src_filenames,
|
|
dst_repo_id,
|
|
dst_canon_path,
|
|
dst_filenames,
|
|
0,
|
|
user,
|
|
cross_repo_copy,
|
|
need_progress);
|
|
if (need_progress && !task_id) {
|
|
seaf_warning ("Failed to start copy task.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"failed to start copy task");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* Synchronous for cross-repo copy */
|
|
if (cross_repo_copy (src_repo_id,
|
|
src_canon_path,
|
|
src_filenames,
|
|
dst_repo_id,
|
|
dst_canon_path,
|
|
dst_filenames,
|
|
0,
|
|
user,
|
|
NULL) < 0) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to move");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
} // Synchronous copy
|
|
} //else diffrent repo
|
|
|
|
out:
|
|
if (src_repo) seaf_repo_unref (src_repo);
|
|
if (dst_repo) seaf_repo_unref (dst_repo);
|
|
|
|
if (dst_head_commit) seaf_commit_unref(dst_head_commit);
|
|
|
|
if (src_canon_path) g_free (src_canon_path);
|
|
if (dst_canon_path) g_free (dst_canon_path);
|
|
|
|
if (src_names)
|
|
g_strfreev (src_names);
|
|
if (dst_names)
|
|
g_strfreev (dst_names);
|
|
if (file_sizes)
|
|
g_free (file_sizes);
|
|
if (src_dents)
|
|
g_free (src_dents);
|
|
if (dst_dents) {
|
|
for (i = 0; i < file_num; i++)
|
|
seaf_dirent_free (dst_dents[i]);
|
|
g_free (dst_dents);
|
|
}
|
|
if (dirent_hash)
|
|
g_hash_table_unref(dirent_hash);
|
|
if (ret == 0) {
|
|
res = seafile_copy_result_new ();
|
|
g_object_set (res, "background", background, "task_id", task_id, NULL);
|
|
g_free (task_id);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static int
|
|
move_file_same_repo (const char *repo_id,
|
|
const char *src_path, SeafDirent *src_dents[],
|
|
const char *dst_path, SeafDirent *dst_dents[],
|
|
int file_num,
|
|
int replace,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
char *root_id_after_put = NULL, *root_id = NULL;
|
|
char buf[SEAF_PATH_MAX];
|
|
int ret = 0, i = 0;
|
|
GString *filenames_str = NULL;
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
filenames_str = g_string_new ("");
|
|
root_id_after_put = head_commit->root_id;
|
|
|
|
GList *dent_list = NULL;
|
|
GList *name_list = NULL;
|
|
for (i = 0; i < file_num; i++) {
|
|
dent_list = g_list_append (dent_list, dst_dents[i]);
|
|
g_string_append_printf (filenames_str, "%s", src_dents[i]->name);
|
|
if ((i + 1) < file_num)
|
|
g_string_append_printf (filenames_str, "\t");
|
|
}
|
|
if (*dst_path == '/')
|
|
dst_path = dst_path + 1;
|
|
|
|
root_id_after_put = post_multi_files_recursive (repo, head_commit->root_id, dst_path, dent_list, user,
|
|
replace, &name_list);
|
|
g_list_free (dent_list);
|
|
g_list_free_full (name_list, (GDestroyNotify)g_free);
|
|
|
|
if (!root_id_after_put) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "move file failed");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
root_id = do_del_file (repo, root_id_after_put, src_path, filenames_str->str,
|
|
NULL, NULL, NULL);
|
|
|
|
if (!root_id) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "move file failed");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Commit. */
|
|
if (file_num > 1) {
|
|
snprintf(buf, SEAF_PATH_MAX, "Moved \"%s\" and %d more files",
|
|
src_dents[0]->name,file_num - 1);
|
|
} else if (S_ISDIR(src_dents[0]->mode)) {
|
|
snprintf(buf, SEAF_PATH_MAX, "Moved directory \"%s\"", src_dents[0]->name);
|
|
} else {
|
|
snprintf(buf, SEAF_PATH_MAX, "Moved \"%s\"", src_dents[0]->name);
|
|
}
|
|
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0)
|
|
ret = -1;
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref (head_commit);
|
|
if (filenames_str)
|
|
g_string_free (filenames_str, TRUE);
|
|
|
|
g_free (root_id_after_put);
|
|
g_free (root_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
cross_repo_move (const char *src_repo_id,
|
|
const char *src_path,
|
|
const char *src_filename,
|
|
const char *dst_repo_id,
|
|
const char *dst_path,
|
|
const char *dst_filename,
|
|
int replace,
|
|
const char *modifier,
|
|
CopyTask *task)
|
|
{
|
|
SeafRepo *src_repo = NULL, *dst_repo = NULL;
|
|
SeafDirent *src_dent = NULL, *dst_dent = NULL;
|
|
SeafDirent **src_dents = NULL, **dst_dents = NULL;
|
|
char **src_names = NULL, **dst_names = NULL;
|
|
char *new_id = NULL;
|
|
guint64 new_size = 0;
|
|
int ret = 0, i = 0;
|
|
int file_num = 1;
|
|
GHashTable *dirent_hash = NULL;
|
|
gint64 total_size_all = 0;
|
|
char *err_str = COPY_ERR_INTERNAL;
|
|
int check_quota_ret;
|
|
|
|
src_repo = seaf_repo_manager_get_repo (seaf->repo_mgr, src_repo_id);
|
|
if (!src_repo) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
seaf_warning ("Failed to get source repo.\n");
|
|
goto out;
|
|
}
|
|
|
|
dst_repo = seaf_repo_manager_get_repo (seaf->repo_mgr, dst_repo_id);
|
|
if (!dst_repo) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
seaf_warning ("Failed to get destination repo.\n");
|
|
goto out;
|
|
}
|
|
|
|
/* get src dirents */
|
|
if (strchr(src_filename, '\t') && strchr(dst_filename, '\t')) {
|
|
src_names = g_strsplit (src_filename, "\t", -1);
|
|
dst_names = g_strsplit (dst_filename, "\t", -1);
|
|
file_num = g_strv_length (src_names);
|
|
|
|
src_dents = g_new0 (SeafDirent *, file_num);
|
|
dst_dents = g_new0 (SeafDirent *, file_num);
|
|
|
|
dirent_hash = get_sub_dirents_hash_map (src_repo, src_path);
|
|
if (!dirent_hash) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
gint64 total_files = -1;
|
|
gint64 total_files_all = 0;
|
|
/* check filename, size and file count */
|
|
for (i = 0; i < file_num; i++) {
|
|
if (strcmp(src_names[i], "") == 0) {
|
|
err_str = COPY_ERR_BAD_ARG;
|
|
ret = -1;
|
|
seaf_warning ("[move files] Bad args: Empty src_filename.\n");
|
|
goto out;
|
|
}
|
|
src_dents[i] = g_hash_table_lookup (dirent_hash, src_names[i]);
|
|
if (!src_dents[i]) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
seaf_warning ("[move files] File %s not Found.\n", src_names[i]);
|
|
goto out;
|
|
}
|
|
if (S_ISDIR(src_dents[i]->mode))
|
|
total_files = seaf_fs_manager_count_fs_files (seaf->fs_mgr,
|
|
src_repo->store_id,
|
|
src_repo->version,
|
|
src_dents[i]->id);
|
|
else
|
|
total_files = 1;
|
|
if (total_files < 0) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("Failed to get file count.\n");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
total_files_all += total_files;
|
|
if (!check_file_count_and_size (src_repo, src_dents[i], total_files_all,
|
|
&total_size_all, &err_str)) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
check_quota_ret = seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, dst_repo_id, total_size_all);
|
|
if (check_quota_ret != 0) {
|
|
if (check_quota_ret == -1) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("Failed to check quota.\n");
|
|
} else {
|
|
err_str = COPY_ERR_QUOTA_IS_FULL;
|
|
}
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (task)
|
|
task->total = total_files_all;
|
|
|
|
/* do copy */
|
|
for (i = 0; i < file_num; i++) {
|
|
new_id = copy_recursive (src_repo, dst_repo,
|
|
src_dents[i]->id, src_dents[i]->mode, modifier, task,
|
|
&new_size);
|
|
if (!new_id) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
seaf_warning ("[move files] Failed to copy file %s.\n", src_dents[i]->name);
|
|
goto out;
|
|
}
|
|
dst_dents[i] = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
|
|
new_id, src_dents[i]->mode, dst_names[i],
|
|
src_dents[i]->mtime, modifier, new_size);
|
|
g_free (new_id);
|
|
}
|
|
} else {
|
|
src_dent = get_dirent_by_path (src_repo, NULL,
|
|
src_path, src_filename, NULL);
|
|
if (!src_dent) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("[move file] File %s not Found.\n", src_filename);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
gint64 total_files = -1;
|
|
if (S_ISDIR(src_dent->mode))
|
|
total_files = seaf_fs_manager_count_fs_files (seaf->fs_mgr,
|
|
src_repo->store_id,
|
|
src_repo->version,
|
|
src_dent->id);
|
|
else
|
|
total_files = 1;
|
|
if (total_files < 0) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("Failed to get file count.\n");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!check_file_count_and_size (src_repo, src_dent, total_files, &total_size_all, &err_str)) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
check_quota_ret = seaf_quota_manager_check_quota_with_delta (seaf->quota_mgr, dst_repo_id, total_size_all);
|
|
if (check_quota_ret != 0) {
|
|
if (check_quota_ret == -1) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
seaf_warning ("Failed to check quota.\n");
|
|
} else {
|
|
err_str = COPY_ERR_QUOTA_IS_FULL;
|
|
}
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (task)
|
|
task->total = total_files;
|
|
|
|
new_id = copy_recursive (src_repo, dst_repo,
|
|
src_dent->id, src_dent->mode, modifier, task,
|
|
&new_size);
|
|
if (!new_id) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
dst_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
|
|
new_id, src_dent->mode, dst_filename,
|
|
src_dent->mtime, modifier, new_size);
|
|
g_free (new_id);
|
|
|
|
}
|
|
|
|
if (put_dirent_and_commit (dst_repo,
|
|
dst_path,
|
|
file_num > 1 ? dst_dents : &dst_dent,
|
|
file_num,
|
|
replace,
|
|
modifier,
|
|
NULL) < 0) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (seaf->repo_mgr, dst_repo_id, NULL);
|
|
|
|
if (seaf_repo_manager_del_file (seaf->repo_mgr, src_repo_id, src_path,
|
|
src_filename, modifier, NULL) < 0) {
|
|
err_str = COPY_ERR_INTERNAL;
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (task)
|
|
task->successful = TRUE;
|
|
|
|
seaf_repo_manager_merge_virtual_repo (seaf->repo_mgr, src_repo_id, NULL);
|
|
|
|
out:
|
|
if (src_repo)
|
|
seaf_repo_unref (src_repo);
|
|
if (dst_repo)
|
|
seaf_repo_unref (dst_repo);
|
|
if (src_dent)
|
|
seaf_dirent_free(src_dent);
|
|
if (dst_dent)
|
|
seaf_dirent_free(dst_dent);
|
|
if (dirent_hash)
|
|
g_hash_table_unref(dirent_hash);
|
|
if (file_num > 1) {
|
|
g_free (src_dents);
|
|
for (i = 0; i < file_num; i++)
|
|
seaf_dirent_free(dst_dents[i]);
|
|
g_free (dst_dents);
|
|
g_strfreev(src_names);
|
|
g_strfreev(dst_names);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
update_repo_size (dst_repo_id);
|
|
} else {
|
|
if (task && !task->canceled) {
|
|
task->failed = TRUE;
|
|
set_failed_reason (&(task->failed_reason), err_str);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
SeafileCopyResult *
|
|
seaf_repo_manager_move_file (SeafRepoManager *mgr,
|
|
const char *src_repo_id,
|
|
const char *src_path,
|
|
const char *src_filename,
|
|
const char *dst_repo_id,
|
|
const char *dst_path,
|
|
const char *dst_filename,
|
|
int replace,
|
|
const char *user,
|
|
int need_progress,
|
|
int synchronous,
|
|
GError **error)
|
|
{
|
|
SeafRepo *src_repo = NULL, *dst_repo = NULL;
|
|
SeafDirent *src_dent = NULL, *dst_dent = NULL;
|
|
char *src_canon_path = NULL, *dst_canon_path = NULL;
|
|
SeafCommit *dst_head_commit = NULL;
|
|
int ret = 0;
|
|
gboolean background = FALSE;
|
|
char *task_id = NULL;
|
|
SeafileCopyResult *res = NULL;
|
|
|
|
GET_REPO_OR_FAIL(src_repo, src_repo_id);
|
|
|
|
if (strcmp(src_repo_id, dst_repo_id) != 0) {
|
|
GET_REPO_OR_FAIL(dst_repo, dst_repo_id);
|
|
|
|
if (src_repo->encrypted || dst_repo->encrypted) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Can't copy files between encrypted repo(s)");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
} else {
|
|
seaf_repo_ref (src_repo);
|
|
dst_repo = src_repo;
|
|
}
|
|
|
|
src_canon_path = get_canonical_path (src_path);
|
|
dst_canon_path = get_canonical_path (dst_path);
|
|
/* first check whether a file with file_name already exists in destination dir */
|
|
GET_COMMIT_OR_FAIL(dst_head_commit,
|
|
dst_repo->id, dst_repo->version,
|
|
dst_repo->head->commit_id);
|
|
/*FAIL_IF_FILE_EXISTS(dst_repo->store_id, dst_repo->version,
|
|
dst_head_commit->root_id, dst_canon_path, dst_filename, NULL);*/
|
|
|
|
/* same repo */
|
|
if (src_repo == dst_repo ) {
|
|
/* get src dirent */
|
|
src_dent = get_dirent_by_path (src_repo, NULL,
|
|
src_canon_path, src_filename, error);
|
|
if (!src_dent) {
|
|
seaf_warning("[move file] file %s/%s doesn't exist.\n", src_canon_path, src_filename);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
gint64 file_size = (src_dent->version > 0) ? src_dent->size : -1;
|
|
|
|
/* duplicate src dirent with new name */
|
|
dst_dent = seaf_dirent_new (dir_version_from_repo_version (dst_repo->version),
|
|
src_dent->id, src_dent->mode, dst_filename,
|
|
src_dent->mtime, user, file_size);
|
|
|
|
/* move file within the same repo */
|
|
if (move_file_same_repo (src_repo_id,
|
|
src_canon_path, &src_dent,
|
|
dst_canon_path, &dst_dent,
|
|
1, replace, user, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, src_repo_id, NULL);
|
|
|
|
update_repo_size (dst_repo_id);
|
|
} else {
|
|
/* move between different repos */
|
|
/* virtual repo */
|
|
if (is_virtual_repo_and_origin (src_repo, dst_repo)) {
|
|
/* get src dirent */
|
|
src_dent = get_dirent_by_path (src_repo, NULL,
|
|
src_canon_path, src_filename, error);
|
|
if (!src_dent) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
gint64 file_size = (src_dent->version > 0) ? src_dent->size : -1;
|
|
|
|
/* duplicate src dirent with new name */
|
|
dst_dent = seaf_dirent_new (dir_version_from_repo_version(dst_repo->version),
|
|
src_dent->id, src_dent->mode, dst_filename,
|
|
src_dent->mtime, user, file_size);
|
|
|
|
/* add this dirent to dst repo */
|
|
if (put_dirent_and_commit (dst_repo,
|
|
dst_canon_path,
|
|
&dst_dent,
|
|
1,
|
|
replace,
|
|
user,
|
|
error) < 0) {
|
|
if (!error)
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"failed to put dirent");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, dst_repo_id, NULL);
|
|
|
|
if (seaf_repo_manager_del_file (mgr, src_repo_id, src_path,
|
|
src_filename, user, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, src_repo_id, NULL);
|
|
|
|
update_repo_size (dst_repo_id);
|
|
} else if (!synchronous) {
|
|
background = TRUE;
|
|
|
|
task_id = seaf_copy_manager_add_task (seaf->copy_mgr,
|
|
src_repo_id,
|
|
src_canon_path,
|
|
src_filename,
|
|
dst_repo_id,
|
|
dst_canon_path,
|
|
dst_filename,
|
|
replace,
|
|
user,
|
|
cross_repo_move,
|
|
need_progress);
|
|
if (need_progress && !task_id) {
|
|
seaf_warning ("Failed to start copy task.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"failed to start copy task");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* Synchronous for cross-repo move */
|
|
if (cross_repo_move (src_repo_id,
|
|
src_canon_path,
|
|
src_filename,
|
|
dst_repo_id,
|
|
dst_canon_path,
|
|
dst_filename,
|
|
replace,
|
|
user,
|
|
NULL) < 0) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to move");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (src_repo) seaf_repo_unref (src_repo);
|
|
if (dst_repo) seaf_repo_unref (dst_repo);
|
|
|
|
if (dst_head_commit) seaf_commit_unref(dst_head_commit);
|
|
|
|
if (src_canon_path) g_free (src_canon_path);
|
|
if (dst_canon_path) g_free (dst_canon_path);
|
|
|
|
seaf_dirent_free(src_dent);
|
|
seaf_dirent_free(dst_dent);
|
|
|
|
if (ret == 0) {
|
|
res = seafile_copy_result_new ();
|
|
g_object_set (res, "background", background, "task_id", task_id, NULL);
|
|
g_free (task_id);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
SeafileCopyResult *
|
|
seaf_repo_manager_move_multiple_files (SeafRepoManager *mgr,
|
|
const char *src_repo_id,
|
|
const char *src_path,
|
|
const char *src_filenames,
|
|
const char *dst_repo_id,
|
|
const char *dst_path,
|
|
const char *dst_filenames,
|
|
int replace,
|
|
const char *user,
|
|
int need_progress,
|
|
int synchronous,
|
|
GError **error)
|
|
{
|
|
SeafRepo *src_repo = NULL, *dst_repo = NULL;
|
|
SeafDirent **src_dents = NULL, **dst_dents = NULL;
|
|
char *src_canon_path = NULL, *dst_canon_path = NULL;
|
|
SeafCommit *dst_head_commit = NULL;
|
|
int i = 0, ret = 0;
|
|
int file_num = 1;
|
|
gint64 *file_sizes = NULL;
|
|
gboolean background = FALSE;
|
|
char *task_id = NULL;
|
|
char **src_names = NULL, **dst_names = NULL;
|
|
SeafileCopyResult *res = NULL;
|
|
GHashTable *dirent_hash = NULL;
|
|
|
|
GET_REPO_OR_FAIL(src_repo, src_repo_id);
|
|
|
|
if (strcmp(src_repo_id, dst_repo_id) != 0) {
|
|
GET_REPO_OR_FAIL(dst_repo, dst_repo_id);
|
|
|
|
if (src_repo->encrypted || dst_repo->encrypted) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Can't copy files between encrypted repo(s)");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
} else {
|
|
seaf_repo_ref (src_repo);
|
|
dst_repo = src_repo;
|
|
}
|
|
|
|
src_canon_path = get_canonical_path (src_path);
|
|
dst_canon_path = get_canonical_path (dst_path);
|
|
|
|
GET_COMMIT_OR_FAIL(dst_head_commit,
|
|
dst_repo->id, dst_repo->version,
|
|
dst_repo->head->commit_id);
|
|
/*FAIL_IF_FILE_EXISTS(dst_repo->store_id, dst_repo->version,
|
|
dst_head_commit->root_id, dst_canon_path, dst_filename, NULL);*/
|
|
|
|
if (!strchr(src_filenames, '\t') || !strchr(dst_filenames, '\t')) {
|
|
ret = -1;
|
|
seaf_warning ("[move files] Bad args: Split filenames with '\\t'.\n");
|
|
goto out;
|
|
}
|
|
src_names = g_strsplit (src_filenames, "\t", -1);
|
|
dst_names = g_strsplit (dst_filenames, "\t", -1);
|
|
file_num = g_strv_length (src_names);
|
|
int dst_file_num = g_strv_length (dst_names);
|
|
if (dst_file_num != file_num) {
|
|
ret = -1;
|
|
seaf_warning ("[move files] Bad args.\n");
|
|
goto out;
|
|
}
|
|
|
|
gboolean is_virtual_origin = is_virtual_repo_and_origin (src_repo, dst_repo);
|
|
if (src_repo == dst_repo || is_virtual_origin) {
|
|
/* get src dirents */
|
|
|
|
src_dents = g_new0 (SeafDirent *, file_num);
|
|
file_sizes = g_new0 (gint64, file_num);
|
|
|
|
dirent_hash = get_sub_dirents_hash_map (src_repo, src_path);
|
|
if (!dirent_hash) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < file_num; i++) {
|
|
if (strcmp(src_names[i], "") == 0) {
|
|
ret = -1;
|
|
seaf_warning ("[move files] Bad args: Empty src_filenames.\n");
|
|
goto out;
|
|
}
|
|
src_dents[i] = g_hash_table_lookup(dirent_hash, src_names[i]);
|
|
if (!src_dents[i]) {
|
|
ret = -1;
|
|
seaf_warning ("[move files] File %s not Found.\n", src_names[i]);
|
|
goto out;
|
|
}
|
|
file_sizes[i] = (src_dents[i]->version > 0) ? src_dents[i]->size : -1;
|
|
}
|
|
|
|
dst_dents = g_new0 (SeafDirent *, file_num);
|
|
for (i = 0; i < file_num; i++) {
|
|
if (strcmp(dst_names[i], "") == 0) {
|
|
ret = -1;
|
|
seaf_warning ("[move files] Bad args: Empty dst_filenames.\n");
|
|
goto out;
|
|
}
|
|
/* duplicate src dirents with new names */
|
|
dst_dents[i] = seaf_dirent_new (dir_version_from_repo_version (dst_repo->version),
|
|
src_dents[i]->id, src_dents[i]->mode, dst_names[i],
|
|
src_dents[i]->mtime, user, file_sizes[i]);
|
|
}
|
|
/* move file within the same repo */
|
|
if (src_repo == dst_repo) {
|
|
if (move_file_same_repo (src_repo_id,
|
|
src_canon_path, src_dents,
|
|
dst_canon_path, dst_dents,
|
|
file_num, replace, user, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* move between virtual and origin repo */
|
|
if (put_dirent_and_commit (dst_repo,
|
|
dst_path,
|
|
dst_dents,
|
|
file_num,
|
|
replace,
|
|
user,
|
|
NULL) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
seaf_repo_manager_merge_virtual_repo (mgr, dst_repo->id, NULL);
|
|
|
|
if (seaf_repo_manager_del_file (mgr, src_repo->id, src_path,
|
|
src_filenames, user, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
seaf_repo_manager_merge_virtual_repo (mgr, src_repo_id, NULL);
|
|
|
|
update_repo_size (dst_repo_id);
|
|
} else {
|
|
/* move between different repos */
|
|
if (!synchronous) {
|
|
background = TRUE;
|
|
|
|
task_id = seaf_copy_manager_add_task (seaf->copy_mgr,
|
|
src_repo_id,
|
|
src_canon_path,
|
|
src_filenames,
|
|
dst_repo_id,
|
|
dst_canon_path,
|
|
dst_filenames,
|
|
0,
|
|
user,
|
|
cross_repo_move,
|
|
need_progress);
|
|
if (need_progress && !task_id) {
|
|
seaf_warning ("Failed to start copy task.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"failed to start copy task");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* Synchronous for cross-repo move */
|
|
if (cross_repo_move (src_repo_id,
|
|
src_canon_path,
|
|
src_filenames,
|
|
dst_repo_id,
|
|
dst_canon_path,
|
|
dst_filenames,
|
|
replace,
|
|
user,
|
|
NULL) < 0) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to move");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
} // Synchronous move
|
|
} //else diffrent repo
|
|
|
|
out:
|
|
if (src_repo) seaf_repo_unref (src_repo);
|
|
if (dst_repo) seaf_repo_unref (dst_repo);
|
|
|
|
if (dst_head_commit) seaf_commit_unref(dst_head_commit);
|
|
|
|
if (src_canon_path) g_free (src_canon_path);
|
|
if (dst_canon_path) g_free (dst_canon_path);
|
|
|
|
if (src_names)
|
|
g_strfreev (src_names);
|
|
if (dst_names)
|
|
g_strfreev (dst_names);
|
|
if (file_sizes)
|
|
g_free (file_sizes);
|
|
|
|
if (dirent_hash)
|
|
g_hash_table_unref(dirent_hash);
|
|
if (src_dents)
|
|
g_free (src_dents);
|
|
if (dst_dents) {
|
|
for (i = 0; i < file_num; i++)
|
|
seaf_dirent_free (dst_dents[i]);
|
|
g_free (dst_dents);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
res = seafile_copy_result_new ();
|
|
g_object_set (res, "background", background, "task_id", task_id, NULL);
|
|
g_free (task_id);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_mkdir_with_parents (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *parent_dir,
|
|
const char *new_dir_path,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
char **sub_folders = NULL;
|
|
int nfolder;
|
|
char buf[SEAF_PATH_MAX];
|
|
char *root_id = NULL;
|
|
SeafDirent *new_dent = NULL;
|
|
char *parent_dir_can = NULL;
|
|
char *relative_dir_can = NULL;
|
|
char *abs_path = NULL;
|
|
int total_path_len;
|
|
int sub_folder_len;
|
|
GList *uncre_dir_list = NULL;
|
|
GList *iter_list = NULL;
|
|
char *uncre_dir;
|
|
int ret = 0;
|
|
|
|
if (new_dir_path[0] == '/' || new_dir_path[0] == '\\') {
|
|
seaf_warning ("[mkdir with parent] Invalid relative path %s.\n", new_dir_path);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid relative path");
|
|
return -1;
|
|
}
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
relative_dir_can = get_canonical_path (new_dir_path);
|
|
sub_folders = g_strsplit (relative_dir_can, "/", 0);
|
|
nfolder = g_strv_length (sub_folders);
|
|
|
|
int i = 0;
|
|
for (; i < nfolder; ++i) {
|
|
if (strcmp (sub_folders[i], "") == 0)
|
|
continue;
|
|
|
|
if (should_ignore_file (sub_folders[i], NULL)) {
|
|
seaf_warning ("[post dir] Invalid dir name %s.\n", sub_folders[i]);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid dir name");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (strcmp (parent_dir, "/") == 0 ||
|
|
strcmp (parent_dir, "\\") == 0) {
|
|
parent_dir_can = g_strdup ("/");
|
|
abs_path = g_strdup_printf ("%s%s", parent_dir_can, relative_dir_can);
|
|
} else {
|
|
parent_dir_can = get_canonical_path (parent_dir);
|
|
abs_path = g_strdup_printf ("%s/%s", parent_dir_can, relative_dir_can);
|
|
}
|
|
if (!abs_path) {
|
|
seaf_warning ("[mkdir with parent] Out of memory.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,
|
|
"Out of memory");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
total_path_len = strlen (abs_path);
|
|
|
|
// from the last, to check the folder exist
|
|
i = nfolder - 1;
|
|
for (; i >= 0; --i) {
|
|
if (strcmp (sub_folders[i], "") == 0)
|
|
continue;
|
|
|
|
sub_folder_len = strlen (sub_folders[i]) + 1;
|
|
total_path_len -= sub_folder_len;
|
|
memset (abs_path + total_path_len, '\0', sub_folder_len);
|
|
|
|
if (check_file_exists (repo->store_id, repo->version,
|
|
head_commit->root_id, abs_path, sub_folders[i], NULL)) {
|
|
// folder exist, skip loop to create unexist subfolder
|
|
strcat (abs_path, "/");
|
|
strcat (abs_path, sub_folders[i]);
|
|
break;
|
|
} else {
|
|
// folder not exist, cache it to create later
|
|
uncre_dir_list = g_list_prepend (uncre_dir_list, sub_folders[i]);
|
|
}
|
|
}
|
|
|
|
if (uncre_dir_list) {
|
|
// exist parent folder has been found, based on it to create unexist subfolder
|
|
char new_root_id[41];
|
|
memcpy (new_root_id, head_commit->root_id, 40);
|
|
new_root_id[40] = '\0';
|
|
|
|
for (iter_list = uncre_dir_list; iter_list; iter_list = iter_list->next) {
|
|
uncre_dir = iter_list->data;
|
|
new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
|
|
EMPTY_SHA1, S_IFDIR, uncre_dir,
|
|
(gint64)time(NULL), NULL, -1);
|
|
|
|
root_id = do_post_file (repo,
|
|
new_root_id, abs_path, new_dent);
|
|
if (!root_id) {
|
|
seaf_warning ("[put dir] Failed to put dir.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to put dir");
|
|
ret = -1;
|
|
seaf_dirent_free (new_dent);
|
|
goto out;
|
|
}
|
|
|
|
// the last folder has been created
|
|
if (!iter_list->next) {
|
|
seaf_dirent_free (new_dent);
|
|
break;
|
|
}
|
|
|
|
strcat (abs_path, "/");
|
|
strcat (abs_path, uncre_dir);
|
|
memcpy (new_root_id, root_id, 40);
|
|
|
|
seaf_dirent_free (new_dent);
|
|
g_free (root_id);
|
|
}
|
|
|
|
/* Commit. */
|
|
snprintf(buf, SEAF_PATH_MAX, "Added directory \"%s\"", relative_dir_can);
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0) {
|
|
ret = -1;
|
|
g_free (root_id);
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
g_free (root_id);
|
|
}
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref(head_commit);
|
|
if (sub_folders)
|
|
g_strfreev (sub_folders);
|
|
if (uncre_dir_list)
|
|
g_list_free (uncre_dir_list);
|
|
if (relative_dir_can)
|
|
g_free (relative_dir_can);
|
|
if (parent_dir_can)
|
|
g_free (parent_dir_can);
|
|
if (abs_path)
|
|
g_free (abs_path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_post_dir (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *parent_dir,
|
|
const char *new_dir_name,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
char *canon_path = NULL;
|
|
char buf[SEAF_PATH_MAX];
|
|
char *root_id = NULL;
|
|
SeafDirent *new_dent = NULL;
|
|
int ret = 0;
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
canon_path = get_canonical_path (parent_dir);
|
|
|
|
if (should_ignore_file (new_dir_name, NULL)) {
|
|
seaf_warning ("[post dir] Invalid dir name %s.\n", new_dir_name);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid dir name");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
FAIL_IF_FILE_EXISTS(repo->store_id, repo->version,
|
|
head_commit->root_id, canon_path, new_dir_name, NULL);
|
|
|
|
if (!new_dent) {
|
|
new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
|
|
EMPTY_SHA1, S_IFDIR, new_dir_name,
|
|
(gint64)time(NULL), NULL, -1);
|
|
}
|
|
|
|
root_id = do_post_file (repo,
|
|
head_commit->root_id, canon_path, new_dent);
|
|
if (!root_id) {
|
|
seaf_warning ("[put dir] Failed to put dir %s to %s in repo %s.\n",
|
|
new_dir_name, canon_path, repo->id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to put dir");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Commit. */
|
|
snprintf(buf, SEAF_PATH_MAX, "Added directory \"%s\"", new_dir_name);
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref(head_commit);
|
|
seaf_dirent_free (new_dent);
|
|
g_free (root_id);
|
|
g_free (canon_path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_post_empty_file (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *parent_dir,
|
|
const char *new_file_name,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
char *canon_path = NULL;
|
|
char buf[SEAF_PATH_MAX];
|
|
char *root_id = NULL;
|
|
SeafDirent *new_dent = NULL;
|
|
int ret = 0;
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
if (!canon_path)
|
|
/* no need to call get_canonical_path again when retry */
|
|
canon_path = get_canonical_path (parent_dir);
|
|
|
|
if (should_ignore_file (new_file_name, NULL)) {
|
|
seaf_warning ("[post file] Invalid file name %s.\n", new_file_name);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid file name");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
FAIL_IF_FILE_EXISTS(repo->store_id, repo->version,
|
|
head_commit->root_id, canon_path, new_file_name, NULL);
|
|
|
|
if (!new_dent) {
|
|
new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
|
|
EMPTY_SHA1, STD_FILE_MODE, new_file_name,
|
|
(gint64)time(NULL), user, 0);
|
|
}
|
|
|
|
root_id = do_post_file (repo,
|
|
head_commit->root_id, canon_path, new_dent);
|
|
if (!root_id) {
|
|
seaf_warning ("[put dir] Failed to create empty file dir.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to put dir");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Commit. */
|
|
snprintf(buf, SEAF_PATH_MAX, "Added \"%s\"", new_file_name);
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
|
|
update_repo_size (repo_id);
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref(head_commit);
|
|
seaf_dirent_free (new_dent);
|
|
g_free (root_id);
|
|
g_free (canon_path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
rename_file_recursive(SeafRepo *repo,
|
|
const char *dir_id,
|
|
const char *to_path,
|
|
const char *oldname,
|
|
const char *newname)
|
|
{
|
|
SeafDir *olddir, *newdir;
|
|
SeafDirent *dent;
|
|
GList *ptr;
|
|
char *to_path_dup = NULL;
|
|
char *remain = NULL;
|
|
char *slash;
|
|
char *id = NULL;
|
|
char *ret = NULL;
|
|
|
|
olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
dir_id);
|
|
if (!olddir)
|
|
return NULL;
|
|
|
|
/* we reach the target dir. */
|
|
if (*to_path == '\0') {
|
|
SeafDirent *old, *newdent = NULL;
|
|
GList *newentries = NULL, *p;
|
|
|
|
/* When renameing, there is a pitfall: we can't simply rename the
|
|
* dirent, since the dirents are required to be sorted in descending
|
|
* order. We need to copy all old dirents except the target dirent,
|
|
* and then rename the target dirent, and then insert the new
|
|
* dirent, so that we can maintain the descending order of dirents. */
|
|
for (p = olddir->entries; p != NULL; p = p->next) {
|
|
old = p->data;
|
|
if (strcmp(old->name, oldname) != 0) {
|
|
newentries = g_list_prepend (newentries, seaf_dirent_dup(old));
|
|
} else {
|
|
newdent = seaf_dirent_new (old->version, old->id, old->mode,
|
|
newname, old->mtime,
|
|
old->modifier, old->size);
|
|
}
|
|
}
|
|
|
|
newentries = g_list_reverse (newentries);
|
|
|
|
if (newdent) {
|
|
newentries = g_list_insert_sorted(newentries, newdent, compare_dirents);
|
|
}
|
|
|
|
newdir = seaf_dir_new (NULL, newentries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strndup (newdir->dir_id, 40);
|
|
seaf_dir_free (newdir);
|
|
|
|
goto out;
|
|
}
|
|
|
|
to_path_dup = g_strdup (to_path);
|
|
slash = strchr (to_path_dup, '/');
|
|
|
|
if (!slash) {
|
|
remain = to_path_dup + strlen(to_path_dup);
|
|
} else {
|
|
*slash = '\0';
|
|
remain = slash + 1;
|
|
}
|
|
|
|
for (ptr = olddir->entries; ptr; ptr = ptr->next) {
|
|
dent = (SeafDirent *)ptr->data;
|
|
|
|
if (strcmp(dent->name, to_path_dup) != 0)
|
|
continue;
|
|
|
|
id = rename_file_recursive (repo, dent->id, remain, oldname, newname);
|
|
if (id != NULL) {
|
|
memcpy(dent->id, id, 40);
|
|
dent->id[40] = '\0';
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (id != NULL) {
|
|
/* Create a new SeafDir. */
|
|
GList *new_entries;
|
|
|
|
new_entries = dup_seafdir_entries (olddir->entries);
|
|
newdir = seaf_dir_new (NULL, new_entries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strdup(newdir->dir_id);
|
|
seaf_dir_free (newdir);
|
|
}
|
|
|
|
out:
|
|
g_free (to_path_dup);
|
|
g_free (id);
|
|
seaf_dir_free(olddir);
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
do_rename_file(SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *parent_dir,
|
|
const char *oldname,
|
|
const char *newname)
|
|
{
|
|
/* if parent_dir is a absolutely path, we will remove the first '/' */
|
|
if (*parent_dir == '/')
|
|
parent_dir = parent_dir + 1;
|
|
|
|
return rename_file_recursive(repo, root_id, parent_dir, oldname, newname);
|
|
}
|
|
|
|
|
|
int
|
|
seaf_repo_manager_rename_file (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *parent_dir,
|
|
const char *oldname,
|
|
const char *newname,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
char *root_id = NULL;
|
|
char *canon_path = NULL;
|
|
char buf[SEAF_PATH_MAX];
|
|
int mode = 0;
|
|
int ret = 0;
|
|
|
|
if (strcmp(oldname, newname) == 0)
|
|
return 0;
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
if (!canon_path)
|
|
canon_path = get_canonical_path (parent_dir);
|
|
|
|
if (should_ignore_file (newname, NULL)) {
|
|
seaf_warning ("[rename file] Invalid filename %s.\n", newname);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid filename");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
FAIL_IF_FILE_NOT_EXISTS(repo->store_id, repo->version,
|
|
head_commit->root_id, canon_path, oldname, &mode);
|
|
FAIL_IF_FILE_EXISTS(repo->store_id, repo->version,
|
|
head_commit->root_id, canon_path, newname, NULL);
|
|
|
|
root_id = do_rename_file (repo, head_commit->root_id, canon_path,
|
|
oldname, newname);
|
|
if (!root_id) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"faile to rename file %s", oldname);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Commit. */
|
|
if (S_ISDIR(mode)) {
|
|
snprintf(buf, SEAF_PATH_MAX, "Renamed directory \"%s\"", oldname);
|
|
} else {
|
|
snprintf(buf, SEAF_PATH_MAX, "Renamed \"%s\"", oldname);
|
|
}
|
|
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref (head_commit);
|
|
g_free (canon_path);
|
|
g_free (root_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
put_file_recursive(SeafRepo *repo,
|
|
const char *dir_id,
|
|
const char *to_path,
|
|
SeafDirent *newdent)
|
|
{
|
|
SeafDir *olddir, *newdir;
|
|
SeafDirent *dent;
|
|
GList *ptr;
|
|
char *to_path_dup = NULL;
|
|
char *remain = NULL;
|
|
char *slash;
|
|
char *id = NULL;
|
|
char *ret = NULL;
|
|
|
|
olddir = seaf_fs_manager_get_seafdir_sorted(seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
dir_id);
|
|
if (!olddir)
|
|
return NULL;
|
|
|
|
/* we reach the target dir. Update the target dirent. */
|
|
if (*to_path == '\0') {
|
|
GList *newentries = NULL, *p;
|
|
SeafDirent *dent;
|
|
|
|
for (p = olddir->entries; p; p = p->next) {
|
|
dent = p->data;
|
|
if (strcmp(dent->name, newdent->name) == 0) {
|
|
newentries = g_list_prepend (newentries, seaf_dirent_dup(newdent));
|
|
} else {
|
|
newentries = g_list_prepend (newentries, seaf_dirent_dup(dent));
|
|
}
|
|
}
|
|
|
|
newentries = g_list_reverse (newentries);
|
|
newdir = seaf_dir_new (NULL, newentries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strdup (newdir->dir_id);
|
|
seaf_dir_free (newdir);
|
|
|
|
goto out;
|
|
}
|
|
|
|
to_path_dup = g_strdup (to_path);
|
|
slash = strchr (to_path_dup, '/');
|
|
|
|
if (!slash) {
|
|
remain = to_path_dup + strlen(to_path_dup);
|
|
} else {
|
|
*slash = '\0';
|
|
remain = slash + 1;
|
|
}
|
|
|
|
for (ptr = olddir->entries; ptr; ptr = ptr->next) {
|
|
dent = (SeafDirent *)ptr->data;
|
|
|
|
if (strcmp(dent->name, to_path_dup) != 0)
|
|
continue;
|
|
|
|
id = put_file_recursive (repo, dent->id, remain, newdent);
|
|
if (id != NULL) {
|
|
memcpy(dent->id, id, 40);
|
|
dent->id[40] = '\0';
|
|
if (repo->version > 0)
|
|
dent->mtime = (guint64)time(NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (id != NULL) {
|
|
/* Create a new SeafDir. */
|
|
GList *new_entries;
|
|
|
|
new_entries = dup_seafdir_entries (olddir->entries);
|
|
newdir = seaf_dir_new (NULL, new_entries,
|
|
dir_version_from_repo_version(repo->version));
|
|
if (seaf_dir_save (seaf->fs_mgr, repo->store_id, repo->version, newdir) == 0)
|
|
ret = g_strdup(newdir->dir_id);
|
|
seaf_dir_free (newdir);
|
|
}
|
|
|
|
out:
|
|
g_free (to_path_dup);
|
|
g_free (id);
|
|
seaf_dir_free(olddir);
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
do_put_file (SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *parent_dir,
|
|
SeafDirent *dent)
|
|
{
|
|
/* if parent_dir is a absolutely path, we will remove the first '/' */
|
|
if (*parent_dir == '/')
|
|
parent_dir = parent_dir + 1;
|
|
|
|
return put_file_recursive(repo, root_id, parent_dir, dent);
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_put_file (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *temp_file_path,
|
|
const char *parent_dir,
|
|
const char *file_name,
|
|
const char *user,
|
|
const char *head_id,
|
|
char **new_file_id,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
char *canon_path = NULL;
|
|
unsigned char sha1[20];
|
|
char buf[SEAF_PATH_MAX];
|
|
char *root_id = NULL;
|
|
SeafileCrypt *crypt = NULL;
|
|
SeafDirent *new_dent = NULL;
|
|
char hex[41];
|
|
char *old_file_id = NULL, *fullpath = NULL;
|
|
int ret = 0;
|
|
|
|
if (g_access (temp_file_path, R_OK) != 0) {
|
|
seaf_warning ("[put file] File %s doesn't exist or not readable.\n",
|
|
temp_file_path);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid input file");
|
|
return -1;
|
|
}
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
const char *base = head_id ? head_id : repo->head->commit_id;
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, base);
|
|
|
|
if (!canon_path)
|
|
canon_path = get_canonical_path (parent_dir);
|
|
|
|
if (should_ignore_file (file_name, NULL)) {
|
|
seaf_warning ("[put file] Invalid filename %s.\n", file_name);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid filename");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (strstr (parent_dir, "//") != NULL) {
|
|
seaf_warning ("[put file] parent_dir cantains // sequence.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
|
|
"Invalid parent dir");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
FAIL_IF_FILE_NOT_EXISTS(repo->store_id, repo->version,
|
|
head_commit->root_id, canon_path, file_name, NULL);
|
|
|
|
/* Write blocks. */
|
|
if (repo->encrypted) {
|
|
unsigned char key[32], iv[16];
|
|
if (seaf_passwd_manager_get_decrypt_key_raw (seaf->passwd_mgr,
|
|
repo_id, user,
|
|
key, iv) < 0) {
|
|
seaf_warning ("Passwd for repo %s is not set.\n", repo_id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Passwd is not set");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
crypt = seafile_crypt_new (repo->enc_version, key, iv);
|
|
}
|
|
|
|
gint64 size;
|
|
if (seaf_fs_manager_index_blocks (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
temp_file_path,
|
|
sha1, &size, crypt, TRUE, FALSE, NULL) < 0) {
|
|
seaf_warning ("failed to index blocks");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to index blocks");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
rawdata_to_hex(sha1, hex, 20);
|
|
new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
|
|
hex, STD_FILE_MODE, file_name,
|
|
(gint64)time(NULL), user, size);
|
|
|
|
if (!fullpath)
|
|
fullpath = g_build_filename(parent_dir, file_name, NULL);
|
|
|
|
old_file_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
head_commit->root_id,
|
|
fullpath, NULL, NULL);
|
|
|
|
if (g_strcmp0(old_file_id, new_dent->id) == 0) {
|
|
if (new_file_id)
|
|
*new_file_id = g_strdup(new_dent->id);
|
|
goto out;
|
|
}
|
|
|
|
root_id = do_put_file (repo, head_commit->root_id, canon_path, new_dent);
|
|
if (!root_id) {
|
|
seaf_warning ("[put file] Failed to put file %s to %s in repo %s.\n",
|
|
file_name, canon_path, repo->id);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to put file");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Commit. */
|
|
snprintf(buf, SEAF_PATH_MAX, "Modified \"%s\"", file_name);
|
|
if (gen_new_commit (repo_id, head_commit, root_id, user, buf, NULL, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (new_file_id)
|
|
*new_file_id = g_strdup(new_dent->id);
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref(head_commit);
|
|
seaf_dirent_free (new_dent);
|
|
g_free (root_id);
|
|
g_free (canon_path);
|
|
g_free (crypt);
|
|
g_free (old_file_id);
|
|
g_free (fullpath);
|
|
|
|
if (ret == 0) {
|
|
update_repo_size (repo_id);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
gen_commit_description (SeafRepo *repo,
|
|
const char *root,
|
|
const char *parent_root)
|
|
{
|
|
GList *p;
|
|
GList *results = NULL;
|
|
char *desc;
|
|
|
|
diff_commit_roots (repo->store_id, repo->version,
|
|
parent_root, root, &results, TRUE);
|
|
|
|
desc = diff_results_to_description (results);
|
|
|
|
for (p = results; p; p = p->next) {
|
|
DiffEntry *de = p->data;
|
|
diff_entry_free (de);
|
|
}
|
|
g_list_free (results);
|
|
|
|
return desc;
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_update_dir (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *dir_path,
|
|
const char *new_dir_id,
|
|
const char *user,
|
|
const char *head_id,
|
|
char *new_commit_id,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
char *canon_path = NULL;
|
|
char *parent = NULL, *dirname = NULL;
|
|
SeafDirent *new_dent = NULL;
|
|
char *root_id = NULL;
|
|
char *commit_desc = NULL;
|
|
int ret = 0;
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
const char *base = head_id ? head_id : repo->head->commit_id;
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, base);
|
|
|
|
/* Are we updating the root? */
|
|
if (strcmp (dir_path, "/") == 0) {
|
|
commit_desc = gen_commit_description (repo, new_dir_id, head_commit->root_id);
|
|
if (!commit_desc)
|
|
commit_desc = g_strdup("Auto merge by system");
|
|
|
|
if (gen_new_commit (repo_id, head_commit, new_dir_id,
|
|
user, commit_desc, new_commit_id, error) < 0)
|
|
ret = -1;
|
|
g_free (commit_desc);
|
|
goto out;
|
|
}
|
|
|
|
parent = g_path_get_dirname (dir_path);
|
|
canon_path = get_canonical_path (parent);
|
|
g_free (parent);
|
|
|
|
dirname = g_path_get_basename (dir_path);
|
|
|
|
FAIL_IF_FILE_NOT_EXISTS(repo->store_id, repo->version,
|
|
head_commit->root_id, canon_path, dirname, NULL);
|
|
|
|
new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version),
|
|
new_dir_id, S_IFDIR, dirname,
|
|
(gint64)time(NULL), NULL, -1);
|
|
|
|
root_id = do_put_file (repo, head_commit->root_id, canon_path, new_dent);
|
|
if (!root_id) {
|
|
seaf_warning ("[update dir] Failed to put file.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to update dir");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
commit_desc = gen_commit_description (repo, root_id, head_commit->root_id);
|
|
if (!commit_desc)
|
|
commit_desc = g_strdup("Auto merge by system");
|
|
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, commit_desc, new_commit_id, error) < 0) {
|
|
ret = -1;
|
|
g_free (commit_desc);
|
|
goto out;
|
|
}
|
|
g_free (commit_desc);
|
|
|
|
out:
|
|
seaf_repo_unref (repo);
|
|
seaf_commit_unref (head_commit);
|
|
seaf_dirent_free (new_dent);
|
|
g_free (canon_path);
|
|
g_free (dirname);
|
|
g_free (root_id);
|
|
|
|
if (ret == 0)
|
|
update_repo_size (repo_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* int */
|
|
/* seaf_repo_manager_put_file_blocks (SeafRepoManager *mgr, */
|
|
/* const char *repo_id, */
|
|
/* const char *parent_dir, */
|
|
/* const char *file_name, */
|
|
/* const char *blockids_json, */
|
|
/* const char *paths_json, */
|
|
/* const char *user, */
|
|
/* const char *head_id, */
|
|
/* gint64 file_size, */
|
|
/* char **new_file_id, */
|
|
/* GError **error) */
|
|
/* { */
|
|
/* SeafRepo *repo = NULL; */
|
|
/* SeafCommit *head_commit = NULL; */
|
|
/* char *canon_path = NULL; */
|
|
/* unsigned char sha1[20]; */
|
|
/* char buf[SEAF_PATH_MAX]; */
|
|
/* char *root_id = NULL; */
|
|
/* SeafDirent *new_dent = NULL; */
|
|
/* char hex[41]; */
|
|
/* GList *blockids = NULL, *paths = NULL, *ptr; */
|
|
/* char *old_file_id = NULL, *fullpath = NULL; */
|
|
/* int ret = 0; */
|
|
|
|
/* blockids = json_to_file_list (blockids_json); */
|
|
/* paths = json_to_file_list (paths_json); */
|
|
/* if (g_list_length(blockids) != g_list_length(paths)) { */
|
|
/* seaf_warning ("[put-blks] Invalid blockids or paths.\n"); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid files"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
|
|
/* for (ptr = paths; ptr; ptr = ptr->next) { */
|
|
/* char *temp_file_path = ptr->data; */
|
|
/* if (g_access (temp_file_path, R_OK) != 0) { */
|
|
/* seaf_warning ("[put-blks] File block %s doesn't exist or not readable.\n", */
|
|
/* temp_file_path); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
|
|
/* "Invalid input file"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
/* } */
|
|
|
|
/* GET_REPO_OR_FAIL(repo, repo_id); */
|
|
/* const char *base = head_id ? head_id : repo->head->commit_id; */
|
|
/* GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, base); */
|
|
|
|
/* if (!canon_path) */
|
|
/* canon_path = get_canonical_path (parent_dir); */
|
|
|
|
/* if (should_ignore_file (file_name, NULL)) { */
|
|
/* seaf_warning ("[put-blks] Invalid filename %s.\n", file_name); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
|
|
/* "Invalid filename"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* if (strstr (parent_dir, "//") != NULL) { */
|
|
/* seaf_warning ("[put-blks] parent_dir cantains // sequence.\n"); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, */
|
|
/* "Invalid parent dir"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* FAIL_IF_FILE_NOT_EXISTS(repo->store_id, repo->version, */
|
|
/* head_commit->root_id, canon_path, file_name, NULL); */
|
|
|
|
/* /\* Write blocks. *\/ */
|
|
/* if (seaf_fs_manager_index_file_blocks (seaf->fs_mgr, */
|
|
/* repo->store_id, repo->version, */
|
|
/* paths, */
|
|
/* blockids, sha1, file_size) < 0) { */
|
|
/* seaf_warning ("failed to index blocks"); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, */
|
|
/* "Failed to index blocks"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* rawdata_to_hex(sha1, hex, 20); */
|
|
/* new_dent = seaf_dirent_new (dir_version_from_repo_version(repo->version), */
|
|
/* hex, STD_FILE_MODE, file_name, */
|
|
/* (gint64)time(NULL), user, file_size); */
|
|
|
|
/* if (!fullpath) */
|
|
/* fullpath = g_build_filename(parent_dir, file_name, NULL); */
|
|
|
|
/* old_file_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr, */
|
|
/* repo->store_id, repo->version, */
|
|
/* head_commit->root_id, */
|
|
/* fullpath, NULL, NULL); */
|
|
|
|
/* if (g_strcmp0(old_file_id, new_dent->id) == 0) { */
|
|
/* if (new_file_id) */
|
|
/* *new_file_id = g_strdup(new_dent->id); */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* root_id = do_put_file (repo, head_commit->root_id, canon_path, new_dent); */
|
|
/* if (!root_id) { */
|
|
/* seaf_warning ("[put-blks] Failed to put file.\n"); */
|
|
/* g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, */
|
|
/* "Failed to put file"); */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* /\* Commit. *\/ */
|
|
/* snprintf(buf, SEAF_PATH_MAX, "Modified \"%s\"", file_name); */
|
|
/* if (gen_new_commit (repo_id, head_commit, root_id, user, buf, NULL, error) < 0) { */
|
|
/* ret = -1; */
|
|
/* goto out; */
|
|
/* } */
|
|
|
|
/* if (new_file_id) */
|
|
/* *new_file_id = g_strdup(new_dent->id); */
|
|
|
|
/* out: */
|
|
/* if (repo) */
|
|
/* seaf_repo_unref (repo); */
|
|
/* if (head_commit) */
|
|
/* seaf_commit_unref(head_commit); */
|
|
/* string_list_free (blockids); */
|
|
/* string_list_free (paths); */
|
|
/* seaf_dirent_free (new_dent); */
|
|
/* g_free (root_id); */
|
|
/* g_free (canon_path); */
|
|
/* g_free (old_file_id); */
|
|
/* g_free (fullpath); */
|
|
|
|
/* if (ret == 0) { */
|
|
/* update_repo_size (repo_id); */
|
|
/* } */
|
|
|
|
/* return ret; */
|
|
/* } */
|
|
|
|
/* split filename into base and extension */
|
|
static void
|
|
filename_splitext (const char *filename,
|
|
char **base,
|
|
char **ext)
|
|
{
|
|
char *dot = strrchr(filename, '.');
|
|
if (!dot) {
|
|
*base = g_strdup(filename);
|
|
*ext = NULL;
|
|
} else {
|
|
*dot = '\0';
|
|
*base = g_strdup(filename);
|
|
*dot = '.';
|
|
|
|
*ext = g_strdup(dot);
|
|
}
|
|
}
|
|
|
|
static char *
|
|
revert_file_to_root (SeafRepo *repo,
|
|
const char *root_id,
|
|
SeafDirent *old_dent,
|
|
gboolean *skipped,
|
|
GError **error)
|
|
{
|
|
SeafDir *dir = NULL;
|
|
SeafDirent *dent = NULL, *newdent = NULL;
|
|
char *basename = NULL, *ext = NULL;
|
|
char new_file_name[SEAF_PATH_MAX];
|
|
char *new_root_id = NULL;
|
|
int i = 1;
|
|
GList *p;
|
|
|
|
*skipped = FALSE;
|
|
|
|
dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
root_id,
|
|
"/", error);
|
|
if (*error) {
|
|
return NULL;
|
|
}
|
|
|
|
snprintf (new_file_name, sizeof(new_file_name), "%s", old_dent->name);
|
|
|
|
filename_splitext(old_dent->name, &basename, &ext);
|
|
for (;;) {
|
|
for (p = dir->entries; p; p = p->next) {
|
|
dent = p->data;
|
|
if (strcmp(dent->name, new_file_name) != 0)
|
|
continue;
|
|
|
|
if (S_ISREG(dent->mode)) {
|
|
/* same named file */
|
|
if (strcmp(dent->id, old_dent->id) == 0) {
|
|
*skipped = TRUE;
|
|
goto out;
|
|
} else {
|
|
/* rename and retry */
|
|
snprintf (new_file_name, sizeof(new_file_name), "%s (%d)%s",
|
|
basename, i++, ext);
|
|
break;
|
|
}
|
|
|
|
} else if (S_ISDIR(dent->mode)) {
|
|
/* rename and retry */
|
|
snprintf (new_file_name, sizeof(new_file_name), "%s (%d)%s",
|
|
basename, i++, ext);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p == NULL)
|
|
break;
|
|
}
|
|
|
|
newdent = seaf_dirent_new (old_dent->version,
|
|
old_dent->id, STD_FILE_MODE, new_file_name,
|
|
old_dent->mtime, old_dent->modifier, old_dent->size);
|
|
new_root_id = do_post_file (repo, root_id, "/", newdent);
|
|
|
|
out:
|
|
if (dir)
|
|
seaf_dir_free (dir);
|
|
|
|
g_free (basename);
|
|
g_free (ext);
|
|
seaf_dirent_free (newdent);
|
|
|
|
return new_root_id;
|
|
}
|
|
|
|
static char *
|
|
revert_file_to_parent_dir (SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *parent_dir,
|
|
SeafDirent *old_dent,
|
|
gboolean *skipped,
|
|
GError **error)
|
|
{
|
|
SeafDir *dir = NULL;
|
|
SeafDirent *dent = NULL, *newdent = NULL;
|
|
char *basename = NULL, *ext = NULL;
|
|
char new_file_name[SEAF_PATH_MAX];
|
|
char *new_root_id = NULL;
|
|
gboolean is_overwrite = FALSE;
|
|
int i = 1;
|
|
GList *p;
|
|
|
|
*skipped = FALSE;
|
|
|
|
dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
root_id,
|
|
parent_dir, error);
|
|
if (*error) {
|
|
return NULL;
|
|
}
|
|
|
|
snprintf (new_file_name, sizeof(new_file_name), "%s", old_dent->name);
|
|
filename_splitext(old_dent->name, &basename, &ext);
|
|
while(TRUE) {
|
|
for (p = dir->entries; p; p = p->next) {
|
|
dent = p->data;
|
|
if (strcmp(dent->name, new_file_name) != 0)
|
|
continue;
|
|
|
|
if (S_ISREG(dent->mode)) {
|
|
/* same named file */
|
|
if (strcmp(dent->id, old_dent->id) == 0) {
|
|
*skipped = TRUE;
|
|
goto out;
|
|
} else {
|
|
/* same name, different id: just overwrite */
|
|
is_overwrite = TRUE;
|
|
goto do_revert;
|
|
}
|
|
|
|
} else if (S_ISDIR(dent->mode)) {
|
|
/* rename and retry */
|
|
snprintf (new_file_name, sizeof(new_file_name), "%s (%d)%s",
|
|
basename, i++, ext);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p == NULL)
|
|
break;
|
|
}
|
|
|
|
do_revert:
|
|
newdent = seaf_dirent_new (old_dent->version,
|
|
old_dent->id, STD_FILE_MODE, new_file_name,
|
|
old_dent->mtime, old_dent->modifier, old_dent->size);
|
|
if (is_overwrite) {
|
|
new_root_id = do_put_file (repo,
|
|
root_id, parent_dir, newdent);
|
|
} else {
|
|
new_root_id = do_post_file (repo,
|
|
root_id, parent_dir, newdent);
|
|
}
|
|
|
|
out:
|
|
if (dir)
|
|
seaf_dir_free (dir);
|
|
|
|
g_free (basename);
|
|
g_free (ext);
|
|
seaf_dirent_free (newdent);
|
|
|
|
return new_root_id;
|
|
}
|
|
|
|
static gboolean
|
|
detect_path_exist (SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *path,
|
|
GError **error)
|
|
{
|
|
SeafDir *dir;
|
|
|
|
dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
root_id, path, error);
|
|
if (*error) {
|
|
if (g_error_matches(*error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST)) {
|
|
/* path does not exist */
|
|
g_clear_error(error);
|
|
return FALSE;
|
|
} else {
|
|
/* Other error */
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
seaf_dir_free(dir);
|
|
return TRUE;
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_revert_file (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *old_commit_id,
|
|
const char *file_path,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL, *old_commit = NULL;
|
|
char *parent_dir = NULL, *filename = NULL;
|
|
SeafDirent *old_dent = NULL;
|
|
char *canon_path = NULL, *root_id = NULL;
|
|
char buf[SEAF_PATH_MAX];
|
|
char time_str[512];
|
|
gboolean parent_dir_exist = FALSE;
|
|
gboolean revert_to_root = FALSE;
|
|
gboolean skipped = FALSE;
|
|
int ret = 0;
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
/* If old_commit_id is head commit, do nothing. */
|
|
if (strcmp(repo->head->commit_id, old_commit_id) == 0) {
|
|
g_debug ("[revert file] commit is head, do nothing\n");
|
|
goto out;
|
|
}
|
|
|
|
if (!old_commit) {
|
|
GET_COMMIT_OR_FAIL(old_commit, repo->id, repo->version, old_commit_id);
|
|
if (strcmp(old_commit->repo_id, repo_id) != 0) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_COMMIT,
|
|
"bad commit id");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!canon_path) {
|
|
canon_path = get_canonical_path (file_path);
|
|
if (canon_path[strlen(canon_path) -1 ] == '/') {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_COMMIT,
|
|
"bad target file path");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
parent_dir = g_path_get_dirname(canon_path);
|
|
filename = g_path_get_basename(canon_path);
|
|
|
|
old_dent = get_dirent_by_path (repo, old_commit->root_id,
|
|
parent_dir, filename, error);
|
|
if (!old_dent || S_ISDIR(old_dent->mode)) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
if (*error) {
|
|
seaf_warning ("[revert file] error: %s\n", (*error)->message);
|
|
g_clear_error (error);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"internal error");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
parent_dir_exist = detect_path_exist (repo,
|
|
head_commit->root_id,
|
|
parent_dir, error);
|
|
if (*error) {
|
|
seaf_warning ("[revert file] error: %s\n", (*error)->message);
|
|
g_clear_error (error);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"internal error");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!parent_dir_exist) {
|
|
/* When parent dir does not exist, revert this file to root dir. */
|
|
revert_to_root = TRUE;
|
|
root_id = revert_file_to_root (repo,
|
|
head_commit->root_id,
|
|
old_dent,
|
|
&skipped, error);
|
|
} else {
|
|
revert_to_root = FALSE;
|
|
root_id = revert_file_to_parent_dir (repo,
|
|
head_commit->root_id, parent_dir,
|
|
old_dent,
|
|
&skipped, error);
|
|
}
|
|
|
|
if (*error) {
|
|
seaf_warning ("[revert file] error: %s\n", (*error)->message);
|
|
g_clear_error (error);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"internal error");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (skipped) {
|
|
goto out;
|
|
}
|
|
|
|
if (!root_id) {
|
|
seaf_warning ("[revert file] Failed to revert file.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to revert file");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Commit. */
|
|
#ifndef WIN32
|
|
strftime (time_str, sizeof(time_str), "%F %T",
|
|
localtime((time_t *)(&old_commit->ctime)));
|
|
#else
|
|
strftime (time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S",
|
|
localtime((time_t *)(&old_commit->ctime)));
|
|
#endif
|
|
snprintf(buf, SEAF_PATH_MAX, "Reverted file \"%s\" to status at %s", filename, time_str);
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref (head_commit);
|
|
if (old_commit)
|
|
seaf_commit_unref (old_commit);
|
|
|
|
g_free (root_id);
|
|
g_free (parent_dir);
|
|
g_free (filename);
|
|
|
|
g_free (canon_path);
|
|
seaf_dirent_free (old_dent);
|
|
|
|
#define REVERT_TO_ROOT 0x1
|
|
if (ret == 0) {
|
|
if (revert_to_root)
|
|
ret |= REVERT_TO_ROOT;
|
|
|
|
update_repo_size (repo_id);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *
|
|
revert_dir (SeafRepo *repo,
|
|
const char *root_id,
|
|
const char *parent_dir,
|
|
SeafDirent *old_dent,
|
|
gboolean *skipped,
|
|
GError **error)
|
|
{
|
|
SeafDir *dir = NULL;
|
|
SeafDirent *dent = NULL, *newdent = NULL;
|
|
char new_dir_name[SEAF_PATH_MAX];
|
|
char *new_root_id = NULL;
|
|
int i = 1;
|
|
GList *p;
|
|
|
|
*skipped = FALSE;
|
|
|
|
dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
root_id,
|
|
parent_dir, error);
|
|
if (*error) {
|
|
return NULL;
|
|
}
|
|
|
|
snprintf (new_dir_name, sizeof(new_dir_name), "%s", old_dent->name);
|
|
|
|
for (;;) {
|
|
for (p = dir->entries; p; p = p->next) {
|
|
dent = p->data;
|
|
if (strcmp(dent->name, new_dir_name) != 0)
|
|
continue;
|
|
|
|
/* the same dir */
|
|
if (S_ISDIR(dent->mode) && strcmp(dent->id, old_dent->id) == 0) {
|
|
*skipped = TRUE;
|
|
goto out;
|
|
} else {
|
|
/* rename and retry */
|
|
snprintf (new_dir_name, sizeof(new_dir_name), "%s (%d)",
|
|
old_dent->name, i++);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (p == NULL)
|
|
break;
|
|
}
|
|
|
|
newdent = seaf_dirent_new (old_dent->version,
|
|
old_dent->id, S_IFDIR, new_dir_name,
|
|
old_dent->mtime, NULL, -1);
|
|
new_root_id = do_post_file (repo, root_id, parent_dir, newdent);
|
|
|
|
out:
|
|
if (dir)
|
|
seaf_dir_free (dir);
|
|
|
|
seaf_dirent_free (newdent);
|
|
|
|
return new_root_id;
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_revert_dir (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *old_commit_id,
|
|
const char *dir_path,
|
|
const char *user,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL, *old_commit = NULL;
|
|
char *parent_dir = NULL, *dirname = NULL;
|
|
SeafDirent *old_dent = NULL;
|
|
char *canon_path = NULL, *root_id = NULL;
|
|
char buf[SEAF_PATH_MAX];
|
|
gboolean parent_dir_exist = FALSE;
|
|
gboolean revert_to_root = FALSE;
|
|
gboolean skipped = FALSE;
|
|
int ret = 0;
|
|
|
|
GET_REPO_OR_FAIL(repo, repo_id);
|
|
GET_COMMIT_OR_FAIL(head_commit, repo->id, repo->version, repo->head->commit_id);
|
|
|
|
/* If old_commit_id is head commit, do nothing. */
|
|
if (strcmp(repo->head->commit_id, old_commit_id) == 0) {
|
|
g_debug ("[revert dir] commit is head, do nothing\n");
|
|
goto out;
|
|
}
|
|
|
|
if (!old_commit) {
|
|
GET_COMMIT_OR_FAIL(old_commit, repo->id, repo->version, old_commit_id);
|
|
if (strcmp(old_commit->repo_id, repo_id) != 0) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_COMMIT,
|
|
"bad commit id");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!canon_path) {
|
|
canon_path = get_canonical_path (dir_path);
|
|
|
|
parent_dir = g_path_get_dirname(canon_path);
|
|
dirname = g_path_get_basename(canon_path);
|
|
|
|
old_dent = get_dirent_by_path (repo, old_commit->root_id,
|
|
parent_dir, dirname, error);
|
|
if (!old_dent || S_ISREG(old_dent->mode)) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
if (*error) {
|
|
seaf_warning ("[revert dir] error: %s\n", (*error)->message);
|
|
g_clear_error (error);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"internal error");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
parent_dir_exist = detect_path_exist (repo,
|
|
head_commit->root_id,
|
|
parent_dir, error);
|
|
if (*error) {
|
|
seaf_warning ("[revert dir] error: %s\n", (*error)->message);
|
|
g_clear_error (error);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"internal error");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!parent_dir_exist) {
|
|
/* When parent dir does not exist, revert this file to root dir. */
|
|
revert_to_root = TRUE;
|
|
root_id = revert_dir (repo,
|
|
head_commit->root_id,
|
|
"/",
|
|
old_dent,
|
|
&skipped, error);
|
|
} else {
|
|
revert_to_root = FALSE;
|
|
root_id = revert_dir (repo,
|
|
head_commit->root_id,
|
|
parent_dir,
|
|
old_dent,
|
|
&skipped, error);
|
|
}
|
|
|
|
if (*error) {
|
|
seaf_warning ("[revert dir] error: %s\n", (*error)->message);
|
|
g_clear_error (error);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"internal error");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (skipped) {
|
|
goto out;
|
|
}
|
|
|
|
if (!root_id) {
|
|
seaf_warning ("[revert dir] Failed to revert dir.\n");
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to revert dir");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Commit. */
|
|
snprintf(buf, SEAF_PATH_MAX, "Recovered deleted directory \"%s\"", dirname);
|
|
if (gen_new_commit (repo_id, head_commit, root_id,
|
|
user, buf, NULL, error) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref (head_commit);
|
|
if (old_commit)
|
|
seaf_commit_unref (old_commit);
|
|
|
|
g_free (root_id);
|
|
g_free (parent_dir);
|
|
g_free (dirname);
|
|
|
|
g_free (canon_path);
|
|
seaf_dirent_free (old_dent);
|
|
|
|
#define REVERT_TO_ROOT 0x1
|
|
if (ret == 0) {
|
|
if (revert_to_root)
|
|
ret |= REVERT_TO_ROOT;
|
|
|
|
update_repo_size (repo_id);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
typedef struct CollectRevisionParam CollectRevisionParam;
|
|
|
|
struct CollectRevisionParam {
|
|
SeafRepo *repo;
|
|
const char *path;
|
|
GList *wanted_commits;
|
|
GList *file_id_list;
|
|
GList *file_size_list;
|
|
int n_commits;
|
|
GHashTable *file_info_cache;
|
|
|
|
/* > 0: keep a period of history;
|
|
* == 0: N/A
|
|
* < 0: keep all history data.
|
|
*/
|
|
gint64 truncate_time;
|
|
gboolean got_latest;
|
|
gboolean got_second;
|
|
gboolean not_found_file;
|
|
|
|
GError **error;
|
|
};
|
|
|
|
typedef struct FileInfo {
|
|
gint64 file_size;
|
|
char *file_id;
|
|
GList *dir_ids;
|
|
} FileInfo;
|
|
|
|
static void
|
|
free_file_info (gpointer info)
|
|
{
|
|
if (!info)
|
|
return;
|
|
|
|
FileInfo *file_info = info;
|
|
g_free (file_info->file_id);
|
|
g_list_free_full (file_info->dir_ids, g_free);
|
|
g_free (file_info);
|
|
}
|
|
|
|
// compare current commit dir_id with pre commit
|
|
// if dir_id doesn't change, it means subdir doesn't change, append all sub_dir ids of prev to current
|
|
// that is it is no need to traverse all sub dir, if root doesn't change
|
|
static gboolean
|
|
compare_or_add_id (GList *dir_ids,
|
|
GList **cur_dir_ids,
|
|
const char *dir_id)
|
|
{
|
|
gboolean ret = FALSE;
|
|
GList *tmp = dir_ids;
|
|
|
|
if (tmp == NULL ||
|
|
strcmp ((char *)tmp->data, dir_id) != 0) {
|
|
*cur_dir_ids = g_list_append (*cur_dir_ids, g_strdup (dir_id));
|
|
} else {
|
|
// file doesn't changed, append all dir ids to this commit cache
|
|
while (tmp) {
|
|
*cur_dir_ids = g_list_append (*cur_dir_ids,
|
|
g_strdup ((char *)tmp->data));
|
|
tmp = tmp->next;
|
|
}
|
|
ret = TRUE;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// dir_ids: all dir_ids in prev commit, in the order of fs tree
|
|
// cur_dir_ids: all dir_ids in current commit
|
|
// if no error and returned seafdir is NULL, then it means
|
|
// searched dir doesn't change in pre and current commit
|
|
static SeafDir*
|
|
get_seafdir_by_path (const char *repo_id,
|
|
int version,
|
|
const char *root_id,
|
|
const char *path,
|
|
GList *dir_ids,
|
|
GList **cur_dir_ids,
|
|
GError **error)
|
|
{
|
|
SeafDir *dir = NULL;
|
|
SeafDirent *dent;
|
|
const char *dir_id = root_id;
|
|
char *name, *saveptr;
|
|
char *tmp_path = NULL;
|
|
GList *tmp = dir_ids;
|
|
|
|
dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr, repo_id, version, dir_id);
|
|
if (!dir) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING, "directory is missing");
|
|
goto out;
|
|
}
|
|
|
|
if (compare_or_add_id (tmp, cur_dir_ids, dir_id)) {
|
|
seaf_dir_free (dir);
|
|
dir = NULL;
|
|
goto out;
|
|
} else if (tmp) {
|
|
tmp = tmp->next;
|
|
}
|
|
|
|
if (strcmp (path, ".") == 0 ||
|
|
strcmp (path, "/") == 0) {
|
|
goto out;
|
|
} else {
|
|
tmp_path = g_strdup (path);
|
|
}
|
|
|
|
name = strtok_r (tmp_path, "/", &saveptr);
|
|
while (name != NULL) {
|
|
GList *l;
|
|
for (l = dir->entries; l != NULL; l = l->next) {
|
|
dent = l->data;
|
|
|
|
if (strcmp(dent->name, name) == 0 && S_ISDIR(dent->mode)) {
|
|
dir_id = dent->id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!l) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST,
|
|
"Path does not exists %s", path);
|
|
seaf_dir_free (dir);
|
|
dir = NULL;
|
|
break;
|
|
}
|
|
|
|
if (compare_or_add_id (tmp, cur_dir_ids, dir_id)) {
|
|
seaf_dir_free (dir);
|
|
dir = NULL;
|
|
goto out;
|
|
} else if (tmp) {
|
|
tmp = tmp->next;
|
|
}
|
|
|
|
SeafDir *prev = dir;
|
|
dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr, repo_id, version, dir_id);
|
|
seaf_dir_free (prev);
|
|
|
|
if (!dir) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_DIR_MISSING,
|
|
"directory is missing");
|
|
break;
|
|
}
|
|
|
|
name = strtok_r (NULL, "/", &saveptr);
|
|
}
|
|
|
|
out:
|
|
g_free (tmp_path);
|
|
return dir;
|
|
}
|
|
|
|
/*
|
|
* Return NULL if file is not found, error is still NULL;
|
|
* If we have IO errors, error is set.
|
|
*/
|
|
static FileInfo*
|
|
get_file_info (SeafRepo *repo,
|
|
SeafCommit *commit,
|
|
const char *path,
|
|
GHashTable *file_info_cache,
|
|
FileInfo *last_info,
|
|
GError **error)
|
|
{
|
|
SeafDir *dir = NULL;
|
|
SeafDirent *dirent = NULL;
|
|
FileInfo *file_info = NULL;
|
|
GList *tmp;
|
|
|
|
file_info = g_hash_table_lookup (file_info_cache, commit->commit_id);
|
|
if (file_info)
|
|
return file_info;
|
|
|
|
char *dir_name = g_path_get_dirname (path);
|
|
char *file_name = g_path_get_basename (path);
|
|
GList *cur_dir_ids = NULL;
|
|
GList *dir_ids = last_info ? last_info->dir_ids : NULL;
|
|
|
|
dir = get_seafdir_by_path (repo->store_id, repo->version,
|
|
commit->root_id, dir_name, dir_ids,
|
|
&cur_dir_ids, error);
|
|
if (*error) {
|
|
if ((*error)->code == SEAF_ERR_PATH_NO_EXIST)
|
|
g_clear_error (error);
|
|
goto out;
|
|
}
|
|
|
|
if (!dir) {
|
|
// if no error and return is null from get_seafdir_by_path, it means dir doesn't
|
|
// change in pre and current commit, so the last_info (file info of pre commit)
|
|
// is also the current file info
|
|
file_info = g_new0 (FileInfo, 1);
|
|
file_info->file_id = g_strdup (last_info->file_id);
|
|
file_info->dir_ids = cur_dir_ids;
|
|
file_info->file_size = last_info->file_size;
|
|
g_hash_table_insert (file_info_cache, g_strdup (commit->commit_id),
|
|
file_info);
|
|
} else {
|
|
for (tmp = dir->entries; tmp; tmp = tmp->next) {
|
|
dirent = tmp->data;
|
|
if (strcmp (file_name, dirent->name) == 0 &&
|
|
S_ISREG (dirent->mode)) {
|
|
break;
|
|
}
|
|
}
|
|
if (tmp) {
|
|
// from parent dir find the file, cache file info for the next compare
|
|
file_info = g_new0 (FileInfo, 1);
|
|
file_info->file_id = g_strdup (dirent->id);
|
|
file_info->dir_ids = cur_dir_ids;
|
|
if (repo->version > 0) {
|
|
file_info->file_size = dirent->size;
|
|
} else {
|
|
file_info->file_size = seaf_fs_manager_get_file_size (seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
dirent->id);
|
|
}
|
|
g_hash_table_insert (file_info_cache, g_strdup (commit->commit_id),
|
|
file_info);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (dir)
|
|
seaf_dir_free (dir);
|
|
if (!file_info) {
|
|
g_list_free_full (cur_dir_ids, g_free);
|
|
}
|
|
g_free (file_name);
|
|
g_free (dir_name);
|
|
|
|
return file_info;
|
|
}
|
|
|
|
static void
|
|
add_revision_info (CollectRevisionParam *data,
|
|
SeafCommit *commit, const char *file_id, gint64 file_size)
|
|
{
|
|
seaf_commit_ref (commit);
|
|
data->wanted_commits = g_list_prepend (data->wanted_commits, commit);
|
|
data->file_id_list = g_list_prepend (data->file_id_list, g_strdup(file_id));
|
|
gint64 *size = g_malloc(sizeof(gint64));
|
|
*size = file_size;
|
|
data->file_size_list = g_list_prepend (data->file_size_list, size);
|
|
++(data->n_commits);
|
|
}
|
|
|
|
static gboolean
|
|
collect_file_revisions (SeafCommit *commit, void *vdata, gboolean *stop)
|
|
{
|
|
CollectRevisionParam *data = vdata;
|
|
SeafRepo *repo = data->repo;
|
|
const char *path = data->path;
|
|
GError **error = data->error;
|
|
GHashTable *file_info_cache = data->file_info_cache;
|
|
FileInfo *file_info = NULL;
|
|
FileInfo *parent1_info = NULL;
|
|
FileInfo *parent2_info = NULL;
|
|
|
|
SeafCommit *parent_commit = NULL;
|
|
SeafCommit *parent_commit2 = NULL;
|
|
|
|
gboolean ret = TRUE;
|
|
|
|
/* At least find the latest revision. */
|
|
if (data->got_latest && data->truncate_time == 0) {
|
|
*stop = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
if (data->got_latest &&
|
|
data->truncate_time > 0 &&
|
|
(gint64)(commit->ctime) < data->truncate_time &&
|
|
data->got_second)
|
|
{
|
|
*stop = TRUE;
|
|
data->not_found_file = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
g_clear_error (error);
|
|
|
|
file_info = get_file_info (data->repo, commit, path,
|
|
file_info_cache, NULL, error);
|
|
if (*error) {
|
|
seaf_warning ("Error when finding %s under %s:%s\n",
|
|
path, data->repo->id, commit->commit_id);
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
if (!file_info) {
|
|
/* Target file is not present in this commit.
|
|
* Stop traversing after finding the initial version.
|
|
* Deleted files with the same path are not included in history.
|
|
*/
|
|
*stop = TRUE;
|
|
data->not_found_file = TRUE;
|
|
goto out;
|
|
}
|
|
|
|
if (!commit->parent_id) {
|
|
/* Initial commit */
|
|
add_revision_info (data, commit, file_info->file_id, file_info->file_size);
|
|
goto out;
|
|
}
|
|
|
|
parent_commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
commit->parent_id);
|
|
if (!parent_commit) {
|
|
seaf_warning ("Failed to get commit %s:%s\n", repo->id, commit->parent_id);
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
parent1_info = get_file_info (data->repo, parent_commit, path,
|
|
file_info_cache, file_info, error);
|
|
if (*error) {
|
|
seaf_warning ("Error when finding %s under %s:%s\n",
|
|
path, data->repo->id, parent_commit->commit_id);
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
if (parent1_info &&
|
|
g_strcmp0 (parent1_info->file_id, file_info->file_id) == 0) {
|
|
/* This commit does not modify the target file */
|
|
goto out;
|
|
}
|
|
|
|
/* In case of a merge, the second parent also need compare */
|
|
if (commit->second_parent_id) {
|
|
parent_commit2 = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
commit->second_parent_id);
|
|
if (!parent_commit2) {
|
|
seaf_warning ("Failed to get commit %s:%s\n",
|
|
repo->id, commit->second_parent_id);
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
parent2_info = get_file_info (data->repo, parent_commit2, path,
|
|
file_info_cache, file_info, error);
|
|
if (*error) {
|
|
seaf_warning ("Error when finding %s under %s:%s\n",
|
|
path, data->repo->id, parent_commit2->commit_id);
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
if (parent2_info &&
|
|
g_strcmp0 (parent2_info->file_id, file_info->file_id) == 0) {
|
|
/* This commit does not modify the target file */
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!data->got_latest) {
|
|
data->got_latest = TRUE;
|
|
} else {
|
|
if (!data->got_second)
|
|
data->got_second = TRUE;
|
|
}
|
|
add_revision_info (data, commit, file_info->file_id, file_info->file_size);
|
|
|
|
out:
|
|
if (parent_commit) seaf_commit_unref (parent_commit);
|
|
if (parent_commit2) seaf_commit_unref (parent_commit2);
|
|
|
|
g_hash_table_remove (file_info_cache, commit->commit_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
path_exists_in_commit (SeafRepo *repo, const char *commit_id, const char *path)
|
|
{
|
|
SeafCommit *c = NULL;
|
|
char *obj_id;
|
|
guint32 mode;
|
|
|
|
c = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
commit_id);
|
|
if (!c) {
|
|
seaf_warning ("Failed to get commit %s:%.8s.\n", repo->id, commit_id);
|
|
return FALSE;
|
|
}
|
|
obj_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
c->root_id,
|
|
path,
|
|
&mode,
|
|
NULL);
|
|
seaf_commit_unref (c);
|
|
if (!obj_id)
|
|
return FALSE;
|
|
g_free (obj_id);
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
detect_rename_revision (SeafRepo *repo,
|
|
SeafCommit *commit,
|
|
const char *path,
|
|
char **parent_id,
|
|
char **old_path)
|
|
{
|
|
GList *diff_res = NULL;
|
|
SeafCommit *p1 = NULL;
|
|
int rc;
|
|
gboolean is_renamed = FALSE;
|
|
|
|
while (*path == '/' && *path != 0)
|
|
++path;
|
|
|
|
if (!commit->second_parent_id) {
|
|
p1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
commit->parent_id);
|
|
if (!p1) {
|
|
seaf_warning ("Failed to get commit %s:%.8s.\n",
|
|
repo->id, commit->parent_id);
|
|
return FALSE;
|
|
}
|
|
/* Don't fold diff results for directories. We need to know a file was
|
|
* renamed when its parent folder was renamed.
|
|
*/
|
|
rc = diff_commits (p1, commit, &diff_res, FALSE);
|
|
seaf_commit_unref (p1);
|
|
if (rc < 0) {
|
|
seaf_warning ("Failed to diff.\n");
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
rc = diff_merge (commit, &diff_res, FALSE);
|
|
if (rc < 0) {
|
|
seaf_warning ("Failed to diff merge.\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
GList *ptr;
|
|
DiffEntry *de;
|
|
for (ptr = diff_res; ptr; ptr = ptr->next) {
|
|
de = ptr->data;
|
|
if (de->status == DIFF_STATUS_RENAMED && strcmp (de->new_name, path) == 0) {
|
|
*old_path = g_strdup(de->name);
|
|
is_renamed = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
for (ptr = diff_res; ptr; ptr = ptr->next)
|
|
diff_entry_free ((DiffEntry *)ptr->data);
|
|
g_list_free (diff_res);
|
|
|
|
if (!is_renamed)
|
|
return FALSE;
|
|
|
|
/* Determine parent commit containing the old path. */
|
|
if (!commit->second_parent_id)
|
|
*parent_id = g_strdup(commit->parent_id);
|
|
else {
|
|
if (path_exists_in_commit (repo, commit->parent_id, *old_path))
|
|
*parent_id = g_strdup(commit->parent_id);
|
|
else if (path_exists_in_commit (repo, commit->second_parent_id, *old_path))
|
|
*parent_id = g_strdup(commit->second_parent_id);
|
|
else {
|
|
g_free (*old_path);
|
|
*old_path = NULL;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static SeafileCommit *
|
|
convert_to_seafile_commit (SeafCommit *c)
|
|
{
|
|
SeafileCommit *commit = seafile_commit_new ();
|
|
g_object_set (commit,
|
|
"id", c->commit_id,
|
|
"creator_name", c->creator_name,
|
|
"creator", c->creator_id,
|
|
"desc", c->desc,
|
|
"ctime", c->ctime,
|
|
"repo_id", c->repo_id,
|
|
"root_id", c->root_id,
|
|
"parent_id", c->parent_id,
|
|
"second_parent_id", c->second_parent_id,
|
|
"version", c->version,
|
|
"new_merge", c->new_merge,
|
|
"conflict", c->conflict,
|
|
"device_name", c->device_name,
|
|
"client_version", c->client_version,
|
|
NULL);
|
|
return commit;
|
|
}
|
|
|
|
static GList *
|
|
convert_rpc_commit_list (GList *commit_list,
|
|
GList *file_id_list,
|
|
GList *file_size_list,
|
|
gboolean is_renamed,
|
|
const char *renamed_old_path)
|
|
{
|
|
GList *ret = NULL;
|
|
GList *ptr1, *ptr2, *ptr3;
|
|
SeafCommit *c;
|
|
char *file_id;
|
|
gint64 *file_size;
|
|
SeafileCommit *commit;
|
|
|
|
for (ptr1 = commit_list, ptr2 = file_id_list, ptr3 = file_size_list;
|
|
ptr1 && ptr2 && ptr3;
|
|
ptr1 = ptr1->next, ptr2 = ptr2->next, ptr3 = ptr3->next) {
|
|
c = ptr1->data;
|
|
file_id = ptr2->data;
|
|
file_size = ptr3->data;
|
|
commit = convert_to_seafile_commit (c);
|
|
g_object_set (commit, "rev_file_id", file_id, "rev_file_size", *file_size,
|
|
NULL);
|
|
if (ptr1->next == NULL && is_renamed)
|
|
g_object_set (commit, "rev_renamed_old_path", renamed_old_path, NULL);
|
|
ret = g_list_prepend (ret, commit);
|
|
}
|
|
|
|
ret = g_list_reverse (ret);
|
|
return ret;
|
|
}
|
|
|
|
GList *
|
|
seaf_repo_manager_list_file_revisions (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *start_commit_id,
|
|
const char *path,
|
|
int limit,
|
|
gboolean got_latest,
|
|
gboolean got_second,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
GList *commit_list = NULL, *file_id_list = NULL, *file_size_list = NULL;
|
|
GList *ret = NULL, *ptr;
|
|
CollectRevisionParam data = {0};
|
|
SeafCommit *last_commit = NULL;
|
|
const char *head_id;
|
|
gboolean is_renamed = FALSE;
|
|
char *parent_id = NULL, *old_path = NULL;
|
|
char *next_start_commit= NULL;
|
|
|
|
repo = seaf_repo_manager_get_repo (mgr, repo_id);
|
|
if (!repo) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"No such repo %s", repo_id);
|
|
goto out;
|
|
}
|
|
|
|
data.repo = repo;
|
|
|
|
if (!start_commit_id)
|
|
head_id = repo->head->commit_id;
|
|
else
|
|
head_id = start_commit_id;
|
|
|
|
data.path = path;
|
|
data.error = error;
|
|
|
|
data.truncate_time = seaf_repo_manager_get_repo_truncate_time (mgr, repo_id);
|
|
|
|
data.wanted_commits = NULL;
|
|
data.file_id_list = NULL;
|
|
data.file_size_list = NULL;
|
|
data.got_latest = got_latest;
|
|
data.got_second = got_second;
|
|
data.not_found_file = FALSE;
|
|
|
|
/* A hash table to cache caculated file info of <path> in <commit> */
|
|
data.file_info_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, free_file_info);
|
|
|
|
if (!seaf_commit_manager_traverse_commit_tree_with_limit (seaf->commit_mgr,
|
|
repo->id,
|
|
repo->version,
|
|
head_id,
|
|
(CommitTraverseFunc)collect_file_revisions,
|
|
limit, &data, &next_start_commit, TRUE)) {
|
|
g_clear_error (error);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"failed to traverse commit of repo %s", repo_id);
|
|
goto out;
|
|
}
|
|
|
|
if (data.wanted_commits) {
|
|
last_commit = data.wanted_commits->data;
|
|
is_renamed = detect_rename_revision (repo,
|
|
last_commit, path, &parent_id, &old_path);
|
|
if (data.not_found_file && !is_renamed) { // reached file initial commit.
|
|
g_free (next_start_commit);
|
|
next_start_commit = NULL;
|
|
} else if (is_renamed){ // file renamed.
|
|
g_free (next_start_commit);
|
|
next_start_commit = g_strdup (parent_id);
|
|
}
|
|
commit_list = g_list_reverse (data.wanted_commits);
|
|
file_id_list = g_list_reverse (data.file_id_list);
|
|
file_size_list = g_list_reverse (data.file_size_list);
|
|
|
|
char *rename_path = NULL;
|
|
if (old_path && *old_path != '/')
|
|
rename_path = g_strconcat ("/", old_path, NULL);
|
|
else
|
|
rename_path = g_strdup (old_path);
|
|
|
|
ret = convert_rpc_commit_list (commit_list, file_id_list, file_size_list,
|
|
is_renamed, rename_path);
|
|
g_free (rename_path);
|
|
} else {
|
|
if (data.not_found_file) {
|
|
g_free (next_start_commit);
|
|
next_start_commit = NULL;
|
|
}
|
|
}
|
|
|
|
/* Append one commit that only contains 'next_start_commit' */
|
|
SeafileCommit *commit = seafile_commit_new ();
|
|
g_object_set (commit, "next_start_commit", next_start_commit, NULL);
|
|
ret = g_list_append (ret, commit);
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
for (ptr = commit_list; ptr; ptr = ptr->next)
|
|
seaf_commit_unref ((SeafCommit *)ptr->data);
|
|
g_list_free (commit_list);
|
|
string_list_free (file_id_list);
|
|
for (ptr = file_size_list; ptr; ptr = ptr->next)
|
|
g_free (ptr->data);
|
|
g_list_free (file_size_list);
|
|
if (data.file_info_cache)
|
|
g_hash_table_destroy (data.file_info_cache);
|
|
g_free (old_path);
|
|
g_free (parent_id);
|
|
g_free (next_start_commit);
|
|
|
|
return ret;
|
|
}
|
|
|
|
typedef struct CalcFilesLastModifiedParam CalcFilesLastModifiedParam;
|
|
|
|
struct CalcFilesLastModifiedParam {
|
|
SeafRepo *repo;
|
|
GError **error;
|
|
const char *parent_dir;
|
|
GHashTable *last_modified_hash;
|
|
GHashTable *current_file_id_hash;
|
|
SeafCommit *current_commit;
|
|
};
|
|
|
|
static gboolean
|
|
check_non_existing_files (void *key, void *value, void *vdata)
|
|
{
|
|
CalcFilesLastModifiedParam *data = vdata;
|
|
gboolean remove = FALSE;
|
|
|
|
char *file_name = key;
|
|
gint64 *ctime = g_hash_table_lookup (data->last_modified_hash, file_name);
|
|
if (!ctime) {
|
|
/* Impossible */
|
|
remove = TRUE;
|
|
} else if (*ctime != data->current_commit->ctime) {
|
|
/* This file does not exist in this commit. So it's last modified in
|
|
* the previous commit.
|
|
*/
|
|
remove = TRUE;
|
|
}
|
|
|
|
return remove;
|
|
}
|
|
|
|
static gboolean
|
|
collect_files_last_modified (SeafCommit *commit, void *vdata, gboolean *stop)
|
|
{
|
|
CalcFilesLastModifiedParam *data = vdata;
|
|
GError **error = data->error;
|
|
SeafDirent *dent = NULL;
|
|
char *file_id = NULL;
|
|
SeafDir *dir = NULL;
|
|
GList *ptr;
|
|
gboolean ret = TRUE;
|
|
|
|
data->current_commit = commit;
|
|
dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
|
|
data->repo->store_id,
|
|
data->repo->version,
|
|
commit->root_id,
|
|
data->parent_dir,
|
|
error);
|
|
if (*error) {
|
|
if (!g_error_matches(*error, SEAFILE_DOMAIN, SEAF_ERR_PATH_NO_EXIST)) {
|
|
*stop = TRUE;
|
|
ret = FALSE;
|
|
goto out;
|
|
} else {
|
|
g_clear_error (error);
|
|
}
|
|
}
|
|
|
|
if (!dir) {
|
|
/* The directory does not exist in this commit. So all files are last
|
|
* modified in the previous commit;
|
|
*/
|
|
*stop = TRUE;
|
|
goto out;
|
|
}
|
|
|
|
for (ptr = dir->entries; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
file_id = g_hash_table_lookup (data->current_file_id_hash, dent->name);
|
|
if (file_id) {
|
|
if (strcmp(file_id, dent->id) != 0) {
|
|
g_hash_table_remove (data->current_file_id_hash, dent->name);
|
|
} else {
|
|
gint64 *ctime = g_new (gint64, 1);
|
|
*ctime = commit->ctime;
|
|
g_hash_table_replace (data->last_modified_hash, g_strdup(dent->name), ctime);
|
|
}
|
|
}
|
|
|
|
if (g_hash_table_size(data->current_file_id_hash) == 0) {
|
|
*stop = TRUE;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Files not found in the current commit are last modified in the previous
|
|
* commit */
|
|
g_hash_table_foreach_remove (data->current_file_id_hash,
|
|
check_non_existing_files, data);
|
|
|
|
if (g_hash_table_size(data->current_file_id_hash) == 0) {
|
|
/* All files under this diretory have been calculated */
|
|
*stop = TRUE;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
seaf_dir_free (dir);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Give a directory, return the last modification timestamps of all the files
|
|
* under this directory.
|
|
*
|
|
* First we record the current id of every file, then traverse the commit
|
|
* tree. Give a commit, for each file, if the file id in that commit is
|
|
* different than its current id, then this file is last modified in the
|
|
* commit previous to that commit.
|
|
*/
|
|
GList *
|
|
seaf_repo_manager_calc_files_last_modified (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *parent_dir,
|
|
int limit,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo = NULL;
|
|
SeafCommit *head_commit = NULL;
|
|
SeafDir *dir = NULL;
|
|
GList *ptr = NULL;
|
|
SeafDirent *dent = NULL;
|
|
CalcFilesLastModifiedParam data = {0};
|
|
GList *ret_list = NULL;
|
|
|
|
repo = seaf_repo_manager_get_repo (mgr, repo_id);
|
|
if (!repo) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"No such repo %s", repo_id);
|
|
goto out;
|
|
}
|
|
|
|
head_commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
repo->head->commit_id);
|
|
if (!head_commit) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Failed to get commit %s", repo->head->commit_id);
|
|
goto out;
|
|
}
|
|
|
|
dir = seaf_fs_manager_get_seafdir_by_path (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
head_commit->root_id,
|
|
parent_dir, error);
|
|
if (*error || !dir) {
|
|
goto out;
|
|
}
|
|
|
|
data.repo = repo;
|
|
|
|
/* A hash table of pattern (file_name, current_file_id) */
|
|
data.current_file_id_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, g_free);
|
|
/* A (file_name, last_modified) hashtable. <last_modified> is a heap
|
|
allocated gint64
|
|
*/
|
|
data.last_modified_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, g_free);
|
|
for (ptr = dir->entries; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
g_hash_table_insert (data.current_file_id_hash,
|
|
g_strdup(dent->name),
|
|
g_strdup(dent->id));
|
|
|
|
gint64 *ctime = g_new (gint64, 1);
|
|
*ctime = head_commit->ctime;
|
|
g_hash_table_insert (data.last_modified_hash,
|
|
g_strdup(dent->name),
|
|
ctime);
|
|
}
|
|
|
|
if (g_hash_table_size (data.current_file_id_hash) == 0) {
|
|
/* An empty directory, no need to traverse */
|
|
goto out;
|
|
}
|
|
|
|
data.parent_dir = parent_dir;
|
|
data.error = error;
|
|
|
|
if (!seaf_commit_manager_traverse_commit_tree_with_limit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
repo->head->commit_id,
|
|
(CommitTraverseFunc)collect_files_last_modified,
|
|
limit, &data, NULL, FALSE)) {
|
|
if (*error)
|
|
seaf_warning ("error when traversing commits: %s\n", (*error)->message);
|
|
else
|
|
seaf_warning ("error when traversing commits.\n");
|
|
g_clear_error (error);
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"failed to traverse commit of repo %s", repo_id);
|
|
goto out;
|
|
}
|
|
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
g_hash_table_iter_init (&iter, data.last_modified_hash);
|
|
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
|
SeafileFileLastModifiedInfo *info;
|
|
gint64 last_modified = *(gint64 *)value;
|
|
info = g_object_new (SEAFILE_TYPE_FILE_LAST_MODIFIED_INFO,
|
|
"file_name", key,
|
|
"last_modified", last_modified,
|
|
NULL);
|
|
ret_list = g_list_prepend (ret_list, info);
|
|
}
|
|
|
|
out:
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
if (head_commit)
|
|
seaf_commit_unref(head_commit);
|
|
if (data.last_modified_hash)
|
|
g_hash_table_destroy (data.last_modified_hash);
|
|
if (data.current_file_id_hash)
|
|
g_hash_table_destroy (data.current_file_id_hash);
|
|
if (dir)
|
|
seaf_dir_free (dir);
|
|
|
|
return g_list_reverse(ret_list);
|
|
}
|
|
|
|
int
|
|
seaf_repo_manager_revert_on_server (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
const char *commit_id,
|
|
const char *user_name,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo;
|
|
SeafCommit *commit = NULL, *new_commit = NULL;
|
|
char desc[512];
|
|
int ret = 0;
|
|
|
|
retry:
|
|
repo = seaf_repo_manager_get_repo (mgr, repo_id);
|
|
if (!repo) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"No such repo");
|
|
return -1;
|
|
}
|
|
|
|
commit = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
commit_id);
|
|
if (!commit) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Commit doesn't exist");
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
#ifndef WIN32
|
|
strftime (desc, sizeof(desc), "Reverted repo to status at %F %T.",
|
|
localtime((time_t *)(&commit->ctime)));
|
|
#else
|
|
strftime (desc, sizeof(desc), "Reverted repo to status at %Y-%m-%d %H:%M:%S.",
|
|
localtime((time_t *)(&commit->ctime)));
|
|
#endif
|
|
|
|
new_commit = seaf_commit_new (NULL, repo->id, commit->root_id,
|
|
user_name, EMPTY_SHA1,
|
|
desc, 0);
|
|
|
|
new_commit->parent_id = g_strdup (repo->head->commit_id);
|
|
seaf_repo_to_commit (repo, new_commit);
|
|
|
|
if (seaf_commit_manager_add_commit (seaf->commit_mgr, new_commit) < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
seaf_branch_set_commit (repo->head, new_commit->commit_id);
|
|
if (seaf_branch_manager_test_and_update_branch (seaf->branch_mgr,
|
|
repo->head,
|
|
new_commit->parent_id) < 0)
|
|
{
|
|
seaf_repo_unref (repo);
|
|
seaf_commit_unref (commit);
|
|
seaf_commit_unref (new_commit);
|
|
repo = NULL;
|
|
commit = new_commit = NULL;
|
|
goto retry;
|
|
}
|
|
|
|
seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);
|
|
|
|
out:
|
|
if (new_commit)
|
|
seaf_commit_unref (new_commit);
|
|
if (commit)
|
|
seaf_commit_unref (commit);
|
|
if (repo)
|
|
seaf_repo_unref (repo);
|
|
|
|
if (ret == 0) {
|
|
update_repo_size (repo_id);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
add_deleted_entry (SeafRepo *repo,
|
|
GHashTable *entries,
|
|
SeafDirent *dent,
|
|
const char *base,
|
|
SeafCommit *child,
|
|
SeafCommit *parent)
|
|
{
|
|
char *path = g_strconcat (base, dent->name, NULL);
|
|
SeafileDeletedEntry *entry;
|
|
Seafile *file;
|
|
|
|
if (g_hash_table_lookup (entries, path) != NULL) {
|
|
/* g_debug ("found dup deleted entry for %s.\n", path); */
|
|
g_free (path);
|
|
return;
|
|
}
|
|
|
|
/* g_debug ("Add deleted entry for %s.\n", path); */
|
|
|
|
entry = g_object_new (SEAFILE_TYPE_DELETED_ENTRY,
|
|
"commit_id", parent->commit_id,
|
|
"obj_id", dent->id,
|
|
"obj_name", dent->name,
|
|
"basedir", base,
|
|
"mode", dent->mode,
|
|
"delete_time", child->ctime,
|
|
NULL);
|
|
|
|
if (S_ISREG(dent->mode)) {
|
|
file = seaf_fs_manager_get_seafile (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
dent->id);
|
|
if (!file) {
|
|
g_free (path);
|
|
g_object_unref (entry);
|
|
return;
|
|
}
|
|
g_object_set (entry, "file_size", file->file_size, NULL);
|
|
seafile_unref (file);
|
|
}
|
|
|
|
g_hash_table_insert (entries, path, entry);
|
|
}
|
|
|
|
static int
|
|
find_deleted_recursive (SeafRepo *repo,
|
|
SeafDir *d1,
|
|
SeafDir *d2,
|
|
const char *base,
|
|
SeafCommit *child,
|
|
SeafCommit *parent,
|
|
GHashTable *entries)
|
|
{
|
|
GList *p1, *p2;
|
|
SeafDirent *dent1, *dent2;
|
|
int res, ret = 0;
|
|
|
|
p1 = d1->entries;
|
|
p2 = d2->entries;
|
|
|
|
/* Since dirents are sorted in descending order, we can use merge
|
|
* algorithm to find out deleted entries.
|
|
* Deleted entries are those:
|
|
* 1. exists in d2 but absent in d1.
|
|
* 2. exists in both d1 and d2 but with different type.
|
|
*/
|
|
|
|
while (p1 && p2) {
|
|
dent1 = p1->data;
|
|
dent2 = p2->data;
|
|
|
|
res = g_strcmp0 (dent1->name, dent2->name);
|
|
if (res < 0) {
|
|
/* exists in d2 but absent in d1. */
|
|
add_deleted_entry (repo, entries, dent2, base, child, parent);
|
|
p2 = p2->next;
|
|
} else if (res == 0) {
|
|
if ((dent1->mode & S_IFMT) != (dent2->mode & S_IFMT)) {
|
|
/* both exists but with diffent type. */
|
|
add_deleted_entry (repo, entries, dent2, base, child, parent);
|
|
} else if (S_ISDIR(dent1->mode) && strcmp(dent1->id, dent2->id) != 0) {
|
|
SeafDir *n1 = seaf_fs_manager_get_seafdir_sorted (seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
dent1->id);
|
|
if (!n1) {
|
|
seaf_warning ("Failed to find dir %s:%s.\n", repo->id, dent1->id);
|
|
return -1;
|
|
}
|
|
|
|
SeafDir *n2 = seaf_fs_manager_get_seafdir_sorted (seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
dent2->id);
|
|
if (!n2) {
|
|
seaf_warning ("Failed to find dir %s:%s.\n", repo->id, dent2->id);
|
|
seaf_dir_free (n1);
|
|
return -1;
|
|
}
|
|
|
|
char *new_base = g_strconcat (base, dent1->name, "/", NULL);
|
|
ret = find_deleted_recursive (repo, n1, n2, new_base,
|
|
child, parent, entries);
|
|
g_free (new_base);
|
|
seaf_dir_free (n1);
|
|
seaf_dir_free (n2);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
p1 = p1->next;
|
|
p2 = p2->next;
|
|
} else {
|
|
p1 = p1->next;
|
|
}
|
|
}
|
|
|
|
for ( ; p2 != NULL; p2 = p2->next) {
|
|
dent2 = p2->data;
|
|
add_deleted_entry (repo, entries, dent2, base, child, parent);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
find_deleted (SeafRepo *repo,
|
|
SeafCommit *child,
|
|
SeafCommit *parent,
|
|
const char *base,
|
|
GHashTable *entries)
|
|
{
|
|
SeafDir *d1, *d2;
|
|
int ret = 0;
|
|
|
|
d1 = seaf_fs_manager_get_seafdir_sorted_by_path (seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
child->root_id, base);
|
|
if (!d1) {
|
|
return ret;
|
|
}
|
|
|
|
d2 = seaf_fs_manager_get_seafdir_sorted_by_path (seaf->fs_mgr,
|
|
repo->store_id,
|
|
repo->version,
|
|
parent->root_id, base);
|
|
if (!d2) {
|
|
seaf_dir_free (d1);
|
|
return ret;
|
|
}
|
|
|
|
ret = find_deleted_recursive (repo, d1, d2, base, child, parent, entries);
|
|
|
|
seaf_dir_free (d2);
|
|
seaf_dir_free (d1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
typedef struct CollectDelData {
|
|
SeafRepo *repo;
|
|
GHashTable *entries;
|
|
gint64 truncate_time;
|
|
char *path;
|
|
} CollectDelData;
|
|
|
|
#define DEFAULT_RECYCLE_DAYS 7
|
|
|
|
static gboolean
|
|
collect_deleted (SeafCommit *commit, void *vdata, gboolean *stop)
|
|
{
|
|
CollectDelData *data = vdata;
|
|
SeafRepo *repo = data->repo;
|
|
GHashTable *entries = data->entries;
|
|
gint64 truncate_time = data->truncate_time;
|
|
SeafCommit *p1, *p2;
|
|
|
|
/* We use <= here. This is for handling clean trash and history.
|
|
* If the user cleans all history, truncate time will be equal to
|
|
* the head commit's ctime. In such case, we don't actually want to display
|
|
* any deleted file.
|
|
*/
|
|
if ((gint64)(commit->ctime) <= truncate_time) {
|
|
*stop = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
if (commit->parent_id == NULL)
|
|
return TRUE;
|
|
|
|
if (!(strstr (commit->desc, PREFIX_DEL_FILE) != NULL ||
|
|
strstr (commit->desc, PREFIX_DEL_DIR) != NULL ||
|
|
strstr (commit->desc, PREFIX_DEL_DIRS) != NULL)) {
|
|
return TRUE;
|
|
}
|
|
|
|
p1 = seaf_commit_manager_get_commit (commit->manager,
|
|
repo->id, repo->version,
|
|
commit->parent_id);
|
|
if (!p1) {
|
|
seaf_warning ("Failed to find commit %s:%s.\n", repo->id, commit->parent_id);
|
|
return FALSE;
|
|
}
|
|
|
|
if (find_deleted (data->repo, commit, p1, data->path, entries) < 0) {
|
|
seaf_commit_unref (p1);
|
|
return FALSE;
|
|
}
|
|
|
|
seaf_commit_unref (p1);
|
|
|
|
if (commit->second_parent_id) {
|
|
p2 = seaf_commit_manager_get_commit (commit->manager,
|
|
repo->id, repo->version,
|
|
commit->second_parent_id);
|
|
if (!p2) {
|
|
seaf_warning ("Failed to find commit %s:%s.\n",
|
|
repo->id, commit->second_parent_id);
|
|
return FALSE;
|
|
}
|
|
|
|
if (find_deleted (data->repo, commit, p2, data->path, entries) < 0) {
|
|
seaf_commit_unref (p2);
|
|
return FALSE;
|
|
}
|
|
|
|
seaf_commit_unref (p2);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
typedef struct RemoveExistingParam {
|
|
SeafRepo *repo;
|
|
SeafCommit *head;
|
|
} RemoveExistingParam;
|
|
|
|
static gboolean
|
|
remove_existing (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
SeafileDeletedEntry *e = value;
|
|
RemoveExistingParam *param = user_data;
|
|
SeafRepo *repo = param->repo;
|
|
SeafCommit *head = param->head;
|
|
guint32 mode = seafile_deleted_entry_get_mode(e), mode_out = 0;
|
|
char *path = key;
|
|
|
|
char *obj_id = seaf_fs_manager_path_to_obj_id (seaf->fs_mgr,
|
|
repo->store_id, repo->version,
|
|
head->root_id,
|
|
path, &mode_out, NULL);
|
|
if (obj_id == NULL)
|
|
return FALSE;
|
|
g_free (obj_id);
|
|
|
|
/* If path exist in head commit and with the same type,
|
|
* remove it from deleted entries.
|
|
*/
|
|
if ((mode & S_IFMT) == (mode_out & S_IFMT)) {
|
|
/* g_debug ("%s exists in head commit.\n", path); */
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int
|
|
filter_out_existing_entries (GHashTable *entries,
|
|
SeafRepo *repo,
|
|
const char *head_id)
|
|
{
|
|
SeafCommit *head;
|
|
RemoveExistingParam param;
|
|
|
|
head = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
head_id);
|
|
if (!head) {
|
|
seaf_warning ("Failed to find head commit %s of repo %s.\n",
|
|
head_id, repo->id);
|
|
return -1;
|
|
}
|
|
|
|
param.repo = repo;
|
|
param.head = head;
|
|
|
|
g_hash_table_foreach_remove (entries, remove_existing, ¶m);
|
|
|
|
seaf_commit_unref (head);
|
|
return 0;
|
|
}
|
|
|
|
static gboolean
|
|
hash_to_list (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
GList **plist = (GList **)user_data;
|
|
|
|
g_free (key);
|
|
*plist = g_list_prepend (*plist, value);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
compare_commit_by_time (gconstpointer a, gconstpointer b, gpointer unused)
|
|
{
|
|
const SeafCommit *commit_a = a;
|
|
const SeafCommit *commit_b = b;
|
|
|
|
/* Latest commit comes first in the list. */
|
|
return (commit_b->ctime - commit_a->ctime);
|
|
}
|
|
|
|
static int
|
|
insert_parent_commit (GList **list, GHashTable *hash,
|
|
const char *repo_id, int version,
|
|
const char *parent_id)
|
|
{
|
|
SeafCommit *p;
|
|
char *key;
|
|
|
|
if (g_hash_table_lookup (hash, parent_id) != NULL)
|
|
return 0;
|
|
|
|
p = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo_id, version,
|
|
parent_id);
|
|
if (!p) {
|
|
seaf_warning ("Failed to find commit %s\n", parent_id);
|
|
return -1;
|
|
}
|
|
|
|
*list = g_list_insert_sorted_with_data (*list, p,
|
|
compare_commit_by_time,
|
|
NULL);
|
|
|
|
key = g_strdup (parent_id);
|
|
g_hash_table_replace (hash, key, key);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static GList *
|
|
scan_stat_to_list(const char *scan_stat, GHashTable *commit_hash, SeafRepo *repo)
|
|
{
|
|
json_t *commit_array = NULL, *commit_obj = NULL;
|
|
char *commit_id = NULL;
|
|
SeafCommit *commit = NULL;
|
|
GList *list = NULL;
|
|
char *key;
|
|
commit_array = json_loadb (scan_stat, strlen(scan_stat), 0, NULL);
|
|
if (!commit_array) {
|
|
return NULL;
|
|
}
|
|
int i;
|
|
for (i = 0; i < json_array_size (commit_array); i++) {
|
|
commit_obj = json_array_get (commit_array, i);
|
|
commit_id = json_string_value (commit_obj);
|
|
if (commit_id && strlen(commit_id) == 40) {
|
|
commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id,
|
|
repo->version, commit_id);
|
|
if (!commit) {
|
|
return NULL;
|
|
}
|
|
list = g_list_prepend (list, commit);
|
|
key = g_strdup (commit->commit_id);
|
|
g_hash_table_replace (commit_hash, key, key);
|
|
}
|
|
}
|
|
json_decref (commit_array);
|
|
list = g_list_sort (list, compare_commit_by_time);
|
|
return list;
|
|
}
|
|
|
|
static int
|
|
scan_commits_for_collect_deleted (CollectDelData *data,
|
|
const char *prev_scan_stat,
|
|
int limit,
|
|
char **next_scan_stat)
|
|
{
|
|
GList *list = NULL;
|
|
SeafCommit *commit;
|
|
GHashTable *commit_hash;
|
|
SeafRepo *repo = data->repo;
|
|
int scan_num = 0;
|
|
gboolean ret = TRUE;
|
|
|
|
/* A hash table for recording id of traversed commits. */
|
|
commit_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
|
|
if (prev_scan_stat == NULL) {
|
|
commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo->id,
|
|
repo->version, repo->head->commit_id);
|
|
if (!commit) {
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
list = g_list_prepend (list, commit);
|
|
char *key = g_strdup (commit->commit_id);
|
|
g_hash_table_replace (commit_hash, key, key);
|
|
} else {
|
|
list = scan_stat_to_list (prev_scan_stat, commit_hash, repo);
|
|
if (list == NULL) {
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
while (list) {
|
|
gboolean stop = FALSE;
|
|
commit = list->data;
|
|
list = g_list_delete_link (list, list);
|
|
|
|
if (!collect_deleted (commit, data, &stop)) {
|
|
seaf_warning("[comit-mgr] CommitTraverseFunc failed\n");
|
|
seaf_commit_unref (commit);
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
if (stop) {
|
|
seaf_commit_unref (commit);
|
|
/* stop traverse down from this commit,
|
|
* but not stop traversing the tree
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
if (commit->parent_id) {
|
|
if (insert_parent_commit (&list, commit_hash, repo->id,
|
|
repo->version,
|
|
commit->parent_id) < 0) {
|
|
seaf_warning("[comit-mgr] insert parent commit failed\n");
|
|
seaf_commit_unref (commit);
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
}
|
|
if (commit->second_parent_id) {
|
|
if (insert_parent_commit (&list, commit_hash, repo->id,
|
|
repo->version,
|
|
commit->second_parent_id) < 0) {
|
|
seaf_warning("[comit-mgr]insert second parent commit failed\n");
|
|
seaf_commit_unref (commit);
|
|
ret = FALSE;
|
|
goto out;
|
|
}
|
|
}
|
|
seaf_commit_unref (commit);
|
|
|
|
if (++scan_num >= limit) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
json_t *commit_array = json_array ();
|
|
while (list) {
|
|
commit = list->data;
|
|
json_array_append_new (commit_array, json_string (commit->commit_id));
|
|
seaf_commit_unref (commit);
|
|
list = g_list_delete_link (list, list);
|
|
}
|
|
if (json_array_size(commit_array) > 0) {
|
|
char *commits = json_dumps (commit_array, JSON_COMPACT);
|
|
*next_scan_stat = commits;
|
|
}
|
|
json_decref (commit_array);
|
|
g_hash_table_destroy (commit_hash);
|
|
|
|
return ret;
|
|
|
|
out:
|
|
g_hash_table_destroy (commit_hash);
|
|
while (list) {
|
|
commit = list->data;
|
|
seaf_commit_unref (commit);
|
|
list = g_list_delete_link (list, list);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
GList *
|
|
seaf_repo_manager_get_deleted_entries (SeafRepoManager *mgr,
|
|
const char *repo_id,
|
|
int show_days,
|
|
const char *path,
|
|
const char *scan_stat,
|
|
int limit,
|
|
GError **error)
|
|
{
|
|
SeafRepo *repo;
|
|
gint64 truncate_time, show_time;
|
|
GList *ret = NULL;
|
|
char *next_scan_stat = NULL;
|
|
|
|
truncate_time = seaf_repo_manager_get_repo_truncate_time (mgr, repo_id);
|
|
if (truncate_time == 0) {
|
|
// Don't keep history, set scan_stat as NULL, indicate no need for next scan
|
|
ret = g_list_append (ret, g_object_new (SEAFILE_TYPE_DELETED_ENTRY,
|
|
"scan_stat", NULL,
|
|
NULL));
|
|
return ret;
|
|
}
|
|
|
|
if (show_days <= 0)
|
|
show_time = -1;
|
|
else
|
|
show_time = (gint64)time(NULL) - show_days * 24 * 3600;
|
|
|
|
repo = seaf_repo_manager_get_repo (mgr, repo_id);
|
|
if (!repo) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
|
|
"Invalid repo id");
|
|
return NULL;
|
|
}
|
|
|
|
CollectDelData data = {0};
|
|
GHashTable *entries = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, g_object_unref);
|
|
data.repo = repo;
|
|
data.entries = entries;
|
|
data.truncate_time = MAX (show_time, truncate_time);
|
|
if (path) {
|
|
if (path[strlen(path) - 1] == '/') {
|
|
data.path = g_strdup (path);
|
|
} else {
|
|
data.path = g_strconcat (path, "/", NULL);
|
|
}
|
|
} else {
|
|
data.path = g_strdup ("/");
|
|
}
|
|
|
|
if (!scan_commits_for_collect_deleted (&data, scan_stat, limit, &next_scan_stat)) {
|
|
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,
|
|
"Internal error");
|
|
g_hash_table_destroy (entries);
|
|
seaf_repo_unref (repo);
|
|
g_free (data.path);
|
|
return NULL;
|
|
}
|
|
|
|
/* Remove entries exist in the current commit.
|
|
* This is necessary because some files may be added back after deletion.
|
|
*/
|
|
if (filter_out_existing_entries (entries, repo,
|
|
repo->head->commit_id) == 0) {
|
|
// filter success, then add collected result to list
|
|
g_hash_table_foreach_steal (entries, hash_to_list, &ret);
|
|
}
|
|
|
|
// Append scan_stat entry to the end to indicate the end of scan result
|
|
ret = g_list_append (ret, g_object_new (SEAFILE_TYPE_DELETED_ENTRY,
|
|
"scan_stat", next_scan_stat,
|
|
NULL));
|
|
|
|
g_hash_table_destroy (entries);
|
|
|
|
seaf_repo_unref (repo);
|
|
g_free (data.path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static SeafCommit *
|
|
get_commit(SeafRepo *repo, const char *branch_or_commit)
|
|
{
|
|
SeafBranch *b;
|
|
SeafCommit *c;
|
|
|
|
b = seaf_branch_manager_get_branch (seaf->branch_mgr, repo->id,
|
|
branch_or_commit);
|
|
if (!b) {
|
|
if (strcmp(branch_or_commit, "HEAD") == 0)
|
|
c = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
repo->head->commit_id);
|
|
else
|
|
c = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
branch_or_commit);
|
|
} else {
|
|
c = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
b->commit_id);
|
|
}
|
|
|
|
if (b)
|
|
seaf_branch_unref (b);
|
|
|
|
return c;
|
|
}
|
|
|
|
GList *
|
|
seaf_repo_diff (SeafRepo *repo, const char *old, const char *new, int fold_dir_results, char **error)
|
|
{
|
|
SeafCommit *c1 = NULL, *c2 = NULL;
|
|
int ret = 0;
|
|
GList *diff_entries = NULL;
|
|
|
|
g_return_val_if_fail (*error == NULL, NULL);
|
|
|
|
c2 = get_commit (repo, new);
|
|
if (!c2) {
|
|
*error = g_strdup("Can't find new commit");
|
|
return NULL;
|
|
}
|
|
|
|
if (old == NULL || old[0] == '\0') {
|
|
if (c2->parent_id && c2->second_parent_id) {
|
|
ret = diff_merge (c2, &diff_entries, fold_dir_results);
|
|
seaf_commit_unref (c2);
|
|
if (ret < 0) {
|
|
*error = g_strdup("Failed to do diff");
|
|
g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);
|
|
return NULL;
|
|
}
|
|
return diff_entries;
|
|
}
|
|
|
|
if (!c2->parent_id) {
|
|
seaf_commit_unref (c2);
|
|
return NULL;
|
|
}
|
|
c1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
|
|
repo->id, repo->version,
|
|
c2->parent_id);
|
|
} else {
|
|
c1 = get_commit (repo, old);
|
|
}
|
|
|
|
if (!c1) {
|
|
*error = g_strdup("Can't find old commit");
|
|
seaf_commit_unref (c2);
|
|
return NULL;
|
|
}
|
|
|
|
/* do diff */
|
|
ret = diff_commits (c1, c2, &diff_entries, fold_dir_results);
|
|
if (ret < 0) {
|
|
g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);
|
|
diff_entries = NULL;
|
|
*error = g_strdup("Failed to do diff");
|
|
}
|
|
|
|
seaf_commit_unref (c1);
|
|
seaf_commit_unref (c2);
|
|
|
|
return diff_entries;
|
|
}
|
|
|