mirror of
https://github.com/haiwen/seafile-server.git
synced 2025-06-26 15:11:34 +00:00
306 lines
7.9 KiB
C
306 lines
7.9 KiB
C
#include "common.h"
|
|
|
|
#define DEBUG_FLAG SEAFILE_DEBUG_OTHER
|
|
#include "log.h"
|
|
|
|
#include <getopt.h>
|
|
|
|
#include <ccnet.h>
|
|
|
|
#include "seafile-session.h"
|
|
|
|
#include "utils.h"
|
|
|
|
static char *config_dir = NULL;
|
|
static char *seafile_dir = NULL;
|
|
static char *central_config_dir = NULL;
|
|
|
|
CcnetClient *ccnet_client;
|
|
SeafileSession *seaf;
|
|
|
|
static const char *short_opts = "hvc:d:VDiF:";
|
|
static const struct option long_opts[] = {
|
|
{ "help", no_argument, NULL, 'h', },
|
|
{ "version", no_argument, NULL, 'v', },
|
|
{ "config-file", required_argument, NULL, 'c', },
|
|
{ "central-config-dir", required_argument, NULL, 'F' },
|
|
{ "seafdir", required_argument, NULL, 'd', },
|
|
{ 0, 0, 0, 0, },
|
|
};
|
|
|
|
static int
|
|
migrate_v0_repos_to_v1_layout ();
|
|
|
|
static void usage ()
|
|
{
|
|
fprintf (stderr,
|
|
"usage: seaf-migrate [-c config_dir] [-d seafile_dir]\n");
|
|
}
|
|
|
|
#ifdef WIN32
|
|
/* Get the commandline arguments in unicode, then convert them to utf8 */
|
|
static char **
|
|
get_argv_utf8 (int *argc)
|
|
{
|
|
int i = 0;
|
|
char **argv = NULL;
|
|
const wchar_t *cmdline = NULL;
|
|
wchar_t **argv_w = NULL;
|
|
|
|
cmdline = GetCommandLineW();
|
|
argv_w = CommandLineToArgvW (cmdline, argc);
|
|
if (!argv_w) {
|
|
printf("failed to CommandLineToArgvW(), GLE=%lu\n", GetLastError());
|
|
return NULL;
|
|
}
|
|
|
|
argv = (char **)malloc (sizeof(char*) * (*argc));
|
|
for (i = 0; i < *argc; i++) {
|
|
argv[i] = wchar_to_utf8 (argv_w[i]);
|
|
}
|
|
|
|
return argv;
|
|
}
|
|
#endif
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
int c;
|
|
|
|
#ifdef WIN32
|
|
argv = get_argv_utf8 (&argc);
|
|
#endif
|
|
|
|
config_dir = DEFAULT_CONFIG_DIR;
|
|
|
|
while ((c = getopt_long(argc, argv,
|
|
short_opts, long_opts, NULL)) != EOF) {
|
|
switch (c) {
|
|
case 'h':
|
|
usage();
|
|
exit(0);
|
|
case 'v':
|
|
exit(-1);
|
|
break;
|
|
case 'c':
|
|
config_dir = strdup(optarg);
|
|
break;
|
|
case 'd':
|
|
seafile_dir = strdup(optarg);
|
|
break;
|
|
case 'F':
|
|
central_config_dir = strdup(optarg);
|
|
break;
|
|
default:
|
|
usage();
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
#if !GLIB_CHECK_VERSION(2, 35, 0)
|
|
g_type_init();
|
|
#endif
|
|
|
|
if (seafile_log_init ("-", "info", "debug") < 0) {
|
|
seaf_warning ("Failed to init log.\n");
|
|
exit (1);
|
|
}
|
|
|
|
ccnet_client = ccnet_client_new();
|
|
if ((ccnet_client_load_confdir(ccnet_client, central_config_dir, config_dir)) < 0) {
|
|
seaf_warning ("Read config dir error\n");
|
|
return -1;
|
|
}
|
|
|
|
if (seafile_dir == NULL)
|
|
seafile_dir = g_build_filename (config_dir, "seafile-data", NULL);
|
|
|
|
seaf = seafile_session_new(central_config_dir, seafile_dir, ccnet_client, TRUE);
|
|
if (!seaf) {
|
|
seaf_warning ("Failed to create seafile session.\n");
|
|
exit (1);
|
|
}
|
|
|
|
migrate_v0_repos_to_v1_layout ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
SeafRepo *repo;
|
|
GHashTable *visited;
|
|
|
|
/* > 0: keep a period of history;
|
|
* == 0: only keep data in head commit;
|
|
* < 0: keep all history data.
|
|
*/
|
|
gint64 truncate_time;
|
|
gboolean traversed_head;
|
|
gboolean stop_copy_blocks;
|
|
} MigrationData;
|
|
|
|
static int
|
|
migrate_file_blocks (SeafFSManager *mgr, MigrationData *data, const char *file_id)
|
|
{
|
|
SeafRepo *repo = data->repo;
|
|
Seafile *seafile;
|
|
int i;
|
|
char *block_id;
|
|
|
|
seafile = seaf_fs_manager_get_seafile (mgr, repo->store_id, repo->version, file_id);
|
|
if (!seafile) {
|
|
seaf_warning ("Failed to find file %s.\n", file_id);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < seafile->n_blocks; ++i) {
|
|
block_id = seafile->blk_sha1s[i];
|
|
if (seaf_block_manager_copy_block (seaf->block_mgr,
|
|
repo->store_id, repo->version,
|
|
repo->store_id, 1,
|
|
block_id) < 0) {
|
|
seaf_warning ("Failed to copy block %s.\n", block_id);
|
|
seafile_unref (seafile);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
seafile_unref (seafile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static gboolean
|
|
fs_callback (SeafFSManager *mgr,
|
|
const char *store_id,
|
|
int version,
|
|
const char *obj_id,
|
|
int type,
|
|
void *user_data,
|
|
gboolean *stop)
|
|
{
|
|
MigrationData *data = user_data;
|
|
SeafRepo *repo = data->repo;
|
|
|
|
if (data->visited != NULL) {
|
|
if (g_hash_table_lookup (data->visited, obj_id) != NULL) {
|
|
*stop = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
char *key = g_strdup(obj_id);
|
|
g_hash_table_replace (data->visited, key, key);
|
|
}
|
|
|
|
if (seaf_obj_store_copy_obj (seaf->fs_mgr->obj_store,
|
|
repo->store_id, repo->version,
|
|
repo->store_id, 1,
|
|
obj_id) < 0) {
|
|
seaf_warning ("Failed to copy fs object %s.\n", obj_id);
|
|
return FALSE;
|
|
}
|
|
|
|
if (data->stop_copy_blocks)
|
|
return TRUE;
|
|
|
|
if (type == SEAF_METADATA_TYPE_FILE &&
|
|
migrate_file_blocks (mgr, data, obj_id) < 0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
traverse_commit (SeafCommit *commit, void *vdata, gboolean *stop)
|
|
{
|
|
MigrationData *data = vdata;
|
|
SeafRepo *repo = data->repo;
|
|
int ret;
|
|
|
|
if (data->truncate_time > 0 &&
|
|
(gint64)(commit->ctime) < data->truncate_time &&
|
|
data->traversed_head && !data->stop_copy_blocks) {
|
|
data->stop_copy_blocks = TRUE;
|
|
}
|
|
|
|
if (!data->traversed_head)
|
|
data->traversed_head = TRUE;
|
|
|
|
if (seaf_obj_store_copy_obj (seaf->commit_mgr->obj_store,
|
|
repo->id, repo->version,
|
|
repo->id, 1,
|
|
commit->commit_id) < 0) {
|
|
seaf_warning ("Failed to copy commit %s.\n", commit->commit_id);
|
|
return FALSE;
|
|
}
|
|
|
|
ret = seaf_fs_manager_traverse_tree (seaf->fs_mgr,
|
|
data->repo->store_id, data->repo->version,
|
|
commit->root_id,
|
|
fs_callback,
|
|
data, FALSE);
|
|
if (ret < 0)
|
|
return FALSE;
|
|
|
|
if (data->truncate_time == 0 && !data->stop_copy_blocks) {
|
|
data->stop_copy_blocks = TRUE;
|
|
/* Stop after traversing the head commit. */
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int
|
|
migrate_repo (SeafRepo *repo)
|
|
{
|
|
MigrationData *data;
|
|
int ret = 0;
|
|
|
|
seaf_message ("Migrating data for repo %.8s.\n", repo->id);
|
|
|
|
data = g_new0(MigrationData, 1);
|
|
data->repo = repo;
|
|
data->visited = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
|
|
gint64 truncate_time = seaf_repo_manager_get_repo_truncate_time (repo->manager,
|
|
repo->id);
|
|
data->truncate_time = truncate_time;
|
|
|
|
gboolean res = seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr,
|
|
repo->id,
|
|
repo->version,
|
|
repo->head->commit_id,
|
|
traverse_commit,
|
|
data,
|
|
FALSE);
|
|
if (!res) {
|
|
seaf_warning ("Migration of repo %s is not completed.\n", repo->id);
|
|
ret = -1;
|
|
}
|
|
|
|
g_hash_table_destroy (data->visited);
|
|
g_free (data);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
migrate_v0_repos_to_v1_layout ()
|
|
{
|
|
GList *repos = NULL, *ptr;
|
|
SeafRepo *repo;
|
|
gboolean error = FALSE;
|
|
|
|
repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, -1, -1, &error);
|
|
for (ptr = repos; ptr; ptr = ptr->next) {
|
|
repo = ptr->data;
|
|
if (!repo->is_corrupted && repo->version == 0)
|
|
migrate_repo (repo);
|
|
seaf_repo_unref (repo);
|
|
}
|
|
g_list_free (repos);
|
|
|
|
return 0;
|
|
}
|