/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include #include #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 in */ 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. 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; }