1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3 #include "common.h"
4
5 #include <glib/gstdio.h>
6
7 #include <openssl/sha.h>
8 #include <openssl/rand.h>
9
10 #include <timer.h>
11 #include "utils.h"
12 #include "log.h"
13
14 #include "seafile-session.h"
15 #include "commit-mgr.h"
16 #include "branch-mgr.h"
17 #include "repo-mgr.h"
18 #include "fs-mgr.h"
19 #include "seafile-error.h"
20 #include "seafile-crypt.h"
21
22 #include "seaf-db.h"
23 #include "seaf-utils.h"
24
25 #define REAP_TOKEN_INTERVAL 300 /* 5 mins */
26 #define DECRYPTED_TOKEN_TTL 3600 /* 1 hour */
27 #define SCAN_TRASH_DAYS 1 /* one day */
28 #define TRASH_EXPIRE_DAYS 30 /* one month */
29
30 typedef struct DecryptedToken {
31 char *token;
32 gint64 reap_time;
33 } DecryptedToken;
34
35 struct _SeafRepoManagerPriv {
36 /* (encrypted_token, session_key) -> decrypted token */
37 GHashTable *decrypted_tokens;
38 pthread_rwlock_t lock;
39 CcnetTimer *reap_token_timer;
40
41 CcnetTimer *scan_trash_timer;
42 };
43
44 static void
45 load_repo (SeafRepoManager *manager, SeafRepo *repo);
46
47 static int create_db_tables_if_not_exist (SeafRepoManager *mgr);
48
49 static int save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch);
50
51 static int reap_token (void *data);
52 static void decrypted_token_free (DecryptedToken *token);
53
54 gboolean
is_repo_id_valid(const char * id)55 is_repo_id_valid (const char *id)
56 {
57 if (!id)
58 return FALSE;
59
60 return is_uuid_valid (id);
61 }
62
63 SeafRepo*
seaf_repo_new(const char * id,const char * name,const char * desc)64 seaf_repo_new (const char *id, const char *name, const char *desc)
65 {
66 SeafRepo* repo;
67
68 /* valid check */
69
70
71 repo = g_new0 (SeafRepo, 1);
72 memcpy (repo->id, id, 36);
73 repo->id[36] = '\0';
74
75 repo->name = g_strdup(name);
76 repo->desc = g_strdup(desc);
77
78 repo->ref_cnt = 1;
79
80 return repo;
81 }
82
83 void
seaf_repo_free(SeafRepo * repo)84 seaf_repo_free (SeafRepo *repo)
85 {
86 if (repo->name) g_free (repo->name);
87 if (repo->desc) g_free (repo->desc);
88 if (repo->head) seaf_branch_unref (repo->head);
89 if (repo->virtual_info)
90 seaf_virtual_repo_info_free (repo->virtual_info);
91 g_free (repo->last_modifier);
92 g_free (repo);
93 }
94
95 void
seaf_repo_ref(SeafRepo * repo)96 seaf_repo_ref (SeafRepo *repo)
97 {
98 g_atomic_int_inc (&repo->ref_cnt);
99 }
100
101 void
seaf_repo_unref(SeafRepo * repo)102 seaf_repo_unref (SeafRepo *repo)
103 {
104 if (!repo)
105 return;
106
107 if (g_atomic_int_dec_and_test (&repo->ref_cnt))
108 seaf_repo_free (repo);
109 }
110
111 static void
set_head_common(SeafRepo * repo,SeafBranch * branch)112 set_head_common (SeafRepo *repo, SeafBranch *branch)
113 {
114 if (repo->head)
115 seaf_branch_unref (repo->head);
116 repo->head = branch;
117 seaf_branch_ref(branch);
118 }
119
120 int
seaf_repo_set_head(SeafRepo * repo,SeafBranch * branch)121 seaf_repo_set_head (SeafRepo *repo, SeafBranch *branch)
122 {
123 if (save_branch_repo_map (repo->manager, branch) < 0)
124 return -1;
125 set_head_common (repo, branch);
126 return 0;
127 }
128
129 void
seaf_repo_from_commit(SeafRepo * repo,SeafCommit * commit)130 seaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit)
131 {
132 repo->name = g_strdup (commit->repo_name);
133 repo->desc = g_strdup (commit->repo_desc);
134 repo->encrypted = commit->encrypted;
135 repo->repaired = commit->repaired;
136 repo->last_modify = commit->ctime;
137 memcpy (repo->root_id, commit->root_id, 40);
138 if (repo->encrypted) {
139 repo->enc_version = commit->enc_version;
140 if (repo->enc_version == 1)
141 memcpy (repo->magic, commit->magic, 32);
142 else if (repo->enc_version == 2) {
143 memcpy (repo->magic, commit->magic, 64);
144 memcpy (repo->random_key, commit->random_key, 96);
145 } else if (repo->enc_version == 3) {
146 memcpy (repo->magic, commit->magic, 64);
147 memcpy (repo->random_key, commit->random_key, 96);
148 memcpy (repo->salt, commit->salt, 64);
149 } else if (repo->enc_version == 4) {
150 memcpy (repo->magic, commit->magic, 64);
151 memcpy (repo->random_key, commit->random_key, 96);
152 memcpy (repo->salt, commit->salt, 64);
153 }
154 }
155 repo->no_local_history = commit->no_local_history;
156 repo->version = commit->version;
157 repo->last_modifier = g_strdup (commit->creator_name);
158 }
159
160 void
seaf_repo_to_commit(SeafRepo * repo,SeafCommit * commit)161 seaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit)
162 {
163 commit->repo_name = g_strdup (repo->name);
164 commit->repo_desc = g_strdup (repo->desc);
165 commit->encrypted = repo->encrypted;
166 commit->repaired = repo->repaired;
167 if (commit->encrypted) {
168 commit->enc_version = repo->enc_version;
169 if (commit->enc_version == 1)
170 commit->magic = g_strdup (repo->magic);
171 else if (commit->enc_version == 2) {
172 commit->magic = g_strdup (repo->magic);
173 commit->random_key = g_strdup (repo->random_key);
174 } else if (commit->enc_version == 3) {
175 commit->magic = g_strdup (repo->magic);
176 commit->random_key = g_strdup (repo->random_key);
177 commit->salt = g_strdup (repo->salt);
178 } else if (commit->enc_version == 4) {
179 commit->magic = g_strdup (repo->magic);
180 commit->random_key = g_strdup (repo->random_key);
181 commit->salt = g_strdup (repo->salt);
182 }
183 }
184 commit->no_local_history = repo->no_local_history;
185 commit->version = repo->version;
186 }
187
188 static gboolean
collect_commit(SeafCommit * commit,void * vlist,gboolean * stop)189 collect_commit (SeafCommit *commit, void *vlist, gboolean *stop)
190 {
191 GList **commits = vlist;
192
193 /* The traverse function will unref the commit, so we need to ref it.
194 */
195 seaf_commit_ref (commit);
196 *commits = g_list_prepend (*commits, commit);
197 return TRUE;
198 }
199
200 GList *
seaf_repo_get_commits(SeafRepo * repo)201 seaf_repo_get_commits (SeafRepo *repo)
202 {
203 GList *branches;
204 GList *ptr;
205 SeafBranch *branch;
206 GList *commits = NULL;
207
208 branches = seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo->id);
209 if (branches == NULL) {
210 seaf_warning ("Failed to get branch list of repo %s.\n", repo->id);
211 return NULL;
212 }
213
214 for (ptr = branches; ptr != NULL; ptr = ptr->next) {
215 branch = ptr->data;
216 gboolean res = seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr,
217 repo->id,
218 repo->version,
219 branch->commit_id,
220 collect_commit,
221 &commits,
222 FALSE);
223 if (!res) {
224 for (ptr = commits; ptr != NULL; ptr = ptr->next)
225 seaf_commit_unref ((SeafCommit *)(ptr->data));
226 g_list_free (commits);
227 goto out;
228 }
229 }
230
231 commits = g_list_reverse (commits);
232
233 out:
234 for (ptr = branches; ptr != NULL; ptr = ptr->next) {
235 seaf_branch_unref ((SeafBranch *)ptr->data);
236 }
237 return commits;
238 }
239
240 gboolean
should_ignore_file(const char * filename,void * data)241 should_ignore_file(const char *filename, void *data)
242 {
243 /* GPatternSpec **spec = ignore_patterns; */
244
245 if (!g_utf8_validate (filename, -1, NULL)) {
246 seaf_warning ("File name %s contains non-UTF8 characters, skip.\n", filename);
247 return TRUE;
248 }
249
250 /* Ignore file/dir if its name is too long. */
251 if (strlen(filename) >= SEAF_DIR_NAME_LEN)
252 return TRUE;
253
254 if (strchr (filename, '/'))
255 return TRUE;
256
257 return FALSE;
258 }
259
260 static gboolean
261 collect_repo_id (SeafDBRow *row, void *data);
262
263 static int
scan_trash(void * data)264 scan_trash (void *data)
265 {
266 GList *repo_ids = NULL;
267 SeafRepoManager *mgr = seaf->repo_mgr;
268 gint64 trash_expire_interval = TRASH_EXPIRE_DAYS * 24 * 3600;
269 int expire_days = seaf_cfg_manager_get_config_int (seaf->cfg_mgr,
270 "library_trash",
271 "expire_days");
272 if (expire_days > 0) {
273 trash_expire_interval = expire_days * 24 * 3600;
274 }
275
276 gint64 expire_time = time(NULL) - trash_expire_interval;
277 char *sql = "SELECT repo_id FROM RepoTrash WHERE del_time <= ?";
278
279 int ret = seaf_db_statement_foreach_row (seaf->db, sql,
280 collect_repo_id, &repo_ids,
281 1, "int64", expire_time);
282 if (ret < 0) {
283 seaf_warning ("Get expired repo from trash failed.");
284 string_list_free (repo_ids);
285 return TRUE;
286 }
287
288 GList *iter;
289 char *repo_id;
290 for (iter=repo_ids; iter; iter=iter->next) {
291 repo_id = iter->data;
292 ret = seaf_repo_manager_del_repo_from_trash (mgr, repo_id, NULL);
293 if (ret < 0)
294 break;
295 }
296
297 string_list_free (repo_ids);
298
299 return TRUE;
300 }
301
302 static void
init_scan_trash_timer(SeafRepoManagerPriv * priv,GKeyFile * config)303 init_scan_trash_timer (SeafRepoManagerPriv *priv, GKeyFile *config)
304 {
305 int scan_days;
306 GError *error = NULL;
307
308 scan_days = g_key_file_get_integer (config,
309 "library_trash", "scan_days",
310 &error);
311 if (error) {
312 scan_days = SCAN_TRASH_DAYS;
313 g_clear_error (&error);
314 }
315
316 priv->scan_trash_timer = ccnet_timer_new (scan_trash, NULL,
317 scan_days * 24 * 3600 * 1000);
318 }
319
320 SeafRepoManager*
seaf_repo_manager_new(SeafileSession * seaf)321 seaf_repo_manager_new (SeafileSession *seaf)
322 {
323 SeafRepoManager *mgr = g_new0 (SeafRepoManager, 1);
324
325 mgr->priv = g_new0 (SeafRepoManagerPriv, 1);
326 mgr->seaf = seaf;
327
328 mgr->priv->decrypted_tokens = g_hash_table_new_full (g_str_hash, g_str_equal,
329 g_free,
330 (GDestroyNotify)decrypted_token_free);
331 pthread_rwlock_init (&mgr->priv->lock, NULL);
332 mgr->priv->reap_token_timer = ccnet_timer_new (reap_token, mgr,
333 REAP_TOKEN_INTERVAL * 1000);
334
335 init_scan_trash_timer (mgr->priv, seaf->config);
336
337 return mgr;
338 }
339
340 int
seaf_repo_manager_init(SeafRepoManager * mgr)341 seaf_repo_manager_init (SeafRepoManager *mgr)
342 {
343 /* On the server, we load repos into memory on-demand, because
344 * there are too many repos.
345 */
346 if (create_db_tables_if_not_exist (mgr) < 0) {
347 seaf_warning ("[repo mgr] failed to create tables.\n");
348 return -1;
349 }
350
351 if (seaf_repo_manager_init_merge_scheduler() < 0) {
352 seaf_warning ("Failed to init merge scheduler.\n");
353 return -1;
354 }
355
356 return 0;
357 }
358
359 int
seaf_repo_manager_start(SeafRepoManager * mgr)360 seaf_repo_manager_start (SeafRepoManager *mgr)
361 {
362 return 0;
363 }
364
365 int
seaf_repo_manager_add_repo(SeafRepoManager * manager,SeafRepo * repo)366 seaf_repo_manager_add_repo (SeafRepoManager *manager,
367 SeafRepo *repo)
368 {
369 SeafDB *db = manager->seaf->db;
370
371 if (seaf_db_statement_query (db, "INSERT INTO Repo (repo_id) VALUES (?)",
372 1, "string", repo->id) < 0)
373 return -1;
374
375 repo->manager = manager;
376
377 return 0;
378 }
379
380 static int
add_deleted_repo_record(SeafRepoManager * mgr,const char * repo_id)381 add_deleted_repo_record (SeafRepoManager *mgr, const char *repo_id)
382 {
383 if (seaf_db_type(seaf->db) == SEAF_DB_TYPE_PGSQL) {
384 gboolean exists, err;
385
386 exists = seaf_db_statement_exists (seaf->db,
387 "SELECT repo_id FROM GarbageRepos "
388 "WHERE repo_id=?",
389 &err, 1, "string", repo_id);
390 if (err)
391 return -1;
392
393 if (!exists) {
394 return seaf_db_statement_query(seaf->db,
395 "INSERT INTO GarbageRepos (repo_id) VALUES (?)",
396 1, "string", repo_id);
397 }
398
399 return 0;
400 } else {
401 return seaf_db_statement_query (seaf->db,
402 "REPLACE INTO GarbageRepos (repo_id) VALUES (?)",
403 1, "string", repo_id);
404 }
405 }
406
407 static int
add_deleted_repo_to_trash(SeafRepoManager * mgr,const char * repo_id,SeafCommit * commit)408 add_deleted_repo_to_trash (SeafRepoManager *mgr, const char *repo_id,
409 SeafCommit *commit)
410 {
411 char *owner = NULL;
412 int ret = -1;
413
414 owner = seaf_repo_manager_get_repo_owner (mgr, repo_id);
415 if (!owner) {
416 seaf_warning ("Failed to get owner for repo %.8s.\n", repo_id);
417 goto out;
418 }
419
420 gint64 size = seaf_repo_manager_get_repo_size (mgr, repo_id);
421 if (size == -1) {
422 seaf_warning ("Failed to get size of repo %.8s.\n", repo_id);
423 goto out;
424 }
425
426 ret = seaf_db_statement_query (mgr->seaf->db,
427 "INSERT INTO RepoTrash (repo_id, repo_name, head_id, "
428 "owner_id, size, org_id, del_time) "
429 "values (?, ?, ?, ?, ?, -1, ?)", 6,
430 "string", repo_id,
431 "string", commit->repo_name,
432 "string", commit->commit_id,
433 "string", owner,
434 "int64", size,
435 "int64", (gint64)time(NULL));
436 out:
437 g_free (owner);
438
439 return ret;
440 }
441
442 static int
remove_virtual_repo_ondisk(SeafRepoManager * mgr,const char * repo_id)443 remove_virtual_repo_ondisk (SeafRepoManager *mgr,
444 const char *repo_id)
445 {
446 SeafDB *db = mgr->seaf->db;
447
448 /* Remove record in repo table first.
449 * Once this is commited, we can gc the other tables later even if
450 * we're interrupted.
451 */
452 if (seaf_db_statement_query (db, "DELETE FROM Repo WHERE repo_id = ?",
453 1, "string", repo_id) < 0)
454 return -1;
455
456 /* remove branch */
457 GList *p;
458 GList *branch_list =
459 seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo_id);
460 for (p = branch_list; p; p = p->next) {
461 SeafBranch *b = (SeafBranch *)p->data;
462 seaf_repo_manager_branch_repo_unmap (mgr, b);
463 seaf_branch_manager_del_branch (seaf->branch_mgr, repo_id, b->name);
464 }
465 seaf_branch_list_free (branch_list);
466
467 seaf_db_statement_query (db, "DELETE FROM RepoOwner WHERE repo_id = ?",
468 1, "string", repo_id);
469
470 seaf_db_statement_query (db, "DELETE FROM SharedRepo WHERE repo_id = ?",
471 1, "string", repo_id);
472
473 seaf_db_statement_query (db, "DELETE FROM RepoGroup WHERE repo_id = ?",
474 1, "string", repo_id);
475
476 if (!seaf->cloud_mode) {
477 seaf_db_statement_query (db, "DELETE FROM InnerPubRepo WHERE repo_id = ?",
478 1, "string", repo_id);
479 }
480
481 seaf_db_statement_query (mgr->seaf->db,
482 "DELETE FROM RepoUserToken WHERE repo_id = ?",
483 1, "string", repo_id);
484
485 seaf_db_statement_query (mgr->seaf->db,
486 "DELETE FROM RepoValidSince WHERE repo_id = ?",
487 1, "string", repo_id);
488
489 seaf_db_statement_query (mgr->seaf->db,
490 "DELETE FROM RepoSize WHERE repo_id = ?",
491 1, "string", repo_id);
492
493 /* For GC commit objects for this virtual repo. Fs and blocks are GC
494 * from the parent repo.
495 */
496 add_deleted_repo_record (mgr, repo_id);
497
498 return 0;
499 }
500
501 static gboolean
get_branch(SeafDBRow * row,void * vid)502 get_branch (SeafDBRow *row, void *vid)
503 {
504 char *ret = vid;
505 const char *commit_id;
506
507 commit_id = seaf_db_row_get_column_text (row, 0);
508 memcpy (ret, commit_id, 41);
509
510 return FALSE;
511 }
512
513 static SeafCommit*
get_head_commit(SeafRepoManager * mgr,const char * repo_id,gboolean * has_err)514 get_head_commit (SeafRepoManager *mgr, const char *repo_id, gboolean *has_err)
515 {
516 char commit_id[41];
517 char *sql;
518
519 commit_id[0] = 0;
520 sql = "SELECT commit_id FROM Branch WHERE name=? AND repo_id=?";
521 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
522 get_branch, commit_id,
523 2, "string", "master", "string", repo_id) < 0) {
524 *has_err = TRUE;
525 return NULL;
526 }
527
528 if (commit_id[0] == 0)
529 return NULL;
530
531 SeafCommit *head_commit = seaf_commit_manager_get_commit (seaf->commit_mgr, repo_id,
532 1, commit_id);
533
534 return head_commit;
535 }
536
537 int
seaf_repo_manager_del_repo(SeafRepoManager * mgr,const char * repo_id,GError ** error)538 seaf_repo_manager_del_repo (SeafRepoManager *mgr,
539 const char *repo_id,
540 GError **error)
541 {
542 gboolean has_err = FALSE;
543
544 SeafCommit *head_commit = get_head_commit (mgr, repo_id, &has_err);
545 if (has_err) {
546 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
547 "Failed to get head commit from db");
548 return -1;
549 }
550 if (!head_commit) {
551 // head commit is missing, del repo directly
552 goto del_repo;
553 }
554
555 if (add_deleted_repo_to_trash (mgr, repo_id, head_commit) < 0) {
556 // Add repo to trash failed, del repo directly
557 seaf_warning ("Failed to add repo %.8s to trash, delete directly.\n",
558 repo_id);
559 }
560
561 seaf_commit_unref (head_commit);
562
563 del_repo:
564 if (seaf_db_statement_query (mgr->seaf->db, "DELETE FROM Repo WHERE repo_id = ?",
565 1, "string", repo_id) < 0) {
566 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
567 "Failed to delete repo from db");
568 return -1;
569 }
570
571 /* remove branch */
572 GList *p;
573 GList *branch_list = seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo_id);
574 for (p = branch_list; p; p = p->next) {
575 SeafBranch *b = (SeafBranch *)p->data;
576 seaf_repo_manager_branch_repo_unmap (mgr, b);
577 seaf_branch_manager_del_branch (seaf->branch_mgr, repo_id, b->name);
578 }
579 seaf_branch_list_free (branch_list);
580
581 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM RepoOwner WHERE repo_id = ?",
582 1, "string", repo_id);
583
584 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM SharedRepo WHERE repo_id = ?",
585 1, "string", repo_id);
586
587 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM RepoGroup WHERE repo_id = ?",
588 1, "string", repo_id);
589
590 if (!seaf->cloud_mode) {
591 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM InnerPubRepo WHERE repo_id = ?",
592 1, "string", repo_id);
593 }
594
595 seaf_db_statement_query (mgr->seaf->db,
596 "DELETE FROM RepoUserToken WHERE repo_id = ?",
597 1, "string", repo_id);
598
599 seaf_db_statement_query (mgr->seaf->db,
600 "DELETE FROM RepoHistoryLimit WHERE repo_id = ?",
601 1, "string", repo_id);
602
603 seaf_db_statement_query (mgr->seaf->db,
604 "DELETE FROM RepoValidSince WHERE repo_id = ?",
605 1, "string", repo_id);
606
607 seaf_db_statement_query (mgr->seaf->db,
608 "DELETE FROM RepoSize WHERE repo_id = ?",
609 1, "string", repo_id);
610
611 /* Remove virtual repos when origin repo is deleted. */
612 GList *vrepos, *ptr;
613 vrepos = seaf_repo_manager_get_virtual_repo_ids_by_origin (mgr, repo_id);
614 for (ptr = vrepos; ptr != NULL; ptr = ptr->next)
615 remove_virtual_repo_ondisk (mgr, (char *)ptr->data);
616 string_list_free (vrepos);
617
618 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM RepoInfo "
619 "WHERE repo_id IN (SELECT repo_id FROM VirtualRepo "
620 "WHERE origin_repo=?)",
621 1, "string", repo_id);
622
623 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM VirtualRepo "
624 "WHERE repo_id=? OR origin_repo=?",
625 2, "string", repo_id, "string", repo_id);
626
627 if (!head_commit)
628 add_deleted_repo_record(mgr, repo_id);
629
630 return 0;
631 }
632
633 int
seaf_repo_manager_del_virtual_repo(SeafRepoManager * mgr,const char * repo_id)634 seaf_repo_manager_del_virtual_repo (SeafRepoManager *mgr,
635 const char *repo_id)
636 {
637 int ret = remove_virtual_repo_ondisk (mgr, repo_id);
638
639 if (ret < 0)
640 return ret;
641
642 return seaf_db_statement_query (mgr->seaf->db,
643 "DELETE FROM VirtualRepo WHERE repo_id = ?",
644 1, "string", repo_id);
645 }
646
647 static gboolean
repo_exists_in_db(SeafDB * db,const char * id,gboolean * db_err)648 repo_exists_in_db (SeafDB *db, const char *id, gboolean *db_err)
649 {
650 return seaf_db_statement_exists (db,
651 "SELECT repo_id FROM Repo WHERE repo_id = ?",
652 db_err, 1, "string", id);
653 }
654
655 gboolean
create_repo_fill_size(SeafDBRow * row,void * data)656 create_repo_fill_size (SeafDBRow *row, void *data)
657 {
658 SeafRepo **repo = data;
659 SeafBranch *head;
660
661 const char *repo_id = seaf_db_row_get_column_text (row, 0);
662 gint64 size = seaf_db_row_get_column_int64 (row, 1);
663 const char *commit_id = seaf_db_row_get_column_text (row, 2);
664 const char *vrepo_id = seaf_db_row_get_column_text (row, 3);
665 gint64 file_count = seaf_db_row_get_column_int64 (row, 7);
666 int status = seaf_db_row_get_column_int(row, 8);
667
668 *repo = seaf_repo_new (repo_id, NULL, NULL);
669 if (!*repo)
670 return FALSE;
671
672 if (!commit_id) {
673 (*repo)->is_corrupted = TRUE;
674 return FALSE;
675 }
676
677 (*repo)->size = size;
678 (*repo)->file_count = file_count;
679 head = seaf_branch_new ("master", repo_id, commit_id);
680 (*repo)->head = head;
681 (*repo)->status = status;
682
683 if (vrepo_id) {
684 const char *origin_repo_id = seaf_db_row_get_column_text (row, 4);
685 const char *origin_path = seaf_db_row_get_column_text (row, 5);
686 const char *base_commit = seaf_db_row_get_column_text (row, 6);
687
688 SeafVirtRepo *vinfo = g_new0 (SeafVirtRepo, 1);
689 memcpy (vinfo->repo_id, vrepo_id, 36);
690 memcpy (vinfo->origin_repo_id, origin_repo_id, 36);
691 vinfo->path = g_strdup(origin_path);
692 memcpy (vinfo->base_commit, base_commit, 40);
693
694 (*repo)->virtual_info = vinfo;
695 memcpy ((*repo)->store_id, origin_repo_id, 36);
696 } else {
697 memcpy ((*repo)->store_id, repo_id, 36);
698 }
699
700 return TRUE;
701 }
702
703 static SeafRepo*
get_repo_from_db(SeafRepoManager * mgr,const char * id,gboolean * db_err)704 get_repo_from_db (SeafRepoManager *mgr, const char *id, gboolean *db_err)
705 {
706 SeafRepo *repo = NULL;
707 const char *sql;
708
709 if (seaf_db_type(mgr->seaf->db) != SEAF_DB_TYPE_PGSQL)
710 sql = "SELECT r.repo_id, s.size, b.commit_id, "
711 "v.repo_id, v.origin_repo, v.path, v.base_commit, fc.file_count, i.status FROM "
712 "Repo r LEFT JOIN Branch b ON r.repo_id = b.repo_id "
713 "LEFT JOIN RepoSize s ON r.repo_id = s.repo_id "
714 "LEFT JOIN VirtualRepo v ON r.repo_id = v.repo_id "
715 "LEFT JOIN RepoFileCount fc ON r.repo_id = fc.repo_id "
716 "LEFT JOIN RepoInfo i on r.repo_id = i.repo_id "
717 "WHERE r.repo_id = ? AND b.name = 'master'";
718 else
719 sql = "SELECT r.repo_id, s.\"size\", b.commit_id, "
720 "v.repo_id, v.origin_repo, v.path, v.base_commit, fc.file_count, i.status FROM "
721 "Repo r LEFT JOIN Branch b ON r.repo_id = b.repo_id "
722 "LEFT JOIN RepoSize s ON r.repo_id = s.repo_id "
723 "LEFT JOIN VirtualRepo v ON r.repo_id = v.repo_id "
724 "LEFT JOIN RepoFileCount fc ON r.repo_id = fc.repo_id "
725 "LEFT JOIN RepoInfo i on r.repo_id = i.repo_id "
726 "WHERE r.repo_id = ? AND b.name = 'master'";
727
728 int ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
729 create_repo_fill_size, &repo,
730 1, "string", id);
731 if (ret < 0)
732 *db_err = TRUE;
733
734 return repo;
735 }
736
737 SeafRepo*
seaf_repo_manager_get_repo(SeafRepoManager * manager,const gchar * id)738 seaf_repo_manager_get_repo (SeafRepoManager *manager, const gchar *id)
739 {
740 int len = strlen(id);
741 SeafRepo *repo = NULL;
742 gboolean has_err = FALSE;
743
744 if (len >= 37)
745 return NULL;
746
747 repo = get_repo_from_db (manager, id, &has_err);
748
749 if (repo) {
750 if (repo->is_corrupted) {
751 seaf_repo_unref (repo);
752 return NULL;
753 }
754
755 load_repo (manager, repo);
756 if (repo->is_corrupted) {
757 seaf_repo_unref (repo);
758 return NULL;
759 }
760 }
761
762 return repo;
763 }
764
765 SeafRepo*
seaf_repo_manager_get_repo_ex(SeafRepoManager * manager,const gchar * id)766 seaf_repo_manager_get_repo_ex (SeafRepoManager *manager, const gchar *id)
767 {
768 int len = strlen(id);
769 gboolean has_err = FALSE;
770 SeafRepo *ret = NULL;
771
772 if (len >= 37)
773 return NULL;
774
775 ret = get_repo_from_db (manager, id, &has_err);
776 if (has_err) {
777 ret = seaf_repo_new(id, NULL, NULL);
778 ret->is_corrupted = TRUE;
779 return ret;
780 }
781
782 if (ret) {
783 if (ret->is_corrupted) {
784 return ret;
785 }
786
787 load_repo (manager, ret);
788 }
789
790 return ret;
791 }
792
793 gboolean
seaf_repo_manager_repo_exists(SeafRepoManager * manager,const gchar * id)794 seaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id)
795 {
796 gboolean db_err = FALSE;
797 return repo_exists_in_db (manager->seaf->db, id, &db_err);
798 }
799
800 static int
save_branch_repo_map(SeafRepoManager * manager,SeafBranch * branch)801 save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch)
802 {
803 if (seaf_db_type(seaf->db) == SEAF_DB_TYPE_PGSQL) {
804 gboolean exists, err;
805 int rc;
806
807 exists = seaf_db_statement_exists (seaf->db,
808 "SELECT repo_id FROM RepoHead WHERE repo_id=?",
809 &err, 1, "string", branch->repo_id);
810 if (err)
811 return -1;
812
813 if (exists)
814 rc = seaf_db_statement_query (seaf->db,
815 "UPDATE RepoHead SET branch_name=? "
816 "WHERE repo_id=?",
817 2, "string", branch->name,
818 "string", branch->repo_id);
819 else
820 rc = seaf_db_statement_query (seaf->db,
821 "INSERT INTO RepoHead (repo_id, branch_name) VALUES (?, ?)",
822 2, "string", branch->repo_id,
823 "string", branch->name);
824 return rc;
825 } else {
826 return seaf_db_statement_query (seaf->db,
827 "REPLACE INTO RepoHead (repo_id, branch_name) VALUES (?, ?)",
828 2, "string", branch->repo_id,
829 "string", branch->name);
830 }
831
832 return -1;
833 }
834
835 int
seaf_repo_manager_branch_repo_unmap(SeafRepoManager * manager,SeafBranch * branch)836 seaf_repo_manager_branch_repo_unmap (SeafRepoManager *manager, SeafBranch *branch)
837 {
838 return seaf_db_statement_query (seaf->db,
839 "DELETE FROM RepoHead WHERE branch_name = ?"
840 " AND repo_id = ?",
841 2, "string", branch->name,
842 "string", branch->repo_id);
843 }
844
845 int
set_repo_commit_to_db(const char * repo_id,const char * repo_name,gint64 update_time,int version,gboolean is_encrypted,const char * last_modifier)846 set_repo_commit_to_db (const char *repo_id, const char *repo_name, gint64 update_time,
847 int version, gboolean is_encrypted, const char *last_modifier)
848 {
849 char *sql;
850 gboolean exists = FALSE, db_err = FALSE;
851
852 sql = "SELECT 1 FROM RepoInfo WHERE repo_id=?";
853 exists = seaf_db_statement_exists (seaf->db, sql, &db_err, 1, "string", repo_id);
854 if (db_err)
855 return -1;
856
857 if (update_time == 0)
858 update_time = (gint64)time(NULL);
859
860 if (exists) {
861 sql = "UPDATE RepoInfo SET name=?, update_time=?, version=?, is_encrypted=?, "
862 "last_modifier=? WHERE repo_id=?";
863 if (seaf_db_statement_query (seaf->db, sql, 6,
864 "string", repo_name,
865 "int64", update_time,
866 "int", version,
867 "int", (is_encrypted ? 1:0),
868 "string", last_modifier,
869 "string", repo_id) < 0) {
870 seaf_warning ("Failed to update repo info for repo %s.\n", repo_id);
871 return -1;
872 }
873 } else {
874 sql = "INSERT INTO RepoInfo (repo_id, name, update_time, version, is_encrypted, last_modifier) "
875 "VALUES (?, ?, ?, ?, ?, ?)";
876 if (seaf_db_statement_query (seaf->db, sql, 6,
877 "string", repo_id,
878 "string", repo_name,
879 "int64", update_time,
880 "int", version,
881 "int", (is_encrypted ? 1:0),
882 "string", last_modifier) < 0) {
883 seaf_warning ("Failed to add repo info for repo %s.\n", repo_id);
884 return -1;
885 }
886 }
887
888 return 0;
889 }
890
891 static void
load_repo_commit(SeafRepoManager * manager,SeafRepo * repo)892 load_repo_commit (SeafRepoManager *manager,
893 SeafRepo *repo)
894 {
895 SeafCommit *commit;
896
897 commit = seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr,
898 repo->id,
899 repo->head->commit_id);
900 if (!commit) {
901 seaf_warning ("Commit %s:%s is missing\n", repo->id, repo->head->commit_id);
902 repo->is_corrupted = TRUE;
903 return;
904 }
905
906 seaf_repo_from_commit (repo, commit);
907
908 seaf_commit_unref (commit);
909 }
910
911 static void
load_repo(SeafRepoManager * manager,SeafRepo * repo)912 load_repo (SeafRepoManager *manager, SeafRepo *repo)
913 {
914 repo->manager = manager;
915
916 load_repo_commit (manager, repo);
917 }
918
919 static void
load_mini_repo(SeafRepoManager * manager,SeafRepo * repo)920 load_mini_repo (SeafRepoManager *manager, SeafRepo *repo)
921 {
922 repo->manager = manager;
923 SeafCommit *commit;
924
925 commit = seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr,
926 repo->id,
927 repo->head->commit_id);
928 if (!commit) {
929 seaf_warning ("Commit %s:%s is missing\n", repo->id, repo->head->commit_id);
930 repo->is_corrupted = TRUE;
931 return;
932 }
933
934 repo->name = g_strdup (commit->repo_name);
935 repo->encrypted = commit->encrypted;
936 repo->last_modify = commit->ctime;
937 repo->version = commit->version;
938 repo->last_modifier = g_strdup (commit->creator_name);
939
940 seaf_commit_unref (commit);
941 }
942
943 static int
create_tables_mysql(SeafRepoManager * mgr)944 create_tables_mysql (SeafRepoManager *mgr)
945 {
946 SeafDB *db = mgr->seaf->db;
947 char *sql;
948
949 sql = "CREATE TABLE IF NOT EXISTS Repo (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
950 "repo_id CHAR(37), UNIQUE INDEX (repo_id))"
951 "ENGINE=INNODB";
952 if (seaf_db_query (db, sql) < 0)
953 return -1;
954
955 sql = "CREATE TABLE IF NOT EXISTS RepoOwner ("
956 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
957 "repo_id CHAR(37), "
958 "owner_id VARCHAR(255),"
959 "UNIQUE INDEX (repo_id), INDEX (owner_id))"
960 "ENGINE=INNODB";
961 if (seaf_db_query (db, sql) < 0)
962 return -1;
963
964 sql = "CREATE TABLE IF NOT EXISTS RepoGroup (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,"
965 "repo_id CHAR(37), "
966 "group_id INTEGER, user_name VARCHAR(255), permission CHAR(15), "
967 "UNIQUE INDEX (group_id, repo_id), "
968 "INDEX (repo_id), INDEX (user_name))"
969 "ENGINE=INNODB";
970 if (seaf_db_query (db, sql) < 0)
971 return -1;
972
973 sql = "CREATE TABLE IF NOT EXISTS InnerPubRepo ("
974 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
975 "repo_id CHAR(37),"
976 "permission CHAR(15), UNIQUE INDEX (repo_id))"
977 "ENGINE=INNODB";
978 if (seaf_db_query (db, sql) < 0)
979 return -1;
980
981 sql = "CREATE TABLE IF NOT EXISTS RepoUserToken ("
982 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
983 "repo_id CHAR(37), "
984 "email VARCHAR(255), "
985 "token CHAR(41), "
986 "UNIQUE INDEX (repo_id, token), INDEX (email))"
987 "ENGINE=INNODB";
988 if (seaf_db_query (db, sql) < 0)
989 return -1;
990
991 sql = "CREATE TABLE IF NOT EXISTS RepoTokenPeerInfo ("
992 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
993 "token CHAR(41), "
994 "peer_id CHAR(41), "
995 "peer_ip VARCHAR(41), "
996 "peer_name VARCHAR(255), "
997 "sync_time BIGINT, "
998 "client_ver VARCHAR(20), UNIQUE INDEX(token))"
999 "ENGINE=INNODB";
1000 if (seaf_db_query (db, sql) < 0)
1001 return -1;
1002
1003 sql = "CREATE TABLE IF NOT EXISTS RepoHead ("
1004 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1005 "repo_id CHAR(37), branch_name VARCHAR(10), UNIQUE INDEX(repo_id))"
1006 "ENGINE=INNODB";
1007 if (seaf_db_query (db, sql) < 0)
1008 return -1;
1009
1010 sql = "CREATE TABLE IF NOT EXISTS RepoSize ("
1011 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1012 "repo_id CHAR(37),"
1013 "size BIGINT UNSIGNED,"
1014 "head_id CHAR(41), UNIQUE INDEX (repo_id))"
1015 "ENGINE=INNODB";
1016 if (seaf_db_query (db, sql) < 0)
1017 return -1;
1018
1019 sql = "CREATE TABLE IF NOT EXISTS RepoHistoryLimit ("
1020 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1021 "repo_id CHAR(37), days INTEGER, UNIQUE INDEX(repo_id))"
1022 "ENGINE=INNODB";
1023 if (seaf_db_query (db, sql) < 0)
1024 return -1;
1025
1026 sql = "CREATE TABLE IF NOT EXISTS RepoValidSince ("
1027 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1028 "repo_id CHAR(37), timestamp BIGINT, UNIQUE INDEX(repo_id))"
1029 "ENGINE=INNODB";
1030 if (seaf_db_query (db, sql) < 0)
1031 return -1;
1032
1033 sql = "CREATE TABLE IF NOT EXISTS WebAP (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1034 "repo_id CHAR(37), "
1035 "access_property CHAR(10), UNIQUE INDEX(repo_id))"
1036 "ENGINE=INNODB";
1037 if (seaf_db_query (db, sql) < 0)
1038 return -1;
1039
1040 sql = "CREATE TABLE IF NOT EXISTS VirtualRepo (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1041 "repo_id CHAR(36),"
1042 "origin_repo CHAR(36), path TEXT, base_commit CHAR(40), UNIQUE INDEX(repo_id), INDEX(origin_repo))"
1043 "ENGINE=INNODB";
1044 if (seaf_db_query (db, sql) < 0)
1045 return -1;
1046
1047 sql = "CREATE TABLE IF NOT EXISTS GarbageRepos (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1048 "repo_id CHAR(36), UNIQUE INDEX(repo_id))";
1049 if (seaf_db_query (db, sql) < 0)
1050 return -1;
1051
1052 sql = "CREATE TABLE IF NOT EXISTS RepoTrash (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1053 "repo_id CHAR(36),"
1054 "repo_name VARCHAR(255), head_id CHAR(40), owner_id VARCHAR(255),"
1055 "size BIGINT(20), org_id INTEGER, del_time BIGINT, "
1056 "UNIQUE INDEX(repo_id), INDEX(owner_id), INDEX(org_id))ENGINE=INNODB";
1057 if (seaf_db_query (db, sql) < 0)
1058 return -1;
1059
1060 sql = "CREATE TABLE IF NOT EXISTS RepoFileCount ("
1061 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1062 "repo_id CHAR(36),"
1063 "file_count BIGINT UNSIGNED, UNIQUE INDEX(repo_id))ENGINE=INNODB";
1064 if (seaf_db_query (db, sql) < 0)
1065 return -1;
1066
1067 sql = "CREATE TABLE IF NOT EXISTS RepoInfo (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
1068 "repo_id CHAR(36), "
1069 "name VARCHAR(255) NOT NULL, update_time BIGINT, version INTEGER, "
1070 "is_encrypted INTEGER, last_modifier VARCHAR(255), status INTEGER DEFAULT 0, UNIQUE INDEX(repo_id)) ENGINE=INNODB";
1071 if (seaf_db_query (db, sql) < 0)
1072 return -1;
1073
1074 sql = "CREATE TABLE IF NOT EXISTS WebUploadTempFiles ( "
1075 "id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, repo_id CHAR(40) NOT NULL, "
1076 "file_path TEXT NOT NULL, tmp_file_path TEXT NOT NULL) ENGINE=INNODB";
1077 if (seaf_db_query (db, sql) < 0)
1078 return -1;
1079
1080 return 0;
1081 }
1082
1083 static int
create_tables_sqlite(SeafRepoManager * mgr)1084 create_tables_sqlite (SeafRepoManager *mgr)
1085 {
1086 SeafDB *db = mgr->seaf->db;
1087 char *sql;
1088
1089 sql = "CREATE TABLE IF NOT EXISTS Repo (repo_id CHAR(37) PRIMARY KEY)";
1090 if (seaf_db_query (db, sql) < 0)
1091 return -1;
1092
1093 /* Owner */
1094
1095 sql = "CREATE TABLE IF NOT EXISTS RepoOwner ("
1096 "repo_id CHAR(37) PRIMARY KEY, "
1097 "owner_id TEXT)";
1098 if (seaf_db_query (db, sql) < 0)
1099 return -1;
1100 sql = "CREATE INDEX IF NOT EXISTS OwnerIndex ON RepoOwner (owner_id)";
1101 if (seaf_db_query (db, sql) < 0)
1102 return -1;
1103
1104 /* Group repo */
1105
1106 sql = "CREATE TABLE IF NOT EXISTS RepoGroup (repo_id CHAR(37), "
1107 "group_id INTEGER, user_name TEXT, permission CHAR(15))";
1108 if (seaf_db_query (db, sql) < 0)
1109 return -1;
1110
1111 sql = "CREATE UNIQUE INDEX IF NOT EXISTS groupid_repoid_indx on "
1112 "RepoGroup (group_id, repo_id)";
1113 if (seaf_db_query (db, sql) < 0)
1114 return -1;
1115
1116 sql = "CREATE INDEX IF NOT EXISTS repogroup_repoid_index on "
1117 "RepoGroup (repo_id)";
1118 if (seaf_db_query (db, sql) < 0)
1119 return -1;
1120
1121 sql = "CREATE INDEX IF NOT EXISTS repogroup_username_indx on "
1122 "RepoGroup (user_name)";
1123 if (seaf_db_query (db, sql) < 0)
1124 return -1;
1125
1126 /* Public repo */
1127
1128 sql = "CREATE TABLE IF NOT EXISTS InnerPubRepo ("
1129 "repo_id CHAR(37) PRIMARY KEY,"
1130 "permission CHAR(15))";
1131 if (seaf_db_query (db, sql) < 0)
1132 return -1;
1133
1134 sql = "CREATE TABLE IF NOT EXISTS RepoUserToken ("
1135 "repo_id CHAR(37), "
1136 "email VARCHAR(255), "
1137 "token CHAR(41))";
1138 if (seaf_db_query (db, sql) < 0)
1139 return -1;
1140
1141 sql = "CREATE UNIQUE INDEX IF NOT EXISTS repo_token_indx on "
1142 "RepoUserToken (repo_id, token)";
1143 if (seaf_db_query (db, sql) < 0)
1144 return -1;
1145
1146 sql = "CREATE INDEX IF NOT EXISTS repo_token_email_indx on "
1147 "RepoUserToken (email)";
1148 if (seaf_db_query (db, sql) < 0)
1149 return -1;
1150
1151 sql = "CREATE TABLE IF NOT EXISTS RepoTokenPeerInfo ("
1152 "token CHAR(41) PRIMARY KEY, "
1153 "peer_id CHAR(41), "
1154 "peer_ip VARCHAR(41), "
1155 "peer_name VARCHAR(255), "
1156 "sync_time BIGINT, "
1157 "client_ver VARCHAR(20))";
1158 if (seaf_db_query (db, sql) < 0)
1159 return -1;
1160
1161 sql = "CREATE TABLE IF NOT EXISTS RepoHead ("
1162 "repo_id CHAR(37) PRIMARY KEY, branch_name VARCHAR(10))";
1163 if (seaf_db_query (db, sql) < 0)
1164 return -1;
1165
1166 sql = "CREATE TABLE IF NOT EXISTS RepoSize ("
1167 "repo_id CHAR(37) PRIMARY KEY,"
1168 "size BIGINT UNSIGNED,"
1169 "head_id CHAR(41))";
1170 if (seaf_db_query (db, sql) < 0)
1171 return -1;
1172
1173 sql = "CREATE TABLE IF NOT EXISTS RepoHistoryLimit ("
1174 "repo_id CHAR(37) PRIMARY KEY, days INTEGER)";
1175 if (seaf_db_query (db, sql) < 0)
1176 return -1;
1177
1178 sql = "CREATE TABLE IF NOT EXISTS RepoValidSince ("
1179 "repo_id CHAR(37) PRIMARY KEY, timestamp BIGINT)";
1180 if (seaf_db_query (db, sql) < 0)
1181 return -1;
1182
1183 sql = "CREATE TABLE IF NOT EXISTS WebAP (repo_id CHAR(37) PRIMARY KEY, "
1184 "access_property CHAR(10))";
1185 if (seaf_db_query (db, sql) < 0)
1186 return -1;
1187
1188 sql = "CREATE TABLE IF NOT EXISTS VirtualRepo (repo_id CHAR(36) PRIMARY KEY,"
1189 "origin_repo CHAR(36), path TEXT, base_commit CHAR(40))";
1190 if (seaf_db_query (db, sql) < 0)
1191 return -1;
1192
1193 sql = "CREATE INDEX IF NOT EXISTS virtualrepo_origin_repo_idx "
1194 "ON VirtualRepo (origin_repo)";
1195 if (seaf_db_query (db, sql) < 0)
1196 return -1;
1197
1198 sql = "CREATE TABLE IF NOT EXISTS GarbageRepos (repo_id CHAR(36) PRIMARY KEY)";
1199 if (seaf_db_query (db, sql) < 0)
1200 return -1;
1201
1202 sql = "CREATE TABLE IF NOT EXISTS RepoTrash (repo_id CHAR(36) PRIMARY KEY,"
1203 "repo_name VARCHAR(255), head_id CHAR(40), owner_id VARCHAR(255), size BIGINT UNSIGNED,"
1204 "org_id INTEGER, del_time BIGINT)";
1205 if (seaf_db_query (db, sql) < 0)
1206 return -1;
1207
1208 sql = "CREATE INDEX IF NOT EXISTS repotrash_owner_id_idx ON RepoTrash(owner_id)";
1209 if (seaf_db_query (db, sql) < 0)
1210 return -1;
1211
1212 sql = "CREATE INDEX IF NOT EXISTS repotrash_org_id_idx ON RepoTrash(org_id)";
1213 if (seaf_db_query (db, sql) < 0)
1214 return -1;
1215
1216 sql = "CREATE TABLE IF NOT EXISTS RepoFileCount ("
1217 "repo_id CHAR(36) PRIMARY KEY,"
1218 "file_count BIGINT UNSIGNED)";
1219 if (seaf_db_query (db, sql) < 0)
1220 return -1;
1221
1222 sql = "CREATE TABLE IF NOT EXISTS RepoInfo (repo_id CHAR(36) PRIMARY KEY, "
1223 "name VARCHAR(255) NOT NULL, update_time INTEGER, version INTEGER, "
1224 "is_encrypted INTEGER, last_modifier VARCHAR(255), status INTEGER DEFAULT 0)";
1225 if (seaf_db_query (db, sql) < 0)
1226 return -1;
1227
1228 sql = "CREATE TABLE IF NOT EXISTS WebUploadTempFiles (repo_id CHAR(40) NOT NULL, "
1229 "file_path TEXT NOT NULL, tmp_file_path TEXT NOT NULL)";
1230 if (seaf_db_query (db, sql) < 0)
1231 return -1;
1232
1233 return 0;
1234 }
1235
1236 /* static int */
1237 /* create_tables_pgsql (SeafRepoManager *mgr) */
1238 /* { */
1239 /* SeafDB *db = mgr->seaf->db; */
1240 /* char *sql; */
1241
1242 /* sql = "CREATE TABLE IF NOT EXISTS Repo (repo_id CHAR(36) PRIMARY KEY)"; */
1243 /* if (seaf_db_query (db, sql) < 0) */
1244 /* return -1; */
1245
1246 /* sql = "CREATE TABLE IF NOT EXISTS RepoOwner (" */
1247 /* "repo_id CHAR(36) PRIMARY KEY, " */
1248 /* "owner_id VARCHAR(255))"; */
1249 /* if (seaf_db_query (db, sql) < 0) */
1250 /* return -1; */
1251
1252 /* if (!pgsql_index_exists (db, "repoowner_owner_idx")) { */
1253 /* sql = "CREATE INDEX repoowner_owner_idx ON RepoOwner (owner_id)"; */
1254 /* if (seaf_db_query (db, sql) < 0) */
1255 /* return -1; */
1256 /* } */
1257
1258 /* sql = "CREATE TABLE IF NOT EXISTS RepoGroup (repo_id CHAR(36), " */
1259 /* "group_id INTEGER, user_name VARCHAR(255), permission VARCHAR(15), " */
1260 /* "UNIQUE (group_id, repo_id))"; */
1261 /* if (seaf_db_query (db, sql) < 0) */
1262 /* return -1; */
1263
1264 /* if (!pgsql_index_exists (db, "repogroup_repoid_idx")) { */
1265 /* sql = "CREATE INDEX repogroup_repoid_idx ON RepoGroup (repo_id)"; */
1266 /* if (seaf_db_query (db, sql) < 0) */
1267 /* return -1; */
1268 /* } */
1269
1270 /* if (!pgsql_index_exists (db, "repogroup_username_idx")) { */
1271 /* sql = "CREATE INDEX repogroup_username_idx ON RepoGroup (user_name)"; */
1272 /* if (seaf_db_query (db, sql) < 0) */
1273 /* return -1; */
1274 /* } */
1275
1276 /* sql = "CREATE TABLE IF NOT EXISTS InnerPubRepo (" */
1277 /* "repo_id CHAR(36) PRIMARY KEY," */
1278 /* "permission VARCHAR(15))"; */
1279 /* if (seaf_db_query (db, sql) < 0) */
1280 /* return -1; */
1281
1282 /* sql = "CREATE TABLE IF NOT EXISTS RepoUserToken (" */
1283 /* "repo_id CHAR(36), " */
1284 /* "email VARCHAR(255), " */
1285 /* "token CHAR(40), " */
1286 /* "UNIQUE (repo_id, token))"; */
1287 /* if (seaf_db_query (db, sql) < 0) */
1288 /* return -1; */
1289
1290 /* if (!pgsql_index_exists (db, "repousertoken_email_idx")) { */
1291 /* sql = "CREATE INDEX repousertoken_email_idx ON RepoUserToken (email)"; */
1292 /* if (seaf_db_query (db, sql) < 0) */
1293 /* return -1; */
1294 /* } */
1295
1296 /* sql = "CREATE TABLE IF NOT EXISTS RepoTokenPeerInfo (" */
1297 /* "token CHAR(40) PRIMARY KEY, " */
1298 /* "peer_id CHAR(40), " */
1299 /* "peer_ip VARCHAR(40), " */
1300 /* "peer_name VARCHAR(255), " */
1301 /* "sync_time BIGINT, " */
1302 /* "client_ver VARCHAR(20))"; */
1303 /* if (seaf_db_query (db, sql) < 0) */
1304 /* return -1; */
1305
1306 /* sql = "CREATE TABLE IF NOT EXISTS RepoHead (" */
1307 /* "repo_id CHAR(36) PRIMARY KEY, branch_name VARCHAR(10))"; */
1308 /* if (seaf_db_query (db, sql) < 0) */
1309 /* return -1; */
1310
1311 /* sql = "CREATE TABLE IF NOT EXISTS RepoSize (" */
1312 /* "repo_id CHAR(36) PRIMARY KEY," */
1313 /* "size BIGINT," */
1314 /* "head_id CHAR(40))"; */
1315 /* if (seaf_db_query (db, sql) < 0) */
1316 /* return -1; */
1317
1318 /* sql = "CREATE TABLE IF NOT EXISTS RepoHistoryLimit (" */
1319 /* "repo_id CHAR(36) PRIMARY KEY, days INTEGER)"; */
1320 /* if (seaf_db_query (db, sql) < 0) */
1321 /* return -1; */
1322
1323 /* sql = "CREATE TABLE IF NOT EXISTS RepoValidSince (" */
1324 /* "repo_id CHAR(36) PRIMARY KEY, timestamp BIGINT)"; */
1325 /* if (seaf_db_query (db, sql) < 0) */
1326 /* return -1; */
1327
1328 /* sql = "CREATE TABLE IF NOT EXISTS WebAP (repo_id CHAR(36) PRIMARY KEY, " */
1329 /* "access_property VARCHAR(10))"; */
1330 /* if (seaf_db_query (db, sql) < 0) */
1331 /* return -1; */
1332
1333 /* sql = "CREATE TABLE IF NOT EXISTS VirtualRepo (repo_id CHAR(36) PRIMARY KEY," */
1334 /* "origin_repo CHAR(36), path TEXT, base_commit CHAR(40))"; */
1335 /* if (seaf_db_query (db, sql) < 0) */
1336 /* return -1; */
1337
1338 /* if (!pgsql_index_exists (db, "virtualrepo_origin_repo_idx")) { */
1339 /* sql = "CREATE INDEX virtualrepo_origin_repo_idx ON VirtualRepo (origin_repo)"; */
1340 /* if (seaf_db_query (db, sql) < 0) */
1341 /* return -1; */
1342 /* } */
1343
1344 /* sql = "CREATE TABLE IF NOT EXISTS GarbageRepos (repo_id CHAR(36) PRIMARY KEY)"; */
1345 /* if (seaf_db_query (db, sql) < 0) */
1346 /* return -1; */
1347
1348 /* sql = "CREATE TABLE IF NOT EXISTS RepoTrash (repo_id CHAR(36) PRIMARY KEY," */
1349 /* "repo_name VARCHAR(255), head_id CHAR(40), owner_id VARCHAR(255), size bigint," */
1350 /* "org_id INTEGER, del_time BIGINT)"; */
1351 /* if (seaf_db_query (db, sql) < 0) */
1352 /* return -1; */
1353
1354 /* if (!pgsql_index_exists (db, "repotrash_owner_id")) { */
1355 /* sql = "CREATE INDEX repotrash_owner_id on RepoTrash(owner_id)"; */
1356 /* if (seaf_db_query (db, sql) < 0) */
1357 /* return -1; */
1358 /* } */
1359 /* if (!pgsql_index_exists (db, "repotrash_org_id")) { */
1360 /* sql = "CREATE INDEX repotrash_org_id on RepoTrash(org_id)"; */
1361 /* if (seaf_db_query (db, sql) < 0) */
1362 /* return -1; */
1363 /* } */
1364
1365 /* sql = "CREATE TABLE IF NOT EXISTS RepoFileCount (" */
1366 /* "repo_id CHAR(36) PRIMARY KEY," */
1367 /* "file_count BIGINT)"; */
1368 /* if (seaf_db_query (db, sql) < 0) */
1369 /* return -1; */
1370
1371 /* sql = "CREATE TABLE IF NOT EXISTS WebUploadTempFiles (repo_id CHAR(40) NOT NULL, " */
1372 /* "file_path TEXT NOT NULL, tmp_file_path TEXT NOT NULL)"; */
1373 /* if (seaf_db_query (db, sql) < 0) */
1374 /* return -1; */
1375
1376 /* sql = "CREATE TABLE IF NOT EXISTS RepoInfo (repo_id CHAR(36) PRIMARY KEY, " */
1377 /* "name VARCHAR(255) NOT NULL, update_time BIGINT, version INTEGER, " */
1378 /* "is_encrypted INTEGER, last_modifier VARCHAR(255), status INTEGER DEFAULT 0)"; */
1379 /* if (seaf_db_query (db, sql) < 0) */
1380 /* return -1; */
1381
1382 /* return 0; */
1383 /* } */
1384
1385 static int
create_db_tables_if_not_exist(SeafRepoManager * mgr)1386 create_db_tables_if_not_exist (SeafRepoManager *mgr)
1387 {
1388 if (!mgr->seaf->create_tables && seaf_db_type (mgr->seaf->db) != SEAF_DB_TYPE_PGSQL)
1389 return 0;
1390
1391 SeafDB *db = mgr->seaf->db;
1392 int db_type = seaf_db_type (db);
1393
1394 if (db_type == SEAF_DB_TYPE_MYSQL)
1395 return create_tables_mysql (mgr);
1396 else if (db_type == SEAF_DB_TYPE_SQLITE)
1397 return create_tables_sqlite (mgr);
1398 /* else if (db_type == SEAF_DB_TYPE_PGSQL) */
1399 /* return create_tables_pgsql (mgr); */
1400
1401 g_return_val_if_reached (-1);
1402 }
1403
1404 /*
1405 * Repo properties functions.
1406 */
1407
1408 static inline char *
generate_repo_token()1409 generate_repo_token ()
1410 {
1411 char *uuid = gen_uuid ();
1412 unsigned char sha1[20];
1413 char token[41];
1414 SHA_CTX s;
1415
1416 SHA1_Init (&s);
1417 SHA1_Update (&s, uuid, strlen(uuid));
1418 SHA1_Final (sha1, &s);
1419
1420 rawdata_to_hex (sha1, token, 20);
1421
1422 g_free (uuid);
1423
1424 return g_strdup (token);
1425 }
1426
1427 static int
add_repo_token(SeafRepoManager * mgr,const char * repo_id,const char * email,const char * token,GError ** error)1428 add_repo_token (SeafRepoManager *mgr,
1429 const char *repo_id,
1430 const char *email,
1431 const char *token,
1432 GError **error)
1433 {
1434 int rc = seaf_db_statement_query (mgr->seaf->db,
1435 "INSERT INTO RepoUserToken (repo_id, email, token) VALUES (?, ?, ?)",
1436 3, "string", repo_id, "string", email,
1437 "string", token);
1438
1439 if (rc < 0) {
1440 seaf_warning ("failed to add repo token. repo = %s, email = %s\n",
1441 repo_id, email);
1442 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "DB error");
1443 return -1;
1444 }
1445
1446 return 0;
1447 }
1448
1449 char *
seaf_repo_manager_generate_repo_token(SeafRepoManager * mgr,const char * repo_id,const char * email,GError ** error)1450 seaf_repo_manager_generate_repo_token (SeafRepoManager *mgr,
1451 const char *repo_id,
1452 const char *email,
1453 GError **error)
1454 {
1455 char *token = generate_repo_token ();
1456 if (add_repo_token (mgr, repo_id, email, token, error) < 0) {
1457 g_free (token);
1458 return NULL;
1459 }
1460
1461 return token;
1462 }
1463
1464 int
seaf_repo_manager_add_token_peer_info(SeafRepoManager * mgr,const char * token,const char * peer_id,const char * peer_ip,const char * peer_name,gint64 sync_time,const char * client_ver)1465 seaf_repo_manager_add_token_peer_info (SeafRepoManager *mgr,
1466 const char *token,
1467 const char *peer_id,
1468 const char *peer_ip,
1469 const char *peer_name,
1470 gint64 sync_time,
1471 const char *client_ver)
1472 {
1473 int ret = 0;
1474
1475 if (seaf_db_statement_query (mgr->seaf->db,
1476 "INSERT INTO RepoTokenPeerInfo (token, peer_id, peer_ip, peer_name, sync_time, client_ver)"
1477 "VALUES (?, ?, ?, ?, ?, ?)",
1478 6, "string", token,
1479 "string", peer_id,
1480 "string", peer_ip,
1481 "string", peer_name,
1482 "int64", sync_time,
1483 "string", client_ver) < 0)
1484 ret = -1;
1485
1486 return ret;
1487 }
1488
1489 int
seaf_repo_manager_update_token_peer_info(SeafRepoManager * mgr,const char * token,const char * peer_ip,gint64 sync_time,const char * client_ver)1490 seaf_repo_manager_update_token_peer_info (SeafRepoManager *mgr,
1491 const char *token,
1492 const char *peer_ip,
1493 gint64 sync_time,
1494 const char *client_ver)
1495 {
1496 int ret = 0;
1497
1498 if (seaf_db_statement_query (mgr->seaf->db,
1499 "UPDATE RepoTokenPeerInfo SET "
1500 "peer_ip=?, sync_time=?, client_ver=? WHERE token=?",
1501 4, "string", peer_ip,
1502 "int64", sync_time,
1503 "string", client_ver,
1504 "string", token) < 0)
1505 ret = -1;
1506
1507 return ret;
1508 }
1509
1510 gboolean
seaf_repo_manager_token_peer_info_exists(SeafRepoManager * mgr,const char * token)1511 seaf_repo_manager_token_peer_info_exists (SeafRepoManager *mgr,
1512 const char *token)
1513 {
1514 gboolean db_error = FALSE;
1515
1516 return seaf_db_statement_exists (mgr->seaf->db,
1517 "SELECT token FROM RepoTokenPeerInfo WHERE token=?",
1518 &db_error, 1, "string", token);
1519 }
1520
1521 int
seaf_repo_manager_delete_token(SeafRepoManager * mgr,const char * repo_id,const char * token,const char * user,GError ** error)1522 seaf_repo_manager_delete_token (SeafRepoManager *mgr,
1523 const char *repo_id,
1524 const char *token,
1525 const char *user,
1526 GError **error)
1527 {
1528 char *token_owner;
1529
1530 token_owner = seaf_repo_manager_get_email_by_token (mgr, repo_id, token);
1531 if (!token_owner || strcmp (user, token_owner) != 0) {
1532 seaf_warning ("Requesting user is %s, token owner is %s, "
1533 "refuse to delete token %.10s.\n", user, token_owner, token);
1534 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Permission denied");
1535 return -1;
1536 }
1537
1538 if (seaf_db_statement_query (mgr->seaf->db,
1539 "DELETE FROM RepoUserToken "
1540 "WHERE repo_id=? and token=?",
1541 2, "string", repo_id, "string", token) < 0) {
1542 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "DB error");
1543 return -1;
1544 }
1545
1546 if (seaf_db_statement_query (mgr->seaf->db,
1547 "DELETE FROM RepoTokenPeerInfo WHERE token=?",
1548 1, "string", token) < 0) {
1549 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "DB error");
1550 return -1;
1551 }
1552
1553 GList *tokens = NULL;
1554 tokens = g_list_append (tokens, g_strdup(token));
1555 seaf_http_server_invalidate_tokens (seaf->http_server, tokens);
1556 g_list_free_full (tokens, (GDestroyNotify)g_free);
1557
1558 return 0;
1559 }
1560
1561 static gboolean
collect_repo_token(SeafDBRow * row,void * data)1562 collect_repo_token (SeafDBRow *row, void *data)
1563 {
1564 GList **ret_list = data;
1565 const char *repo_id, *repo_owner, *email, *token;
1566 const char *peer_id, *peer_ip, *peer_name;
1567 gint64 sync_time;
1568 const char *client_ver;
1569
1570 repo_id = seaf_db_row_get_column_text (row, 0);
1571 repo_owner = seaf_db_row_get_column_text (row, 1);
1572 email = seaf_db_row_get_column_text (row, 2);
1573 token = seaf_db_row_get_column_text (row, 3);
1574
1575 peer_id = seaf_db_row_get_column_text (row, 4);
1576 peer_ip = seaf_db_row_get_column_text (row, 5);
1577 peer_name = seaf_db_row_get_column_text (row, 6);
1578 sync_time = seaf_db_row_get_column_int64 (row, 7);
1579 client_ver = seaf_db_row_get_column_text (row, 8);
1580
1581 char *owner_l = g_ascii_strdown (repo_owner, -1);
1582 char *email_l = g_ascii_strdown (email, -1);
1583
1584 SeafileRepoTokenInfo *repo_token_info;
1585 repo_token_info = g_object_new (SEAFILE_TYPE_REPO_TOKEN_INFO,
1586 "repo_id", repo_id,
1587 "repo_owner", owner_l,
1588 "email", email_l,
1589 "token", token,
1590 "peer_id", peer_id,
1591 "peer_ip", peer_ip,
1592 "peer_name", peer_name,
1593 "sync_time", sync_time,
1594 "client_ver", client_ver,
1595 NULL);
1596
1597 *ret_list = g_list_prepend (*ret_list, repo_token_info);
1598
1599 g_free (owner_l);
1600 g_free (email_l);
1601
1602 return TRUE;
1603 }
1604
1605 static void
fill_in_token_info(GList * info_list)1606 fill_in_token_info (GList *info_list)
1607 {
1608 GList *ptr;
1609 SeafileRepoTokenInfo *info;
1610 SeafRepo *repo;
1611 char *repo_name;
1612
1613 for (ptr = info_list; ptr; ptr = ptr->next) {
1614 info = ptr->data;
1615 repo = seaf_repo_manager_get_repo (seaf->repo_mgr,
1616 seafile_repo_token_info_get_repo_id(info));
1617 if (repo)
1618 repo_name = g_strdup(repo->name);
1619 else
1620 repo_name = g_strdup("Unknown");
1621 seaf_repo_unref (repo);
1622
1623 g_object_set (info, "repo_name", repo_name, NULL);
1624 g_free (repo_name);
1625 }
1626 }
1627
1628 GList *
seaf_repo_manager_list_repo_tokens(SeafRepoManager * mgr,const char * repo_id,GError ** error)1629 seaf_repo_manager_list_repo_tokens (SeafRepoManager *mgr,
1630 const char *repo_id,
1631 GError **error)
1632 {
1633 GList *ret_list = NULL;
1634 char *sql;
1635 gboolean db_err = FALSE;
1636
1637 if (!repo_exists_in_db (mgr->seaf->db, repo_id, &db_err)) {
1638 if (db_err) {
1639 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "DB error");
1640 }
1641 return NULL;
1642 }
1643
1644 sql = "SELECT u.repo_id, o.owner_id, u.email, u.token, "
1645 "p.peer_id, p.peer_ip, p.peer_name, p.sync_time, p.client_ver "
1646 "FROM RepoUserToken u LEFT JOIN RepoTokenPeerInfo p "
1647 "ON u.token = p.token, RepoOwner o "
1648 "WHERE u.repo_id = ? and o.repo_id = ? ";
1649
1650 int n_row = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
1651 collect_repo_token, &ret_list,
1652 2, "string", repo_id,
1653 "string", repo_id);
1654 if (n_row < 0) {
1655 seaf_warning ("DB error when get token info for repo %.10s.\n",
1656 repo_id);
1657 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "DB error");
1658 }
1659
1660 fill_in_token_info (ret_list);
1661
1662 return g_list_reverse(ret_list);
1663 }
1664
1665 GList *
seaf_repo_manager_list_repo_tokens_by_email(SeafRepoManager * mgr,const char * email,GError ** error)1666 seaf_repo_manager_list_repo_tokens_by_email (SeafRepoManager *mgr,
1667 const char *email,
1668 GError **error)
1669 {
1670 GList *ret_list = NULL;
1671 char *sql;
1672
1673 sql = "SELECT u.repo_id, o.owner_id, u.email, u.token, "
1674 "p.peer_id, p.peer_ip, p.peer_name, p.sync_time, p.client_ver "
1675 "FROM RepoUserToken u LEFT JOIN RepoTokenPeerInfo p "
1676 "ON u.token = p.token, RepoOwner o "
1677 "WHERE u.email = ? and u.repo_id = o.repo_id";
1678
1679 int n_row = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
1680 collect_repo_token, &ret_list,
1681 1, "string", email);
1682 if (n_row < 0) {
1683 seaf_warning ("DB error when get token info for email %s.\n",
1684 email);
1685 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "DB error");
1686 }
1687
1688 fill_in_token_info (ret_list);
1689
1690 return g_list_reverse(ret_list);
1691 }
1692
1693 static gboolean
collect_token_list(SeafDBRow * row,void * data)1694 collect_token_list (SeafDBRow *row, void *data)
1695 {
1696 GList **p_tokens = data;
1697 const char *token;
1698
1699 token = seaf_db_row_get_column_text (row, 0);
1700 *p_tokens = g_list_prepend (*p_tokens, g_strdup(token));
1701
1702 return TRUE;
1703 }
1704
1705 /**
1706 * Delete all repo tokens for a given user on a given client
1707 */
1708
1709 int
seaf_repo_manager_delete_repo_tokens_by_peer_id(SeafRepoManager * mgr,const char * email,const char * peer_id,GList ** tokens,GError ** error)1710 seaf_repo_manager_delete_repo_tokens_by_peer_id (SeafRepoManager *mgr,
1711 const char *email,
1712 const char *peer_id,
1713 GList **tokens,
1714 GError **error)
1715 {
1716 int ret = 0;
1717 const char *template;
1718 GList *token_list = NULL;
1719 GString *token_list_str = g_string_new ("");
1720 GString *sql = g_string_new ("");
1721 GList *ptr;
1722 int rc = 0;
1723
1724 template = "SELECT u.token "
1725 "FROM RepoUserToken u, RepoTokenPeerInfo p "
1726 "WHERE u.token = p.token "
1727 "AND u.email = ? AND p.peer_id = ?";
1728 rc = seaf_db_statement_foreach_row (mgr->seaf->db, template,
1729 collect_token_list, &token_list,
1730 2, "string", email, "string", peer_id);
1731 if (rc < 0) {
1732 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "DB error");
1733 goto out;
1734 }
1735
1736 if (rc == 0)
1737 goto out;
1738
1739 for (ptr = token_list; ptr; ptr = ptr->next) {
1740 const char *token = (char *)ptr->data;
1741 if (token_list_str->len == 0)
1742 g_string_append_printf (token_list_str, "'%s'", token);
1743 else
1744 g_string_append_printf (token_list_str, ",'%s'", token);
1745 }
1746
1747 /* Note that there is a size limit on sql query. In MySQL it's 1MB by default.
1748 * Normally the token_list won't be that long.
1749 */
1750 g_string_printf (sql, "DELETE FROM RepoUserToken WHERE token in (%s)",
1751 token_list_str->str);
1752 rc = seaf_db_statement_query (mgr->seaf->db, sql->str, 0);
1753 if (rc < 0) {
1754 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "DB error");
1755 goto out;
1756 }
1757
1758 g_string_printf (sql, "DELETE FROM RepoTokenPeerInfo WHERE token in (%s)",
1759 token_list_str->str);
1760 rc = seaf_db_statement_query (mgr->seaf->db, sql->str, 0);
1761 if (rc < 0)
1762 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "DB error");
1763
1764 out:
1765 g_string_free (token_list_str, TRUE);
1766 g_string_free (sql, TRUE);
1767
1768 if (rc < 0) {
1769 ret = -1;
1770 g_list_free_full (token_list, (GDestroyNotify)g_free);
1771 } else {
1772 *tokens = token_list;
1773 }
1774
1775 return ret;
1776 }
1777
1778 int
seaf_repo_manager_delete_repo_tokens_by_email(SeafRepoManager * mgr,const char * email,GError ** error)1779 seaf_repo_manager_delete_repo_tokens_by_email (SeafRepoManager *mgr,
1780 const char *email,
1781 GError **error)
1782 {
1783 int ret = 0;
1784 const char *template;
1785 GList *token_list = NULL;
1786 GList *ptr;
1787 GString *token_list_str = g_string_new ("");
1788 GString *sql = g_string_new ("");
1789 int rc;
1790
1791 template = "SELECT u.token "
1792 "FROM RepoUserToken u, RepoTokenPeerInfo p "
1793 "WHERE u.token = p.token "
1794 "AND u.email = ?";
1795 rc = seaf_db_statement_foreach_row (mgr->seaf->db, template,
1796 collect_token_list, &token_list,
1797 1, "string", email);
1798 if (rc < 0) {
1799 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "DB error");
1800 goto out;
1801 }
1802
1803 if (rc == 0)
1804 goto out;
1805
1806 for (ptr = token_list; ptr; ptr = ptr->next) {
1807 const char *token = (char *)ptr->data;
1808 if (token_list_str->len == 0)
1809 g_string_append_printf (token_list_str, "'%s'", token);
1810 else
1811 g_string_append_printf (token_list_str, ",'%s'", token);
1812 }
1813
1814 /* Note that there is a size limit on sql query. In MySQL it's 1MB by default.
1815 * Normally the token_list won't be that long.
1816 */
1817 g_string_printf (sql, "DELETE FROM RepoUserToken WHERE token in (%s)",
1818 token_list_str->str);
1819 rc = seaf_db_statement_query (mgr->seaf->db, sql->str, 0);
1820 if (rc < 0) {
1821 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "DB error");
1822 goto out;
1823 }
1824
1825 g_string_printf (sql, "DELETE FROM RepoTokenPeerInfo WHERE token in (%s)",
1826 token_list_str->str);
1827 rc = seaf_db_statement_query (mgr->seaf->db, sql->str, 0);
1828 if (rc < 0) {
1829 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "DB error");
1830 goto out;
1831 }
1832
1833 seaf_http_server_invalidate_tokens (seaf->http_server, token_list);
1834
1835 out:
1836 g_string_free (token_list_str, TRUE);
1837 g_string_free (sql, TRUE);
1838 g_list_free_full (token_list, (GDestroyNotify)g_free);
1839
1840 if (rc < 0) {
1841 ret = -1;
1842 }
1843
1844 return ret;
1845 }
1846
1847 static gboolean
get_email_by_token_cb(SeafDBRow * row,void * data)1848 get_email_by_token_cb (SeafDBRow *row, void *data)
1849 {
1850 char **email_ptr = data;
1851
1852 const char *email = (const char *) seaf_db_row_get_column_text (row, 0);
1853 *email_ptr = g_ascii_strdown (email, -1);
1854 /* There should be only one result. */
1855 return FALSE;
1856 }
1857
1858 char *
seaf_repo_manager_get_email_by_token(SeafRepoManager * manager,const char * repo_id,const char * token)1859 seaf_repo_manager_get_email_by_token (SeafRepoManager *manager,
1860 const char *repo_id,
1861 const char *token)
1862 {
1863 if (!repo_id || !token)
1864 return NULL;
1865
1866 char *email = NULL;
1867 char *sql;
1868
1869 sql = "SELECT email FROM RepoUserToken "
1870 "WHERE repo_id = ? AND token = ?";
1871
1872 seaf_db_statement_foreach_row (seaf->db, sql,
1873 get_email_by_token_cb, &email,
1874 2, "string", repo_id, "string", token);
1875
1876 return email;
1877 }
1878
1879 static gboolean
get_repo_size(SeafDBRow * row,void * vsize)1880 get_repo_size (SeafDBRow *row, void *vsize)
1881 {
1882 gint64 *psize = vsize;
1883
1884 *psize = seaf_db_row_get_column_int64 (row, 0);
1885
1886 return FALSE;
1887 }
1888
1889 gint64
seaf_repo_manager_get_repo_size(SeafRepoManager * mgr,const char * repo_id)1890 seaf_repo_manager_get_repo_size (SeafRepoManager *mgr, const char *repo_id)
1891 {
1892 gint64 size = 0;
1893 char *sql;
1894
1895 sql = "SELECT size FROM RepoSize WHERE repo_id=?";
1896
1897 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
1898 get_repo_size, &size,
1899 1, "string", repo_id) < 0)
1900 return -1;
1901
1902 return size;
1903 }
1904
1905 int
seaf_repo_manager_set_repo_history_limit(SeafRepoManager * mgr,const char * repo_id,int days)1906 seaf_repo_manager_set_repo_history_limit (SeafRepoManager *mgr,
1907 const char *repo_id,
1908 int days)
1909 {
1910 SeafVirtRepo *vinfo;
1911 SeafDB *db = mgr->seaf->db;
1912
1913 vinfo = seaf_repo_manager_get_virtual_repo_info (mgr, repo_id);
1914 if (vinfo) {
1915 seaf_virtual_repo_info_free (vinfo);
1916 return 0;
1917 }
1918
1919 if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
1920 gboolean exists, err;
1921 int rc;
1922
1923 exists = seaf_db_statement_exists (db,
1924 "SELECT repo_id FROM RepoHistoryLimit "
1925 "WHERE repo_id=?",
1926 &err, 1, "string", repo_id);
1927 if (err)
1928 return -1;
1929
1930 if (exists)
1931 rc = seaf_db_statement_query (db,
1932 "UPDATE RepoHistoryLimit SET days=? "
1933 "WHERE repo_id=?",
1934 2, "int", days, "string", repo_id);
1935 else
1936 rc = seaf_db_statement_query (db,
1937 "INSERT INTO RepoHistoryLimit (repo_id, days) VALUES "
1938 "(?, ?)",
1939 2, "string", repo_id, "int", days);
1940 return rc;
1941 } else {
1942 if (seaf_db_statement_query (db,
1943 "REPLACE INTO RepoHistoryLimit (repo_id, days) VALUES (?, ?)",
1944 2, "string", repo_id, "int", days) < 0)
1945 return -1;
1946 }
1947
1948 return 0;
1949 }
1950
1951 static gboolean
get_history_limit_cb(SeafDBRow * row,void * data)1952 get_history_limit_cb (SeafDBRow *row, void *data)
1953 {
1954 int *limit = data;
1955
1956 *limit = seaf_db_row_get_column_int (row, 0);
1957
1958 return FALSE;
1959 }
1960
1961 int
seaf_repo_manager_get_repo_history_limit(SeafRepoManager * mgr,const char * repo_id)1962 seaf_repo_manager_get_repo_history_limit (SeafRepoManager *mgr,
1963 const char *repo_id)
1964 {
1965 SeafVirtRepo *vinfo;
1966 const char *r_repo_id = repo_id;
1967 char *sql;
1968 int per_repo_days = -1;
1969 int ret;
1970
1971 vinfo = seaf_repo_manager_get_virtual_repo_info (mgr, repo_id);
1972 if (vinfo)
1973 r_repo_id = vinfo->origin_repo_id;
1974
1975 sql = "SELECT days FROM RepoHistoryLimit WHERE repo_id=?";
1976
1977 ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_history_limit_cb,
1978 &per_repo_days, 1, "string", r_repo_id);
1979 if (ret == 0) {
1980 // limit not set, return global one
1981 per_repo_days= seaf_cfg_manager_get_config_int (mgr->seaf->cfg_mgr,
1982 "history", "keep_days");
1983 }
1984
1985 // db error or limit set as negative, means keep full history, return -1
1986 if (per_repo_days < 0)
1987 per_repo_days = -1;
1988
1989 seaf_virtual_repo_info_free (vinfo);
1990
1991 return per_repo_days;
1992 }
1993
1994 int
seaf_repo_manager_set_repo_valid_since(SeafRepoManager * mgr,const char * repo_id,gint64 timestamp)1995 seaf_repo_manager_set_repo_valid_since (SeafRepoManager *mgr,
1996 const char *repo_id,
1997 gint64 timestamp)
1998 {
1999 SeafDB *db = mgr->seaf->db;
2000
2001 if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
2002 gboolean exists, err;
2003 int rc;
2004
2005 exists = seaf_db_statement_exists (db,
2006 "SELECT repo_id FROM RepoValidSince WHERE "
2007 "repo_id=?", &err, 1, "string", repo_id);
2008 if (err)
2009 return -1;
2010
2011 if (exists)
2012 rc = seaf_db_statement_query (db,
2013 "UPDATE RepoValidSince SET timestamp=?"
2014 " WHERE repo_id=?",
2015 2, "int64", timestamp, "string", repo_id);
2016 else
2017 rc = seaf_db_statement_query (db,
2018 "INSERT INTO RepoValidSince (repo_id, timestamp) VALUES "
2019 "(?, ?)", 2, "string", repo_id,
2020 "int64", timestamp);
2021 if (rc < 0)
2022 return -1;
2023 } else {
2024 if (seaf_db_statement_query (db,
2025 "REPLACE INTO RepoValidSince (repo_id, timestamp) VALUES (?, ?)",
2026 2, "string", repo_id, "int64", timestamp) < 0)
2027 return -1;
2028 }
2029
2030 return 0;
2031 }
2032
2033 gint64
seaf_repo_manager_get_repo_valid_since(SeafRepoManager * mgr,const char * repo_id)2034 seaf_repo_manager_get_repo_valid_since (SeafRepoManager *mgr,
2035 const char *repo_id)
2036 {
2037 char *sql;
2038
2039 sql = "SELECT timestamp FROM RepoValidSince WHERE repo_id=?";
2040 /* Also return -1 if doesn't exist. */
2041 return seaf_db_statement_get_int64 (mgr->seaf->db, sql, 1, "string", repo_id);
2042 }
2043
2044 gint64
seaf_repo_manager_get_repo_truncate_time(SeafRepoManager * mgr,const char * repo_id)2045 seaf_repo_manager_get_repo_truncate_time (SeafRepoManager *mgr,
2046 const char *repo_id)
2047 {
2048 int days;
2049 gint64 timestamp;
2050
2051 days = seaf_repo_manager_get_repo_history_limit (mgr, repo_id);
2052 timestamp = seaf_repo_manager_get_repo_valid_since (mgr, repo_id);
2053
2054 gint64 now = (gint64)time(NULL);
2055 if (days > 0)
2056 return MAX (now - days * 24 * 3600, timestamp);
2057 else if (days < 0)
2058 return timestamp;
2059 else
2060 return 0;
2061 }
2062
2063 /*
2064 * Permission related functions.
2065 */
2066
2067 /* Owner functions. */
2068
2069 int
seaf_repo_manager_set_repo_owner(SeafRepoManager * mgr,const char * repo_id,const char * email)2070 seaf_repo_manager_set_repo_owner (SeafRepoManager *mgr,
2071 const char *repo_id,
2072 const char *email)
2073 {
2074 SeafDB *db = mgr->seaf->db;
2075 char sql[256];
2076 char *orig_owner = NULL;
2077 int ret = 0;
2078
2079 orig_owner = seaf_repo_manager_get_repo_owner (mgr, repo_id);
2080 if (g_strcmp0 (orig_owner, email) == 0)
2081 goto out;
2082
2083 if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
2084 gboolean err;
2085 snprintf(sql, sizeof(sql),
2086 "SELECT repo_id FROM RepoOwner WHERE repo_id=?");
2087 if (seaf_db_statement_exists (db, sql, &err,
2088 1, "string", repo_id))
2089 snprintf(sql, sizeof(sql),
2090 "UPDATE RepoOwner SET owner_id='%s' WHERE "
2091 "repo_id='%s'", email, repo_id);
2092 else
2093 snprintf(sql, sizeof(sql),
2094 "INSERT INTO RepoOwner (repo_id, owner_id) VALUES ('%s', '%s')",
2095 repo_id, email);
2096 if (err) {
2097 ret = -1;
2098 goto out;
2099 }
2100
2101 if (seaf_db_query (db, sql) < 0) {
2102 ret = -1;
2103 goto out;
2104 }
2105 } else {
2106 if (seaf_db_statement_query (db, "REPLACE INTO RepoOwner (repo_id, owner_id) VALUES (?, ?)",
2107 2, "string", repo_id, "string", email) < 0) {
2108 ret = -1;
2109 goto out;
2110 }
2111 }
2112
2113 /* If the repo was newly created, no need to remove share and virtual repos. */
2114 if (!orig_owner)
2115 goto out;
2116
2117 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM SharedRepo WHERE repo_id = ?",
2118 1, "string", repo_id);
2119
2120 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM RepoGroup WHERE repo_id = ?",
2121 1, "string", repo_id);
2122
2123 if (!seaf->cloud_mode) {
2124 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM InnerPubRepo WHERE repo_id = ?",
2125 1, "string", repo_id);
2126 }
2127
2128 /* Remove virtual repos when repo ownership changes. */
2129 GList *vrepos, *ptr;
2130 vrepos = seaf_repo_manager_get_virtual_repo_ids_by_origin (mgr, repo_id);
2131 for (ptr = vrepos; ptr != NULL; ptr = ptr->next)
2132 remove_virtual_repo_ondisk (mgr, (char *)ptr->data);
2133 string_list_free (vrepos);
2134
2135 seaf_db_statement_query (mgr->seaf->db, "DELETE FROM VirtualRepo "
2136 "WHERE repo_id=? OR origin_repo=?",
2137 2, "string", repo_id, "string", repo_id);
2138
2139 out:
2140 g_free (orig_owner);
2141 return ret;
2142 }
2143
2144 static gboolean
get_owner(SeafDBRow * row,void * data)2145 get_owner (SeafDBRow *row, void *data)
2146 {
2147 char **owner_id = data;
2148
2149 const char *owner = (const char *) seaf_db_row_get_column_text (row, 0);
2150 *owner_id = g_ascii_strdown (owner, -1);
2151 /* There should be only one result. */
2152 return FALSE;
2153 }
2154
2155 char *
seaf_repo_manager_get_repo_owner(SeafRepoManager * mgr,const char * repo_id)2156 seaf_repo_manager_get_repo_owner (SeafRepoManager *mgr,
2157 const char *repo_id)
2158 {
2159 char *sql;
2160 char *ret = NULL;
2161
2162 sql = "SELECT owner_id FROM RepoOwner WHERE repo_id=?";
2163 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
2164 get_owner, &ret,
2165 1, "string", repo_id) < 0) {
2166 seaf_warning ("Failed to get owner id for repo %s.\n", repo_id);
2167 return NULL;
2168 }
2169
2170 return ret;
2171 }
2172
2173 static gboolean
collect_repo_id(SeafDBRow * row,void * data)2174 collect_repo_id (SeafDBRow *row, void *data)
2175 {
2176 GList **p_ids = data;
2177 const char *repo_id;
2178
2179 repo_id = seaf_db_row_get_column_text (row, 0);
2180 *p_ids = g_list_prepend (*p_ids, g_strdup(repo_id));
2181
2182 return TRUE;
2183 }
2184
2185 GList *
seaf_repo_manager_get_orphan_repo_list(SeafRepoManager * mgr)2186 seaf_repo_manager_get_orphan_repo_list (SeafRepoManager *mgr)
2187 {
2188 GList *id_list = NULL, *ptr;
2189 GList *ret = NULL;
2190 char sql[256];
2191
2192 snprintf (sql, sizeof(sql), "SELECT Repo.repo_id FROM Repo LEFT JOIN "
2193 "RepoOwner ON Repo.repo_id = RepoOwner.repo_id WHERE "
2194 "RepoOwner.owner_id is NULL");
2195
2196 if (seaf_db_foreach_selected_row (mgr->seaf->db, sql,
2197 collect_repo_id, &id_list) < 0)
2198 return NULL;
2199
2200 for (ptr = id_list; ptr; ptr = ptr->next) {
2201 char *repo_id = ptr->data;
2202 SeafRepo *repo = seaf_repo_manager_get_repo (mgr, repo_id);
2203 if (repo != NULL)
2204 ret = g_list_prepend (ret, repo);
2205 }
2206
2207 string_list_free (id_list);
2208
2209 return ret;
2210 }
2211
2212 gboolean
collect_repos_fill_size_commit(SeafDBRow * row,void * data)2213 collect_repos_fill_size_commit (SeafDBRow *row, void *data)
2214 {
2215 GList **prepos = data;
2216 SeafRepo *repo;
2217 SeafBranch *head;
2218
2219 const char *repo_id = seaf_db_row_get_column_text (row, 0);
2220 gint64 size = seaf_db_row_get_column_int64 (row, 1);
2221 const char *commit_id = seaf_db_row_get_column_text (row, 2);
2222 const char *repo_name = seaf_db_row_get_column_text (row, 3);
2223 gint64 update_time = seaf_db_row_get_column_int64 (row, 4);
2224 int version = seaf_db_row_get_column_int (row, 5);
2225 gboolean is_encrypted = seaf_db_row_get_column_int (row, 6) ? TRUE : FALSE;
2226 const char *last_modifier = seaf_db_row_get_column_text (row, 7);
2227 int status = seaf_db_row_get_column_int (row, 8);
2228
2229 repo = seaf_repo_new (repo_id, NULL, NULL);
2230 if (!repo)
2231 return TRUE;
2232
2233 if (!commit_id) {
2234 repo->is_corrupted = TRUE;
2235 goto out;
2236 }
2237
2238 repo->size = size;
2239 if (seaf_db_row_get_column_count (row) == 10) {
2240 gint64 file_count = seaf_db_row_get_column_int64 (row, 9);
2241 repo->file_count = file_count;
2242 }
2243 head = seaf_branch_new ("master", repo_id, commit_id);
2244 repo->head = head;
2245 if (repo_name) {
2246 repo->name = g_strdup (repo_name);
2247 repo->last_modify = update_time;
2248 repo->version = version;
2249 repo->encrypted = is_encrypted;
2250 repo->last_modifier = g_strdup (last_modifier);
2251 repo->status = status;
2252 }
2253
2254 out:
2255 *prepos = g_list_prepend (*prepos, repo);
2256
2257 return TRUE;
2258 }
2259
2260 GList *
seaf_repo_manager_get_repos_by_owner(SeafRepoManager * mgr,const char * email,int ret_corrupted,int start,int limit,gboolean * db_err)2261 seaf_repo_manager_get_repos_by_owner (SeafRepoManager *mgr,
2262 const char *email,
2263 int ret_corrupted,
2264 int start,
2265 int limit,
2266 gboolean *db_err)
2267 {
2268 GList *repo_list = NULL, *ptr;
2269 GList *ret = NULL;
2270 char *sql;
2271 SeafRepo *repo = NULL;
2272 int db_type = seaf_db_type(mgr->seaf->db);
2273
2274 if (start == -1 && limit == -1) {
2275 if (db_type != SEAF_DB_TYPE_PGSQL)
2276 sql = "SELECT o.repo_id, s.size, b.commit_id, i.name, i.update_time, "
2277 "i.version, i.is_encrypted, i.last_modifier, i.status FROM "
2278 "RepoOwner o LEFT JOIN RepoSize s ON o.repo_id = s.repo_id "
2279 "LEFT JOIN Branch b ON o.repo_id = b.repo_id "
2280 "LEFT JOIN RepoInfo i ON o.repo_id = i.repo_id "
2281 "WHERE owner_id=? AND "
2282 "o.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) "
2283 "ORDER BY i.update_time DESC, o.repo_id";
2284 else
2285 sql = "SELECT o.repo_id, s.\"size\", b.commit_id, i.name, i.update_time, "
2286 "i.version, i.is_encrypted, i.last_modifier, i.status FROM "
2287 "RepoOwner o LEFT JOIN RepoSize s ON o.repo_id = s.repo_id "
2288 "LEFT JOIN Branch b ON o.repo_id = b.repo_id "
2289 "LEFT JOIN RepoInfo i ON o.repo_id = i.repo_id "
2290 "WHERE owner_id=? AND "
2291 "o.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) "
2292 "ORDER BY i.update_time DESC, o.repo_id";
2293
2294 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
2295 collect_repos_fill_size_commit, &repo_list,
2296 1, "string", email) < 0) {
2297 if (db_err)
2298 *db_err = TRUE;
2299 return NULL;
2300 }
2301 } else {
2302 if (db_type != SEAF_DB_TYPE_PGSQL)
2303 sql = "SELECT o.repo_id, s.size, b.commit_id, i.name, i.update_time, "
2304 "i.version, i.is_encrypted, i.last_modifier, i.status FROM "
2305 "RepoOwner o LEFT JOIN RepoSize s ON o.repo_id = s.repo_id "
2306 "LEFT JOIN Branch b ON o.repo_id = b.repo_id "
2307 "LEFT JOIN RepoInfo i ON o.repo_id = i.repo_id "
2308 "WHERE owner_id=? AND "
2309 "o.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) "
2310 "ORDER BY i.update_time DESC, o.repo_id "
2311 "LIMIT ? OFFSET ?";
2312 else
2313 sql = "SELECT o.repo_id, s.\"size\", b.commit_id, i.name, i.update_time, "
2314 "i.version, i.is_encrypted, i.last_modifier, i.status FROM "
2315 "RepoOwner o LEFT JOIN RepoSize s ON o.repo_id = s.repo_id "
2316 "LEFT JOIN Branch b ON o.repo_id = b.repo_id "
2317 "LEFT JOIN RepoInfo i ON o.repo_id = i.repo_id "
2318 "WHERE owner_id=? AND "
2319 "o.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) "
2320 "ORDER BY i.update_time DESC, o.repo_id "
2321 "LIMIT ? OFFSET ?";
2322
2323 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
2324 collect_repos_fill_size_commit,
2325 &repo_list,
2326 3, "string", email,
2327 "int", limit,
2328 "int", start) < 0) {
2329 if (db_err)
2330 *db_err = TRUE;
2331 return NULL;
2332 }
2333 }
2334
2335 for (ptr = repo_list; ptr; ptr = ptr->next) {
2336 repo = ptr->data;
2337 if (ret_corrupted) {
2338 if (!repo->is_corrupted && (!repo->name || !repo->last_modifier)) {
2339 load_mini_repo (mgr, repo);
2340 if (!repo->is_corrupted)
2341 set_repo_commit_to_db (repo->id, repo->name, repo->last_modify,
2342 repo->version, (repo->encrypted ? 1 : 0),
2343 repo->last_modifier);
2344 }
2345 } else {
2346 if (repo->is_corrupted) {
2347 seaf_repo_unref (repo);
2348 continue;
2349 }
2350 if (!repo->name || !repo->last_modifier) {
2351 load_mini_repo (mgr, repo);
2352 if (!repo->is_corrupted)
2353 set_repo_commit_to_db (repo->id, repo->name, repo->last_modify,
2354 repo->version, (repo->encrypted ? 1 : 0),
2355 repo->last_modifier);
2356 }
2357 if (repo->is_corrupted) {
2358 seaf_repo_unref (repo);
2359 continue;
2360 }
2361 }
2362 if (repo != NULL)
2363 ret = g_list_prepend (ret, repo);
2364 }
2365 g_list_free (repo_list);
2366
2367 return ret;
2368 }
2369
2370 GList *
seaf_repo_manager_search_repos_by_name(SeafRepoManager * mgr,const char * name)2371 seaf_repo_manager_search_repos_by_name (SeafRepoManager *mgr, const char *name)
2372 {
2373 GList *repo_list = NULL;
2374 char *sql = NULL;
2375
2376 char *db_patt = g_strdup_printf ("%%%s%%", name);
2377
2378 switch (seaf_db_type(seaf->db)) {
2379 case SEAF_DB_TYPE_MYSQL:
2380 sql = "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, "
2381 "i.version, i.is_encrypted, i.last_modifier, i.status, fc.file_count FROM "
2382 "RepoInfo i LEFT JOIN RepoSize s ON i.repo_id = s.repo_id "
2383 "LEFT JOIN Branch b ON i.repo_id = b.repo_id "
2384 "LEFT JOIN RepoFileCount fc ON i.repo_id = fc.repo_id "
2385 "WHERE i.name COLLATE UTF8_GENERAL_CI LIKE ? AND "
2386 "i.repo_id IN (SELECT r.repo_id FROM Repo r) AND "
2387 "i.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) "
2388 "ORDER BY i.update_time DESC, i.repo_id";
2389 break;
2390 case SEAF_DB_TYPE_PGSQL:
2391 sql = "SELECT i.repo_id, s.\"size\", b.commit_id, i.name, i.update_time, "
2392 "i.version, i.is_encrypted, i.last_modifier, i.status, fc.file_count FROM "
2393 "RepoInfo i LEFT JOIN RepoSize s ON i.repo_id = s.repo_id "
2394 "LEFT JOIN Branch b ON i.repo_id = b.repo_id "
2395 "LEFT JOIN RepoFileCount fc ON i.repo_id = fc.repo_id "
2396 "WHERE i.name ILIKE ? AND "
2397 "i.repo_id IN (SELECT r.repo_id FROM Repo r) AND "
2398 "i.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) "
2399 "ORDER BY i.update_time DESC, i.repo_id";
2400 break;
2401 case SEAF_DB_TYPE_SQLITE:
2402 sql = "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, "
2403 "i.version, i.is_encrypted, i.last_modifier, i.status, fc.file_count FROM "
2404 "RepoInfo i LEFT JOIN RepoSize s ON i.repo_id = s.repo_id "
2405 "LEFT JOIN Branch b ON i.repo_id = b.repo_id "
2406 "LEFT JOIN RepoFileCount fc ON i.repo_id = fc.repo_id "
2407 "WHERE i.name LIKE ? COLLATE NOCASE AND "
2408 "i.repo_id IN (SELECT r.repo_id FROM Repo r) AND "
2409 "i.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) "
2410 "ORDER BY i.update_time DESC, i.repo_id";
2411 break;
2412 default:
2413 g_free (db_patt);
2414 return NULL;
2415 }
2416
2417 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
2418 collect_repos_fill_size_commit, &repo_list,
2419 1, "string", db_patt) < 0) {
2420 g_free (db_patt);
2421 return NULL;
2422 }
2423
2424 g_free (db_patt);
2425 return repo_list;
2426 }
2427
2428 GList *
seaf_repo_manager_get_repo_id_list(SeafRepoManager * mgr)2429 seaf_repo_manager_get_repo_id_list (SeafRepoManager *mgr)
2430 {
2431 GList *ret = NULL;
2432 char sql[256];
2433
2434 snprintf (sql, 256, "SELECT repo_id FROM Repo");
2435
2436 if (seaf_db_foreach_selected_row (mgr->seaf->db, sql,
2437 collect_repo_id, &ret) < 0)
2438 return NULL;
2439
2440 return ret;
2441 }
2442
2443 GList *
seaf_repo_manager_get_repo_list(SeafRepoManager * mgr,int start,int limit,const char * order_by)2444 seaf_repo_manager_get_repo_list (SeafRepoManager *mgr, int start, int limit, const char *order_by)
2445 {
2446 GList *ret = NULL;
2447 char *sql_base = NULL;
2448 char sql[512];
2449 int rc;
2450
2451 if (start == -1 && limit == -1) {
2452 switch (seaf_db_type(mgr->seaf->db)) {
2453 case SEAF_DB_TYPE_MYSQL:
2454 sql_base = "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, "
2455 "i.version, i.is_encrypted, i.last_modifier, i.status, f.file_count FROM "
2456 "RepoInfo i LEFT JOIN RepoSize s ON i.repo_id = s.repo_id "
2457 "LEFT JOIN Branch b ON i.repo_id = b.repo_id "
2458 "LEFT JOIN RepoFileCount f ON i.repo_id = f.repo_id "
2459 "WHERE i.repo_id IN (SELECT r.repo_id FROM Repo r) AND "
2460 "i.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) ";
2461 if (g_strcmp0 (order_by, "size") == 0)
2462 snprintf (sql, sizeof(sql), "%sORDER BY s.size DESC, i.repo_id", sql_base);
2463 else if (g_strcmp0 (order_by, "file_count") == 0)
2464 snprintf (sql, sizeof(sql), "%sORDER BY f.file_count DESC, i.repo_id", sql_base);
2465 else
2466 snprintf (sql, sizeof(sql), "%sORDER BY i.update_time DESC, i.repo_id", sql_base);
2467 break;
2468 case SEAF_DB_TYPE_SQLITE:
2469 sql_base= "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, "
2470 "i.version, i.is_encrypted, i.last_modifier, i.status, f.file_count FROM "
2471 "RepoInfo i LEFT JOIN RepoSize s ON i.repo_id = s.repo_id "
2472 "LEFT JOIN Branch b ON i.repo_id = b.repo_id "
2473 "LEFT JOIN RepoFileCount f ON i.repo_id = f.repo_id "
2474 "WHERE i.repo_id IN (SELECT r.repo_id FROM Repo r) AND "
2475 "i.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) ";
2476 if (g_strcmp0 (order_by, "size") == 0)
2477 snprintf (sql, sizeof(sql), "%sORDER BY s.size DESC, i.repo_id", sql_base);
2478 else if (g_strcmp0 (order_by, "file_count") == 0)
2479 snprintf (sql, sizeof(sql), "%sORDER BY f.file_count DESC, i.repo_id", sql_base);
2480 else
2481 snprintf (sql, sizeof(sql), "%sORDER BY i.update_time DESC, i.repo_id", sql_base);
2482 break;
2483 default:
2484 return NULL;
2485 }
2486
2487 rc = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
2488 collect_repos_fill_size_commit, &ret,
2489 0);
2490 } else {
2491 switch (seaf_db_type(mgr->seaf->db)) {
2492 case SEAF_DB_TYPE_MYSQL:
2493 sql_base = "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, "
2494 "i.version, i.is_encrypted, i.last_modifier, i.status, f.file_count FROM "
2495 "RepoInfo i LEFT JOIN RepoSize s ON i.repo_id = s.repo_id "
2496 "LEFT JOIN Branch b ON i.repo_id = b.repo_id "
2497 "LEFT JOIN RepoFileCount f ON i.repo_id = f.repo_id "
2498 "WHERE i.repo_id IN (SELECT r.repo_id FROM Repo r) AND "
2499 "i.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) ";
2500 if (g_strcmp0 (order_by, "size") == 0)
2501 snprintf (sql, sizeof(sql), "%sORDER BY s.size DESC, i.repo_id LIMIT ? OFFSET ?", sql_base);
2502 else if (g_strcmp0 (order_by, "file_count") == 0)
2503 snprintf (sql, sizeof(sql), "%sORDER BY f.file_count DESC, i.repo_id LIMIT ? OFFSET ?", sql_base);
2504 else
2505 snprintf (sql, sizeof(sql), "%sORDER BY i.update_time DESC, i.repo_id LIMIT ? OFFSET ?", sql_base);
2506 break;
2507 case SEAF_DB_TYPE_SQLITE:
2508 sql_base = "SELECT i.repo_id, s.size, b.commit_id, i.name, i.update_time, "
2509 "i.version, i.is_encrypted, i.last_modifier, i.status, f.file_count FROM "
2510 "RepoInfo i LEFT JOIN RepoSize s ON i.repo_id = s.repo_id "
2511 "LEFT JOIN Branch b ON i.repo_id = b.repo_id "
2512 "LEFT JOIN RepoFileCount f ON i.repo_id = f.repo_id "
2513 "WHERE i.repo_id IN (SELECT r.repo_id FROM Repo r) AND "
2514 "i.repo_id NOT IN (SELECT v.repo_id FROM VirtualRepo v) ";
2515 if (g_strcmp0 (order_by, "size") == 0)
2516 snprintf (sql, sizeof(sql), "%sORDER BY s.size DESC, i.repo_id LIMIT ? OFFSET ?", sql_base);
2517 else if (g_strcmp0 (order_by, "file_count") == 0)
2518 snprintf (sql, sizeof(sql), "%sORDER BY f.file_count DESC, i.repo_id LIMIT ? OFFSET ?", sql_base);
2519 else
2520 snprintf (sql, sizeof(sql), "%sORDER BY i.update_time DESC, i.repo_id LIMIT ? OFFSET ?", sql_base);
2521 break;
2522 default:
2523 return NULL;
2524 }
2525
2526 rc = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
2527 collect_repos_fill_size_commit, &ret,
2528 2, "int", limit, "int", start);
2529 }
2530
2531 if (rc < 0)
2532 return NULL;
2533
2534 return g_list_reverse (ret);
2535 }
2536
2537 gint64
seaf_repo_manager_count_repos(SeafRepoManager * mgr,GError ** error)2538 seaf_repo_manager_count_repos (SeafRepoManager *mgr, GError **error)
2539 {
2540 gint64 num = seaf_db_get_int64 (mgr->seaf->db,
2541 "SELECT COUNT(repo_id) FROM Repo");
2542 if (num < 0) {
2543 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2544 "Failed to count repos from db");
2545 }
2546
2547 return num;
2548 }
2549
2550 GList *
seaf_repo_manager_get_repo_ids_by_owner(SeafRepoManager * mgr,const char * email)2551 seaf_repo_manager_get_repo_ids_by_owner (SeafRepoManager *mgr,
2552 const char *email)
2553 {
2554 GList *ret = NULL;
2555 char *sql;
2556
2557 sql = "SELECT repo_id FROM RepoOwner WHERE owner_id=?";
2558
2559 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
2560 collect_repo_id, &ret,
2561 1, "string", email) < 0) {
2562 string_list_free (ret);
2563 return NULL;
2564 }
2565
2566 return ret;
2567 }
2568
2569 static gboolean
collect_trash_repo(SeafDBRow * row,void * data)2570 collect_trash_repo (SeafDBRow *row, void *data)
2571 {
2572 GList **trash_repos = data;
2573 const char *repo_id;
2574 const char *repo_name;
2575 const char *head_id;
2576 const char *owner_id;
2577 gint64 size;
2578 gint64 del_time;
2579
2580 repo_id = seaf_db_row_get_column_text (row, 0);
2581 repo_name = seaf_db_row_get_column_text (row, 1);
2582 head_id = seaf_db_row_get_column_text (row, 2);
2583 owner_id = seaf_db_row_get_column_text (row, 3);
2584 size = seaf_db_row_get_column_int64 (row, 4);
2585 del_time = seaf_db_row_get_column_int64 (row, 5);
2586
2587
2588 if (!repo_id || !repo_name || !head_id || !owner_id)
2589 return TRUE;
2590
2591 SeafileTrashRepo *trash_repo = g_object_new (SEAFILE_TYPE_TRASH_REPO,
2592 "repo_id", repo_id,
2593 "repo_name", repo_name,
2594 "head_id", head_id,
2595 "owner_id", owner_id,
2596 "size", size,
2597 "del_time", del_time,
2598 NULL);
2599 if (!trash_repo)
2600 return FALSE;
2601
2602 SeafCommit *commit = seaf_commit_manager_get_commit_compatible (seaf->commit_mgr,
2603 repo_id, head_id);
2604 if (!commit) {
2605 seaf_warning ("Commit %s not found in repo %s\n", head_id, repo_id);
2606 g_object_unref (trash_repo);
2607 return TRUE;
2608 }
2609 g_object_set (trash_repo, "encrypted", commit->encrypted, NULL);
2610 seaf_commit_unref (commit);
2611
2612 *trash_repos = g_list_prepend (*trash_repos, trash_repo);
2613
2614 return TRUE;
2615 }
2616
2617 GList *
seaf_repo_manager_get_trash_repo_list(SeafRepoManager * mgr,int start,int limit,GError ** error)2618 seaf_repo_manager_get_trash_repo_list (SeafRepoManager *mgr,
2619 int start,
2620 int limit,
2621 GError **error)
2622 {
2623 GList *trash_repos = NULL;
2624 int rc;
2625
2626 if (start == -1 && limit == -1)
2627 rc = seaf_db_statement_foreach_row (mgr->seaf->db,
2628 "SELECT repo_id, repo_name, head_id, owner_id, "
2629 "size, del_time FROM RepoTrash ORDER BY del_time DESC",
2630 collect_trash_repo, &trash_repos,
2631 0);
2632 else
2633 rc = seaf_db_statement_foreach_row (mgr->seaf->db,
2634 "SELECT repo_id, repo_name, head_id, owner_id, "
2635 "size, del_time FROM RepoTrash "
2636 "ORDER BY del_time DESC LIMIT ? OFFSET ?",
2637 collect_trash_repo, &trash_repos,
2638 2, "int", limit, "int", start);
2639
2640 if (rc < 0) {
2641 while (trash_repos) {
2642 g_object_unref (trash_repos->data);
2643 trash_repos = g_list_delete_link (trash_repos, trash_repos);
2644 }
2645 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2646 "Failed to get trashed repo from db.");
2647 return NULL;
2648 }
2649
2650 return g_list_reverse (trash_repos);
2651 }
2652
2653 GList *
seaf_repo_manager_get_trash_repos_by_owner(SeafRepoManager * mgr,const char * owner,GError ** error)2654 seaf_repo_manager_get_trash_repos_by_owner (SeafRepoManager *mgr,
2655 const char *owner,
2656 GError **error)
2657 {
2658 GList *trash_repos = NULL;
2659 int rc;
2660
2661 rc = seaf_db_statement_foreach_row (mgr->seaf->db,
2662 "SELECT repo_id, repo_name, head_id, owner_id, "
2663 "size, del_time FROM RepoTrash WHERE owner_id = ?",
2664 collect_trash_repo, &trash_repos,
2665 1, "string", owner);
2666
2667 if (rc < 0) {
2668 while (trash_repos) {
2669 g_object_unref (trash_repos->data);
2670 trash_repos = g_list_delete_link (trash_repos, trash_repos);
2671 }
2672 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2673 "Failed to get trashed repo from db.");
2674 return NULL;
2675 }
2676
2677 return trash_repos;
2678 }
2679
2680 SeafileTrashRepo *
seaf_repo_manager_get_repo_from_trash(SeafRepoManager * mgr,const char * repo_id)2681 seaf_repo_manager_get_repo_from_trash (SeafRepoManager *mgr,
2682 const char *repo_id)
2683 {
2684 SeafileTrashRepo *ret = NULL;
2685 GList *trash_repos = NULL;
2686 char *sql;
2687 int rc;
2688
2689 sql = "SELECT repo_id, repo_name, head_id, owner_id, size, del_time FROM RepoTrash "
2690 "WHERE repo_id = ?";
2691 rc = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
2692 collect_trash_repo, &trash_repos,
2693 1, "string", repo_id);
2694 if (rc < 0)
2695 return NULL;
2696
2697 /* There should be only one results, since repo_id is a PK. */
2698 if (trash_repos)
2699 ret = trash_repos->data;
2700
2701 g_list_free (trash_repos);
2702 return ret;
2703 }
2704
2705 int
seaf_repo_manager_del_repo_from_trash(SeafRepoManager * mgr,const char * repo_id,GError ** error)2706 seaf_repo_manager_del_repo_from_trash (SeafRepoManager *mgr,
2707 const char *repo_id,
2708 GError **error)
2709 {
2710 /* As long as the repo is successfully moved into GarbageRepo table,
2711 * we consider this operation successful.
2712 */
2713 if (add_deleted_repo_record (mgr, repo_id) < 0) {
2714 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2715 "DB error: Add deleted record");
2716 return -1;
2717 }
2718
2719 seaf_db_statement_query (mgr->seaf->db,
2720 "DELETE FROM RepoFileCount WHERE repo_id = ?",
2721 1, "string", repo_id);
2722
2723 seaf_db_statement_query (mgr->seaf->db,
2724 "DELETE FROM RepoTrash WHERE repo_id = ?",
2725 1, "string", repo_id);
2726
2727 seaf_db_statement_query (mgr->seaf->db,
2728 "DELETE FROM RepoInfo WHERE repo_id = ?",
2729 1, "string", repo_id);
2730
2731 return 0;
2732 }
2733
2734 int
seaf_repo_manager_empty_repo_trash(SeafRepoManager * mgr,GError ** error)2735 seaf_repo_manager_empty_repo_trash (SeafRepoManager *mgr, GError **error)
2736 {
2737 GList *trash_repos = NULL, *ptr;
2738 SeafileTrashRepo *repo;
2739
2740 trash_repos = seaf_repo_manager_get_trash_repo_list (mgr, -1, -1, error);
2741 if (*error)
2742 return -1;
2743
2744 for (ptr = trash_repos; ptr; ptr = ptr->next) {
2745 repo = ptr->data;
2746 seaf_repo_manager_del_repo_from_trash (mgr,
2747 seafile_trash_repo_get_repo_id(repo),
2748 NULL);
2749 g_object_unref (repo);
2750 }
2751
2752 g_list_free (trash_repos);
2753 return 0;
2754 }
2755
2756 int
seaf_repo_manager_empty_repo_trash_by_owner(SeafRepoManager * mgr,const char * owner,GError ** error)2757 seaf_repo_manager_empty_repo_trash_by_owner (SeafRepoManager *mgr,
2758 const char *owner,
2759 GError **error)
2760 {
2761 GList *trash_repos = NULL, *ptr;
2762 SeafileTrashRepo *repo;
2763
2764 trash_repos = seaf_repo_manager_get_trash_repos_by_owner (mgr, owner, error);
2765 if (*error)
2766 return -1;
2767
2768 for (ptr = trash_repos; ptr; ptr = ptr->next) {
2769 repo = ptr->data;
2770 seaf_repo_manager_del_repo_from_trash (mgr,
2771 seafile_trash_repo_get_repo_id(repo),
2772 NULL);
2773 g_object_unref (repo);
2774 }
2775
2776 g_list_free (trash_repos);
2777 return 0;
2778 }
2779
2780 int
seaf_repo_manager_restore_repo_from_trash(SeafRepoManager * mgr,const char * repo_id,GError ** error)2781 seaf_repo_manager_restore_repo_from_trash (SeafRepoManager *mgr,
2782 const char *repo_id,
2783 GError **error)
2784 {
2785 SeafileTrashRepo *repo = NULL;
2786 int ret = 0;
2787 gboolean exists = FALSE;
2788 gboolean db_err;
2789
2790 repo = seaf_repo_manager_get_repo_from_trash (mgr, repo_id);
2791 if (!repo) {
2792 seaf_warning ("Repo %.8s not found in trash.\n", repo_id);
2793 return -1;
2794 }
2795
2796 SeafDBTrans *trans = seaf_db_begin_transaction (mgr->seaf->db);
2797
2798 exists = seaf_db_trans_check_for_existence (trans,
2799 "SELECT 1 FROM Repo WHERE repo_id=?",
2800 &db_err, 1, "string", repo_id);
2801
2802 if (!exists) {
2803 ret = seaf_db_trans_query (trans,
2804 "INSERT INTO Repo(repo_id) VALUES (?)",
2805 1, "string", repo_id) < 0;
2806 if (ret < 0) {
2807 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2808 "DB error: Insert Repo.");
2809 seaf_db_rollback (trans);
2810 seaf_db_trans_close (trans);
2811 goto out;
2812 }
2813 }
2814
2815 exists = seaf_db_trans_check_for_existence (trans,
2816 "SELECT 1 FROM RepoOwner WHERE repo_id=?",
2817 &db_err, 1, "string", repo_id);
2818
2819 if (!exists) {
2820 ret = seaf_db_trans_query (trans,
2821 "INSERT INTO RepoOwner (repo_id, owner_id) VALUES (?, ?)",
2822 2, "string", repo_id,
2823 "string", seafile_trash_repo_get_owner_id(repo));
2824 if (ret < 0) {
2825 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2826 "DB error: Insert Repo Owner.");
2827 seaf_db_rollback (trans);
2828 seaf_db_trans_close (trans);
2829 goto out;
2830 }
2831 }
2832
2833 exists = seaf_db_trans_check_for_existence (trans,
2834 "SELECT 1 FROM Branch WHERE repo_id=?",
2835 &db_err, 1, "string", repo_id);
2836 if (!exists) {
2837 ret = seaf_db_trans_query (trans,
2838 "INSERT INTO Branch (name, repo_id, commit_id) VALUES ('master', ?, ?)",
2839 2, "string", repo_id,
2840 "string", seafile_trash_repo_get_head_id(repo));
2841 if (ret < 0) {
2842 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2843 "DB error: Insert Branch.");
2844 seaf_db_rollback (trans);
2845 seaf_db_trans_close (trans);
2846 goto out;
2847 }
2848 }
2849
2850 exists = seaf_db_trans_check_for_existence (trans,
2851 "SELECT 1 FROM RepoHead WHERE repo_id=?",
2852 &db_err, 1, "string", repo_id);
2853 if (!exists) {
2854 ret = seaf_db_trans_query (trans,
2855 "INSERT INTO RepoHead (repo_id, branch_name) VALUES (?, 'master')",
2856 1, "string", repo_id);
2857 if (ret < 0) {
2858 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2859 "DB error: Set RepoHead.");
2860 seaf_db_rollback (trans);
2861 seaf_db_trans_close (trans);
2862 goto out;
2863 }
2864 }
2865
2866 // Restore repo size
2867 exists = seaf_db_trans_check_for_existence (trans,
2868 "SELECT 1 FROM RepoSize WHERE repo_id=?",
2869 &db_err, 1, "string", repo_id);
2870
2871 if (!exists) {
2872 ret = seaf_db_trans_query (trans,
2873 "INSERT INTO RepoSize (repo_id, size, head_id) VALUES (?, ?, ?)",
2874 3, "string", repo_id,
2875 "int64", seafile_trash_repo_get_size (repo),
2876 "string", seafile_trash_repo_get_head_id (repo));
2877 if (ret < 0) {
2878 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2879 "DB error: Insert Repo Size.");
2880 seaf_db_rollback (trans);
2881 seaf_db_trans_close (trans);
2882 goto out;
2883 }
2884 }
2885
2886 ret = seaf_db_trans_query (trans,
2887 "DELETE FROM RepoTrash WHERE repo_id = ?",
2888 1, "string", repo_id);
2889 if (ret < 0) {
2890 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2891 "DB error: delete from RepoTrash.");
2892 seaf_db_rollback (trans);
2893 seaf_db_trans_close (trans);
2894 goto out;
2895 }
2896
2897 if (seaf_db_commit (trans) < 0) {
2898 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
2899 "DB error: Failed to commit.");
2900 seaf_db_rollback (trans);
2901 ret = -1;
2902 }
2903
2904 seaf_db_trans_close (trans);
2905
2906 out:
2907 g_object_unref (repo);
2908 return ret;
2909 }
2910
2911 /* Web access permission. */
2912
2913 int
seaf_repo_manager_set_access_property(SeafRepoManager * mgr,const char * repo_id,const char * ap)2914 seaf_repo_manager_set_access_property (SeafRepoManager *mgr, const char *repo_id,
2915 const char *ap)
2916 {
2917 int rc;
2918
2919 if (seaf_repo_manager_query_access_property (mgr, repo_id) == NULL) {
2920 rc = seaf_db_statement_query (mgr->seaf->db,
2921 "INSERT INTO WebAP (repo_id, access_property) VALUES (?, ?)",
2922 2, "string", repo_id, "string", ap);
2923 } else {
2924 rc = seaf_db_statement_query (mgr->seaf->db,
2925 "UPDATE WebAP SET access_property=? "
2926 "WHERE repo_id=?",
2927 2, "string", ap, "string", repo_id);
2928 }
2929
2930 if (rc < 0) {
2931 seaf_warning ("DB error when set access property for repo %s, %s.\n", repo_id, ap);
2932 return -1;
2933 }
2934
2935 return 0;
2936 }
2937
2938 static gboolean
get_ap(SeafDBRow * row,void * data)2939 get_ap (SeafDBRow *row, void *data)
2940 {
2941 char **ap = data;
2942
2943 *ap = g_strdup (seaf_db_row_get_column_text (row, 0));
2944
2945 return FALSE;
2946 }
2947
2948 char *
seaf_repo_manager_query_access_property(SeafRepoManager * mgr,const char * repo_id)2949 seaf_repo_manager_query_access_property (SeafRepoManager *mgr, const char *repo_id)
2950 {
2951 char *sql;
2952 char *ret = NULL;
2953
2954 sql = "SELECT access_property FROM WebAP WHERE repo_id=?";
2955
2956 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_ap, &ret,
2957 1, "string", repo_id) < 0) {
2958 seaf_warning ("DB error when get access property for repo %s.\n", repo_id);
2959 return NULL;
2960 }
2961
2962 return ret;
2963 }
2964
2965 /* Group repos. */
2966
2967 int
seaf_repo_manager_add_group_repo(SeafRepoManager * mgr,const char * repo_id,int group_id,const char * owner,const char * permission,GError ** error)2968 seaf_repo_manager_add_group_repo (SeafRepoManager *mgr,
2969 const char *repo_id,
2970 int group_id,
2971 const char *owner,
2972 const char *permission,
2973 GError **error)
2974 {
2975 if (seaf_db_statement_query (mgr->seaf->db,
2976 "INSERT INTO RepoGroup (repo_id, group_id, user_name, permission) VALUES (?, ?, ?, ?)",
2977 4, "string", repo_id, "int", group_id,
2978 "string", owner, "string", permission) < 0)
2979 return -1;
2980
2981 return 0;
2982 }
2983
2984 int
seaf_repo_manager_del_group_repo(SeafRepoManager * mgr,const char * repo_id,int group_id,GError ** error)2985 seaf_repo_manager_del_group_repo (SeafRepoManager *mgr,
2986 const char *repo_id,
2987 int group_id,
2988 GError **error)
2989 {
2990 return seaf_db_statement_query (mgr->seaf->db,
2991 "DELETE FROM RepoGroup WHERE group_id=? "
2992 "AND repo_id=?",
2993 2, "int", group_id, "string", repo_id);
2994 }
2995
2996 static gboolean
get_group_ids_cb(SeafDBRow * row,void * data)2997 get_group_ids_cb (SeafDBRow *row, void *data)
2998 {
2999 GList **plist = data;
3000
3001 int group_id = seaf_db_row_get_column_int (row, 0);
3002
3003 *plist = g_list_prepend (*plist, (gpointer)(long)group_id);
3004
3005 return TRUE;
3006 }
3007
3008 GList *
seaf_repo_manager_get_groups_by_repo(SeafRepoManager * mgr,const char * repo_id,GError ** error)3009 seaf_repo_manager_get_groups_by_repo (SeafRepoManager *mgr,
3010 const char *repo_id,
3011 GError **error)
3012 {
3013 char *sql;
3014 GList *group_ids = NULL;
3015
3016 sql = "SELECT group_id FROM RepoGroup WHERE repo_id = ?";
3017
3018 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_group_ids_cb,
3019 &group_ids, 1, "string", repo_id) < 0) {
3020 g_list_free (group_ids);
3021 return NULL;
3022 }
3023
3024 return g_list_reverse (group_ids);
3025 }
3026
3027 static gboolean
get_group_perms_cb(SeafDBRow * row,void * data)3028 get_group_perms_cb (SeafDBRow *row, void *data)
3029 {
3030 GList **plist = data;
3031 GroupPerm *perm = g_new0 (GroupPerm, 1);
3032
3033 perm->group_id = seaf_db_row_get_column_int (row, 0);
3034 const char *permission = seaf_db_row_get_column_text(row, 1);
3035 g_strlcpy (perm->permission, permission, sizeof(perm->permission));
3036
3037 *plist = g_list_prepend (*plist, perm);
3038
3039 return TRUE;
3040 }
3041
3042 GList *
seaf_repo_manager_get_group_perm_by_repo(SeafRepoManager * mgr,const char * repo_id,GError ** error)3043 seaf_repo_manager_get_group_perm_by_repo (SeafRepoManager *mgr,
3044 const char *repo_id,
3045 GError **error)
3046 {
3047 char *sql;
3048 GList *group_perms = NULL, *p;
3049
3050 sql = "SELECT group_id, permission FROM RepoGroup WHERE repo_id = ?";
3051
3052 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_group_perms_cb,
3053 &group_perms, 1, "string", repo_id) < 0) {
3054 for (p = group_perms; p != NULL; p = p->next)
3055 g_free (p->data);
3056 g_list_free (group_perms);
3057 return NULL;
3058 }
3059
3060 return g_list_reverse (group_perms);
3061 }
3062
3063 int
seaf_repo_manager_set_group_repo_perm(SeafRepoManager * mgr,const char * repo_id,int group_id,const char * permission,GError ** error)3064 seaf_repo_manager_set_group_repo_perm (SeafRepoManager *mgr,
3065 const char *repo_id,
3066 int group_id,
3067 const char *permission,
3068 GError **error)
3069 {
3070 return seaf_db_statement_query (mgr->seaf->db,
3071 "UPDATE RepoGroup SET permission=? WHERE "
3072 "repo_id=? AND group_id=?",
3073 3, "string", permission, "string", repo_id,
3074 "int", group_id);
3075 }
3076
3077 int
seaf_repo_manager_set_subdir_group_perm_by_path(SeafRepoManager * mgr,const char * repo_id,const char * username,int group_id,const char * permission,const char * path)3078 seaf_repo_manager_set_subdir_group_perm_by_path (SeafRepoManager *mgr,
3079 const char *repo_id,
3080 const char *username,
3081 int group_id,
3082 const char *permission,
3083 const char *path)
3084 {
3085 return seaf_db_statement_query (mgr->seaf->db,
3086 "UPDATE RepoGroup SET permission=? WHERE repo_id IN "
3087 "(SELECT repo_id FROM VirtualRepo WHERE origin_repo=? AND path=?) "
3088 "AND group_id=? AND user_name=?",
3089 5, "string", permission,
3090 "string", repo_id,
3091 "string", path,
3092 "int", group_id,
3093 "string", username);
3094 }
3095 static gboolean
get_group_repoids_cb(SeafDBRow * row,void * data)3096 get_group_repoids_cb (SeafDBRow *row, void *data)
3097 {
3098 GList **p_list = data;
3099
3100 char *repo_id = g_strdup ((const char *)seaf_db_row_get_column_text (row, 0));
3101
3102 *p_list = g_list_prepend (*p_list, repo_id);
3103
3104 return TRUE;
3105 }
3106
3107 GList *
seaf_repo_manager_get_group_repoids(SeafRepoManager * mgr,int group_id,GError ** error)3108 seaf_repo_manager_get_group_repoids (SeafRepoManager *mgr,
3109 int group_id,
3110 GError **error)
3111 {
3112 char *sql;
3113 GList *repo_ids = NULL;
3114
3115 sql = "SELECT repo_id FROM RepoGroup WHERE group_id = ?";
3116 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_group_repoids_cb,
3117 &repo_ids, 1, "int", group_id) < 0)
3118 return NULL;
3119
3120 return g_list_reverse (repo_ids);
3121 }
3122
3123 static gboolean
get_group_repos_cb(SeafDBRow * row,void * data)3124 get_group_repos_cb (SeafDBRow *row, void *data)
3125 {
3126 GList **p_list = data;
3127 SeafileRepo *srepo = NULL;
3128
3129 const char *repo_id = seaf_db_row_get_column_text (row, 0);
3130 const char *vrepo_id = seaf_db_row_get_column_text (row, 1);
3131 int group_id = seaf_db_row_get_column_int (row, 2);
3132 const char *user_name = seaf_db_row_get_column_text (row, 3);
3133 const char *permission = seaf_db_row_get_column_text (row, 4);
3134 const char *commit_id = seaf_db_row_get_column_text (row, 5);
3135 gint64 size = seaf_db_row_get_column_int64 (row, 6);
3136 const char *repo_name = seaf_db_row_get_column_text (row, 9);
3137 gint64 update_time = seaf_db_row_get_column_int64 (row, 10);
3138 int version = seaf_db_row_get_column_int (row, 11);
3139 gboolean is_encrypted = seaf_db_row_get_column_int (row, 12) ? TRUE : FALSE;
3140 const char *last_modifier = seaf_db_row_get_column_text (row, 13);
3141 int status = seaf_db_row_get_column_int (row, 14);
3142
3143 char *user_name_l = g_ascii_strdown (user_name, -1);
3144
3145 srepo = g_object_new (SEAFILE_TYPE_REPO,
3146 "share_type", "group",
3147 "repo_id", repo_id,
3148 "id", repo_id,
3149 "head_cmmt_id", commit_id,
3150 "group_id", group_id,
3151 "user", user_name_l,
3152 "permission", permission,
3153 "is_virtual", (vrepo_id != NULL),
3154 "size", size,
3155 "status", status,
3156 NULL);
3157 g_free (user_name_l);
3158
3159 if (srepo != NULL) {
3160 if (vrepo_id) {
3161 const char *origin_repo_id = seaf_db_row_get_column_text (row, 7);
3162 const char *origin_path = seaf_db_row_get_column_text (row, 8);
3163 const char *origin_repo_name = seaf_db_row_get_column_text (row, 15);
3164 g_object_set (srepo, "store_id", origin_repo_id,
3165 "origin_repo_id", origin_repo_id,
3166 "origin_repo_name", origin_repo_name,
3167 "origin_path", origin_path, NULL);
3168 } else {
3169 g_object_set (srepo, "store_id", repo_id, NULL);
3170 }
3171 if (repo_name) {
3172 g_object_set (srepo, "name", repo_name,
3173 "repo_name", repo_name,
3174 "last_modify", update_time,
3175 "last_modified", update_time,
3176 "version", version,
3177 "encrypted", is_encrypted,
3178 "last_modifier", last_modifier, NULL);
3179 }
3180 *p_list = g_list_prepend (*p_list, srepo);
3181 }
3182
3183 return TRUE;
3184 }
3185
3186 void
seaf_fill_repo_obj_from_commit(GList ** repos)3187 seaf_fill_repo_obj_from_commit (GList **repos)
3188 {
3189 SeafileRepo *repo;
3190 SeafCommit *commit;
3191 char *repo_id;
3192 char *commit_id;
3193 char *repo_name = NULL;
3194 char *last_modifier = NULL;
3195 GList *p = *repos;
3196 GList *next;
3197
3198 while (p) {
3199 repo = p->data;
3200 g_object_get (repo, "name", &repo_name, NULL);
3201 g_object_get (repo, "last_modifier", &last_modifier, NULL);
3202 if (!repo_name || !last_modifier) {
3203 g_object_get (repo, "repo_id", &repo_id, "head_cmmt_id", &commit_id, NULL);
3204 commit = seaf_commit_manager_get_commit_compatible (seaf->commit_mgr,
3205 repo_id, commit_id);
3206 if (!commit) {
3207 seaf_warning ("Commit %s not found in repo %s\n", commit_id, repo_id);
3208 g_object_unref (repo);
3209 next = p->next;
3210 *repos = g_list_delete_link (*repos, p);
3211 p = next;
3212 if (repo_name)
3213 g_free (repo_name);
3214 if (last_modifier)
3215 g_free (last_modifier);
3216 } else {
3217 g_object_set (repo, "name", commit->repo_name,
3218 "repo_name", commit->repo_name,
3219 "last_modify", commit->ctime,
3220 "last_modified", commit->ctime,
3221 "version", commit->version,
3222 "encrypted", commit->encrypted,
3223 "last_modifier", commit->creator_name,
3224 NULL);
3225
3226 /* Set to database */
3227 set_repo_commit_to_db (repo_id, commit->repo_name, commit->ctime, commit->version,
3228 commit->encrypted, commit->creator_name);
3229 seaf_commit_unref (commit);
3230 }
3231 g_free (repo_id);
3232 g_free (commit_id);
3233 }
3234 if (repo_name)
3235 g_free (repo_name);
3236 if (last_modifier)
3237 g_free (last_modifier);
3238
3239 p = p->next;
3240 }
3241 }
3242
3243 GList *
seaf_repo_manager_get_repos_by_group(SeafRepoManager * mgr,int group_id,GError ** error)3244 seaf_repo_manager_get_repos_by_group (SeafRepoManager *mgr,
3245 int group_id,
3246 GError **error)
3247 {
3248 char *sql;
3249 GList *repos = NULL;
3250 GList *p;
3251
3252 sql = "SELECT RepoGroup.repo_id, v.repo_id, "
3253 "group_id, user_name, permission, commit_id, s.size, "
3254 "v.origin_repo, v.path, i.name, "
3255 "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, "
3256 "(SELECT name FROM RepoInfo WHERE repo_id=v.origin_repo) "
3257 "FROM RepoGroup LEFT JOIN VirtualRepo v ON "
3258 "RepoGroup.repo_id = v.repo_id "
3259 "LEFT JOIN RepoInfo i ON RepoGroup.repo_id = i.repo_id "
3260 "LEFT JOIN RepoSize s ON RepoGroup.repo_id = s.repo_id, "
3261 "Branch WHERE group_id = ? AND "
3262 "RepoGroup.repo_id = Branch.repo_id AND "
3263 "Branch.name = 'master'";
3264
3265 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_group_repos_cb,
3266 &repos, 1, "int", group_id) < 0) {
3267 for (p = repos; p; p = p->next) {
3268 g_object_unref (p->data);
3269 }
3270 g_list_free (repos);
3271 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3272 "Failed to get repos by group from db.");
3273 return NULL;
3274 }
3275
3276 seaf_fill_repo_obj_from_commit (&repos);
3277
3278 return g_list_reverse (repos);
3279 }
3280
3281 GList *
seaf_repo_manager_get_group_repos_by_owner(SeafRepoManager * mgr,const char * owner,GError ** error)3282 seaf_repo_manager_get_group_repos_by_owner (SeafRepoManager *mgr,
3283 const char *owner,
3284 GError **error)
3285 {
3286 char *sql;
3287 GList *repos = NULL;
3288 GList *p;
3289
3290 sql = "SELECT RepoGroup.repo_id, v.repo_id, "
3291 "group_id, user_name, permission, commit_id, s.size, "
3292 "v.origin_repo, v.path, i.name, "
3293 "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, "
3294 "(SELECT name FROM RepoInfo WHERE repo_id=v.origin_repo) "
3295 "FROM RepoGroup LEFT JOIN VirtualRepo v ON "
3296 "RepoGroup.repo_id = v.repo_id "
3297 "LEFT JOIN RepoInfo i ON RepoGroup.repo_id = i.repo_id "
3298 "LEFT JOIN RepoSize s ON RepoGroup.repo_id = s.repo_id, "
3299 "Branch WHERE user_name = ? AND "
3300 "RepoGroup.repo_id = Branch.repo_id AND "
3301 "Branch.name = 'master'";
3302 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_group_repos_cb,
3303 &repos, 1, "string", owner) < 0) {
3304 for (p = repos; p; p = p->next) {
3305 g_object_unref (p->data);
3306 }
3307 g_list_free (repos);
3308 return NULL;
3309 }
3310
3311 seaf_fill_repo_obj_from_commit (&repos);
3312
3313 return g_list_reverse (repos);
3314 }
3315
3316 static gboolean
get_group_repo_owner(SeafDBRow * row,void * data)3317 get_group_repo_owner (SeafDBRow *row, void *data)
3318 {
3319 char **share_from = data;
3320
3321 const char *owner = (const char *) seaf_db_row_get_column_text (row, 0);
3322 *share_from = g_ascii_strdown (owner, -1);
3323 /* There should be only one result. */
3324 return FALSE;
3325 }
3326
3327 char *
seaf_repo_manager_get_group_repo_owner(SeafRepoManager * mgr,const char * repo_id,GError ** error)3328 seaf_repo_manager_get_group_repo_owner (SeafRepoManager *mgr,
3329 const char *repo_id,
3330 GError **error)
3331 {
3332 char *sql;
3333 char *ret = NULL;
3334
3335 sql = "SELECT user_name FROM RepoGroup WHERE repo_id = ?";
3336 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
3337 get_group_repo_owner, &ret,
3338 1, "string", repo_id) < 0) {
3339 seaf_warning ("DB error when get repo share from for repo %s.\n",
3340 repo_id);
3341 return NULL;
3342 }
3343
3344 return ret;
3345 }
3346
3347 int
seaf_repo_manager_remove_group_repos(SeafRepoManager * mgr,int group_id,const char * owner,GError ** error)3348 seaf_repo_manager_remove_group_repos (SeafRepoManager *mgr,
3349 int group_id,
3350 const char *owner,
3351 GError **error)
3352 {
3353 SeafDB *db = mgr->seaf->db;
3354 int rc;
3355
3356 if (!owner) {
3357 rc = seaf_db_statement_query (db, "DELETE FROM RepoGroup WHERE group_id=?",
3358 1, "int", group_id);
3359 } else {
3360 rc = seaf_db_statement_query (db,
3361 "DELETE FROM RepoGroup WHERE group_id=? AND "
3362 "user_name = ?",
3363 2, "int", group_id, "string", owner);
3364 }
3365
3366 return rc;
3367 }
3368
3369 /* Inner public repos */
3370
3371 int
seaf_repo_manager_set_inner_pub_repo(SeafRepoManager * mgr,const char * repo_id,const char * permission)3372 seaf_repo_manager_set_inner_pub_repo (SeafRepoManager *mgr,
3373 const char *repo_id,
3374 const char *permission)
3375 {
3376 SeafDB *db = mgr->seaf->db;
3377 char sql[256];
3378
3379 if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
3380 gboolean err;
3381 snprintf(sql, sizeof(sql),
3382 "SELECT repo_id FROM InnerPubRepo WHERE repo_id=?");
3383 if (seaf_db_statement_exists (db, sql, &err,
3384 1, "string", repo_id))
3385 snprintf(sql, sizeof(sql),
3386 "UPDATE InnerPubRepo SET permission='%s' "
3387 "WHERE repo_id='%s'", permission, repo_id);
3388 else
3389 snprintf(sql, sizeof(sql),
3390 "INSERT INTO InnerPubRepo (repo_id, permission) VALUES "
3391 "('%s', '%s')", repo_id, permission);
3392 if (err)
3393 return -1;
3394 return seaf_db_query (db, sql);
3395 } else {
3396 return seaf_db_statement_query (db,
3397 "REPLACE INTO InnerPubRepo (repo_id, permission) VALUES (?, ?)",
3398 2, "string", repo_id, "string", permission);
3399 }
3400
3401 return -1;
3402 }
3403
3404 int
seaf_repo_manager_unset_inner_pub_repo(SeafRepoManager * mgr,const char * repo_id)3405 seaf_repo_manager_unset_inner_pub_repo (SeafRepoManager *mgr,
3406 const char *repo_id)
3407 {
3408 return seaf_db_statement_query (mgr->seaf->db,
3409 "DELETE FROM InnerPubRepo WHERE repo_id = ?",
3410 1, "string", repo_id);
3411 }
3412
3413 gboolean
seaf_repo_manager_is_inner_pub_repo(SeafRepoManager * mgr,const char * repo_id)3414 seaf_repo_manager_is_inner_pub_repo (SeafRepoManager *mgr,
3415 const char *repo_id)
3416 {
3417 gboolean db_err = FALSE;
3418
3419 return seaf_db_statement_exists (mgr->seaf->db,
3420 "SELECT repo_id FROM InnerPubRepo WHERE repo_id=?",
3421 &db_err, 1, "string", repo_id);
3422 }
3423
3424 static gboolean
collect_public_repos(SeafDBRow * row,void * data)3425 collect_public_repos (SeafDBRow *row, void *data)
3426 {
3427 GList **ret = (GList **)data;
3428 SeafileRepo *srepo;
3429 const char *repo_id, *vrepo_id, *owner, *permission, *commit_id;
3430 gint64 size;
3431
3432 repo_id = seaf_db_row_get_column_text (row, 0);
3433 vrepo_id = seaf_db_row_get_column_text (row, 1);
3434 owner = seaf_db_row_get_column_text (row, 2);
3435 permission = seaf_db_row_get_column_text (row, 3);
3436 commit_id = seaf_db_row_get_column_text (row, 4);
3437 size = seaf_db_row_get_column_int64 (row, 5);
3438 const char *repo_name = seaf_db_row_get_column_text (row, 8);
3439 gint64 update_time = seaf_db_row_get_column_int64 (row, 9);
3440 int version = seaf_db_row_get_column_int (row, 10);
3441 gboolean is_encrypted = seaf_db_row_get_column_int (row, 11) ? TRUE : FALSE;
3442 const char *last_modifier = seaf_db_row_get_column_text (row, 12);
3443 int status = seaf_db_row_get_column_int (row, 13);
3444
3445 char *owner_l = g_ascii_strdown (owner, -1);
3446
3447 srepo = g_object_new (SEAFILE_TYPE_REPO,
3448 "share_type", "public",
3449 "repo_id", repo_id,
3450 "id", repo_id,
3451 "head_cmmt_id", commit_id,
3452 "permission", permission,
3453 "user", owner_l,
3454 "is_virtual", (vrepo_id != NULL),
3455 "size", size,
3456 "status", status,
3457 NULL);
3458 g_free (owner_l);
3459
3460 if (srepo) {
3461 if (vrepo_id) {
3462 const char *origin_repo_id = seaf_db_row_get_column_text (row, 6);
3463 const char *origin_path = seaf_db_row_get_column_text (row, 7);
3464 g_object_set (srepo, "store_id", origin_repo_id,
3465 "origin_repo_id", origin_repo_id,
3466 "origin_path", origin_path, NULL);
3467 } else {
3468 g_object_set (srepo, "store_id", repo_id, NULL);
3469 }
3470
3471 if (repo_name) {
3472 g_object_set (srepo, "name", repo_name,
3473 "repo_name", repo_name,
3474 "last_modify", update_time,
3475 "last_modified", update_time,
3476 "version", version,
3477 "encrypted", is_encrypted,
3478 "last_modifier", last_modifier, NULL);
3479 }
3480
3481 *ret = g_list_prepend (*ret, srepo);
3482 }
3483
3484 return TRUE;
3485 }
3486
3487 GList *
seaf_repo_manager_list_inner_pub_repos(SeafRepoManager * mgr,gboolean * db_err)3488 seaf_repo_manager_list_inner_pub_repos (SeafRepoManager *mgr, gboolean *db_err)
3489 {
3490 GList *ret = NULL, *p;
3491 char *sql;
3492
3493 sql = "SELECT InnerPubRepo.repo_id, VirtualRepo.repo_id, "
3494 "owner_id, permission, commit_id, s.size, "
3495 "VirtualRepo.origin_repo, VirtualRepo.path, i.name, "
3496 "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status "
3497 "FROM InnerPubRepo LEFT JOIN VirtualRepo ON "
3498 "InnerPubRepo.repo_id=VirtualRepo.repo_id "
3499 "LEFT JOIN RepoInfo i ON InnerPubRepo.repo_id = i.repo_id "
3500 "LEFT JOIN RepoSize s ON InnerPubRepo.repo_id = s.repo_id, RepoOwner, Branch "
3501 "WHERE InnerPubRepo.repo_id=RepoOwner.repo_id AND "
3502 "InnerPubRepo.repo_id = Branch.repo_id AND Branch.name = 'master'";
3503
3504 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
3505 collect_public_repos, &ret,
3506 0) < 0) {
3507 for (p = ret; p != NULL; p = p->next)
3508 g_object_unref (p->data);
3509 g_list_free (ret);
3510 if (db_err)
3511 *db_err = TRUE;
3512 return NULL;
3513 }
3514
3515 seaf_fill_repo_obj_from_commit (&ret);
3516
3517 return g_list_reverse (ret);
3518 }
3519
3520 gint64
seaf_repo_manager_count_inner_pub_repos(SeafRepoManager * mgr)3521 seaf_repo_manager_count_inner_pub_repos (SeafRepoManager *mgr)
3522 {
3523 char sql[256];
3524
3525 snprintf (sql, 256, "SELECT COUNT(*) FROM InnerPubRepo");
3526
3527 return seaf_db_get_int64(mgr->seaf->db, sql);
3528 }
3529
3530 GList *
seaf_repo_manager_list_inner_pub_repos_by_owner(SeafRepoManager * mgr,const char * user)3531 seaf_repo_manager_list_inner_pub_repos_by_owner (SeafRepoManager *mgr,
3532 const char *user)
3533 {
3534 GList *ret = NULL, *p;
3535 char *sql;
3536
3537 sql = "SELECT InnerPubRepo.repo_id, VirtualRepo.repo_id, "
3538 "owner_id, permission, commit_id, s.size, "
3539 "VirtualRepo.origin_repo, VirtualRepo.path, i.name, "
3540 "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status "
3541 "FROM InnerPubRepo LEFT JOIN VirtualRepo ON "
3542 "InnerPubRepo.repo_id=VirtualRepo.repo_id "
3543 "LEFT JOIN RepoInfo i ON InnerPubRepo.repo_id = i.repo_id "
3544 "LEFT JOIN RepoSize s ON InnerPubRepo.repo_id = s.repo_id, RepoOwner, Branch "
3545 "WHERE InnerPubRepo.repo_id=RepoOwner.repo_id AND owner_id=? "
3546 "AND InnerPubRepo.repo_id = Branch.repo_id AND Branch.name = 'master'";
3547
3548 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
3549 collect_public_repos, &ret,
3550 1, "string", user) < 0) {
3551 for (p = ret; p != NULL; p = p->next)
3552 g_object_unref (p->data);
3553 g_list_free (ret);
3554 return NULL;
3555 }
3556
3557 seaf_fill_repo_obj_from_commit (&ret);
3558
3559 return g_list_reverse (ret);
3560 }
3561
3562 char *
seaf_repo_manager_get_inner_pub_repo_perm(SeafRepoManager * mgr,const char * repo_id)3563 seaf_repo_manager_get_inner_pub_repo_perm (SeafRepoManager *mgr,
3564 const char *repo_id)
3565 {
3566 char *sql;
3567
3568 sql = "SELECT permission FROM InnerPubRepo WHERE repo_id=?";
3569 return seaf_db_statement_get_string(mgr->seaf->db, sql, 1, "string", repo_id);
3570 }
3571
3572
3573 int
seaf_repo_manager_is_valid_filename(SeafRepoManager * mgr,const char * repo_id,const char * filename,GError ** error)3574 seaf_repo_manager_is_valid_filename (SeafRepoManager *mgr,
3575 const char *repo_id,
3576 const char *filename,
3577 GError **error)
3578 {
3579 if (should_ignore_file(filename, NULL))
3580 return 0;
3581 else
3582 return 1;
3583 }
3584
3585 static int
create_repo_common(SeafRepoManager * mgr,const char * repo_id,const char * repo_name,const char * repo_desc,const char * user,const char * magic,const char * random_key,const char * salt,int enc_version,GError ** error)3586 create_repo_common (SeafRepoManager *mgr,
3587 const char *repo_id,
3588 const char *repo_name,
3589 const char *repo_desc,
3590 const char *user,
3591 const char *magic,
3592 const char *random_key,
3593 const char *salt,
3594 int enc_version,
3595 GError **error)
3596 {
3597 SeafRepo *repo = NULL;
3598 SeafCommit *commit = NULL;
3599 SeafBranch *master = NULL;
3600 int ret = -1;
3601
3602 if (enc_version != 4 && enc_version != 3 && enc_version != 2 && enc_version != -1) {
3603 seaf_warning ("Unsupported enc version %d.\n", enc_version);
3604 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3605 "Unsupported encryption version");
3606 return -1;
3607 }
3608
3609 if (enc_version >= 2) {
3610 if (!magic || strlen(magic) != 64) {
3611 seaf_warning ("Bad magic.\n");
3612 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3613 "Bad magic");
3614 return -1;
3615 }
3616 if (!random_key || strlen(random_key) != 96) {
3617 seaf_warning ("Bad random key.\n");
3618 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3619 "Bad random key");
3620 return -1;
3621 }
3622 }
3623 if (enc_version >= 3) {
3624 if (!salt || strlen(salt) != 64) {
3625 seaf_warning ("Bad salt.\n");
3626 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3627 "Bad salt");
3628 return -1;
3629 }
3630 }
3631
3632 repo = seaf_repo_new (repo_id, repo_name, repo_desc);
3633
3634 repo->no_local_history = TRUE;
3635 if (enc_version >= 2) {
3636 repo->encrypted = TRUE;
3637 repo->enc_version = enc_version;
3638 memcpy (repo->magic, magic, 64);
3639 memcpy (repo->random_key, random_key, 96);
3640 }
3641 if (enc_version >= 3)
3642 memcpy (repo->salt, salt, 64);
3643
3644 repo->version = CURRENT_REPO_VERSION;
3645 memcpy (repo->store_id, repo_id, 36);
3646
3647 commit = seaf_commit_new (NULL, repo->id,
3648 EMPTY_SHA1, /* root id */
3649 user, /* creator */
3650 EMPTY_SHA1, /* creator id */
3651 "Created library", /* description */
3652 0); /* ctime */
3653
3654 seaf_repo_to_commit (repo, commit);
3655 if (seaf_commit_manager_add_commit (seaf->commit_mgr, commit) < 0) {
3656 seaf_warning ("Failed to add commit.\n");
3657 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3658 "Failed to add commit");
3659 goto out;
3660 }
3661
3662 master = seaf_branch_new ("master", repo->id, commit->commit_id);
3663 if (seaf_branch_manager_add_branch (seaf->branch_mgr, master) < 0) {
3664 seaf_warning ("Failed to add branch.\n");
3665 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3666 "Failed to add branch");
3667 goto out;
3668 }
3669
3670 if (seaf_repo_set_head (repo, master) < 0) {
3671 seaf_warning ("Failed to set repo head.\n");
3672 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3673 "Failed to set repo head.");
3674 goto out;
3675 }
3676
3677 if (seaf_repo_manager_add_repo (mgr, repo) < 0) {
3678 seaf_warning ("Failed to add repo.\n");
3679 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3680 "Failed to add repo.");
3681 goto out;
3682 }
3683
3684 seaf_repo_manager_update_repo_info (mgr, repo->id, repo->head->commit_id);
3685
3686 ret = 0;
3687 out:
3688 if (repo)
3689 seaf_repo_unref (repo);
3690 if (commit)
3691 seaf_commit_unref (commit);
3692 if (master)
3693 seaf_branch_unref (master);
3694
3695 return ret;
3696 }
3697
3698 char *
seaf_repo_manager_create_new_repo(SeafRepoManager * mgr,const char * repo_name,const char * repo_desc,const char * owner_email,const char * passwd,int enc_version,GError ** error)3699 seaf_repo_manager_create_new_repo (SeafRepoManager *mgr,
3700 const char *repo_name,
3701 const char *repo_desc,
3702 const char *owner_email,
3703 const char *passwd,
3704 int enc_version,
3705 GError **error)
3706 {
3707 char *repo_id = NULL;
3708 char salt[65], magic[65], random_key[97];
3709
3710 repo_id = gen_uuid ();
3711
3712 if (passwd && passwd[0] != 0) {
3713 if (seafile_generate_repo_salt (salt) < 0) {
3714 goto bad;
3715 }
3716 seafile_generate_magic (enc_version, repo_id, passwd, salt, magic);
3717 if (seafile_generate_random_key (passwd, enc_version, salt, random_key) < 0) {
3718 goto bad;
3719 }
3720 }
3721
3722 int rc;
3723 if (passwd)
3724 rc = create_repo_common (mgr, repo_id, repo_name, repo_desc, owner_email,
3725 magic, random_key, salt, enc_version, error);
3726 else
3727 rc = create_repo_common (mgr, repo_id, repo_name, repo_desc, owner_email,
3728 NULL, NULL, NULL, -1, error);
3729 if (rc < 0)
3730 goto bad;
3731
3732 if (seaf_repo_manager_set_repo_owner (mgr, repo_id, owner_email) < 0) {
3733 seaf_warning ("Failed to set repo owner.\n");
3734 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3735 "Failed to set repo owner.");
3736 goto bad;
3737 }
3738
3739 return repo_id;
3740
3741 bad:
3742 if (repo_id)
3743 g_free (repo_id);
3744 return NULL;
3745 }
3746
3747 char *
seaf_repo_manager_create_enc_repo(SeafRepoManager * mgr,const char * repo_id,const char * repo_name,const char * repo_desc,const char * owner_email,const char * magic,const char * random_key,const char * salt,int enc_version,GError ** error)3748 seaf_repo_manager_create_enc_repo (SeafRepoManager *mgr,
3749 const char *repo_id,
3750 const char *repo_name,
3751 const char *repo_desc,
3752 const char *owner_email,
3753 const char *magic,
3754 const char *random_key,
3755 const char *salt,
3756 int enc_version,
3757 GError **error)
3758 {
3759 if (!repo_id || !is_uuid_valid (repo_id)) {
3760 seaf_warning ("Invalid repo_id.\n");
3761 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3762 "Invalid repo id");
3763 return NULL;
3764 }
3765
3766 if (seaf_repo_manager_repo_exists (mgr, repo_id)) {
3767 seaf_warning ("Repo %s exists, refuse to create.\n", repo_id);
3768 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3769 "Repo already exists");
3770 return NULL;
3771 }
3772
3773 if (create_repo_common (mgr, repo_id, repo_name, repo_desc, owner_email,
3774 magic, random_key, salt, enc_version, error) < 0)
3775 return NULL;
3776
3777 if (seaf_repo_manager_set_repo_owner (mgr, repo_id, owner_email) < 0) {
3778 seaf_warning ("Failed to set repo owner.\n");
3779 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3780 "Failed to set repo owner.");
3781 return NULL;
3782 }
3783
3784 return g_strdup (repo_id);
3785 }
3786
reap_token(void * data)3787 static int reap_token (void *data)
3788 {
3789 SeafRepoManager *mgr = data;
3790 GHashTableIter iter;
3791 gpointer key, value;
3792 DecryptedToken *t;
3793
3794 pthread_rwlock_wrlock (&mgr->priv->lock);
3795
3796 gint64 now = (gint64)time(NULL);
3797
3798 g_hash_table_iter_init (&iter, mgr->priv->decrypted_tokens);
3799 while (g_hash_table_iter_next (&iter, &key, &value)) {
3800 t = value;
3801 if (now >= t->reap_time)
3802 g_hash_table_iter_remove (&iter);
3803 }
3804
3805 pthread_rwlock_unlock (&mgr->priv->lock);
3806
3807 return TRUE;
3808 }
3809
decrypted_token_free(DecryptedToken * token)3810 static void decrypted_token_free (DecryptedToken *token)
3811 {
3812 if (!token)
3813 return;
3814 g_free (token->token);
3815 g_free (token);
3816 }
3817
3818 void
seaf_repo_manager_add_decrypted_token(SeafRepoManager * mgr,const char * encrypted_token,const char * session_key,const char * decrypted_token)3819 seaf_repo_manager_add_decrypted_token (SeafRepoManager *mgr,
3820 const char *encrypted_token,
3821 const char *session_key,
3822 const char *decrypted_token)
3823 {
3824 char key[256];
3825 DecryptedToken *token;
3826
3827 snprintf (key, sizeof(key), "%s%s", encrypted_token, session_key);
3828 key[255] = 0;
3829
3830 pthread_rwlock_wrlock (&mgr->priv->lock);
3831
3832 token = g_new0 (DecryptedToken, 1);
3833 token->token = g_strdup(decrypted_token);
3834 token->reap_time = (gint64)time(NULL) + DECRYPTED_TOKEN_TTL;
3835
3836 g_hash_table_insert (mgr->priv->decrypted_tokens,
3837 g_strdup(key),
3838 token);
3839
3840 pthread_rwlock_unlock (&mgr->priv->lock);
3841 }
3842
3843 char *
seaf_repo_manager_get_decrypted_token(SeafRepoManager * mgr,const char * encrypted_token,const char * session_key)3844 seaf_repo_manager_get_decrypted_token (SeafRepoManager *mgr,
3845 const char *encrypted_token,
3846 const char *session_key)
3847 {
3848 char key[256];
3849 DecryptedToken *token;
3850
3851 snprintf (key, sizeof(key), "%s%s", encrypted_token, session_key);
3852 key[255] = 0;
3853
3854 pthread_rwlock_rdlock (&mgr->priv->lock);
3855 token = g_hash_table_lookup (mgr->priv->decrypted_tokens, key);
3856 pthread_rwlock_unlock (&mgr->priv->lock);
3857
3858 if (token)
3859 return g_strdup(token->token);
3860 return NULL;
3861 }
3862
3863 static gboolean
get_shared_users(SeafDBRow * row,void * data)3864 get_shared_users (SeafDBRow *row, void *data)
3865 {
3866 GList **shared_users = data;
3867 const char *user = seaf_db_row_get_column_text (row, 0);
3868 const char *perm = seaf_db_row_get_column_text (row, 1);
3869 const char *repo_id = seaf_db_row_get_column_text (row, 2);
3870
3871 SeafileSharedUser *uobj = g_object_new (SEAFILE_TYPE_SHARED_USER,
3872 "repo_id", repo_id,
3873 "user", user,
3874 "perm", perm,
3875 NULL);
3876 *shared_users = g_list_prepend (*shared_users, uobj);
3877
3878 return TRUE;
3879 }
3880
3881 GList *
seaf_repo_manager_get_shared_users_for_subdir(SeafRepoManager * mgr,const char * repo_id,const char * path,const char * from_user,GError ** error)3882 seaf_repo_manager_get_shared_users_for_subdir (SeafRepoManager *mgr,
3883 const char *repo_id,
3884 const char *path,
3885 const char *from_user,
3886 GError **error)
3887 {
3888 GList *shared_users = NULL;
3889 int ret = seaf_db_statement_foreach_row (mgr->seaf->db,
3890 "SELECT to_email, permission, v.repo_id "
3891 "FROM SharedRepo s, VirtualRepo v "
3892 "WHERE s.repo_id = v.repo_id AND v.origin_repo = ? "
3893 "AND v.path = ? AND s.from_email = ?",
3894 get_shared_users, &shared_users, 3, "string", repo_id,
3895 "string", path, "string", from_user);
3896 if (ret < 0) {
3897 seaf_warning ("Failed to get shared users for %.8s(%s).\n", repo_id, path);
3898 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3899 "Failed to get shared users for subdir from db");
3900 while (shared_users) {
3901 g_object_unref (shared_users->data);
3902 shared_users = g_list_delete_link (shared_users, shared_users);
3903 }
3904 return NULL;
3905 }
3906
3907 return shared_users;
3908 }
3909
3910 static gboolean
get_shared_groups(SeafDBRow * row,void * data)3911 get_shared_groups (SeafDBRow *row, void *data)
3912 {
3913 GList **shared_groups = data;
3914 int group = seaf_db_row_get_column_int (row, 0);
3915 const char *perm = seaf_db_row_get_column_text (row, 1);
3916 const char *repo_id = seaf_db_row_get_column_text (row, 2);
3917
3918 SeafileSharedGroup *gobj = g_object_new (SEAFILE_TYPE_SHARED_GROUP,
3919 "repo_id", repo_id,
3920 "group_id", group,
3921 "perm", perm,
3922 NULL);
3923
3924 *shared_groups = g_list_prepend (*shared_groups, gobj);
3925
3926 return TRUE;
3927 }
3928
3929 GList *
seaf_repo_manager_get_shared_groups_for_subdir(SeafRepoManager * mgr,const char * repo_id,const char * path,const char * from_user,GError ** error)3930 seaf_repo_manager_get_shared_groups_for_subdir (SeafRepoManager *mgr,
3931 const char *repo_id,
3932 const char *path,
3933 const char *from_user,
3934 GError **error)
3935 {
3936 GList *shared_groups = NULL;
3937 int ret = seaf_db_statement_foreach_row (mgr->seaf->db,
3938 "SELECT group_id, permission, v.repo_id "
3939 "FROM RepoGroup r, VirtualRepo v "
3940 "WHERE r.repo_id = v.repo_id AND v.origin_repo = ? "
3941 "AND v.path = ? AND r.user_name = ?",
3942 get_shared_groups, &shared_groups, 3, "string", repo_id,
3943 "string", path, "string", from_user);
3944 if (ret < 0) {
3945 seaf_warning ("Failed to get shared groups for %.8s(%s).\n", repo_id, path);
3946 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
3947 "Failed to get shared groups fro subdir from db");
3948 while (shared_groups) {
3949 g_object_unref (shared_groups->data);
3950 shared_groups = g_list_delete_link (shared_groups, shared_groups);
3951 }
3952 return NULL;
3953 }
3954
3955 return shared_groups;
3956 }
3957 int
seaf_repo_manager_edit_repo(const char * repo_id,const char * name,const char * description,const char * user,GError ** error)3958 seaf_repo_manager_edit_repo (const char *repo_id,
3959 const char *name,
3960 const char *description,
3961 const char *user,
3962 GError **error)
3963 {
3964 SeafRepo *repo = NULL;
3965 SeafCommit *commit = NULL, *parent = NULL;
3966 int ret = 0;
3967
3968 if (!name && !description) {
3969 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS,
3970 "At least one argument should be non-null");
3971 return -1;
3972 }
3973
3974 if (!is_uuid_valid (repo_id)) {
3975 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Invalid repo id");
3976 return -1;
3977 }
3978
3979 retry:
3980 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
3981 if (!repo) {
3982 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "No such library");
3983 return -1;
3984 }
3985 if (!name)
3986 name = repo->name;
3987 if (!description)
3988 description = repo->desc;
3989
3990 /*
3991 * We only change repo_name or repo_desc, so just copy the head commit
3992 * and change these two fields.
3993 */
3994 parent = seaf_commit_manager_get_commit (seaf->commit_mgr,
3995 repo->id, repo->version,
3996 repo->head->commit_id);
3997 if (!parent) {
3998 seaf_warning ("Failed to get commit %s:%s.\n",
3999 repo->id, repo->head->commit_id);
4000 ret = -1;
4001 goto out;
4002 }
4003 if (!user) {
4004 user = parent->creator_name;
4005 }
4006
4007 commit = seaf_commit_new (NULL,
4008 repo->id,
4009 parent->root_id,
4010 user,
4011 EMPTY_SHA1,
4012 "Changed library name or description",
4013 0);
4014 commit->parent_id = g_strdup(parent->commit_id);
4015 seaf_repo_to_commit (repo, commit);
4016
4017 g_free (commit->repo_name);
4018 commit->repo_name = g_strdup(name);
4019 g_free (commit->repo_desc);
4020 commit->repo_desc = g_strdup(description);
4021
4022 if (seaf_commit_manager_add_commit (seaf->commit_mgr, commit) < 0) {
4023 ret = -1;
4024 goto out;
4025 }
4026
4027 seaf_branch_set_commit (repo->head, commit->commit_id);
4028 if (seaf_branch_manager_test_and_update_branch (seaf->branch_mgr,
4029 repo->head,
4030 parent->commit_id) < 0) {
4031 seaf_repo_unref (repo);
4032 seaf_commit_unref (commit);
4033 seaf_commit_unref (parent);
4034 repo = NULL;
4035 commit = NULL;
4036 parent = NULL;
4037 goto retry;
4038 }
4039
4040 seaf_repo_manager_update_repo_info (seaf->repo_mgr, repo_id, repo->head->commit_id);
4041
4042 out:
4043 seaf_commit_unref (commit);
4044 seaf_commit_unref (parent);
4045 seaf_repo_unref (repo);
4046
4047 return ret;
4048 }
4049
4050 gboolean
get_total_file_number_cb(SeafDBRow * row,void * vdata)4051 get_total_file_number_cb (SeafDBRow *row, void *vdata)
4052 {
4053 gint64 *data = (gint64 *)vdata;
4054 gint64 count = seaf_db_row_get_column_int64 (row, 0);
4055 *data = count;
4056
4057 return FALSE;
4058 }
4059
4060 gint64
seaf_get_total_file_number(GError ** error)4061 seaf_get_total_file_number (GError **error)
4062 {
4063 gint64 count = 0;
4064 int ret = seaf_db_statement_foreach_row (seaf->db,
4065 "SELECT SUM(file_count) FROM RepoFileCount f "
4066 "LEFT JOIN VirtualRepo v "
4067 "ON f.repo_id=v.repo_id,"
4068 "Repo r "
4069 "WHERE v.repo_id IS NULL AND "
4070 "f.repo_id=r.repo_id",
4071 get_total_file_number_cb,
4072 &count, 0);
4073 if (ret < 0) {
4074 seaf_warning ("Failed to get total file number.\n");
4075 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4076 "Failed to get total file number from db.");
4077 return -1;
4078 }
4079
4080 return count;
4081 }
4082
4083 gboolean
get_total_storage_cb(SeafDBRow * row,void * vdata)4084 get_total_storage_cb(SeafDBRow *row, void *vdata)
4085 {
4086 gint64 *data = (gint64 *)vdata;
4087 gint64 size = seaf_db_row_get_column_int64 (row, 0);
4088 *data = size;
4089
4090 return FALSE;
4091 }
4092
4093 gint64
seaf_get_total_storage(GError ** error)4094 seaf_get_total_storage (GError **error)
4095 {
4096 gint64 size = 0;
4097 int ret;
4098 if (seaf_db_type(seaf->db) == SEAF_DB_TYPE_PGSQL) {
4099 ret = seaf_db_statement_foreach_row (seaf->db,
4100 "SELECT SUM(\"size\") FROM RepoSize s "
4101 "LEFT JOIN VirtualRepo v "
4102 "ON s.repo_id=v.repo_id "
4103 "WHERE v.repo_id IS NULL",
4104 get_total_storage_cb,
4105 &size, 0);
4106 } else {
4107 ret = seaf_db_statement_foreach_row (seaf->db,
4108 "SELECT SUM(size) FROM RepoSize s "
4109 "LEFT JOIN VirtualRepo v "
4110 "ON s.repo_id=v.repo_id "
4111 "WHERE v.repo_id IS NULL",
4112 get_total_storage_cb,
4113 &size, 0);
4114 }
4115 if (ret < 0) {
4116 seaf_warning ("Failed to get total storage occupation.\n");
4117 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4118 "Failed to get total storage occupation from db.");
4119 return -1;
4120 }
4121
4122 return size;
4123 }
4124
4125 int
seaf_repo_manager_add_upload_tmp_file(SeafRepoManager * mgr,const char * repo_id,const char * file_path,const char * tmp_file,GError ** error)4126 seaf_repo_manager_add_upload_tmp_file (SeafRepoManager *mgr,
4127 const char *repo_id,
4128 const char *file_path,
4129 const char *tmp_file,
4130 GError **error)
4131 {
4132 char *file_path_with_slash = NULL;
4133
4134 if (file_path[0] == '/') {
4135 file_path_with_slash = g_strdup(file_path);
4136 } else {
4137 file_path_with_slash = g_strconcat("/", file_path, NULL);
4138 }
4139
4140 int ret = seaf_db_statement_query (mgr->seaf->db,
4141 "INSERT INTO WebUploadTempFiles "
4142 "(repo_id, file_path, tmp_file_path) "
4143 "VALUES (?, ?, ?)", 3, "string", repo_id,
4144 "string", file_path_with_slash, "string", tmp_file);
4145
4146 if (ret < 0) {
4147 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4148 "Failed to add upload tmp file record to db.");
4149 }
4150
4151 g_free (file_path_with_slash);
4152 return ret;
4153 }
4154
4155 int
seaf_repo_manager_del_upload_tmp_file(SeafRepoManager * mgr,const char * repo_id,const char * file_path,GError ** error)4156 seaf_repo_manager_del_upload_tmp_file (SeafRepoManager *mgr,
4157 const char *repo_id,
4158 const char *file_path,
4159 GError **error)
4160 {
4161 char *file_path_with_slash = NULL, *file_path_no_slash = NULL;
4162
4163 /* Due to a bug in early versions of 7.0, some file_path may be stored in the db without
4164 * a leading slash. To be compatible with those records, we need to check the path
4165 * with and without leading slash.
4166 */
4167 if (file_path[0] == '/') {
4168 file_path_with_slash = g_strdup(file_path);
4169 file_path_no_slash = g_strdup(file_path+1);
4170 } else {
4171 file_path_with_slash = g_strconcat("/", file_path, NULL);
4172 file_path_no_slash = g_strdup(file_path);
4173 }
4174
4175 int ret = seaf_db_statement_query (mgr->seaf->db,
4176 "DELETE FROM WebUploadTempFiles WHERE "
4177 "repo_id = ? AND file_path IN (?, ?)",
4178 3, "string", repo_id,
4179 "string", file_path_with_slash,
4180 "string", file_path_no_slash);
4181 if (ret < 0) {
4182 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4183 "Failed to delete upload tmp file record from db.");
4184 }
4185
4186 g_free (file_path_with_slash);
4187 g_free (file_path_no_slash);
4188 return ret;
4189 }
4190
4191 static gboolean
get_tmp_file_path(SeafDBRow * row,void * data)4192 get_tmp_file_path (SeafDBRow *row, void *data)
4193 {
4194 char **path = data;
4195
4196 *path = g_strdup (seaf_db_row_get_column_text (row, 0));
4197
4198 return FALSE;
4199 }
4200
4201 char *
seaf_repo_manager_get_upload_tmp_file(SeafRepoManager * mgr,const char * repo_id,const char * file_path,GError ** error)4202 seaf_repo_manager_get_upload_tmp_file (SeafRepoManager *mgr,
4203 const char *repo_id,
4204 const char *file_path,
4205 GError **error)
4206 {
4207 char *tmp_file_path = NULL;
4208 char *file_path_with_slash = NULL, *file_path_no_slash = NULL;
4209
4210 /* Due to a bug in early versions of 7.0, some file_path may be stored in the db without
4211 * a leading slash. To be compatible with those records, we need to check the path
4212 * with and without leading slash.
4213 * The correct file_path in db should be with a leading slash.
4214 */
4215 if (file_path[0] == '/') {
4216 file_path_with_slash = g_strdup(file_path);
4217 file_path_no_slash = g_strdup(file_path+1);
4218 } else {
4219 file_path_with_slash = g_strconcat("/", file_path, NULL);
4220 file_path_no_slash = g_strdup(file_path);
4221 }
4222
4223 int ret = seaf_db_statement_foreach_row (mgr->seaf->db,
4224 "SELECT tmp_file_path FROM WebUploadTempFiles "
4225 "WHERE repo_id = ? AND file_path = ?",
4226 get_tmp_file_path, &tmp_file_path,
4227 2, "string", repo_id,
4228 "string", file_path_with_slash);
4229 if (ret < 0) {
4230 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4231 "Failed to get upload temp file path from db.");
4232 goto out;
4233 }
4234
4235 if (!tmp_file_path) {
4236 /* Try file_path without slash. */
4237 int ret = seaf_db_statement_foreach_row (mgr->seaf->db,
4238 "SELECT tmp_file_path FROM WebUploadTempFiles "
4239 "WHERE repo_id = ? AND file_path = ?",
4240 get_tmp_file_path, &tmp_file_path,
4241 2, "string", repo_id,
4242 "string", file_path_no_slash);
4243 if (ret < 0) {
4244 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4245 "Failed to get upload temp file path from db.");
4246 goto out;
4247 }
4248 }
4249
4250 out:
4251 g_free (file_path_with_slash);
4252 g_free (file_path_no_slash);
4253 return tmp_file_path;
4254 }
4255
4256 gint64
seaf_repo_manager_get_upload_tmp_file_offset(SeafRepoManager * mgr,const char * repo_id,const char * file_path,GError ** error)4257 seaf_repo_manager_get_upload_tmp_file_offset (SeafRepoManager *mgr,
4258 const char *repo_id,
4259 const char *file_path,
4260 GError **error)
4261 {
4262 char *tmp_file_path = NULL;
4263 SeafStat file_stat;
4264
4265 tmp_file_path = seaf_repo_manager_get_upload_tmp_file (mgr, repo_id,
4266 file_path, error);
4267 if (*error) {
4268 return -1;
4269 }
4270
4271 if (!tmp_file_path)
4272 return 0;
4273
4274 if (seaf_stat (tmp_file_path, &file_stat) < 0) {
4275 if (errno == ENOENT) {
4276 seaf_message ("Temp file %s doesn't exist, remove reocrd from db.\n",
4277 tmp_file_path);
4278 if (seaf_repo_manager_del_upload_tmp_file (mgr, repo_id,
4279 file_path, error) < 0) {
4280 g_free (tmp_file_path);
4281 return -1;
4282 }
4283 return 0;
4284 }
4285 seaf_warning ("Failed to stat temp file %s: %s.\n",
4286 tmp_file_path, strerror(errno));
4287 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4288 "Failed to stat temp file.");
4289 g_free (tmp_file_path);
4290 return -1;
4291 }
4292
4293 g_free (tmp_file_path);
4294
4295 return file_stat.st_size;
4296 }
4297
4298 void
seaf_repo_manager_update_repo_info(SeafRepoManager * mgr,const char * repo_id,const char * head_commit_id)4299 seaf_repo_manager_update_repo_info (SeafRepoManager *mgr,
4300 const char *repo_id, const char *head_commit_id)
4301 {
4302 SeafCommit *head;
4303
4304 head = seaf_commit_manager_get_commit (seaf->commit_mgr,
4305 repo_id, 1, head_commit_id);
4306 if (!head) {
4307 seaf_warning ("Failed to get commit %s:%s.\n", repo_id, head_commit_id);
4308 return;
4309 }
4310
4311 set_repo_commit_to_db (repo_id, head->repo_name, head->ctime, head->version,
4312 (head->encrypted ? 1 : 0), head->creator_name);
4313
4314 seaf_commit_unref (head);
4315 }
4316
4317 char *
seaf_get_trash_repo_owner(const char * repo_id)4318 seaf_get_trash_repo_owner (const char *repo_id)
4319 {
4320 char *sql = "SELECT owner_id from RepoTrash WHERE repo_id = ?";
4321 return seaf_db_statement_get_string(seaf->db, sql, 1, "string", repo_id);
4322 }
4323
4324 GObject *
seaf_get_group_shared_repo_by_path(SeafRepoManager * mgr,const char * repo_id,const char * path,int group_id,gboolean is_org,GError ** error)4325 seaf_get_group_shared_repo_by_path (SeafRepoManager *mgr,
4326 const char *repo_id,
4327 const char *path,
4328 int group_id,
4329 gboolean is_org,
4330 GError **error)
4331 {
4332 char *sql;
4333 char *real_repo_id = NULL;
4334 GList *repo = NULL;
4335 GObject *ret = NULL;
4336
4337 /* If path is NULL, 'repo_id' represents for the repo we want,
4338 * otherwise, 'repo_id' represents for the origin repo,
4339 * find virtual repo by path first.
4340 */
4341 if (path != NULL) {
4342 real_repo_id = seaf_repo_manager_get_virtual_repo_id (mgr, repo_id, path, NULL);
4343 if (!real_repo_id) {
4344 seaf_warning ("Failed to get virtual repo_id by path %s, origin_repo: %s\n", path, repo_id);
4345 return NULL;
4346 }
4347 }
4348 if (!real_repo_id)
4349 real_repo_id = g_strdup (repo_id);
4350
4351 if (!is_org)
4352 sql = "SELECT RepoGroup.repo_id, v.repo_id, "
4353 "group_id, user_name, permission, commit_id, s.size, "
4354 "v.origin_repo, v.path, i.name, "
4355 "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, "
4356 "(SELECT name FROM RepoInfo WHERE repo_id=v.origin_repo) "
4357 "FROM RepoGroup LEFT JOIN VirtualRepo v ON "
4358 "RepoGroup.repo_id = v.repo_id "
4359 "LEFT JOIN RepoInfo i ON RepoGroup.repo_id = i.repo_id "
4360 "LEFT JOIN RepoSize s ON RepoGroup.repo_id = s.repo_id, "
4361 "Branch WHERE group_id = ? AND "
4362 "RepoGroup.repo_id = Branch.repo_id AND "
4363 "RepoGroup.repo_id = ? AND "
4364 "Branch.name = 'master'";
4365 else
4366 sql = "SELECT OrgGroupRepo.repo_id, v.repo_id, "
4367 "group_id, owner, permission, commit_id, s.size, "
4368 "v.origin_repo, v.path, i.name, "
4369 "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, "
4370 "(SELECT name FROM RepoInfo WHERE repo_id=v.origin_repo) "
4371 "FROM OrgGroupRepo LEFT JOIN VirtualRepo v ON "
4372 "OrgGroupRepo.repo_id = v.repo_id "
4373 "LEFT JOIN RepoInfo i ON OrgRepoGroup.repo_id = i.repo_id "
4374 "LEFT JOIN RepoSize s ON OrgGroupRepo.repo_id = s.repo_id, "
4375 "Branch WHERE group_id = ? AND "
4376 "OrgGroupRepo.repo_id = Branch.repo_id AND "
4377 "OrgGroupRepo.repo_id = ? AND "
4378 "Branch.name = 'master'";
4379
4380 /* The list 'repo' should have only one repo,
4381 * use existing api get_group_repos_cb() to get it.
4382 */
4383 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_group_repos_cb,
4384 &repo, 2, "int", group_id,
4385 "string", real_repo_id) < 0) {
4386 g_free (real_repo_id);
4387 g_list_free (repo);
4388 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4389 "Failed to get repo by group_id from db.");
4390 return NULL;
4391 }
4392 g_free (real_repo_id);
4393
4394 if (repo) {
4395 seaf_fill_repo_obj_from_commit (&repo);
4396 if (repo)
4397 ret = (GObject *)(repo->data);
4398 g_list_free (repo);
4399 }
4400
4401 return ret;
4402 }
4403
4404 GList *
seaf_get_group_repos_by_user(SeafRepoManager * mgr,const char * user,int org_id,GError ** error)4405 seaf_get_group_repos_by_user (SeafRepoManager *mgr,
4406 const char *user,
4407 int org_id,
4408 GError **error)
4409 {
4410 CcnetGroup *group;
4411 GList *groups = NULL, *p, *q;
4412 GList *repos = NULL;
4413 SeafileRepo *repo = NULL;
4414 GString *sql = NULL;
4415 int group_id = 0;
4416
4417 /* Get the groups this user belongs to. */
4418 groups = ccnet_group_manager_get_groups_by_user (seaf->group_mgr, user,
4419 1, NULL);
4420 if (!groups) {
4421 goto out;
4422 }
4423
4424 sql = g_string_new ("");
4425 g_string_printf (sql, "SELECT g.repo_id, v.repo_id, "
4426 "group_id, %s, permission, commit_id, s.size, "
4427 "v.origin_repo, v.path, i.name, "
4428 "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, "
4429 "(SELECT name FROM RepoInfo WHERE repo_id=v.origin_repo)"
4430 "FROM %s g LEFT JOIN VirtualRepo v ON "
4431 "g.repo_id = v.repo_id "
4432 "LEFT JOIN RepoInfo i ON g.repo_id = i.repo_id "
4433 "LEFT JOIN RepoSize s ON g.repo_id = s.repo_id, "
4434 "Branch b WHERE g.repo_id = b.repo_id AND "
4435 "b.name = 'master' AND group_id IN (",
4436 org_id < 0 ? "user_name" : "owner",
4437 org_id < 0 ? "RepoGroup" : "OrgGroupRepo");
4438 for (p = groups; p != NULL; p = p->next) {
4439 group = p->data;
4440 g_object_get (group, "id", &group_id, NULL);
4441
4442 g_string_append_printf (sql, "%d", group_id);
4443 if (p->next)
4444 g_string_append_printf (sql, ",");
4445 }
4446 g_string_append_printf (sql, " ) ORDER BY group_id");
4447
4448 if (seaf_db_statement_foreach_row (mgr->seaf->db, sql->str, get_group_repos_cb,
4449 &repos, 0) < 0) {
4450 for (p = repos; p; p = p->next) {
4451 g_object_unref (p->data);
4452 }
4453 g_list_free (repos);
4454 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4455 "Failed to get user group repos from db.");
4456 seaf_warning ("Failed to get user[%s] group repos from db.\n", user);
4457 goto out;
4458 }
4459
4460 int repo_group_id = 0;
4461 char *group_name = NULL;
4462 q = repos;
4463
4464 /* Add group_name to repo. Both groups and repos are listed by group_id in descending order */
4465 for (p = groups; p; p = p->next) {
4466 group = p->data;
4467 g_object_get (group, "id", &group_id, NULL);
4468 g_object_get (group, "group_name", &group_name, NULL);
4469
4470 for (; q; q = q->next) {
4471 repo = q->data;
4472 g_object_get (repo, "group_id", &repo_group_id, NULL);
4473 if (repo_group_id == group_id)
4474 g_object_set (repo, "group_name", group_name, NULL);
4475 else
4476 break;
4477 }
4478 g_free (group_name);
4479 if (q == NULL)
4480 break;
4481 }
4482
4483 seaf_fill_repo_obj_from_commit (&repos);
4484
4485 out:
4486 if (sql)
4487 g_string_free (sql, TRUE);
4488
4489 for (p = groups; p != NULL; p = p->next)
4490 g_object_unref ((GObject *)p->data);
4491 g_list_free (groups);
4492
4493 return g_list_reverse (repos);
4494 }
4495
4496 typedef struct RepoPath {
4497 char *repo_id;
4498 char *path;
4499 int group_id;
4500 } RepoPath;
4501
4502
4503 gboolean
convert_repo_path_cb(SeafDBRow * row,void * data)4504 convert_repo_path_cb (SeafDBRow *row, void *data)
4505 {
4506 GList **repo_paths = data;
4507
4508 const char *repo_id = seaf_db_row_get_column_text (row, 0);
4509 const char *path = seaf_db_row_get_column_text (row, 1);
4510 int group_id = seaf_db_row_get_column_int (row, 2);
4511
4512 RepoPath *rp = g_new0(RepoPath, 1);
4513 rp->repo_id = g_strdup(repo_id);
4514 rp->path = g_strdup(path);
4515 rp->group_id = group_id;
4516 *repo_paths = g_list_append (*repo_paths, rp);
4517
4518 return TRUE;
4519 }
4520
4521 static void
free_repo_path(gpointer data)4522 free_repo_path (gpointer data)
4523 {
4524 if (!data)
4525 return;
4526
4527 RepoPath *rp = data;
4528 g_free (rp->repo_id);
4529 g_free (rp->path);
4530 g_free (rp);
4531 }
4532
4533 static char *
filter_path(GList * repo_paths,const char * path)4534 filter_path (GList *repo_paths, const char *path)
4535 {
4536 GList *ptr = NULL;
4537 int len;
4538 const char *relative_path;
4539 char *ret = NULL;
4540 RepoPath *rp = NULL, res;
4541 res.repo_id = NULL;
4542 res.path = NULL;
4543 res.group_id = 0;
4544
4545 /* Find nearest item which contains @path, */
4546 for (ptr = repo_paths; ptr; ptr = ptr->next) {
4547 rp = ptr->data;
4548 len = strlen(rp->path);
4549 if (strncmp(rp->path, path, len) == 0 && (path[len] == '/' || path[len] == '\0')) {
4550
4551 if (g_strcmp0(rp->path, res.path) > 0) {
4552 res.path = rp->path;
4553 res.repo_id = rp->repo_id;
4554 res.group_id = rp->group_id;
4555 }
4556 }
4557 }
4558 if (res.repo_id && res.path) {
4559 relative_path = path + strlen(res.path);
4560 if (relative_path[0] == '\0')
4561 relative_path = "/";
4562
4563 json_t *json = json_object ();
4564 json_object_set_string_member(json, "repo_id", res.repo_id);
4565 json_object_set_string_member(json, "path", relative_path);
4566 if (res.group_id > 0)
4567 json_object_set_int_member(json, "group_id", res.group_id);
4568 ret = json_dumps (json, 0);
4569 json_decref (json);
4570 }
4571
4572 return ret;
4573 }
4574
4575 /* Convert origin repo and path to virtual repo and relative path */
4576 char *
seaf_repo_manager_convert_repo_path(SeafRepoManager * mgr,const char * repo_id,const char * path,const char * user,gboolean is_org,GError ** error)4577 seaf_repo_manager_convert_repo_path (SeafRepoManager *mgr,
4578 const char *repo_id,
4579 const char *path,
4580 const char *user,
4581 gboolean is_org,
4582 GError **error)
4583 {
4584 char *ret = NULL;
4585 int rc;
4586 int group_id;
4587 GString *sql;
4588 CcnetGroup *group;
4589 GList *groups = NULL, *p1;
4590 GList *repo_paths = NULL;
4591 SeafVirtRepo *vinfo = NULL;
4592 const char *r_repo_id = repo_id;
4593 char *r_path = NULL;
4594
4595 vinfo = seaf_repo_manager_get_virtual_repo_info (mgr, repo_id);
4596 if (vinfo) {
4597 r_repo_id = vinfo->origin_repo_id;
4598 r_path = g_strconcat (vinfo->path, path, NULL);
4599 } else {
4600 r_path = g_strdup(path);
4601 }
4602
4603 sql = g_string_new ("");
4604 g_string_printf (sql, "SELECT v.repo_id, path, 0 FROM VirtualRepo v, %s s WHERE "
4605 "v.origin_repo=? AND v.repo_id=s.repo_id AND s.to_email=?",
4606 is_org ? "OrgSharedRepo" : "SharedRepo");
4607 rc = seaf_db_statement_foreach_row (seaf->db,
4608 sql->str, convert_repo_path_cb,
4609 &repo_paths, 2,
4610 "string", r_repo_id, "string", user);
4611 if (rc < 0) {
4612 seaf_warning("Failed to convert repo path [%s:%s] to virtual repo path, db_error.\n",
4613 repo_id, path);
4614 goto out;
4615 }
4616 ret = filter_path(repo_paths, r_path);
4617 g_list_free_full(repo_paths, free_repo_path);
4618 repo_paths = NULL;
4619 if (ret)
4620 goto out;
4621
4622 /* Get the groups this user belongs to. */
4623
4624 groups = ccnet_group_manager_get_groups_by_user (seaf->group_mgr, user,
4625 1, NULL);
4626 if (!groups) {
4627 goto out;
4628 }
4629
4630 g_string_printf (sql, "SELECT v.repo_id, path, r.group_id FROM VirtualRepo v, %s r WHERE "
4631 "v.origin_repo=? AND v.repo_id=r.repo_id AND r.group_id IN(",
4632 is_org ? "OrgGroupRepo" : "RepoGroup");
4633 for (p1 = groups; p1 != NULL; p1 = p1->next) {
4634 group = p1->data;
4635 g_object_get (group, "id", &group_id, NULL);
4636
4637 g_string_append_printf (sql, "%d", group_id);
4638 if (p1->next)
4639 g_string_append_printf (sql, ",");
4640 }
4641 g_string_append_printf (sql, ")");
4642
4643 rc = seaf_db_statement_foreach_row (seaf->db,
4644 sql->str, convert_repo_path_cb,
4645 &repo_paths, 1,
4646 "string", r_repo_id);
4647 if (rc < 0) {
4648 seaf_warning("Failed to convert repo path [%s:%s] to virtual repo path, db error.\n",
4649 repo_id, path);
4650 g_string_free (sql, TRUE);
4651 goto out;
4652 }
4653 ret = filter_path(repo_paths, r_path);
4654 g_list_free_full(repo_paths, free_repo_path);
4655
4656 out:
4657 g_free (r_path);
4658 if (vinfo)
4659 seaf_virtual_repo_info_free (vinfo);
4660 g_string_free (sql, TRUE);
4661 for (p1 = groups; p1 != NULL; p1 = p1->next)
4662 g_object_unref ((GObject *)p1->data);
4663 g_list_free (groups);
4664
4665 return ret;
4666 }
4667
4668 int
seaf_repo_manager_set_repo_status(SeafRepoManager * mgr,const char * repo_id,RepoStatus status)4669 seaf_repo_manager_set_repo_status(SeafRepoManager *mgr,
4670 const char *repo_id, RepoStatus status)
4671 {
4672 int ret = 0;
4673
4674 if (seaf_db_statement_query (mgr->seaf->db,
4675 "UPDATE RepoInfo SET status=? "
4676 "WHERE repo_id=? OR repo_id IN "
4677 "(SELECT repo_id FROM VirtualRepo WHERE origin_repo=?)",
4678 3, "int", status,
4679 "string", repo_id, "string", repo_id) < 0)
4680 ret = -1;
4681
4682 return ret;
4683 }
4684
4685 int
seaf_repo_manager_get_repo_status(SeafRepoManager * mgr,const char * repo_id)4686 seaf_repo_manager_get_repo_status(SeafRepoManager *mgr,
4687 const char *repo_id)
4688 {
4689 char *sql = "SELECT status FROM RepoInfo WHERE repo_id=?";
4690
4691 return seaf_db_statement_get_int (mgr->seaf->db, sql,
4692 1, "string", repo_id);
4693 }
4694