1 #include "common.h"
2 
3 #include "log.h"
4 
5 #ifndef SEAFILE_SERVER
6 #include "db.h"
7 #else
8 #include "seaf-db.h"
9 #endif
10 
11 #include "seafile-session.h"
12 
13 #include "branch-mgr.h"
14 
15 #define BRANCH_DB "branch.db"
16 
17 SeafBranch *
seaf_branch_new(const char * name,const char * repo_id,const char * commit_id)18 seaf_branch_new (const char *name, const char *repo_id, const char *commit_id)
19 {
20     SeafBranch *branch;
21 
22     branch = g_new0 (SeafBranch, 1);
23 
24     branch->name = g_strdup (name);
25     memcpy (branch->repo_id, repo_id, 36);
26     branch->repo_id[36] = '\0';
27     memcpy (branch->commit_id, commit_id, 40);
28     branch->commit_id[40] = '\0';
29 
30     branch->ref = 1;
31 
32     return branch;
33 }
34 
35 void
seaf_branch_free(SeafBranch * branch)36 seaf_branch_free (SeafBranch *branch)
37 {
38     if (branch == NULL) return;
39     g_free (branch->name);
40     g_free (branch);
41 }
42 
43 void
seaf_branch_list_free(GList * blist)44 seaf_branch_list_free (GList *blist)
45 {
46     GList *ptr;
47 
48     for (ptr = blist; ptr; ptr = ptr->next) {
49         seaf_branch_unref (ptr->data);
50     }
51     g_list_free (blist);
52 }
53 
54 
55 void
seaf_branch_set_commit(SeafBranch * branch,const char * commit_id)56 seaf_branch_set_commit (SeafBranch *branch, const char *commit_id)
57 {
58     memcpy (branch->commit_id, commit_id, 40);
59     branch->commit_id[40] = '\0';
60 }
61 
62 void
seaf_branch_ref(SeafBranch * branch)63 seaf_branch_ref (SeafBranch *branch)
64 {
65     branch->ref++;
66 }
67 
68 void
seaf_branch_unref(SeafBranch * branch)69 seaf_branch_unref (SeafBranch *branch)
70 {
71     if (!branch)
72         return;
73 
74     if (--branch->ref <= 0)
75         seaf_branch_free (branch);
76 }
77 
78 struct _SeafBranchManagerPriv {
79     sqlite3 *db;
80 #ifndef SEAFILE_SERVER
81     pthread_mutex_t db_lock;
82 #endif
83 
84 #if defined( SEAFILE_SERVER ) && defined( FULL_FEATURE )
85     uint32_t cevent_id;
86 #endif
87 };
88 
89 #if defined( SEAFILE_SERVER ) && defined( FULL_FEATURE )
90 
91 #include "mq-mgr.h"
92 #include <ccnet/cevent.h>
93 static void publish_repo_update_event (CEvent *event, void *data);
94 
95 #endif
96 
97 static int open_db (SeafBranchManager *mgr);
98 
99 SeafBranchManager *
seaf_branch_manager_new(struct _SeafileSession * seaf)100 seaf_branch_manager_new (struct _SeafileSession *seaf)
101 {
102     SeafBranchManager *mgr;
103 
104     mgr = g_new0 (SeafBranchManager, 1);
105     mgr->priv = g_new0 (SeafBranchManagerPriv, 1);
106     mgr->seaf = seaf;
107 
108 #ifndef SEAFILE_SERVER
109     pthread_mutex_init (&mgr->priv->db_lock, NULL);
110 #endif
111 
112     return mgr;
113 }
114 
115 int
seaf_branch_manager_init(SeafBranchManager * mgr)116 seaf_branch_manager_init (SeafBranchManager *mgr)
117 {
118 #if defined( SEAFILE_SERVER ) && defined( FULL_FEATURE )
119     mgr->priv->cevent_id = cevent_manager_register (seaf->ev_mgr,
120                                     (cevent_handler)publish_repo_update_event,
121                                                     NULL);
122 #endif
123 
124     return open_db (mgr);
125 }
126 
127 static int
open_db(SeafBranchManager * mgr)128 open_db (SeafBranchManager *mgr)
129 {
130 #ifndef SEAFILE_SERVER
131 
132     char *db_path;
133     const char *sql;
134 
135     db_path = g_build_filename (mgr->seaf->seaf_dir, BRANCH_DB, NULL);
136     if (sqlite_open_db (db_path, &mgr->priv->db) < 0) {
137         g_critical ("[Branch mgr] Failed to open branch db\n");
138         g_free (db_path);
139         return -1;
140     }
141     g_free (db_path);
142 
143     sql = "CREATE TABLE IF NOT EXISTS Branch ("
144           "name TEXT, repo_id TEXT, commit_id TEXT);";
145     if (sqlite_query_exec (mgr->priv->db, sql) < 0)
146         return -1;
147 
148     sql = "CREATE INDEX IF NOT EXISTS branch_index ON Branch(repo_id, name);";
149     if (sqlite_query_exec (mgr->priv->db, sql) < 0)
150         return -1;
151 
152 #elif defined FULL_FEATURE
153 
154     char *sql;
155     switch (seaf_db_type (mgr->seaf->db)) {
156     case SEAF_DB_TYPE_MYSQL:
157         sql = "CREATE TABLE IF NOT EXISTS Branch ("
158             "name VARCHAR(10), repo_id CHAR(41), commit_id CHAR(41),"
159             "PRIMARY KEY (repo_id, name)) ENGINE = INNODB";
160         if (seaf_db_query (mgr->seaf->db, sql) < 0)
161             return -1;
162         break;
163     case SEAF_DB_TYPE_PGSQL:
164         sql = "CREATE TABLE IF NOT EXISTS Branch ("
165             "name VARCHAR(10), repo_id CHAR(40), commit_id CHAR(40),"
166             "PRIMARY KEY (repo_id, name))";
167         if (seaf_db_query (mgr->seaf->db, sql) < 0)
168             return -1;
169         break;
170     case SEAF_DB_TYPE_SQLITE:
171         sql = "CREATE TABLE IF NOT EXISTS Branch ("
172             "name VARCHAR(10), repo_id CHAR(41), commit_id CHAR(41),"
173             "PRIMARY KEY (repo_id, name))";
174         if (seaf_db_query (mgr->seaf->db, sql) < 0)
175             return -1;
176         break;
177     }
178 
179 #endif
180 
181     return 0;
182 }
183 
184 int
seaf_branch_manager_add_branch(SeafBranchManager * mgr,SeafBranch * branch)185 seaf_branch_manager_add_branch (SeafBranchManager *mgr, SeafBranch *branch)
186 {
187 #ifndef SEAFILE_SERVER
188     char sql[256];
189 
190     pthread_mutex_lock (&mgr->priv->db_lock);
191 
192     sqlite3_snprintf (sizeof(sql), sql,
193                       "SELECT 1 FROM Branch WHERE name=%Q and repo_id=%Q",
194                       branch->name, branch->repo_id);
195     if (sqlite_check_for_existence (mgr->priv->db, sql))
196         sqlite3_snprintf (sizeof(sql), sql,
197                           "UPDATE Branch SET commit_id=%Q WHERE "
198                           "name=%Q and repo_id=%Q",
199                           branch->commit_id, branch->name, branch->repo_id);
200     else
201         sqlite3_snprintf (sizeof(sql), sql,
202                           "INSERT INTO Branch VALUES (%Q, %Q, %Q)",
203                           branch->name, branch->repo_id, branch->commit_id);
204 
205     sqlite_query_exec (mgr->priv->db, sql);
206 
207     pthread_mutex_unlock (&mgr->priv->db_lock);
208 
209     return 0;
210 #else
211     char *sql;
212     SeafDB *db = mgr->seaf->db;
213 
214     if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
215         gboolean exists, err;
216         int rc;
217 
218         sql = "SELECT repo_id FROM Branch WHERE name=? AND repo_id=?";
219         exists = seaf_db_statement_exists(db, sql, &err,
220                                           2, "string", branch->name,
221                                           "string", branch->repo_id);
222         if (err)
223             return -1;
224 
225         if (exists)
226             rc = seaf_db_statement_query (db,
227                                           "UPDATE Branch SET commit_id=? "
228                                           "WHERE name=? AND repo_id=?",
229                                           3, "string", branch->commit_id,
230                                           "string", branch->name,
231                                           "string", branch->repo_id);
232         else
233             rc = seaf_db_statement_query (db,
234                                           "INSERT INTO Branch VALUES (?, ?, ?)",
235                                           3, "string", branch->name,
236                                           "string", branch->repo_id,
237                                           "string", branch->commit_id);
238         if (rc < 0)
239             return -1;
240     } else {
241         int rc = seaf_db_statement_query (db,
242                                  "REPLACE INTO Branch VALUES (?, ?, ?)",
243                                  3, "string", branch->name,
244                                  "string", branch->repo_id,
245                                  "string", branch->commit_id);
246         if (rc < 0)
247             return -1;
248     }
249     return 0;
250 #endif
251 }
252 
253 int
seaf_branch_manager_del_branch(SeafBranchManager * mgr,const char * repo_id,const char * name)254 seaf_branch_manager_del_branch (SeafBranchManager *mgr,
255                                 const char *repo_id,
256                                 const char *name)
257 {
258 #ifndef SEAFILE_SERVER
259     char *sql;
260 
261     pthread_mutex_lock (&mgr->priv->db_lock);
262 
263     sql = sqlite3_mprintf ("DELETE FROM Branch WHERE name = %Q AND "
264                            "repo_id = '%s'", name, repo_id);
265     if (sqlite_query_exec (mgr->priv->db, sql) < 0)
266         seaf_warning ("Delete branch %s failed\n", name);
267     sqlite3_free (sql);
268 
269     pthread_mutex_unlock (&mgr->priv->db_lock);
270 
271     return 0;
272 #else
273     int rc = seaf_db_statement_query (mgr->seaf->db,
274                                       "DELETE FROM Branch WHERE name=? AND repo_id=?",
275                                       2, "string", name, "string", repo_id);
276     if (rc < 0)
277         return -1;
278     return 0;
279 #endif
280 }
281 
282 int
seaf_branch_manager_update_branch(SeafBranchManager * mgr,SeafBranch * branch)283 seaf_branch_manager_update_branch (SeafBranchManager *mgr, SeafBranch *branch)
284 {
285 #ifndef SEAFILE_SERVER
286     sqlite3 *db;
287     char *sql;
288 
289     pthread_mutex_lock (&mgr->priv->db_lock);
290 
291     db = mgr->priv->db;
292     sql = sqlite3_mprintf ("UPDATE Branch SET commit_id = %Q "
293                            "WHERE name = %Q AND repo_id = %Q",
294                            branch->commit_id, branch->name, branch->repo_id);
295     sqlite_query_exec (db, sql);
296     sqlite3_free (sql);
297 
298     pthread_mutex_unlock (&mgr->priv->db_lock);
299 
300     return 0;
301 #else
302     int rc = seaf_db_statement_query (mgr->seaf->db,
303                                       "UPDATE Branch SET commit_id = ? "
304                                       "WHERE name = ? AND repo_id = ?",
305                                       3, "string", branch->commit_id,
306                                       "string", branch->name,
307                                       "string", branch->repo_id);
308     if (rc < 0)
309         return -1;
310     return 0;
311 #endif
312 }
313 
314 #if defined( SEAFILE_SERVER ) && defined( FULL_FEATURE )
315 
316 static gboolean
get_commit_id(SeafDBRow * row,void * data)317 get_commit_id (SeafDBRow *row, void *data)
318 {
319     char *out_commit_id = data;
320     const char *commit_id;
321 
322     commit_id = seaf_db_row_get_column_text (row, 0);
323     memcpy (out_commit_id, commit_id, 41);
324     out_commit_id[40] = '\0';
325 
326     return FALSE;
327 }
328 
329 typedef struct {
330     char *repo_id;
331     char *commit_id;
332 } RepoUpdateEventData;
333 
334 static void
publish_repo_update_event(CEvent * event,void * data)335 publish_repo_update_event (CEvent *event, void *data)
336 {
337     RepoUpdateEventData *rdata = event->data;
338 
339     char buf[128];
340     snprintf (buf, sizeof(buf), "repo-update\t%s\t%s",
341               rdata->repo_id, rdata->commit_id);
342 
343     seaf_mq_manager_publish_event (seaf->mq_mgr, buf);
344 
345     g_free (rdata->repo_id);
346     g_free (rdata->commit_id);
347     g_free (rdata);
348 }
349 
350 static void
on_branch_updated(SeafBranchManager * mgr,SeafBranch * branch)351 on_branch_updated (SeafBranchManager *mgr, SeafBranch *branch)
352 {
353     if (seaf_repo_manager_is_virtual_repo (seaf->repo_mgr, branch->repo_id))
354         return;
355 
356     RepoUpdateEventData *rdata = g_new0 (RepoUpdateEventData, 1);
357 
358     rdata->repo_id = g_strdup (branch->repo_id);
359     rdata->commit_id = g_strdup (branch->commit_id);
360 
361     cevent_manager_add_event (seaf->ev_mgr, mgr->priv->cevent_id, rdata);
362 }
363 
364 int
seaf_branch_manager_test_and_update_branch(SeafBranchManager * mgr,SeafBranch * branch,const char * old_commit_id)365 seaf_branch_manager_test_and_update_branch (SeafBranchManager *mgr,
366                                             SeafBranch *branch,
367                                             const char *old_commit_id)
368 {
369     SeafDBTrans *trans;
370     char *sql;
371     char commit_id[41] = { 0 };
372 
373     trans = seaf_db_begin_transaction (mgr->seaf->db);
374     if (!trans)
375         return -1;
376 
377     switch (seaf_db_type (mgr->seaf->db)) {
378     case SEAF_DB_TYPE_MYSQL:
379     case SEAF_DB_TYPE_PGSQL:
380         sql = "SELECT commit_id FROM Branch WHERE name=? "
381             "AND repo_id=? FOR UPDATE";
382         break;
383     case SEAF_DB_TYPE_SQLITE:
384         sql = "SELECT commit_id FROM Branch WHERE name=? "
385             "AND repo_id=?";
386         break;
387     default:
388         g_return_val_if_reached (-1);
389     }
390     if (seaf_db_trans_foreach_selected_row (trans, sql,
391                                             get_commit_id, commit_id,
392                                             2, "string", branch->name,
393                                             "string", branch->repo_id) < 0) {
394         seaf_db_rollback (trans);
395         seaf_db_trans_close (trans);
396         return -1;
397     }
398     if (strcmp (old_commit_id, commit_id) != 0) {
399         seaf_db_rollback (trans);
400         seaf_db_trans_close (trans);
401         return -1;
402     }
403 
404     sql = "UPDATE Branch SET commit_id = ? "
405         "WHERE name = ? AND repo_id = ?";
406     if (seaf_db_trans_query (trans, sql, 3, "string", branch->commit_id,
407                              "string", branch->name,
408                              "string", branch->repo_id) < 0) {
409         seaf_db_rollback (trans);
410         seaf_db_trans_close (trans);
411         return -1;
412     }
413 
414     if (seaf_db_commit (trans) < 0) {
415         seaf_db_rollback (trans);
416         seaf_db_trans_close (trans);
417         return -1;
418     }
419 
420     seaf_db_trans_close (trans);
421 
422     on_branch_updated (mgr, branch);
423 
424     return 0;
425 }
426 
427 #endif
428 
429 #ifndef SEAFILE_SERVER
430 static SeafBranch *
real_get_branch(SeafBranchManager * mgr,const char * repo_id,const char * name)431 real_get_branch (SeafBranchManager *mgr,
432                  const char *repo_id,
433                  const char *name)
434 {
435     SeafBranch *branch = NULL;
436     sqlite3_stmt *stmt;
437     sqlite3 *db;
438     char *sql;
439     int result;
440 
441     pthread_mutex_lock (&mgr->priv->db_lock);
442 
443     db = mgr->priv->db;
444     sql = sqlite3_mprintf ("SELECT commit_id FROM Branch "
445                            "WHERE name = %Q and repo_id='%s'",
446                            name, repo_id);
447     if (!(stmt = sqlite_query_prepare (db, sql))) {
448         seaf_warning ("[Branch mgr] Couldn't prepare query %s\n", sql);
449         sqlite3_free (sql);
450         pthread_mutex_unlock (&mgr->priv->db_lock);
451         return NULL;
452     }
453     sqlite3_free (sql);
454 
455     result = sqlite3_step (stmt);
456     if (result == SQLITE_ROW) {
457         char *commit_id = (char *)sqlite3_column_text (stmt, 0);
458 
459         branch = seaf_branch_new (name, repo_id, commit_id);
460         pthread_mutex_unlock (&mgr->priv->db_lock);
461         sqlite3_finalize (stmt);
462         return branch;
463     } else if (result == SQLITE_ERROR) {
464         const char *str = sqlite3_errmsg (db);
465         seaf_warning ("Couldn't prepare query, error: %d->'%s'\n",
466                    result, str ? str : "no error given");
467     }
468 
469     sqlite3_finalize (stmt);
470     pthread_mutex_unlock (&mgr->priv->db_lock);
471     return NULL;
472 }
473 
474 SeafBranch *
seaf_branch_manager_get_branch(SeafBranchManager * mgr,const char * repo_id,const char * name)475 seaf_branch_manager_get_branch (SeafBranchManager *mgr,
476                                 const char *repo_id,
477                                 const char *name)
478 {
479     SeafBranch *branch;
480 
481     /* "fetch_head" maps to "local" or "master" on client (LAN sync) */
482     if (strcmp (name, "fetch_head") == 0) {
483         branch = real_get_branch (mgr, repo_id, "local");
484         if (!branch) {
485             branch = real_get_branch (mgr, repo_id, "master");
486         }
487         return branch;
488     } else {
489         return real_get_branch (mgr, repo_id, name);
490     }
491 }
492 
493 #else
494 
495 static gboolean
get_branch(SeafDBRow * row,void * vid)496 get_branch (SeafDBRow *row, void *vid)
497 {
498     char *ret = vid;
499     const char *commit_id;
500 
501     commit_id = seaf_db_row_get_column_text (row, 0);
502     memcpy (ret, commit_id, 41);
503 
504     return FALSE;
505 }
506 
507 static SeafBranch *
real_get_branch(SeafBranchManager * mgr,const char * repo_id,const char * name)508 real_get_branch (SeafBranchManager *mgr,
509                  const char *repo_id,
510                  const char *name)
511 {
512     char commit_id[41];
513     char *sql;
514 
515     commit_id[0] = 0;
516     sql = "SELECT commit_id FROM Branch WHERE name=? AND repo_id=?";
517     if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
518                                        get_branch, commit_id,
519                                        2, "string", name, "string", repo_id) < 0) {
520         seaf_warning ("[branch mgr] DB error when get branch %s.\n", name);
521         return NULL;
522     }
523 
524     if (commit_id[0] == 0)
525         return NULL;
526 
527     return seaf_branch_new (name, repo_id, commit_id);
528 }
529 
530 SeafBranch *
seaf_branch_manager_get_branch(SeafBranchManager * mgr,const char * repo_id,const char * name)531 seaf_branch_manager_get_branch (SeafBranchManager *mgr,
532                                 const char *repo_id,
533                                 const char *name)
534 {
535     SeafBranch *branch;
536 
537     /* "fetch_head" maps to "master" on server. */
538     if (strcmp (name, "fetch_head") == 0) {
539         branch = real_get_branch (mgr, repo_id, "master");
540         return branch;
541     } else {
542         return real_get_branch (mgr, repo_id, name);
543     }
544 }
545 
546 #endif  /* not SEAFILE_SERVER */
547 
548 gboolean
seaf_branch_manager_branch_exists(SeafBranchManager * mgr,const char * repo_id,const char * name)549 seaf_branch_manager_branch_exists (SeafBranchManager *mgr,
550                                    const char *repo_id,
551                                    const char *name)
552 {
553 #ifndef SEAFILE_SERVER
554     char *sql;
555     gboolean ret;
556 
557     pthread_mutex_lock (&mgr->priv->db_lock);
558 
559     sql = sqlite3_mprintf ("SELECT name FROM Branch WHERE name = %Q "
560                            "AND repo_id='%s'", name, repo_id);
561     ret = sqlite_check_for_existence (mgr->priv->db, sql);
562     sqlite3_free (sql);
563 
564     pthread_mutex_unlock (&mgr->priv->db_lock);
565     return ret;
566 #else
567     gboolean db_err = FALSE;
568 
569     return seaf_db_statement_exists (mgr->seaf->db,
570                                      "SELECT name FROM Branch WHERE name=? "
571                                      "AND repo_id=?", &db_err,
572                                      2, "string", name, "string", repo_id);
573 #endif
574 }
575 
576 #ifndef SEAFILE_SERVER
577 GList *
seaf_branch_manager_get_branch_list(SeafBranchManager * mgr,const char * repo_id)578 seaf_branch_manager_get_branch_list (SeafBranchManager *mgr,
579                                      const char *repo_id)
580 {
581     sqlite3 *db = mgr->priv->db;
582 
583     int result;
584     sqlite3_stmt *stmt;
585     char sql[256];
586     char *name;
587     char *commit_id;
588     GList *ret = NULL;
589     SeafBranch *branch;
590 
591     snprintf (sql, 256, "SELECT name, commit_id FROM branch WHERE repo_id ='%s'",
592               repo_id);
593 
594     pthread_mutex_lock (&mgr->priv->db_lock);
595 
596     if ( !(stmt = sqlite_query_prepare(db, sql)) ) {
597         pthread_mutex_unlock (&mgr->priv->db_lock);
598         return NULL;
599     }
600 
601     while (1) {
602         result = sqlite3_step (stmt);
603         if (result == SQLITE_ROW) {
604             name = (char *)sqlite3_column_text(stmt, 0);
605             commit_id = (char *)sqlite3_column_text(stmt, 1);
606             branch = seaf_branch_new (name, repo_id, commit_id);
607             ret = g_list_prepend (ret, branch);
608         }
609         if (result == SQLITE_DONE)
610             break;
611         if (result == SQLITE_ERROR) {
612             const gchar *str = sqlite3_errmsg (db);
613             seaf_warning ("Couldn't prepare query, error: %d->'%s'\n",
614                        result, str ? str : "no error given");
615             sqlite3_finalize (stmt);
616             seaf_branch_list_free (ret);
617             pthread_mutex_unlock (&mgr->priv->db_lock);
618             return NULL;
619         }
620     }
621 
622     sqlite3_finalize (stmt);
623     pthread_mutex_unlock (&mgr->priv->db_lock);
624     return g_list_reverse(ret);
625 }
626 #else
627 static gboolean
get_branches(SeafDBRow * row,void * vplist)628 get_branches (SeafDBRow *row, void *vplist)
629 {
630     GList **plist = vplist;
631     const char *commit_id;
632     const char *name;
633     const char *repo_id;
634     SeafBranch *branch;
635 
636     name = seaf_db_row_get_column_text (row, 0);
637     repo_id = seaf_db_row_get_column_text (row, 1);
638     commit_id = seaf_db_row_get_column_text (row, 2);
639 
640     branch = seaf_branch_new (name, repo_id, commit_id);
641     *plist = g_list_prepend (*plist, branch);
642 
643     return TRUE;
644 }
645 
646 GList *
seaf_branch_manager_get_branch_list(SeafBranchManager * mgr,const char * repo_id)647 seaf_branch_manager_get_branch_list (SeafBranchManager *mgr,
648                                      const char *repo_id)
649 {
650     GList *ret = NULL;
651     char *sql;
652 
653     sql = "SELECT name, repo_id, commit_id FROM Branch WHERE repo_id=?";
654     if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
655                                        get_branches, &ret,
656                                        1, "string", repo_id) < 0) {
657         seaf_warning ("[branch mgr] DB error when get branch list.\n");
658         return NULL;
659     }
660 
661     return ret;
662 }
663 #endif
664