mirror of
https://github.com/haiwen/seafile-server.git
synced 2025-08-13 12:38:35 +00:00
* Add batch del files RPC * Add pack multi-level files * Go add pack multi-level files * Use change set to batch del files * Improve args --------- Co-authored-by: 杨赫然 <heran.yang@seafile.com>
382 lines
10 KiB
C
382 lines
10 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
#include "common.h"
|
|
|
|
#include "seafile-session.h"
|
|
|
|
#include "utils.h"
|
|
#include "log.h"
|
|
|
|
#include "change-set.h"
|
|
|
|
struct _ChangeSetDir {
|
|
int version;
|
|
char dir_id[41];
|
|
/* A hash table of dirents for fast lookup and insertion. */
|
|
GHashTable *dents;
|
|
|
|
};
|
|
typedef struct _ChangeSetDir ChangeSetDir;
|
|
|
|
struct _ChangeSetDirent {
|
|
guint32 mode;
|
|
char id[41];
|
|
char *name;
|
|
gint64 mtime;
|
|
char *modifier;
|
|
gint64 size;
|
|
/* Only used for directory. Most of time this is NULL
|
|
* unless we change the subdir too.
|
|
*/
|
|
ChangeSetDir *subdir;
|
|
};
|
|
typedef struct _ChangeSetDirent ChangeSetDirent;
|
|
|
|
/* Change set dirent. */
|
|
|
|
static ChangeSetDirent *
|
|
changeset_dirent_new (const char *id, guint32 mode, const char *name,
|
|
gint64 mtime, const char *modifier, gint64 size)
|
|
{
|
|
ChangeSetDirent *dent = g_new0 (ChangeSetDirent, 1);
|
|
|
|
dent->mode = mode;
|
|
memcpy (dent->id, id, 40);
|
|
dent->name = g_strdup(name);
|
|
dent->mtime = mtime;
|
|
dent->modifier = g_strdup(modifier);
|
|
dent->size = size;
|
|
|
|
return dent;
|
|
}
|
|
|
|
static ChangeSetDirent *
|
|
seaf_dirent_to_changeset_dirent (SeafDirent *seaf_dent)
|
|
{
|
|
return changeset_dirent_new (seaf_dent->id, seaf_dent->mode, seaf_dent->name,
|
|
seaf_dent->mtime, seaf_dent->modifier, seaf_dent->size);
|
|
}
|
|
|
|
static SeafDirent *
|
|
changeset_dirent_to_seaf_dirent (int version, ChangeSetDirent *dent)
|
|
{
|
|
return seaf_dirent_new (version, dent->id, dent->mode, dent->name,
|
|
dent->mtime, dent->modifier, dent->size);
|
|
}
|
|
|
|
static void
|
|
changeset_dir_free (ChangeSetDir *dir);
|
|
|
|
static void
|
|
changeset_dirent_free (ChangeSetDirent *dent)
|
|
{
|
|
if (!dent)
|
|
return;
|
|
|
|
g_free (dent->name);
|
|
g_free (dent->modifier);
|
|
/* Recursively free subdir. */
|
|
if (dent->subdir)
|
|
changeset_dir_free (dent->subdir);
|
|
g_free (dent);
|
|
}
|
|
|
|
/* Change set dir. */
|
|
|
|
static void
|
|
add_dent_to_dir (ChangeSetDir *dir, ChangeSetDirent *dent)
|
|
{
|
|
g_hash_table_insert (dir->dents,
|
|
g_strdup(dent->name),
|
|
dent);
|
|
}
|
|
|
|
static void
|
|
remove_dent_from_dir (ChangeSetDir *dir, const char *dname)
|
|
{
|
|
char *key;
|
|
|
|
if (g_hash_table_lookup_extended (dir->dents, dname,
|
|
(gpointer*)&key, NULL)) {
|
|
g_hash_table_steal (dir->dents, dname);
|
|
g_free (key);
|
|
}
|
|
}
|
|
|
|
static ChangeSetDir *
|
|
changeset_dir_new (int version, const char *id, GList *dirents)
|
|
{
|
|
ChangeSetDir *dir = g_new0 (ChangeSetDir, 1);
|
|
GList *ptr;
|
|
SeafDirent *dent;
|
|
ChangeSetDirent *changeset_dent;
|
|
|
|
dir->version = version;
|
|
if (id)
|
|
memcpy (dir->dir_id, id, 40);
|
|
dir->dents = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, (GDestroyNotify)changeset_dirent_free);
|
|
for (ptr = dirents; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
changeset_dent = seaf_dirent_to_changeset_dirent(dent);
|
|
add_dent_to_dir (dir, changeset_dent);
|
|
}
|
|
|
|
return dir;
|
|
}
|
|
|
|
static void
|
|
changeset_dir_free (ChangeSetDir *dir)
|
|
{
|
|
if (!dir)
|
|
return;
|
|
g_hash_table_destroy (dir->dents);
|
|
g_free (dir);
|
|
}
|
|
|
|
static ChangeSetDir *
|
|
seaf_dir_to_changeset_dir (SeafDir *seaf_dir)
|
|
{
|
|
return changeset_dir_new (seaf_dir->version, seaf_dir->dir_id, seaf_dir->entries);
|
|
}
|
|
|
|
static gint
|
|
compare_dents (gconstpointer a, gconstpointer b)
|
|
{
|
|
const SeafDirent *denta = a, *dentb = b;
|
|
|
|
return strcmp(dentb->name, denta->name);
|
|
}
|
|
|
|
static SeafDir *
|
|
changeset_dir_to_seaf_dir (ChangeSetDir *dir)
|
|
{
|
|
GList *dents = NULL, *seaf_dents = NULL;
|
|
GList *ptr;
|
|
ChangeSetDirent *dent;
|
|
SeafDirent *seaf_dent;
|
|
SeafDir *seaf_dir;
|
|
|
|
dents = g_hash_table_get_values (dir->dents);
|
|
for (ptr = dents; ptr; ptr = ptr->next) {
|
|
dent = ptr->data;
|
|
seaf_dent = changeset_dirent_to_seaf_dirent (dir->version, dent);
|
|
seaf_dents = g_list_prepend (seaf_dents, seaf_dent);
|
|
}
|
|
/* Sort it in descending order. */
|
|
seaf_dents = g_list_sort (seaf_dents, compare_dents);
|
|
|
|
/* seaf_dir_new() computes the dir id. */
|
|
seaf_dir = seaf_dir_new (NULL, seaf_dents, dir->version);
|
|
|
|
g_list_free (dents);
|
|
return seaf_dir;
|
|
}
|
|
|
|
/* Change set. */
|
|
|
|
ChangeSet *
|
|
changeset_new (const char *repo_id, SeafDir *dir)
|
|
{
|
|
ChangeSetDir *changeset_dir = NULL;
|
|
ChangeSet *changeset = NULL;
|
|
|
|
changeset_dir = seaf_dir_to_changeset_dir (dir);
|
|
if (!changeset_dir)
|
|
goto out;
|
|
|
|
changeset = g_new0 (ChangeSet, 1);
|
|
memcpy (changeset->repo_id, repo_id, 36);
|
|
changeset->tree_root = changeset_dir;
|
|
|
|
out:
|
|
return changeset;
|
|
}
|
|
|
|
void
|
|
changeset_free (ChangeSet *changeset)
|
|
{
|
|
if (!changeset)
|
|
return;
|
|
|
|
changeset_dir_free (changeset->tree_root);
|
|
g_free (changeset);
|
|
}
|
|
|
|
static ChangeSetDirent *
|
|
delete_from_tree (ChangeSet *changeset,
|
|
const char *path,
|
|
gboolean *parent_empty)
|
|
{
|
|
char *repo_id = changeset->repo_id;
|
|
ChangeSetDir *root = changeset->tree_root;
|
|
char **parts, *dname;
|
|
int n, i;
|
|
ChangeSetDir *dir;
|
|
ChangeSetDirent *dent, *ret = NULL;
|
|
ChangeSetDirent *parent_dent = NULL;
|
|
SeafDir *seaf_dir;
|
|
|
|
*parent_empty = FALSE;
|
|
|
|
parts = g_strsplit (path, "/", 0);
|
|
n = g_strv_length(parts);
|
|
dir = root;
|
|
for (i = 0; i < n; i++) {
|
|
dname = parts[i];
|
|
|
|
dent = g_hash_table_lookup (dir->dents, dname);
|
|
if (!dent)
|
|
break;
|
|
|
|
if (S_ISDIR(dent->mode)) {
|
|
if (i == (n-1)) {
|
|
/* Remove from hash table without freeing dent. */
|
|
remove_dent_from_dir (dir, dname);
|
|
if (g_hash_table_size (dir->dents) == 0)
|
|
*parent_empty = TRUE;
|
|
ret = dent;
|
|
// update parent dir mtime when delete dirs locally.
|
|
if (parent_dent) {
|
|
parent_dent->mtime = time (NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!dent->subdir) {
|
|
seaf_dir = seaf_fs_manager_get_seafdir(seaf->fs_mgr,
|
|
repo_id,
|
|
root->version,
|
|
dent->id);
|
|
if (!seaf_dir) {
|
|
seaf_warning ("Failed to load seafdir %s:%s\n",
|
|
repo_id, dent->id);
|
|
break;
|
|
}
|
|
dent->subdir = seaf_dir_to_changeset_dir (seaf_dir);
|
|
seaf_dir_free (seaf_dir);
|
|
}
|
|
dir = dent->subdir;
|
|
parent_dent = dent;
|
|
} else if (S_ISREG(dent->mode)) {
|
|
if (i == (n-1)) {
|
|
/* Remove from hash table without freeing dent. */
|
|
remove_dent_from_dir (dir, dname);
|
|
if (g_hash_table_size (dir->dents) == 0)
|
|
*parent_empty = TRUE;
|
|
ret = dent;
|
|
// update parent dir mtime when delete files locally.
|
|
if (parent_dent) {
|
|
parent_dent->mtime = time (NULL);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_strfreev (parts);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
remove_from_changeset_recursive (ChangeSet *changeset,
|
|
const char *path,
|
|
gboolean remove_parent,
|
|
const char *top_dir,
|
|
int *mode)
|
|
{
|
|
ChangeSetDirent *dent;
|
|
gboolean parent_empty = FALSE;
|
|
|
|
dent = delete_from_tree (changeset, path, &parent_empty);
|
|
if (mode && dent)
|
|
*mode = dent->mode;
|
|
changeset_dirent_free (dent);
|
|
|
|
if (remove_parent && parent_empty) {
|
|
char *parent = g_strdup(path);
|
|
char *slash = strrchr (parent, '/');
|
|
if (slash) {
|
|
*slash = '\0';
|
|
if (strlen(parent) >= strlen(top_dir)) {
|
|
/* Recursively remove parent dirs. */
|
|
remove_from_changeset_recursive (changeset,
|
|
parent,
|
|
remove_parent,
|
|
top_dir,
|
|
mode);
|
|
}
|
|
}
|
|
g_free (parent);
|
|
}
|
|
}
|
|
|
|
void
|
|
remove_from_changeset (ChangeSet *changeset,
|
|
const char *path,
|
|
gboolean remove_parent,
|
|
const char *top_dir,
|
|
int *mode)
|
|
{
|
|
remove_from_changeset_recursive (changeset, path, remove_parent, top_dir, mode);
|
|
}
|
|
|
|
static char *
|
|
commit_tree_recursive (const char *repo_id, ChangeSetDir *dir)
|
|
{
|
|
ChangeSetDirent *dent;
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
char *new_id;
|
|
SeafDir *seaf_dir;
|
|
char *ret = NULL;
|
|
|
|
g_hash_table_iter_init (&iter, dir->dents);
|
|
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
|
dent = value;
|
|
if (dent->subdir) {
|
|
new_id = commit_tree_recursive (repo_id, dent->subdir);
|
|
if (!new_id)
|
|
return NULL;
|
|
|
|
memcpy (dent->id, new_id, 40);
|
|
g_free (new_id);
|
|
}
|
|
}
|
|
|
|
seaf_dir = changeset_dir_to_seaf_dir (dir);
|
|
|
|
memcpy (dir->dir_id, seaf_dir->dir_id, 40);
|
|
|
|
if (!seaf_fs_manager_object_exists (seaf->fs_mgr,
|
|
repo_id, dir->version,
|
|
seaf_dir->dir_id)) {
|
|
if (seaf_dir_save (seaf->fs_mgr, repo_id, dir->version, seaf_dir) < 0) {
|
|
seaf_warning ("Failed to save dir object %s to repo %s.\n",
|
|
seaf_dir->dir_id, repo_id);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ret = g_strdup(seaf_dir->dir_id);
|
|
|
|
out:
|
|
seaf_dir_free (seaf_dir);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* This function does two things:
|
|
* - calculate dir id from bottom up;
|
|
* - create and save seaf dir objects.
|
|
* It returns root dir id of the new commit.
|
|
*/
|
|
char *
|
|
commit_tree_from_changeset (ChangeSet *changeset)
|
|
{
|
|
char *root_id = commit_tree_recursive (changeset->repo_id,
|
|
changeset->tree_root);
|
|
|
|
return root_id;
|
|
}
|