Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add batch del files RPC #694

Merged
merged 5 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions common/rpc-service.c
Original file line number Diff line number Diff line change
Expand Up @@ -2812,6 +2812,46 @@ seafile_del_file (const char *repo_id, const char *parent_dir,
return ret;
}

int
seafile_batch_del_files (const char *repo_id,
const char *filepaths,
const char *user,
GError **error)
{
char *norm_file_list = NULL, *rpath = NULL;
int ret = 0;

if (!repo_id || !filepaths || !user) {
g_set_error (error, 0, SEAF_ERR_BAD_ARGS, "Argument should not be null");
return -1;
}

if (!is_uuid_valid (repo_id)) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo id");
return -1;
}


norm_file_list = normalize_utf8_path (filepaths);
if (!norm_file_list) {
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
"Path is in valid UTF8 encoding");
ret = -1;
goto out;
}

if (seaf_repo_manager_batch_del_files (seaf->repo_mgr, repo_id,
norm_file_list,
user, error) < 0) {
ret = -1;
}

out:
g_free (norm_file_list);

return ret;
}

GObject *
seafile_copy_file (const char *src_repo_id,
const char *src_dir,
Expand Down
24 changes: 19 additions & 5 deletions fileserver/fileop.go
Original file line number Diff line number Diff line change
Expand Up @@ -802,14 +802,28 @@ func parseDirFilelist(repo *repomgr.Repo, obj map[string]interface{}) ([]fsmgr.S
err := fmt.Errorf("invalid download multi data")
return nil, err
}

v, ok := direntHash[name]
if !ok {
err := fmt.Errorf("invalid download multi data")
if name == "" {
err := fmt.Errorf("invalid download file name")
return nil, err
}

direntList = append(direntList, v)
if strings.Contains(name, "/") {
rpath := filepath.Join(parentDir, name)
dent, err := fsmgr.GetDirentByPath(repo.StoreID, repo.RootID, rpath)
if err != nil {
err := fmt.Errorf("failed to get path %s for repo %s: %v", rpath, repo.StoreID, err)
return nil, err
}
direntList = append(direntList, *dent)
} else {
v, ok := direntHash[name]
if !ok {
err := fmt.Errorf("invalid download multi data")
return nil, err
}

direntList = append(direntList, v)
}
}

return direntList, nil
Expand Down
28 changes: 28 additions & 0 deletions fileserver/fsmgr/fsmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -908,3 +908,31 @@ func getFileCountInfo(repoID, dirID string) (*FileCountInfo, error) {

return info, nil
}

func GetDirentByPath(repoID, rootID, rpath string) (*SeafDirent, error) {
parentDir := filepath.Dir(rpath)
fileName := filepath.Base(rpath)

var dir *SeafDir
var err error

if parentDir == "." {
dir, err = GetSeafdir(repoID, rootID)
if err != nil {
return nil, err
}
} else {
dir, err = GetSeafdirByPath(repoID, rootID, parentDir)
if err != nil {
return nil, err
}
}

for _, de := range dir.Entries {
if de.Name == fileName {
return de, nil
}
}

return nil, fmt.Errorf("failed to get dirent %s", rpath)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里应该返回 ErrPathNotExist。

}
6 changes: 6 additions & 0 deletions include/seafile-rpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,12 @@ seafile_del_file (const char *repo_id,
const char *user,
GError **error);

int
seafile_batch_del_files (const char *repo_id,
const char *file_list,
const char *user,
GError **error);

/**
* copy a file/directory from a repo to another on server.
*/
Expand Down
5 changes: 5 additions & 0 deletions python/seafile/rpcclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ def seafile_del_file(repo_id, parent_dir, filename, user):
pass
del_file = seafile_del_file

@searpc_func("int", ["string", "string", "string"])
def seafile_batch_del_files(repo_id, filepaths, user):
pass
batch_del_files = seafile_batch_del_files

@searpc_func("object", ["string", "string", "string", "string", "string", "string", "string", "int", "int"])
def seafile_copy_file(src_repo, src_dir, src_filename, dst_repo, dst_dir, dst_filename, user, need_progress, synchronous):
pass
Expand Down
3 changes: 3 additions & 0 deletions python/seaserv/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ def put_file(self, repo_id, tmp_file_path, parent_dir, filename,
def del_file(self, repo_id, parent_dir, filename, username):
return seafserv_threaded_rpc.del_file(repo_id, parent_dir, filename, username)

def batch_del_files(self, repo_id, filepaths, username):
return seafserv_threaded_rpc.batch_del_files(repo_id, filepaths, username)

'''
If you want to move or copy multiple files in a batch, @src_filename and @dst_filename
should be json array, make sure the number of files
Expand Down
7 changes: 7 additions & 0 deletions server/repo-mgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,13 @@ seaf_repo_manager_del_file (SeafRepoManager *mgr,
const char *user,
GError **error);

int
seaf_repo_manager_batch_del_files (SeafRepoManager *mgr,
const char *repo_id,
const char *file_list,
const char *user,
GError **error);

SeafileCopyResult *
seaf_repo_manager_copy_file (SeafRepoManager *mgr,
const char *src_repo_id,
Expand Down
120 changes: 119 additions & 1 deletion server/repo-op.c
Original file line number Diff line number Diff line change
Expand Up @@ -1735,7 +1735,7 @@ del_file_recursive(SeafRepo *repo,

out:
if (p_deleted_num)
*p_deleted_num = deleted_num;
*p_deleted_num += deleted_num;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里不用改了吧。


g_free (to_path_dup);
g_free (id);
Expand Down Expand Up @@ -1844,6 +1844,124 @@ seaf_repo_manager_del_file (SeafRepoManager *mgr,
return ret;
}

static char *
do_batch_del_files (SeafRepo *repo,
const char *root_id,
const char *file_list,
int *mode, int *deleted_num, char **desc_file)
{
char *ret = NULL;
GList *filenames = NULL, *ptr;
char *name;
const char *next_root_id = root_id;

filenames = json_to_file_list (file_list);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filenames -> filepaths


for (ptr = filenames; ptr; ptr = ptr->next) {
name = ptr->data;
char *base_name = g_path_get_basename (name);
char *parent_dir = g_path_get_dirname (name);
char *canon_path = get_canonical_path (parent_dir);
char *tmp_file_list = g_strdup_printf ("[\"%s\"]", base_name);

char *new_root_id = do_del_file (repo, next_root_id, canon_path, tmp_file_list, mode, deleted_num, desc_file);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

现在这样实现有一个潜在问题的吧,如果这个 RPC 被调用很多次,或者一次删除很多个文件,就会产生很多没用的 dir 对象,都保存下来了,会导致存储里面没用的 fs 对象数量增多。这里应该要花心思做一下缓存优化,可以把客户端的 change set 代码借用过来,如果多个被删除的文件实在同一个目录下面的,不用把这个目录保存很多次。如果只是现在这样的实现,就等同于让 seahub 直接多次调用原有的 del_file RPC。

if (new_root_id) {
g_free (ret);
ret = g_strdup (new_root_id);
g_free (new_root_id);
next_root_id = ret;
}
g_free (base_name);
g_free (parent_dir);
g_free (canon_path);
g_free (tmp_file_list);
}

string_list_free (filenames);

return ret;
}

int
seaf_repo_manager_batch_del_files (SeafRepoManager *mgr,
const char *repo_id,
const char *file_list,
const char *user,
GError **error)
{
SeafRepo *repo = NULL;
SeafCommit *head_commit = NULL;
SeafDir *dir = 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);

dir = seaf_fs_manager_get_seafdir (seaf->fs_mgr,
repo->store_id, repo->version,
head_commit->root_id);
if (!dir) {
seaf_warning ("root dir doesn't exist in repo %s.\n",
repo->store_id);
ret = -1;
goto out;
}

root_id = do_batch_del_files (repo,
head_commit->root_id, file_list, &mode,
&deleted_num, &desc_file);
if (!root_id) {
seaf_warning ("[batch del files] Failed to del files in repo %s.\n",
repo->id);
g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
"Failed to batch del files");
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, TRUE, error) < 0) {
ret = -1;
goto out;
}

seaf_repo_manager_merge_virtual_repo (mgr, repo_id, NULL);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应该参考 del_file 的实现吧,还需要处理一下 locked files 和 folder permission。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是因为开源版没有文件锁定和目录权限。

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 (desc_file);

if (ret == 0) {
update_repo_size (repo_id);
}

return ret;
}

static SeafDirent *
get_dirent_by_path (SeafRepo *repo,
const char *root_id,
Expand Down
5 changes: 5 additions & 0 deletions server/seaf-server.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ static void start_rpc_service (const char *seafile_dir,
"seafile_del_file",
searpc_signature_int__string_string_string_string());

searpc_server_register_function ("seafserv-threaded-rpcserver",
seafile_batch_del_files,
"seafile_batch_del_files",
searpc_signature_int__string_string_string());

searpc_server_register_function ("seafserv-threaded-rpcserver",
seafile_copy_file,
"seafile_copy_file",
Expand Down
46 changes: 35 additions & 11 deletions server/zip-download-mgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ parse_download_multi_data (DownloadObj *obj, const char *data)

for (i = 0; i < len; i++) {
file_name = json_string_value (json_array_get (name_array, i));
if (strcmp (file_name, "") == 0 || strchr (file_name, '/') != NULL) {
if (strcmp (file_name, "") == 0) {
seaf_warning ("Invalid download file name: %s.\n", file_name);
if (dirent_list) {
g_list_free_full (dirent_list, (GDestroyNotify)seaf_dirent_free);
Expand All @@ -339,18 +339,42 @@ parse_download_multi_data (DownloadObj *obj, const char *data)
break;
}

dirent = g_hash_table_lookup (dirent_hash, file_name);
if (!dirent) {
seaf_warning ("Failed to get dirent for %s in dir %s in repo %.8s.\n",
file_name, parent_dir, repo->store_id);
if (dirent_list) {
g_list_free_full (dirent_list, (GDestroyNotify)seaf_dirent_free);
dirent_list = NULL;
// Packing files in multi-level directories.
if (strchr (file_name, '/') != NULL) {
char *fullpath = g_build_path ("/", parent_dir, file_name, NULL);
dirent = seaf_fs_manager_get_dirent_by_path (seaf->fs_mgr, repo->store_id, repo->version, repo->root_id, fullpath, &error);
if (!dirent) {
if (error) {
seaf_warning ("Failed to get path %s repo %.8s: %s.\n",
fullpath, repo->store_id, error->message);
g_clear_error(&error);
} else {
seaf_warning ("Path %s doesn't exist in repo %.8s.\n",
parent_dir, repo->store_id);
}
if (dirent_list) {
g_list_free_full (dirent_list, (GDestroyNotify)seaf_dirent_free);
dirent_list = NULL;
}
g_free (fullpath);
break;
}
g_free (fullpath);
dirent_list = g_list_prepend (dirent_list, dirent);
} else {
dirent = g_hash_table_lookup (dirent_hash, file_name);
if (!dirent) {
seaf_warning ("Failed to get dirent for %s in dir %s in repo %.8s.\n",
file_name, parent_dir, repo->store_id);
if (dirent_list) {
g_list_free_full (dirent_list, (GDestroyNotify)seaf_dirent_free);
dirent_list = NULL;
}
break;
}
break;
}

dirent_list = g_list_prepend (dirent_list, seaf_dirent_dup(dirent));
dirent_list = g_list_prepend (dirent_list, seaf_dirent_dup(dirent));
}
}

g_hash_table_unref(dirent_hash);
Expand Down
Loading
Loading