1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3 #include "common.h"
4 #include <glib/gstdio.h>
5
6 #ifdef WIN32
7 #include <windows.h>
8 #include <shlobj.h>
9 #endif
10
11 #include <pthread.h>
12
13 #include "utils.h"
14 #define DEBUG_FLAG SEAFILE_DEBUG_SYNC
15 #include "log.h"
16
17 #include "vc-utils.h"
18
19 #include "seafile-session.h"
20 #include "seafile-config.h"
21 #include "commit-mgr.h"
22 #include "branch-mgr.h"
23 #include "repo-mgr.h"
24 #include "fs-mgr.h"
25 #include "seafile-error.h"
26 #include "seafile-crypt.h"
27 #include "index/index.h"
28 #include "index/cache-tree.h"
29 #include "diff-simple.h"
30 #include "change-set.h"
31
32 #include "db.h"
33
34 #include "seafile-object.h"
35
36 #define INDEX_DIR "index"
37 #define IGNORE_FILE "seafile-ignore.txt"
38
39 #ifdef HAVE_KEYSTORAGE_GK
40 #include "repokey/seafile-gnome-keyring.h"
41 #endif // HAVE_KEYSTORAGE_GK
42
43 #ifndef SEAFILE_CLIENT_VERSION
44 #define SEAFILE_CLIENT_VERSION PACKAGE_VERSION
45 #endif
46
47 struct _SeafRepoManagerPriv {
48 GHashTable *repo_hash;
49 sqlite3 *db;
50 pthread_mutex_t db_lock;
51 GHashTable *checkout_tasks_hash;
52 pthread_rwlock_t lock;
53
54 GHashTable *user_perms; /* repo_id -> folder user perms */
55 GHashTable *group_perms; /* repo_id -> folder group perms */
56 pthread_mutex_t perm_lock;
57
58 GAsyncQueue *lock_office_job_queue;
59 };
60
61 static const char *ignore_table[] = {
62 /* tmp files under Linux */
63 "*~",
64 /* Seafile's backup file */
65 "*.sbak",
66 /* Emacs tmp files */
67 "#*#",
68 /* windows image cache */
69 "Thumbs.db",
70 /* For Mac */
71 ".DS_Store",
72 "._*",
73 NULL,
74 };
75
76 #define CONFLICT_PATTERN " \\(SFConflict .+\\)"
77
78 #define OFFICE_LOCK_PATTERN "~\\$(.+)$"
79
80 static GPatternSpec** ignore_patterns;
81 static GPatternSpec* office_temp_ignore_patterns[4];
82 static GRegex *conflict_pattern = NULL;
83 static GRegex *office_lock_pattern = NULL;
84
85 static SeafRepo *
86 load_repo (SeafRepoManager *manager, const char *repo_id);
87
88 static void load_repos (SeafRepoManager *manager, const char *seaf_dir);
89 static void seaf_repo_manager_del_repo_property (SeafRepoManager *manager,
90 const char *repo_id);
91
92 static int save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch);
93 static void save_repo_property (SeafRepoManager *manager,
94 const char *repo_id,
95 const char *key, const char *value);
96
97 static void
locked_file_free(LockedFile * file)98 locked_file_free (LockedFile *file)
99 {
100 if (!file)
101 return;
102 g_free (file->operation);
103 g_free (file);
104 }
105
106 static gboolean
load_locked_file(sqlite3_stmt * stmt,void * data)107 load_locked_file (sqlite3_stmt *stmt, void *data)
108 {
109 GHashTable *ret = data;
110 LockedFile *file;
111 const char *path, *operation, *file_id;
112 gint64 old_mtime;
113
114 path = (const char *)sqlite3_column_text (stmt, 0);
115 operation = (const char *)sqlite3_column_text (stmt, 1);
116 old_mtime = sqlite3_column_int64 (stmt, 2);
117 file_id = (const char *)sqlite3_column_text (stmt, 3);
118
119 file = g_new0 (LockedFile, 1);
120 file->operation = g_strdup(operation);
121 file->old_mtime = old_mtime;
122 if (file_id)
123 memcpy (file->file_id, file_id, 40);
124
125 g_hash_table_insert (ret, g_strdup(path), file);
126
127 return TRUE;
128 }
129
130 LockedFileSet *
seaf_repo_manager_get_locked_file_set(SeafRepoManager * mgr,const char * repo_id)131 seaf_repo_manager_get_locked_file_set (SeafRepoManager *mgr, const char *repo_id)
132 {
133 GHashTable *locked_files = g_hash_table_new_full (g_str_hash, g_str_equal,
134 g_free,
135 (GDestroyNotify)locked_file_free);
136 char sql[256];
137
138 sqlite3_snprintf (sizeof(sql), sql,
139 "SELECT path, operation, old_mtime, file_id FROM LockedFiles "
140 "WHERE repo_id = '%q'",
141 repo_id);
142
143 pthread_mutex_lock (&mgr->priv->db_lock);
144
145 /* Ingore database error. We return an empty set on error. */
146 sqlite_foreach_selected_row (mgr->priv->db, sql,
147 load_locked_file, locked_files);
148
149 pthread_mutex_unlock (&mgr->priv->db_lock);
150
151 LockedFileSet *ret = g_new0 (LockedFileSet, 1);
152 ret->mgr = mgr;
153 memcpy (ret->repo_id, repo_id, 36);
154 ret->locked_files = locked_files;
155
156 return ret;
157 }
158
159 void
locked_file_set_free(LockedFileSet * fset)160 locked_file_set_free (LockedFileSet *fset)
161 {
162 if (!fset)
163 return;
164 g_hash_table_destroy (fset->locked_files);
165 g_free (fset);
166 }
167
168 int
locked_file_set_add_update(LockedFileSet * fset,const char * path,const char * operation,gint64 old_mtime,const char * file_id)169 locked_file_set_add_update (LockedFileSet *fset,
170 const char *path,
171 const char *operation,
172 gint64 old_mtime,
173 const char *file_id)
174 {
175 SeafRepoManager *mgr = fset->mgr;
176 char *sql;
177 sqlite3_stmt *stmt;
178 LockedFile *file;
179 gboolean exists;
180
181 exists = (g_hash_table_lookup (fset->locked_files, path) != NULL);
182
183 pthread_mutex_lock (&mgr->priv->db_lock);
184
185 if (!exists) {
186 seaf_debug ("New locked file record %.8s, %s, %s, %"
187 G_GINT64_FORMAT".\n",
188 fset->repo_id, path, operation, old_mtime);
189
190 sql = "INSERT INTO LockedFiles VALUES (?, ?, ?, ?, ?, NULL)";
191 stmt = sqlite_query_prepare (mgr->priv->db, sql);
192 sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT);
193 sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
194 sqlite3_bind_text (stmt, 3, operation, -1, SQLITE_TRANSIENT);
195 sqlite3_bind_int64 (stmt, 4, old_mtime);
196 sqlite3_bind_text (stmt, 5, file_id, -1, SQLITE_TRANSIENT);
197 if (sqlite3_step (stmt) != SQLITE_DONE) {
198 seaf_warning ("Failed to insert locked file %s to db: %s.\n",
199 path, sqlite3_errmsg (mgr->priv->db));
200 sqlite3_finalize (stmt);
201 pthread_mutex_unlock (&mgr->priv->db_lock);
202 return -1;
203 }
204 sqlite3_finalize (stmt);
205
206 file = g_new0 (LockedFile, 1);
207 file->operation = g_strdup(operation);
208 file->old_mtime = old_mtime;
209 if (file_id)
210 memcpy (file->file_id, file_id, 40);
211
212 g_hash_table_insert (fset->locked_files, g_strdup(path), file);
213 } else {
214 seaf_debug ("Update locked file record %.8s, %s, %s.\n",
215 fset->repo_id, path, operation);
216
217 /* If a UPDATE record exists, don't update the old_mtime.
218 * We need to keep the old mtime when the locked file was first detected.
219 */
220
221 sql = "UPDATE LockedFiles SET operation = ?, file_id = ? "
222 "WHERE repo_id = ? AND path = ?";
223 stmt = sqlite_query_prepare (mgr->priv->db, sql);
224 sqlite3_bind_text (stmt, 1, operation, -1, SQLITE_TRANSIENT);
225 sqlite3_bind_text (stmt, 2, file_id, -1, SQLITE_TRANSIENT);
226 sqlite3_bind_text (stmt, 3, fset->repo_id, -1, SQLITE_TRANSIENT);
227 sqlite3_bind_text (stmt, 4, path, -1, SQLITE_TRANSIENT);
228 if (sqlite3_step (stmt) != SQLITE_DONE) {
229 seaf_warning ("Failed to update locked file %s to db: %s.\n",
230 path, sqlite3_errmsg (mgr->priv->db));
231 sqlite3_finalize (stmt);
232 pthread_mutex_unlock (&mgr->priv->db_lock);
233 return -1;
234 }
235 sqlite3_finalize (stmt);
236
237 file = g_hash_table_lookup (fset->locked_files, path);
238 g_free (file->operation);
239 file->operation = g_strdup(operation);
240 if (file_id)
241 memcpy (file->file_id, file_id, 40);
242 }
243
244 pthread_mutex_unlock (&mgr->priv->db_lock);
245
246 return 0;
247 }
248
249 int
locked_file_set_remove(LockedFileSet * fset,const char * path,gboolean db_only)250 locked_file_set_remove (LockedFileSet *fset, const char *path, gboolean db_only)
251 {
252 SeafRepoManager *mgr = fset->mgr;
253 char *sql;
254 sqlite3_stmt *stmt;
255
256 if (g_hash_table_lookup (fset->locked_files, path) == NULL)
257 return 0;
258
259 seaf_debug ("Remove locked file record %.8s, %s.\n",
260 fset->repo_id, path);
261
262 pthread_mutex_lock (&mgr->priv->db_lock);
263
264 sql = "DELETE FROM LockedFiles WHERE repo_id = ? AND path = ?";
265 stmt = sqlite_query_prepare (mgr->priv->db, sql);
266 sqlite3_bind_text (stmt, 1, fset->repo_id, -1, SQLITE_TRANSIENT);
267 sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT);
268 if (sqlite3_step (stmt) != SQLITE_DONE) {
269 seaf_warning ("Failed to remove locked file %s from db: %s.\n",
270 path, sqlite3_errmsg (mgr->priv->db));
271 sqlite3_finalize (stmt);
272 pthread_mutex_unlock (&mgr->priv->db_lock);
273 return -1;
274 }
275 sqlite3_finalize (stmt);
276 pthread_mutex_unlock (&mgr->priv->db_lock);
277
278 if (!db_only)
279 g_hash_table_remove (fset->locked_files, path);
280
281 return 0;
282 }
283
284 LockedFile *
locked_file_set_lookup(LockedFileSet * fset,const char * path)285 locked_file_set_lookup (LockedFileSet *fset, const char *path)
286 {
287 return (LockedFile *) g_hash_table_lookup (fset->locked_files, path);
288 }
289
290 /* Folder permissions. */
291
292 FolderPerm *
folder_perm_new(const char * path,const char * permission)293 folder_perm_new (const char *path, const char *permission)
294 {
295 FolderPerm *perm = g_new0 (FolderPerm, 1);
296
297 perm->path = g_strdup(path);
298 perm->permission = g_strdup(permission);
299
300 return perm;
301 }
302
303 void
folder_perm_free(FolderPerm * perm)304 folder_perm_free (FolderPerm *perm)
305 {
306 if (!perm)
307 return;
308
309 g_free (perm->path);
310 g_free (perm->permission);
311 g_free (perm);
312 }
313
314 static GList *
folder_perm_list_copy(GList * perms)315 folder_perm_list_copy (GList *perms)
316 {
317 GList *ret = NULL, *ptr;
318 FolderPerm *perm, *new_perm;
319
320 for (ptr = perms; ptr; ptr = ptr->next) {
321 perm = ptr->data;
322 new_perm = folder_perm_new (perm->path, perm->permission);
323 ret = g_list_append (ret, new_perm);
324 }
325
326 return ret;
327 }
328
329 static gint
comp_folder_perms(gconstpointer a,gconstpointer b)330 comp_folder_perms (gconstpointer a, gconstpointer b)
331 {
332 const FolderPerm *perm_a = a, *perm_b = b;
333
334 return (strcmp (perm_b->path, perm_a->path));
335 }
336
337 int
seaf_repo_manager_update_folder_perms(SeafRepoManager * mgr,const char * repo_id,FolderPermType type,GList * folder_perms)338 seaf_repo_manager_update_folder_perms (SeafRepoManager *mgr,
339 const char *repo_id,
340 FolderPermType type,
341 GList *folder_perms)
342 {
343 char *sql;
344 sqlite3_stmt *stmt;
345 GList *ptr;
346 FolderPerm *perm;
347
348 g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||
349 type == FOLDER_PERM_TYPE_GROUP),
350 -1);
351
352 /* Update db. */
353
354 pthread_mutex_lock (&mgr->priv->db_lock);
355
356 if (type == FOLDER_PERM_TYPE_USER)
357 sql = "DELETE FROM FolderUserPerms WHERE repo_id = ?";
358 else
359 sql = "DELETE FROM FolderGroupPerms WHERE repo_id = ?";
360 stmt = sqlite_query_prepare (mgr->priv->db, sql);
361 sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
362 if (sqlite3_step (stmt) != SQLITE_DONE) {
363 seaf_warning ("Failed to remove folder perms for %.8s: %s.\n",
364 repo_id, sqlite3_errmsg (mgr->priv->db));
365 sqlite3_finalize (stmt);
366 pthread_mutex_unlock (&mgr->priv->db_lock);
367 return -1;
368 }
369 sqlite3_finalize (stmt);
370
371 if (!folder_perms) {
372 pthread_mutex_unlock (&mgr->priv->db_lock);
373 return 0;
374 }
375
376 if (type == FOLDER_PERM_TYPE_USER)
377 sql = "INSERT INTO FolderUserPerms VALUES (?, ?, ?)";
378 else
379 sql = "INSERT INTO FolderGroupPerms VALUES (?, ?, ?)";
380 stmt = sqlite_query_prepare (mgr->priv->db, sql);
381
382 for (ptr = folder_perms; ptr; ptr = ptr->next) {
383 perm = ptr->data;
384
385 sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT);
386 sqlite3_bind_text (stmt, 2, perm->path, -1, SQLITE_TRANSIENT);
387 sqlite3_bind_text (stmt, 3, perm->permission, -1, SQLITE_TRANSIENT);
388
389 if (sqlite3_step (stmt) != SQLITE_DONE) {
390 seaf_warning ("Failed to insert folder perms for %.8s: %s.\n",
391 repo_id, sqlite3_errmsg (mgr->priv->db));
392 sqlite3_finalize (stmt);
393 pthread_mutex_unlock (&mgr->priv->db_lock);
394 return -1;
395 }
396
397 sqlite3_reset (stmt);
398 sqlite3_clear_bindings (stmt);
399 }
400
401 sqlite3_finalize (stmt);
402
403 pthread_mutex_unlock (&mgr->priv->db_lock);
404
405 /* Update in memory */
406 GList *new, *old;
407 new = folder_perm_list_copy (folder_perms);
408 new = g_list_sort (new, comp_folder_perms);
409
410 pthread_mutex_lock (&mgr->priv->perm_lock);
411 if (type == FOLDER_PERM_TYPE_USER) {
412 old = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
413 if (old)
414 g_list_free_full (old, (GDestroyNotify)folder_perm_free);
415 g_hash_table_insert (mgr->priv->user_perms, g_strdup(repo_id), new);
416 } else if (type == FOLDER_PERM_TYPE_GROUP) {
417 old = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
418 if (old)
419 g_list_free_full (old, (GDestroyNotify)folder_perm_free);
420 g_hash_table_insert (mgr->priv->group_perms, g_strdup(repo_id), new);
421 }
422 pthread_mutex_unlock (&mgr->priv->perm_lock);
423
424 return 0;
425 }
426
427 static gboolean
load_folder_perm(sqlite3_stmt * stmt,void * data)428 load_folder_perm (sqlite3_stmt *stmt, void *data)
429 {
430 GList **p_perms = data;
431 const char *path, *permission;
432
433 path = (const char *)sqlite3_column_text (stmt, 0);
434 permission = (const char *)sqlite3_column_text (stmt, 1);
435
436 FolderPerm *perm = folder_perm_new (path, permission);
437 *p_perms = g_list_prepend (*p_perms, perm);
438
439 return TRUE;
440 }
441
442 static GList *
load_folder_perms_for_repo(SeafRepoManager * mgr,const char * repo_id,FolderPermType type)443 load_folder_perms_for_repo (SeafRepoManager *mgr,
444 const char *repo_id,
445 FolderPermType type)
446 {
447 GList *perms = NULL;
448 char sql[256];
449
450 g_return_val_if_fail ((type == FOLDER_PERM_TYPE_USER ||
451 type == FOLDER_PERM_TYPE_GROUP),
452 NULL);
453
454 if (type == FOLDER_PERM_TYPE_USER)
455 sqlite3_snprintf (sizeof(sql), sql,
456 "SELECT path, permission FROM FolderUserPerms "
457 "WHERE repo_id = '%q'",
458 repo_id);
459 else
460 sqlite3_snprintf (sizeof(sql), sql,
461 "SELECT path, permission FROM FolderGroupPerms "
462 "WHERE repo_id = '%q'",
463 repo_id);
464
465 pthread_mutex_lock (&mgr->priv->db_lock);
466
467 if (sqlite_foreach_selected_row (mgr->priv->db, sql,
468 load_folder_perm, &perms) < 0) {
469 pthread_mutex_unlock (&mgr->priv->db_lock);
470 GList *ptr;
471 for (ptr = perms; ptr; ptr = ptr->next)
472 folder_perm_free ((FolderPerm *)ptr->data);
473 g_list_free (perms);
474 return NULL;
475 }
476
477 pthread_mutex_unlock (&mgr->priv->db_lock);
478
479 /* Sort list in descending order by perm->path (longer path first). */
480 perms = g_list_sort (perms, comp_folder_perms);
481
482 return perms;
483 }
484
485 static void
init_folder_perms(SeafRepoManager * mgr)486 init_folder_perms (SeafRepoManager *mgr)
487 {
488 SeafRepoManagerPriv *priv = mgr->priv;
489 GList *repo_ids = g_hash_table_get_keys (priv->repo_hash);
490 GList *ptr;
491 GList *perms;
492 char *repo_id;
493
494 priv->user_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
495 priv->group_perms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
496 pthread_mutex_init (&priv->perm_lock, NULL);
497
498 for (ptr = repo_ids; ptr; ptr = ptr->next) {
499 repo_id = ptr->data;
500 perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_USER);
501 if (perms) {
502 pthread_mutex_lock (&priv->perm_lock);
503 g_hash_table_insert (priv->user_perms, g_strdup(repo_id), perms);
504 pthread_mutex_unlock (&priv->perm_lock);
505 }
506 perms = load_folder_perms_for_repo (mgr, repo_id, FOLDER_PERM_TYPE_GROUP);
507 if (perms) {
508 pthread_mutex_lock (&priv->perm_lock);
509 g_hash_table_insert (priv->group_perms, g_strdup(repo_id), perms);
510 pthread_mutex_unlock (&priv->perm_lock);
511 }
512 }
513
514 g_list_free (repo_ids);
515 }
516
517 static void
remove_folder_perms(SeafRepoManager * mgr,const char * repo_id)518 remove_folder_perms (SeafRepoManager *mgr, const char *repo_id)
519 {
520 GList *perms = NULL;
521
522 pthread_mutex_lock (&mgr->priv->perm_lock);
523
524 perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
525 if (perms) {
526 g_list_free_full (perms, (GDestroyNotify)folder_perm_free);
527 g_hash_table_remove (mgr->priv->user_perms, repo_id);
528 }
529
530 perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
531 if (perms) {
532 g_list_free_full (perms, (GDestroyNotify)folder_perm_free);
533 g_hash_table_remove (mgr->priv->group_perms, repo_id);
534 }
535
536 pthread_mutex_unlock (&mgr->priv->perm_lock);
537 }
538
539 int
seaf_repo_manager_update_folder_perm_timestamp(SeafRepoManager * mgr,const char * repo_id,gint64 timestamp)540 seaf_repo_manager_update_folder_perm_timestamp (SeafRepoManager *mgr,
541 const char *repo_id,
542 gint64 timestamp)
543 {
544 char sql[256];
545 int ret;
546
547 snprintf (sql, sizeof(sql),
548 "REPLACE INTO FolderPermTimestamp VALUES ('%s', %"G_GINT64_FORMAT")",
549 repo_id, timestamp);
550
551 pthread_mutex_lock (&mgr->priv->db_lock);
552
553 ret = sqlite_query_exec (mgr->priv->db, sql);
554
555 pthread_mutex_unlock (&mgr->priv->db_lock);
556
557 return ret;
558 }
559
560 gint64
seaf_repo_manager_get_folder_perm_timestamp(SeafRepoManager * mgr,const char * repo_id)561 seaf_repo_manager_get_folder_perm_timestamp (SeafRepoManager *mgr,
562 const char *repo_id)
563 {
564 char sql[256];
565 gint64 ret;
566
567 sqlite3_snprintf (sizeof(sql), sql,
568 "SELECT timestamp FROM FolderPermTimestamp WHERE repo_id = '%q'",
569 repo_id);
570
571 pthread_mutex_lock (&mgr->priv->db_lock);
572
573 ret = sqlite_get_int64 (mgr->priv->db, sql);
574
575 pthread_mutex_unlock (&mgr->priv->db_lock);
576
577 return ret;
578 }
579
580 static char *
lookup_folder_perm(GList * perms,const char * path)581 lookup_folder_perm (GList *perms, const char *path)
582 {
583 GList *ptr;
584 FolderPerm *perm;
585 char *folder;
586 int len;
587 char *permission = NULL;
588
589 for (ptr = perms; ptr; ptr = ptr->next) {
590 perm = ptr->data;
591
592 if (strcmp (perm->path, "/") != 0)
593 folder = g_strconcat (perm->path, "/", NULL);
594 else
595 folder = g_strdup(perm->path);
596
597 len = strlen(folder);
598 if (strcmp (perm->path, path) == 0 || strncmp(folder, path, len) == 0) {
599 permission = perm->permission;
600 g_free (folder);
601 break;
602 }
603 g_free (folder);
604 }
605
606 return permission;
607 }
608
609 static gboolean
is_path_writable(const char * repo_id,gboolean is_repo_readonly,const char * path)610 is_path_writable (const char *repo_id,
611 gboolean is_repo_readonly,
612 const char *path)
613 {
614 SeafRepoManager *mgr = seaf->repo_mgr;
615 GList *user_perms = NULL, *group_perms = NULL;
616 char *permission = NULL;
617 char *abs_path = NULL;
618
619 pthread_mutex_lock (&mgr->priv->perm_lock);
620
621 user_perms = g_hash_table_lookup (mgr->priv->user_perms, repo_id);
622 group_perms = g_hash_table_lookup (mgr->priv->group_perms, repo_id);
623
624 if (user_perms || group_perms)
625 abs_path = g_strconcat ("/", path, NULL);
626
627 if (user_perms)
628 permission = lookup_folder_perm (user_perms, abs_path);
629 if (!permission && group_perms)
630 permission = lookup_folder_perm (group_perms, abs_path);
631
632 pthread_mutex_unlock (&mgr->priv->perm_lock);
633
634 g_free (abs_path);
635
636 if (!permission)
637 return !is_repo_readonly;
638
639 if (strcmp (permission, "rw") == 0)
640 return TRUE;
641 else
642 return FALSE;
643 }
644
645 gboolean
seaf_repo_manager_is_path_writable(SeafRepoManager * mgr,const char * repo_id,const char * path)646 seaf_repo_manager_is_path_writable (SeafRepoManager *mgr,
647 const char *repo_id,
648 const char *path)
649 {
650 SeafRepo *repo = seaf_repo_manager_get_repo (mgr, repo_id);
651 if (!repo) {
652 seaf_warning ("Failed to get repo %s.\n", repo_id);
653 return FALSE;
654 }
655
656 return is_path_writable (repo_id, repo->is_readonly, path);
657 }
658
659 gboolean
is_repo_id_valid(const char * id)660 is_repo_id_valid (const char *id)
661 {
662 if (!id)
663 return FALSE;
664
665 return is_uuid_valid (id);
666 }
667
668 /*
669 * Sync error related. These functions should belong to the sync-mgr module.
670 * But since we have to store the errors in repo database, we have to put the code here.
671 */
672
673 int
seaf_repo_manager_record_sync_error(const char * repo_id,const char * repo_name,const char * path,int error_id)674 seaf_repo_manager_record_sync_error (const char *repo_id,
675 const char *repo_name,
676 const char *path,
677 int error_id)
678 {
679 char *sql;
680 int ret;
681
682 pthread_mutex_lock (&seaf->repo_mgr->priv->db_lock);
683
684 if (path != NULL)
685 sql = sqlite3_mprintf ("DELETE FROM FileSyncError WHERE repo_id='%q' AND path='%q'",
686 repo_id, path);
687 else
688 sql = sqlite3_mprintf ("DELETE FROM FileSyncError WHERE repo_id='%q' AND path IS NULL",
689 repo_id);
690 ret = sqlite_query_exec (seaf->repo_mgr->priv->db, sql);
691 sqlite3_free (sql);
692 if (ret < 0)
693 goto out;
694
695 /* REPLACE INTO will update the primary key id automatically.
696 * So new errors are always on top.
697 */
698 if (path != NULL)
699 sql = sqlite3_mprintf ("INSERT INTO FileSyncError "
700 "(repo_id, repo_name, path, err_id, timestamp) "
701 "VALUES ('%q', '%q', '%q', %d, %"G_GINT64_FORMAT")",
702 repo_id, repo_name, path, error_id, (gint64)time(NULL));
703 else
704 sql = sqlite3_mprintf ("INSERT INTO FileSyncError "
705 "(repo_id, repo_name, err_id, timestamp) "
706 "VALUES ('%q', '%q', %d, %"G_GINT64_FORMAT")",
707 repo_id, repo_name, error_id, (gint64)time(NULL));
708
709 ret = sqlite_query_exec (seaf->repo_mgr->priv->db, sql);
710 sqlite3_free (sql);
711
712 out:
713 pthread_mutex_unlock (&seaf->repo_mgr->priv->db_lock);
714 return ret;
715 }
716
717 static gboolean
collect_file_sync_errors(sqlite3_stmt * stmt,void * data)718 collect_file_sync_errors (sqlite3_stmt *stmt, void *data)
719 {
720 GList **pret = data;
721 const char *repo_id, *repo_name, *path;
722 int id, err_id;
723 gint64 timestamp;
724 SeafileFileSyncError *error;
725
726 id = sqlite3_column_int (stmt, 0);
727 repo_id = (const char *)sqlite3_column_text (stmt, 1);
728 repo_name = (const char *)sqlite3_column_text (stmt, 2);
729 path = (const char *)sqlite3_column_text (stmt, 3);
730 err_id = sqlite3_column_int (stmt, 4);
731 timestamp = sqlite3_column_int64 (stmt, 5);
732
733 error = g_object_new (SEAFILE_TYPE_FILE_SYNC_ERROR,
734 "id", id,
735 "repo_id", repo_id,
736 "repo_name", repo_name,
737 "path", path,
738 "err_id", err_id,
739 "timestamp", timestamp,
740 NULL);
741 *pret = g_list_prepend (*pret, error);
742
743 return TRUE;
744 }
745
746 int
seaf_repo_manager_del_file_sync_error_by_id(SeafRepoManager * mgr,int id)747 seaf_repo_manager_del_file_sync_error_by_id (SeafRepoManager *mgr, int id)
748 {
749 int ret = 0;
750 char *sql = NULL;
751
752 pthread_mutex_lock (&mgr->priv->db_lock);
753
754 sql = sqlite3_mprintf ("DELETE FROM FileSyncError WHERE id=%d",
755 id);
756 ret = sqlite_query_exec (mgr->priv->db, sql);
757 sqlite3_free (sql);
758
759 pthread_mutex_unlock (&mgr->priv->db_lock);
760
761 return ret;
762 }
763
764 GList *
seaf_repo_manager_get_file_sync_errors(SeafRepoManager * mgr,int offset,int limit)765 seaf_repo_manager_get_file_sync_errors (SeafRepoManager *mgr, int offset, int limit)
766 {
767 GList *ret = NULL;
768 char *sql;
769
770 pthread_mutex_lock (&mgr->priv->db_lock);
771
772 sql = sqlite3_mprintf ("SELECT id, repo_id, repo_name, path, err_id, timestamp FROM "
773 "FileSyncError ORDER BY id DESC LIMIT %d OFFSET %d",
774 limit, offset);
775 sqlite_foreach_selected_row (mgr->priv->db, sql,
776 collect_file_sync_errors, &ret);
777 sqlite3_free (sql);
778
779 pthread_mutex_unlock (&mgr->priv->db_lock);
780
781 ret = g_list_reverse (ret);
782
783 return ret;
784 }
785
786 /*
787 * Record file-level sync errors and send system notification.
788 */
789 void
send_file_sync_error_notification(const char * repo_id,const char * repo_name,const char * path,int err_id)790 send_file_sync_error_notification (const char *repo_id,
791 const char *repo_name,
792 const char *path,
793 int err_id)
794 {
795 if (!repo_name) {
796 SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
797 if (!repo)
798 return;
799 repo_name = repo->name;
800 }
801
802 seaf_repo_manager_record_sync_error (repo_id, repo_name, path, err_id);
803
804 seaf_sync_manager_set_task_error_code (seaf->sync_mgr, repo_id, err_id);
805
806 json_t *object;
807 char *str;
808
809 object = json_object ();
810 json_object_set_new (object, "repo_id", json_string(repo_id));
811 json_object_set_new (object, "repo_name", json_string(repo_name));
812 json_object_set_new (object, "path", json_string(path));
813 json_object_set_new (object, "err_id", json_integer(err_id));
814
815 str = json_dumps (object, 0);
816
817 seaf_mq_manager_publish_notification (seaf->mq_mgr,
818 "sync.error",
819 str);
820
821 free (str);
822 json_decref (object);
823 }
824
825 SeafRepo*
seaf_repo_new(const char * id,const char * name,const char * desc)826 seaf_repo_new (const char *id, const char *name, const char *desc)
827 {
828 SeafRepo* repo;
829
830 /* valid check */
831
832
833 repo = g_new0 (SeafRepo, 1);
834 memcpy (repo->id, id, 36);
835 repo->id[36] = '\0';
836
837 repo->name = g_strdup(name);
838 repo->desc = g_strdup(desc);
839
840 repo->worktree_invalid = TRUE;
841 repo->auto_sync = 1;
842 pthread_mutex_init (&repo->lock, NULL);
843
844 return repo;
845 }
846
847 int
seaf_repo_check_worktree(SeafRepo * repo)848 seaf_repo_check_worktree (SeafRepo *repo)
849 {
850 SeafStat st;
851
852 if (repo->worktree == NULL) {
853 seaf_warning ("Worktree for repo '%s'(%.8s) is not set.\n",
854 repo->name, repo->id);
855 return -1;
856 }
857
858 /* check repo worktree */
859 if (g_access(repo->worktree, F_OK) < 0) {
860 seaf_warning ("Failed to access worktree %s for repo '%s'(%.8s)\n",
861 repo->worktree, repo->name, repo->id);
862 return -1;
863 }
864 if (seaf_stat(repo->worktree, &st) < 0) {
865 seaf_warning ("Failed to stat worktree %s for repo '%s'(%.8s)\n",
866 repo->worktree, repo->name, repo->id);
867 return -1;
868 }
869 if (!S_ISDIR(st.st_mode)) {
870 seaf_warning ("Worktree %s for repo '%s'(%.8s) is not a directory.\n",
871 repo->worktree, repo->name, repo->id);
872 return -1;
873 }
874
875 return 0;
876 }
877
878
879 static gboolean
check_worktree_common(SeafRepo * repo)880 check_worktree_common (SeafRepo *repo)
881 {
882 if (!repo->head) {
883 seaf_warning ("Head for repo '%s'(%.8s) is not set.\n",
884 repo->name, repo->id);
885 return FALSE;
886 }
887
888 if (seaf_repo_check_worktree (repo) < 0) {
889 return FALSE;
890 }
891
892 return TRUE;
893 }
894
895 void
seaf_repo_free(SeafRepo * repo)896 seaf_repo_free (SeafRepo *repo)
897 {
898 if (repo->head) seaf_branch_unref (repo->head);
899
900 g_free (repo->name);
901 g_free (repo->desc);
902 g_free (repo->category);
903 g_free (repo->worktree);
904 g_free (repo->relay_id);
905 g_free (repo->email);
906 g_free (repo->token);
907 g_free (repo);
908 }
909
910 static void
set_head_common(SeafRepo * repo,SeafBranch * branch)911 set_head_common (SeafRepo *repo, SeafBranch *branch)
912 {
913 if (repo->head)
914 seaf_branch_unref (repo->head);
915 repo->head = branch;
916 seaf_branch_ref(branch);
917 }
918
919 int
seaf_repo_set_head(SeafRepo * repo,SeafBranch * branch)920 seaf_repo_set_head (SeafRepo *repo, SeafBranch *branch)
921 {
922 if (save_branch_repo_map (repo->manager, branch) < 0)
923 return -1;
924 set_head_common (repo, branch);
925 return 0;
926 }
927
928 SeafCommit *
seaf_repo_get_head_commit(const char * repo_id)929 seaf_repo_get_head_commit (const char *repo_id)
930 {
931 SeafRepo *repo;
932 SeafCommit *head;
933
934 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
935 if (!repo) {
936 seaf_warning ("Failed to get repo %s.\n", repo_id);
937 return NULL;
938 }
939
940 head = seaf_commit_manager_get_commit (seaf->commit_mgr,
941 repo_id, repo->version,
942 repo->head->commit_id);
943 if (!head) {
944 seaf_warning ("Failed to get head for repo %s.\n", repo_id);
945 return NULL;
946 }
947
948 return head;
949 }
950
951 void
seaf_repo_from_commit(SeafRepo * repo,SeafCommit * commit)952 seaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit)
953 {
954 repo->name = g_strdup (commit->repo_name);
955 repo->desc = g_strdup (commit->repo_desc);
956 repo->encrypted = commit->encrypted;
957 repo->last_modify = commit->ctime;
958 memcpy (repo->root_id, commit->root_id, 40);
959 if (repo->encrypted) {
960 repo->enc_version = commit->enc_version;
961 if (repo->enc_version == 1)
962 memcpy (repo->magic, commit->magic, 32);
963 else if (repo->enc_version == 2) {
964 memcpy (repo->magic, commit->magic, 64);
965 memcpy (repo->random_key, commit->random_key, 96);
966 }
967 else if (repo->enc_version == 3) {
968 memcpy (repo->magic, commit->magic, 64);
969 memcpy (repo->random_key, commit->random_key, 96);
970 memcpy (repo->salt, commit->salt, 64);
971 }
972 }
973 repo->no_local_history = commit->no_local_history;
974 repo->version = commit->version;
975 }
976
977 void
seaf_repo_to_commit(SeafRepo * repo,SeafCommit * commit)978 seaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit)
979 {
980 commit->repo_name = g_strdup (repo->name);
981 commit->repo_desc = g_strdup (repo->desc);
982 commit->encrypted = repo->encrypted;
983 if (commit->encrypted) {
984 commit->enc_version = repo->enc_version;
985 if (commit->enc_version == 1)
986 commit->magic = g_strdup (repo->magic);
987 else if (commit->enc_version == 2) {
988 commit->magic = g_strdup (repo->magic);
989 commit->random_key = g_strdup (repo->random_key);
990 }
991 else if (commit->enc_version == 3) {
992 commit->magic = g_strdup (repo->magic);
993 commit->random_key = g_strdup (repo->random_key);
994 commit->salt = g_strdup (repo->salt);
995 }
996 }
997 commit->no_local_history = repo->no_local_history;
998 commit->version = repo->version;
999 }
1000
1001 static gboolean
need_to_sync_worktree_name(const char * repo_id)1002 need_to_sync_worktree_name (const char *repo_id)
1003 {
1004 char *need_sync_wt_name = seaf_repo_manager_get_repo_property (seaf->repo_mgr,
1005 repo_id,
1006 REPO_SYNC_WORKTREE_NAME);
1007 gboolean ret = (g_strcmp0(need_sync_wt_name, "true") == 0);
1008 g_free (need_sync_wt_name);
1009 return ret;
1010 }
1011
1012 static void
update_repo_worktree_name(SeafRepo * repo,const char * new_name,gboolean rewatch)1013 update_repo_worktree_name (SeafRepo *repo, const char *new_name, gboolean rewatch)
1014 {
1015 char *dirname = NULL, *basename = NULL;
1016 char *new_worktree = NULL;
1017
1018 seaf_message ("Update worktree folder name of repo %s to %s.\n",
1019 repo->id, new_name);
1020
1021 dirname = g_path_get_dirname (repo->worktree);
1022 if (g_strcmp0 (dirname, ".") == 0)
1023 return;
1024 basename = g_path_get_basename (repo->worktree);
1025
1026 new_worktree = g_build_filename (dirname, new_name, NULL);
1027
1028 /* This can possibly fail on Windows if some files are opened under the worktree.
1029 * The rename operation will be retried on next restart.
1030 */
1031 if (seaf_util_rename (repo->worktree, new_worktree) < 0) {
1032 seaf_warning ("Failed to rename worktree from %s to %s: %s.\n",
1033 repo->worktree, new_worktree, strerror(errno));
1034 goto out;
1035 }
1036
1037 if (seaf_repo_manager_set_repo_worktree (seaf->repo_mgr, repo, new_worktree) < 0) {
1038 goto out;
1039 }
1040
1041 if (rewatch) {
1042 if (seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id) < 0) {
1043 seaf_warning ("Failed to unwatch repo %s old worktree.\n", repo->id);
1044 goto out;
1045 }
1046
1047 if (seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree) < 0) {
1048 seaf_warning ("Failed to watch repo %s new worktree.\n", repo->id);
1049 }
1050 }
1051
1052 out:
1053 g_free (dirname);
1054 g_free (basename);
1055 g_free (new_worktree);
1056 }
1057
1058 void
seaf_repo_set_name(SeafRepo * repo,const char * new_name)1059 seaf_repo_set_name (SeafRepo *repo, const char *new_name)
1060 {
1061 char *old_name = repo->name;
1062 repo->name = g_strdup(new_name);
1063 g_free (old_name);
1064
1065 if (need_to_sync_worktree_name (repo->id))
1066 update_repo_worktree_name (repo, new_name, TRUE);
1067 }
1068
1069 static gboolean
collect_commit(SeafCommit * commit,void * vlist,gboolean * stop)1070 collect_commit (SeafCommit *commit, void *vlist, gboolean *stop)
1071 {
1072 GList **commits = vlist;
1073
1074 /* The traverse function will unref the commit, so we need to ref it.
1075 */
1076 seaf_commit_ref (commit);
1077 *commits = g_list_prepend (*commits, commit);
1078 return TRUE;
1079 }
1080
1081 GList *
seaf_repo_get_commits(SeafRepo * repo)1082 seaf_repo_get_commits (SeafRepo *repo)
1083 {
1084 GList *branches;
1085 GList *ptr;
1086 SeafBranch *branch;
1087 GList *commits = NULL;
1088
1089 branches = seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo->id);
1090 if (branches == NULL) {
1091 seaf_warning ("Failed to get branch list of repo %s.\n", repo->id);
1092 return NULL;
1093 }
1094
1095 for (ptr = branches; ptr != NULL; ptr = ptr->next) {
1096 branch = ptr->data;
1097 gboolean res = seaf_commit_manager_traverse_commit_tree (seaf->commit_mgr,
1098 repo->id,
1099 repo->version,
1100 branch->commit_id,
1101 collect_commit,
1102 &commits, FALSE);
1103 if (!res) {
1104 for (ptr = commits; ptr != NULL; ptr = ptr->next)
1105 seaf_commit_unref ((SeafCommit *)(ptr->data));
1106 g_list_free (commits);
1107 goto out;
1108 }
1109 }
1110
1111 commits = g_list_reverse (commits);
1112
1113 out:
1114 for (ptr = branches; ptr != NULL; ptr = ptr->next) {
1115 seaf_branch_unref ((SeafBranch *)ptr->data);
1116 }
1117 return commits;
1118 }
1119
1120 void
seaf_repo_set_readonly(SeafRepo * repo)1121 seaf_repo_set_readonly (SeafRepo *repo)
1122 {
1123 repo->is_readonly = TRUE;
1124 save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, "true");
1125 }
1126
1127 void
seaf_repo_unset_readonly(SeafRepo * repo)1128 seaf_repo_unset_readonly (SeafRepo *repo)
1129 {
1130 repo->is_readonly = FALSE;
1131 save_repo_property (repo->manager, repo->id, REPO_PROP_IS_READONLY, "false");
1132 }
1133
1134 gboolean
seaf_repo_manager_is_ignored_hidden_file(const char * filename)1135 seaf_repo_manager_is_ignored_hidden_file (const char *filename)
1136 {
1137 GPatternSpec **spec = ignore_patterns;
1138
1139 while (*spec) {
1140 if (g_pattern_match_string(*spec, filename))
1141 return TRUE;
1142 spec++;
1143 }
1144
1145 return FALSE;
1146 }
1147
1148 static gboolean
should_ignore(const char * basepath,const char * filename,void * data)1149 should_ignore(const char *basepath, const char *filename, void *data)
1150 {
1151 GPatternSpec **spec = ignore_patterns;
1152 GList *ignore_list = (GList *)data;
1153
1154 if (!g_utf8_validate (filename, -1, NULL)) {
1155 seaf_warning ("File name %s contains non-UTF8 characters, skip.\n", filename);
1156 return TRUE;
1157 }
1158
1159 /* Ignore file/dir if its name is too long. */
1160 if (strlen(filename) >= SEAF_DIR_NAME_LEN)
1161 return TRUE;
1162
1163 if (strchr (filename, '/'))
1164 return TRUE;
1165
1166 while (*spec) {
1167 if (g_pattern_match_string(*spec, filename))
1168 return TRUE;
1169 spec++;
1170 }
1171
1172 if (!seaf->sync_extra_temp_file) {
1173 spec = office_temp_ignore_patterns;
1174 while (*spec) {
1175 if (g_pattern_match_string(*spec, filename))
1176 return TRUE;
1177 spec++;
1178 }
1179 }
1180
1181 if (basepath) {
1182 char *fullpath = g_build_path ("/", basepath, filename, NULL);
1183 if (seaf_repo_check_ignore_file (ignore_list, fullpath)) {
1184 g_free (fullpath);
1185 return TRUE;
1186 }
1187 g_free (fullpath);
1188 }
1189
1190 return FALSE;
1191 }
1192
1193 static int
index_cb(const char * repo_id,int version,const char * path,unsigned char sha1[],SeafileCrypt * crypt,gboolean write_data)1194 index_cb (const char *repo_id,
1195 int version,
1196 const char *path,
1197 unsigned char sha1[],
1198 SeafileCrypt *crypt,
1199 gboolean write_data)
1200 {
1201 gint64 size;
1202
1203 /* Check in blocks and get object ID. */
1204 if (seaf_fs_manager_index_blocks (seaf->fs_mgr, repo_id, version,
1205 path, sha1, &size, crypt, write_data, !seaf->disable_block_hash) < 0) {
1206 seaf_warning ("Failed to index file %s.\n", path);
1207 return -1;
1208 }
1209 return 0;
1210 }
1211
1212 #define MAX_COMMIT_SIZE 100 * (1 << 20) /* 100MB */
1213
1214 typedef struct _AddOptions {
1215 LockedFileSet *fset;
1216 ChangeSet *changeset;
1217 gboolean is_repo_ro;
1218 gboolean startup_scan;
1219 } AddOptions;
1220
1221 static int
add_file(const char * repo_id,int version,const char * modifier,struct index_state * istate,const char * path,const char * full_path,SeafStat * st,SeafileCrypt * crypt,gint64 * total_size,GQueue ** remain_files,AddOptions * options)1222 add_file (const char *repo_id,
1223 int version,
1224 const char *modifier,
1225 struct index_state *istate,
1226 const char *path,
1227 const char *full_path,
1228 SeafStat *st,
1229 SeafileCrypt *crypt,
1230 gint64 *total_size,
1231 GQueue **remain_files,
1232 AddOptions *options)
1233 {
1234 gboolean added = FALSE;
1235 int ret = 0;
1236 gboolean is_writable = TRUE, is_locked = FALSE;
1237 struct cache_entry *ce;
1238
1239 if (options)
1240 is_writable = is_path_writable(repo_id,
1241 options->is_repo_ro, path);
1242
1243 is_locked = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
1244 repo_id, path);
1245 if (is_locked && options && !(options->startup_scan)) {
1246 /* send_sync_error_notification (repo_id, NULL, path, */
1247 /* SYNC_ERROR_ID_FILE_LOCKED); */
1248 }
1249
1250 if (options && options->startup_scan) {
1251 SyncStatus status;
1252
1253 ce = index_name_exists (istate, path, strlen(path), 0);
1254 if (!ce || ie_match_stat(ce, st, 0) != 0)
1255 status = SYNC_STATUS_SYNCING;
1256 else
1257 status = SYNC_STATUS_SYNCED;
1258
1259 /* Don't set "syncing" status for read-only path. */
1260 if (status == SYNC_STATUS_SYNCED || (is_writable && !is_locked))
1261 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1262 repo_id,
1263 path,
1264 S_IFREG,
1265 status,
1266 FALSE);
1267 /* send an error notification for read-only repo when modifying a file. */
1268 if (status == SYNC_STATUS_SYNCING && !is_writable)
1269 send_file_sync_error_notification (repo_id, NULL, path,
1270 SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO);
1271 }
1272
1273 if (!is_writable || is_locked)
1274 return ret;
1275
1276 #if defined WIN32 || defined __APPLE__
1277 if (options && options->fset) {
1278 LockedFile *file = locked_file_set_lookup (options->fset, path);
1279 if (file) {
1280 if (strcmp (file->operation, LOCKED_OP_DELETE) == 0) {
1281 /* Only remove the lock record if the file is changed. */
1282 if (st->st_mtime == file->old_mtime) {
1283 return ret;
1284 }
1285 locked_file_set_remove (options->fset, path, FALSE);
1286 } else if (strcmp (file->operation, LOCKED_OP_UPDATE) == 0) {
1287 return ret;
1288 }
1289 }
1290 }
1291 #endif
1292
1293 if (!remain_files) {
1294 ret = add_to_index (repo_id, version, istate, path, full_path,
1295 st, 0, crypt, index_cb, modifier, &added);
1296 if (!added) {
1297 /* If the contents of the file doesn't change, move it to
1298 synced status.
1299 */
1300 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1301 repo_id,
1302 path,
1303 S_IFREG,
1304 SYNC_STATUS_SYNCED,
1305 FALSE);
1306 } else {
1307 if (total_size)
1308 *total_size += (gint64)(st->st_size);
1309 if (options && options->changeset) {
1310 /* ce may be updated. */
1311 ce = index_name_exists (istate, path, strlen(path), 0);
1312 add_to_changeset (options->changeset,
1313 DIFF_STATUS_ADDED,
1314 ce->sha1,
1315 st,
1316 modifier,
1317 path,
1318 NULL);
1319 }
1320 }
1321 } else if (*remain_files == NULL) {
1322 ret = add_to_index (repo_id, version, istate, path, full_path,
1323 st, 0, crypt, index_cb, modifier, &added);
1324 if (added) {
1325 *total_size += (gint64)(st->st_size);
1326 if (*total_size >= MAX_COMMIT_SIZE)
1327 *remain_files = g_queue_new ();
1328 } else {
1329 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1330 repo_id,
1331 path,
1332 S_IFREG,
1333 SYNC_STATUS_SYNCED,
1334 FALSE);
1335 }
1336 if (added && options && options->changeset) {
1337 /* ce may be updated. */
1338 ce = index_name_exists (istate, path, strlen(path), 0);
1339 add_to_changeset (options->changeset,
1340 DIFF_STATUS_ADDED,
1341 ce->sha1,
1342 st,
1343 modifier,
1344 path,
1345 NULL);
1346 }
1347 } else {
1348 *total_size += (gint64)(st->st_size);
1349 g_queue_push_tail (*remain_files, g_strdup(path));
1350 }
1351
1352 if (ret < 0) {
1353 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1354 repo_id,
1355 path,
1356 S_IFREG,
1357 SYNC_STATUS_ERROR,
1358 TRUE);
1359 send_file_sync_error_notification (repo_id, NULL, path,
1360 SYNC_ERROR_ID_INDEX_ERROR);
1361 }
1362
1363 return ret;
1364 }
1365
1366 typedef struct AddParams {
1367 const char *repo_id;
1368 int version;
1369 const char *modifier;
1370 struct index_state *istate;
1371 const char *worktree;
1372 SeafileCrypt *crypt;
1373 gboolean ignore_empty_dir;
1374 GList *ignore_list;
1375 gint64 *total_size;
1376 GQueue **remain_files;
1377 AddOptions *options;
1378 } AddParams;
1379
1380 #ifndef WIN32
1381
1382 static int
add_dir_recursive(const char * path,const char * full_path,SeafStat * st,AddParams * params,gboolean ignored)1383 add_dir_recursive (const char *path, const char *full_path, SeafStat *st,
1384 AddParams *params, gboolean ignored)
1385 {
1386 AddOptions *options = params->options;
1387 GDir *dir;
1388 const char *dname;
1389 char *subpath, *full_subpath;
1390 int n, total;
1391 gboolean is_writable = TRUE;
1392 struct stat sub_st;
1393
1394 dir = g_dir_open (full_path, 0, NULL);
1395 if (!dir) {
1396 seaf_warning ("Failed to open dir %s: %s.\n", full_path, strerror(errno));
1397
1398 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1399 params->repo_id,
1400 path,
1401 S_IFDIR,
1402 SYNC_STATUS_ERROR,
1403 TRUE);
1404
1405 return 0;
1406 }
1407
1408 n = 0;
1409 total = 0;
1410 while ((dname = g_dir_read_name(dir)) != NULL) {
1411 ++total;
1412
1413 #ifdef __APPLE__
1414 char *norm_dname = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);
1415 subpath = g_build_path (PATH_SEPERATOR, path, norm_dname, NULL);
1416 g_free (norm_dname);
1417 #else
1418 subpath = g_build_path (PATH_SEPERATOR, path, dname, NULL);
1419 #endif
1420 full_subpath = g_build_filename (params->worktree, subpath, NULL);
1421
1422 if (stat (full_subpath, &sub_st) < 0) {
1423 seaf_warning ("Failed to stat %s: %s.\n", full_subpath, strerror(errno));
1424 g_free (subpath);
1425 g_free (full_subpath);
1426 continue;
1427 }
1428
1429 if (ignored || should_ignore(full_path, dname, params->ignore_list)) {
1430 if (options && options->startup_scan) {
1431 if (S_ISDIR(sub_st.st_mode))
1432 add_dir_recursive (subpath, full_subpath, &sub_st, params, TRUE);
1433 else
1434 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1435 params->repo_id,
1436 subpath,
1437 S_IFREG,
1438 SYNC_STATUS_IGNORED,
1439 TRUE);
1440 }
1441 g_free (subpath);
1442 g_free (full_subpath);
1443 continue;
1444 }
1445
1446 ++n;
1447
1448 if (S_ISDIR(sub_st.st_mode))
1449 add_dir_recursive (subpath, full_subpath, &sub_st, params, FALSE);
1450 else if (S_ISREG(sub_st.st_mode))
1451 add_file (params->repo_id,
1452 params->version,
1453 params->modifier,
1454 params->istate,
1455 subpath,
1456 full_subpath,
1457 &sub_st,
1458 params->crypt,
1459 params->total_size,
1460 params->remain_files,
1461 params->options);
1462
1463 g_free (subpath);
1464 g_free (full_subpath);
1465 }
1466 g_dir_close (dir);
1467
1468 if (ignored) {
1469 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1470 params->repo_id,
1471 path,
1472 S_IFDIR,
1473 SYNC_STATUS_IGNORED,
1474 TRUE);
1475 return 0;
1476 }
1477
1478 if (options)
1479 is_writable = is_path_writable(params->repo_id,
1480 options->is_repo_ro, path);
1481
1482 /* Update active path status for empty dir */
1483 if (options && options->startup_scan && total == 0) {
1484 SyncStatus status;
1485 struct cache_entry *ce = index_name_exists (params->istate, path,
1486 strlen(path), 0);
1487 if (!ce)
1488 status = SYNC_STATUS_SYNCING;
1489 else
1490 status = SYNC_STATUS_SYNCED;
1491
1492
1493 if (status == SYNC_STATUS_SYNCED || is_writable)
1494 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1495 params->repo_id,
1496 path,
1497 S_IFDIR,
1498 status,
1499 FALSE);
1500 }
1501
1502 if (n == 0 && path[0] != 0 && is_writable) {
1503 if (!params->remain_files || *(params->remain_files) == NULL) {
1504 int rc = add_empty_dir_to_index (params->istate, path, st);
1505 if (rc == 1 && options && options->changeset) {
1506 unsigned char allzero[20] = {0};
1507 add_to_changeset (options->changeset,
1508 DIFF_STATUS_DIR_ADDED,
1509 allzero,
1510 st,
1511 NULL,
1512 path,
1513 NULL);
1514 }
1515 } else
1516 g_queue_push_tail (*(params->remain_files), g_strdup(path));
1517 }
1518
1519 return 0;
1520 }
1521
1522 /*
1523 * @remain_files: returns the files haven't been added under this path.
1524 * If it's set to NULL, no partial commit will be created.
1525 */
1526 static int
add_recursive(const char * repo_id,int version,const char * modifier,struct index_state * istate,const char * worktree,const char * path,SeafileCrypt * crypt,gboolean ignore_empty_dir,GList * ignore_list,gint64 * total_size,GQueue ** remain_files,AddOptions * options)1527 add_recursive (const char *repo_id,
1528 int version,
1529 const char *modifier,
1530 struct index_state *istate,
1531 const char *worktree,
1532 const char *path,
1533 SeafileCrypt *crypt,
1534 gboolean ignore_empty_dir,
1535 GList *ignore_list,
1536 gint64 *total_size,
1537 GQueue **remain_files,
1538 AddOptions *options)
1539 {
1540 char *full_path;
1541 SeafStat st;
1542
1543 full_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);
1544 if (seaf_stat (full_path, &st) < 0) {
1545 /* Ignore broken symlinks on Linux and Mac OS X */
1546 if (lstat (full_path, &st) == 0 && S_ISLNK(st.st_mode)) {
1547 g_free (full_path);
1548 return 0;
1549 }
1550 seaf_warning ("Failed to stat %s.\n", full_path);
1551 g_free (full_path);
1552 /* Ignore error. */
1553
1554 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1555 repo_id,
1556 path,
1557 0,
1558 SYNC_STATUS_ERROR,
1559 TRUE);
1560
1561 return 0;
1562 }
1563
1564 if (S_ISREG(st.st_mode)) {
1565 add_file (repo_id,
1566 version,
1567 modifier,
1568 istate,
1569 path,
1570 full_path,
1571 &st,
1572 crypt,
1573 total_size,
1574 remain_files,
1575 options);
1576 } else if (S_ISDIR(st.st_mode)) {
1577 AddParams params = {
1578 .repo_id = repo_id,
1579 .version = version,
1580 .modifier = modifier,
1581 .istate = istate,
1582 .worktree = worktree,
1583 .crypt = crypt,
1584 .ignore_empty_dir = ignore_empty_dir,
1585 .ignore_list = ignore_list,
1586 .total_size = total_size,
1587 .remain_files = remain_files,
1588 .options = options,
1589 };
1590
1591 add_dir_recursive (path, full_path, &st, ¶ms, FALSE);
1592 }
1593
1594 g_free (full_path);
1595 return 0;
1596 }
1597
1598 static gboolean
is_empty_dir(const char * path,GList * ignore_list)1599 is_empty_dir (const char *path, GList *ignore_list)
1600 {
1601 GDir *dir;
1602 const char *dname;
1603 gboolean ret = TRUE;
1604
1605 dir = g_dir_open (path, 0, NULL);
1606 if (!dir) {
1607 return FALSE;
1608 }
1609
1610 while ((dname = g_dir_read_name(dir)) != NULL) {
1611 if (!should_ignore(path, dname, ignore_list)) {
1612 ret = FALSE;
1613 break;
1614 }
1615 }
1616 g_dir_close (dir);
1617
1618 return ret;
1619 }
1620
1621 #else
1622
1623 typedef struct IterCBData {
1624 AddParams *add_params;
1625 const char *parent;
1626 const char *full_parent;
1627 int n;
1628
1629 /* If parent dir is ignored, all children are ignored too. */
1630 gboolean ignored;
1631 } IterCBData;
1632
1633 static int
1634 add_dir_recursive (const char *path, const char *full_path, SeafStat *st,
1635 AddParams *params, gboolean ignored);
1636
1637 static int
iter_dir_cb(wchar_t * full_parent_w,WIN32_FIND_DATAW * fdata,void * user_data,gboolean * stop)1638 iter_dir_cb (wchar_t *full_parent_w,
1639 WIN32_FIND_DATAW *fdata,
1640 void *user_data,
1641 gboolean *stop)
1642 {
1643 IterCBData *data = user_data;
1644 AddParams *params = data->add_params;
1645 AddOptions *options = params->options;
1646 char *dname = NULL, *path = NULL, *full_path = NULL;
1647 SeafStat st;
1648 int ret = 0;
1649
1650 dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
1651 if (!dname) {
1652 goto out;
1653 }
1654
1655 path = g_build_path ("/", data->parent, dname, NULL);
1656 full_path = g_build_path ("/", params->worktree, path, NULL);
1657
1658 seaf_stat_from_find_data (fdata, &st);
1659
1660 if (data->ignored ||
1661 should_ignore(data->full_parent, dname, params->ignore_list)) {
1662 if (options && options->startup_scan) {
1663 if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1664 add_dir_recursive (path, full_path, &st, params, TRUE);
1665 else
1666 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1667 params->repo_id,
1668 path,
1669 S_IFREG,
1670 SYNC_STATUS_IGNORED,
1671 TRUE);
1672 }
1673 goto out;
1674 }
1675
1676 if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1677 ret = add_dir_recursive (path, full_path, &st, params, FALSE);
1678 else
1679 ret = add_file (params->repo_id,
1680 params->version,
1681 params->modifier,
1682 params->istate,
1683 path,
1684 full_path,
1685 &st,
1686 params->crypt,
1687 params->total_size,
1688 params->remain_files,
1689 params->options);
1690
1691 ++(data->n);
1692
1693 out:
1694 g_free (dname);
1695 g_free (path);
1696 g_free (full_path);
1697
1698 return 0;
1699 }
1700
1701 static int
add_dir_recursive(const char * path,const char * full_path,SeafStat * st,AddParams * params,gboolean ignored)1702 add_dir_recursive (const char *path, const char *full_path, SeafStat *st,
1703 AddParams *params, gboolean ignored)
1704 {
1705 AddOptions *options = params->options;
1706 IterCBData data;
1707 wchar_t *full_path_w;
1708 int ret = 0;
1709 gboolean is_writable = TRUE;
1710
1711 memset (&data, 0, sizeof(data));
1712 data.add_params = params;
1713 data.parent = path;
1714 data.full_parent = full_path;
1715 data.ignored = ignored;
1716
1717 full_path_w = win32_long_path (full_path);
1718 ret = traverse_directory_win32 (full_path_w, iter_dir_cb, &data);
1719 g_free (full_path_w);
1720
1721 /* Ignore traverse dir error. */
1722 if (ret < 0) {
1723 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1724 params->repo_id,
1725 path,
1726 S_IFDIR,
1727 SYNC_STATUS_ERROR,
1728 TRUE);
1729 return 0;
1730 }
1731
1732 if (ignored) {
1733 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1734 params->repo_id,
1735 path,
1736 S_IFDIR,
1737 SYNC_STATUS_IGNORED,
1738 TRUE);
1739 return 0;
1740 }
1741
1742 if (options)
1743 is_writable = is_path_writable(params->repo_id,
1744 options->is_repo_ro, path);
1745
1746 /* Update active path status for empty dir */
1747 if (options && options->startup_scan && ret == 0) {
1748 SyncStatus status;
1749 struct cache_entry *ce = index_name_exists (params->istate, path,
1750 strlen(path), 0);
1751 if (!ce)
1752 status = SYNC_STATUS_SYNCING;
1753 else
1754 status = SYNC_STATUS_SYNCED;
1755
1756
1757 if (status == SYNC_STATUS_SYNCED || is_writable)
1758 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1759 params->repo_id,
1760 path,
1761 S_IFDIR,
1762 status,
1763 FALSE);
1764 }
1765
1766 if (data.n == 0 && path[0] != 0 && !params->ignore_empty_dir && is_writable) {
1767 if (!params->remain_files || *(params->remain_files) == NULL) {
1768 int rc = add_empty_dir_to_index (params->istate, path, st);
1769 if (rc == 1 && options && options->changeset) {
1770 unsigned char allzero[20] = {0};
1771 add_to_changeset (options->changeset,
1772 DIFF_STATUS_DIR_ADDED,
1773 allzero,
1774 st,
1775 NULL,
1776 path,
1777 NULL);
1778 }
1779 } else
1780 g_queue_push_tail (*(params->remain_files), g_strdup(path));
1781 }
1782
1783 return ret;
1784 }
1785
1786 static int
add_recursive(const char * repo_id,int version,const char * modifier,struct index_state * istate,const char * worktree,const char * path,SeafileCrypt * crypt,gboolean ignore_empty_dir,GList * ignore_list,gint64 * total_size,GQueue ** remain_files,AddOptions * options)1787 add_recursive (const char *repo_id,
1788 int version,
1789 const char *modifier,
1790 struct index_state *istate,
1791 const char *worktree,
1792 const char *path,
1793 SeafileCrypt *crypt,
1794 gboolean ignore_empty_dir,
1795 GList *ignore_list,
1796 gint64 *total_size,
1797 GQueue **remain_files,
1798 AddOptions *options)
1799 {
1800 char *full_path;
1801 SeafStat st;
1802 int ret = 0;
1803
1804 full_path = g_build_path (PATH_SEPERATOR, worktree, path, NULL);
1805 if (seaf_stat (full_path, &st) < 0) {
1806 seaf_warning ("Failed to stat %s.\n", full_path);
1807 g_free (full_path);
1808 seaf_sync_manager_update_active_path (seaf->sync_mgr,
1809 repo_id,
1810 path,
1811 0,
1812 SYNC_STATUS_ERROR,
1813 TRUE);
1814 /* Ignore error */
1815 return 0;
1816 }
1817
1818 if (S_ISREG(st.st_mode)) {
1819 ret = add_file (repo_id,
1820 version,
1821 modifier,
1822 istate,
1823 path,
1824 full_path,
1825 &st,
1826 crypt,
1827 total_size,
1828 remain_files,
1829 options);
1830 } else if (S_ISDIR(st.st_mode)) {
1831 AddParams params = {
1832 .repo_id = repo_id,
1833 .version = version,
1834 .modifier = modifier,
1835 .istate = istate,
1836 .worktree = worktree,
1837 .crypt = crypt,
1838 .ignore_empty_dir = ignore_empty_dir,
1839 .ignore_list = ignore_list,
1840 .total_size = total_size,
1841 .remain_files = remain_files,
1842 .options = options,
1843 };
1844
1845 ret = add_dir_recursive (path, full_path, &st, ¶ms, FALSE);
1846 }
1847
1848 g_free (full_path);
1849 return ret;
1850 }
1851
1852 static gboolean
is_empty_dir(const char * path,GList * ignore_list)1853 is_empty_dir (const char *path, GList *ignore_list)
1854 {
1855 WIN32_FIND_DATAW fdata;
1856 HANDLE handle;
1857 wchar_t *pattern;
1858 wchar_t *path_w;
1859 char *dname;
1860 int path_len_w;
1861 DWORD error;
1862 gboolean ret = TRUE;
1863
1864 path_w = win32_long_path (path);
1865
1866 path_len_w = wcslen(path_w);
1867
1868 pattern = g_new0 (wchar_t, (path_len_w + 3));
1869 wcscpy (pattern, path_w);
1870 wcscat (pattern, L"\\*");
1871
1872 handle = FindFirstFileW (pattern, &fdata);
1873 if (handle == INVALID_HANDLE_VALUE) {
1874 seaf_warning ("FindFirstFile failed %s: %lu.\n",
1875 path, GetLastError());
1876 ret = FALSE;
1877 goto out;
1878 }
1879
1880 do {
1881 if (wcscmp (fdata.cFileName, L".") == 0 ||
1882 wcscmp (fdata.cFileName, L"..") == 0)
1883 continue;
1884
1885 dname = g_utf16_to_utf8 (fdata.cFileName, -1, NULL, NULL, NULL);
1886 if (!dname || !should_ignore (path, dname, ignore_list)) {
1887 ret = FALSE;
1888 g_free (dname);
1889 FindClose (handle);
1890 goto out;
1891 }
1892 g_free (dname);
1893 } while (FindNextFileW (handle, &fdata) != 0);
1894
1895 error = GetLastError();
1896 if (error != ERROR_NO_MORE_FILES) {
1897 seaf_warning ("FindNextFile failed %s: %lu.\n",
1898 path, error);
1899 }
1900
1901 FindClose (handle);
1902
1903 out:
1904 g_free (path_w);
1905 g_free (pattern);
1906 return ret;
1907 }
1908
1909 #endif /* WIN32 */
1910
1911 /* Returns whether the file should be removed from index. */
1912 static gboolean
check_locked_file_before_remove(LockedFileSet * fset,const char * path)1913 check_locked_file_before_remove (LockedFileSet *fset, const char *path)
1914 {
1915 #if defined WIN32 || defined __APPLE__
1916 if (!fset)
1917 return TRUE;
1918
1919 LockedFile *file = locked_file_set_lookup (fset, path);
1920 gboolean ret = TRUE;
1921
1922 if (file)
1923 ret = FALSE;
1924
1925 return ret;
1926 #else
1927 return TRUE;
1928 #endif
1929 }
1930
1931 static void
remove_deleted(struct index_state * istate,const char * worktree,const char * prefix,GList * ignore_list,LockedFileSet * fset,const char * repo_id,gboolean is_repo_ro,ChangeSet * changeset)1932 remove_deleted (struct index_state *istate, const char *worktree, const char *prefix,
1933 GList *ignore_list, LockedFileSet *fset,
1934 const char *repo_id, gboolean is_repo_ro,
1935 ChangeSet *changeset)
1936 {
1937 struct cache_entry **ce_array = istate->cache;
1938 struct cache_entry *ce;
1939 char path[SEAF_PATH_MAX];
1940 unsigned int i;
1941 SeafStat st;
1942 int ret;
1943 gboolean not_exist;
1944
1945 char *full_prefix = g_strconcat (prefix, "/", NULL);
1946 int len = strlen(full_prefix);
1947
1948 for (i = 0; i < istate->cache_nr; ++i) {
1949 ce = ce_array[i];
1950
1951 if (!is_path_writable (repo_id, is_repo_ro, ce->name))
1952 continue;
1953
1954 if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
1955 repo_id, ce->name)) {
1956 seaf_debug ("Remove deleted: %s is locked on server, ignore.\n", ce->name);
1957 continue;
1958 }
1959
1960 if (prefix[0] != 0 && strcmp (ce->name, prefix) != 0 &&
1961 strncmp (ce->name, full_prefix, len) != 0)
1962 continue;
1963
1964 snprintf (path, SEAF_PATH_MAX, "%s/%s", worktree, ce->name);
1965 not_exist = FALSE;
1966 ret = seaf_stat (path, &st);
1967 if (ret < 0 && errno == ENOENT)
1968 not_exist = TRUE;
1969
1970 if (S_ISDIR (ce->ce_mode)) {
1971 if (ce->ce_ctime.sec != 0 || ce_stage(ce) != 0) {
1972 if (not_exist || (ret == 0 && !S_ISDIR (st.st_mode))) {
1973 /* Add to changeset only if dir is removed. */
1974 ce->ce_flags |= CE_REMOVE;
1975 if (changeset)
1976 /* Remove the parent dir from change set if it becomes
1977 * empty. If in the work tree the empty dir still exist,
1978 * we'll add it back to changeset in add_recursive() later.
1979 */
1980 remove_from_changeset (changeset,
1981 DIFF_STATUS_DIR_DELETED,
1982 ce->name,
1983 TRUE,
1984 prefix);
1985 } else if (!is_empty_dir (path, ignore_list)) {
1986 /* Don't add to changeset if empty dir became non-empty. */
1987 ce->ce_flags |= CE_REMOVE;
1988 }
1989 }
1990 } else {
1991 /* If ce->ctime is 0 and stage is 0, it was not successfully checked out.
1992 * In this case we don't want to mistakenly remove the file
1993 * from the repo.
1994 */
1995 if ((not_exist || (ret == 0 && !S_ISREG (st.st_mode))) &&
1996 (ce->ce_ctime.sec != 0 || ce_stage(ce) != 0) &&
1997 check_locked_file_before_remove (fset, ce->name))
1998 {
1999 ce_array[i]->ce_flags |= CE_REMOVE;
2000 if (changeset)
2001 remove_from_changeset (changeset,
2002 DIFF_STATUS_DELETED,
2003 ce->name,
2004 TRUE,
2005 prefix);
2006 }
2007 }
2008 }
2009
2010 remove_marked_cache_entries (istate);
2011
2012 g_free (full_prefix);
2013 }
2014
2015 static int
scan_worktree_for_changes(struct index_state * istate,SeafRepo * repo,SeafileCrypt * crypt,GList * ignore_list,LockedFileSet * fset)2016 scan_worktree_for_changes (struct index_state *istate, SeafRepo *repo,
2017 SeafileCrypt *crypt, GList *ignore_list,
2018 LockedFileSet *fset)
2019 {
2020 remove_deleted (istate, repo->worktree, "", ignore_list, fset,
2021 repo->id, repo->is_readonly, repo->changeset);
2022
2023 AddOptions options;
2024 memset (&options, 0, sizeof(options));
2025 options.fset = fset;
2026 options.is_repo_ro = repo->is_readonly;
2027 options.changeset = repo->changeset;
2028
2029 if (add_recursive (repo->id, repo->version, repo->email,
2030 istate, repo->worktree, "", crypt, FALSE, ignore_list,
2031 NULL, NULL, &options) < 0)
2032 return -1;
2033
2034 return 0;
2035 }
2036
2037 static gboolean
check_full_path_ignore(const char * worktree,const char * path,GList * ignore_list)2038 check_full_path_ignore (const char *worktree, const char *path, GList *ignore_list)
2039 {
2040 char **tokens;
2041 guint i;
2042 guint n;
2043 gboolean ret = FALSE;
2044
2045 tokens = g_strsplit (path, "/", 0);
2046 n = g_strv_length (tokens);
2047 for (i = 0; i < n; ++i) {
2048 /* don't check ignore_list */
2049 if (should_ignore (NULL, tokens[i], ignore_list)) {
2050 ret = TRUE;
2051 goto out;
2052 }
2053 }
2054
2055 char *full_path = g_build_path ("/", worktree, path, NULL);
2056 if (seaf_repo_check_ignore_file (ignore_list, full_path))
2057 ret = TRUE;
2058 g_free (full_path);
2059
2060 out:
2061 g_strfreev (tokens);
2062 return ret;
2063 }
2064
2065 static int
add_path_to_index(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,const char * path,GList * ignore_list,GList ** scanned_dirs,gint64 * total_size,GQueue ** remain_files,LockedFileSet * fset)2066 add_path_to_index (SeafRepo *repo, struct index_state *istate,
2067 SeafileCrypt *crypt, const char *path, GList *ignore_list,
2068 GList **scanned_dirs, gint64 *total_size, GQueue **remain_files,
2069 LockedFileSet *fset)
2070 {
2071 char *full_path;
2072 SeafStat st;
2073 AddOptions options;
2074
2075 /* When a repo is initially added, a SCAN_DIR event will be created
2076 * for the worktree root "".
2077 */
2078 if (path[0] == 0) {
2079 remove_deleted (istate, repo->worktree, "", ignore_list, fset,
2080 repo->id, repo->is_readonly, repo->changeset);
2081
2082 memset (&options, 0, sizeof(options));
2083 options.fset = fset;
2084 options.is_repo_ro = repo->is_readonly;
2085 options.startup_scan = TRUE;
2086 options.changeset = repo->changeset;
2087
2088 add_recursive (repo->id, repo->version, repo->email, istate,
2089 repo->worktree, path,
2090 crypt, FALSE, ignore_list,
2091 total_size, remain_files, &options);
2092
2093 return 0;
2094 }
2095
2096 /* If we've recursively scanned the parent directory, don't need to scan
2097 * any files under it any more.
2098 */
2099 GList *ptr;
2100 char *dir, *full_dir;
2101 for (ptr = *scanned_dirs; ptr; ptr = ptr->next) {
2102 dir = ptr->data;
2103 /* exact match */
2104 if (strcmp (dir, path) == 0) {
2105 seaf_debug ("%s has been scanned before, skip adding.\n", path);
2106 return 0;
2107 }
2108
2109 /* prefix match. */
2110 full_dir = g_strconcat (dir, "/", NULL);
2111 if (strncmp (full_dir, path, strlen(full_dir)) == 0) {
2112 g_free (full_dir);
2113 seaf_debug ("%s has been scanned before, skip adding.\n", path);
2114 return 0;
2115 }
2116 g_free (full_dir);
2117 }
2118
2119 if (check_full_path_ignore (repo->worktree, path, ignore_list))
2120 return 0;
2121
2122 full_path = g_build_filename (repo->worktree, path, NULL);
2123
2124 if (seaf_stat (full_path, &st) < 0) {
2125 if (errno != ENOENT)
2126 send_file_sync_error_notification (repo->id, repo->name, path,
2127 SYNC_ERROR_ID_INDEX_ERROR);
2128 seaf_warning ("Failed to stat %s: %s.\n", path, strerror(errno));
2129 g_free (full_path);
2130 return -1;
2131 }
2132
2133 if (S_ISDIR(st.st_mode))
2134 *scanned_dirs = g_list_prepend (*scanned_dirs, g_strdup(path));
2135
2136 memset (&options, 0, sizeof(options));
2137 options.fset = fset;
2138 options.is_repo_ro = repo->is_readonly;
2139 options.changeset = repo->changeset;
2140
2141 /* Add is always recursive */
2142 add_recursive (repo->id, repo->version, repo->email, istate, repo->worktree, path,
2143 crypt, FALSE, ignore_list, total_size, remain_files, &options);
2144
2145 g_free (full_path);
2146 return 0;
2147 }
2148
2149 #if 0
2150
2151 static int
2152 add_path_to_index (SeafRepo *repo, struct index_state *istate,
2153 SeafileCrypt *crypt, const char *path, GList *ignore_list,
2154 GList **scanned_dirs, gint64 *total_size, GQueue **remain_files,
2155 LockedFileSet *fset)
2156 {
2157 /* If we've recursively scanned the parent directory, don't need to scan
2158 * any files under it any more.
2159 */
2160 GList *ptr;
2161 char *dir, *full_dir;
2162 for (ptr = *scanned_dirs; ptr; ptr = ptr->next) {
2163 dir = ptr->data;
2164
2165 /* Have scanned from root directory. */
2166 if (dir[0] == 0) {
2167 seaf_debug ("%s has been scanned before, skip adding.\n", path);
2168 return 0;
2169 }
2170
2171 /* exact match */
2172 if (strcmp (dir, path) == 0) {
2173 seaf_debug ("%s has been scanned before, skip adding.\n", path);
2174 return 0;
2175 }
2176
2177 /* prefix match. */
2178 full_dir = g_strconcat (dir, "/", NULL);
2179 if (strncmp (full_dir, path, strlen(full_dir)) == 0) {
2180 g_free (full_dir);
2181 seaf_debug ("%s has been scanned before, skip adding.\n", path);
2182 return 0;
2183 }
2184 g_free (full_dir);
2185 }
2186
2187 if (path[0] != 0 && check_full_path_ignore (repo->worktree, path, ignore_list))
2188 return 0;
2189
2190 remove_deleted (istate, repo->worktree, path, ignore_list, NULL,
2191 repo->id, repo->is_readonly, repo->changeset);
2192
2193 *scanned_dirs = g_list_prepend (*scanned_dirs, g_strdup(path));
2194
2195 AddOptions options;
2196 memset (&options, 0, sizeof(options));
2197 options.fset = fset;
2198 options.is_repo_ro = repo->is_readonly;
2199 options.changeset = repo->changeset;
2200 /* When something is changed in the root directory, update active path
2201 * sync status when scanning the worktree. This is inaccurate. This will
2202 * be changed after we process fs events on Mac more precisely.
2203 */
2204 if (path[0] == 0)
2205 options.startup_scan = TRUE;
2206
2207 /* Add is always recursive */
2208 add_recursive (repo->id, repo->version, repo->email, istate, repo->worktree, path,
2209 crypt, FALSE, ignore_list, total_size, remain_files, &options);
2210
2211 return 0;
2212 }
2213
2214 #endif /* __APPLE__ */
2215
2216 static int
add_remain_files(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,GQueue * remain_files,GList * ignore_list,gint64 * total_size)2217 add_remain_files (SeafRepo *repo, struct index_state *istate,
2218 SeafileCrypt *crypt, GQueue *remain_files,
2219 GList *ignore_list, gint64 *total_size)
2220 {
2221 char *path;
2222 char *full_path;
2223 SeafStat st;
2224 struct cache_entry *ce;
2225
2226 while ((path = g_queue_pop_head (remain_files)) != NULL) {
2227 full_path = g_build_filename (repo->worktree, path, NULL);
2228 if (seaf_stat (full_path, &st) < 0) {
2229 seaf_warning ("Failed to stat %s: %s.\n", full_path, strerror(errno));
2230 g_free (path);
2231 g_free (full_path);
2232 continue;
2233 }
2234
2235 if (S_ISREG(st.st_mode)) {
2236 gboolean added = FALSE;
2237 int ret = 0;
2238 ret = add_to_index (repo->id, repo->version, istate, path, full_path,
2239 &st, 0, crypt, index_cb, repo->email, &added);
2240 if (added) {
2241 ce = index_name_exists (istate, path, strlen(path), 0);
2242 add_to_changeset (repo->changeset,
2243 DIFF_STATUS_ADDED,
2244 ce->sha1,
2245 &st,
2246 repo->email,
2247 path,
2248 NULL);
2249
2250 *total_size += (gint64)(st.st_size);
2251 if (*total_size >= MAX_COMMIT_SIZE) {
2252 g_free (path);
2253 g_free (full_path);
2254 break;
2255 }
2256 } else {
2257 seaf_sync_manager_update_active_path (seaf->sync_mgr,
2258 repo->id,
2259 path,
2260 S_IFREG,
2261 SYNC_STATUS_SYNCED,
2262 TRUE);
2263 }
2264 if (ret < 0) {
2265 seaf_sync_manager_update_active_path (seaf->sync_mgr,
2266 repo->id,
2267 path,
2268 S_IFREG,
2269 SYNC_STATUS_ERROR,
2270 TRUE);
2271 send_file_sync_error_notification (repo->id, NULL, path,
2272 SYNC_ERROR_ID_INDEX_ERROR);
2273 }
2274 } else if (S_ISDIR(st.st_mode)) {
2275 if (is_empty_dir (full_path, ignore_list)) {
2276 int rc = add_empty_dir_to_index (istate, path, &st);
2277 if (rc == 1) {
2278 unsigned char allzero[20] = {0};
2279 add_to_changeset (repo->changeset,
2280 DIFF_STATUS_DIR_ADDED,
2281 allzero,
2282 &st,
2283 NULL,
2284 path,
2285 NULL);
2286 }
2287 }
2288 }
2289 g_free (path);
2290 g_free (full_path);
2291 }
2292
2293 return 0;
2294 }
2295
2296 static void
try_add_empty_parent_dir_entry(const char * worktree,struct index_state * istate,const char * path)2297 try_add_empty_parent_dir_entry (const char *worktree,
2298 struct index_state *istate,
2299 const char *path)
2300 {
2301 if (index_name_exists (istate, path, strlen(path), 0) != NULL)
2302 return;
2303
2304 char *parent_dir = g_path_get_dirname (path);
2305
2306 /* Parent dir is the worktree dir. */
2307 if (strcmp (parent_dir, ".") == 0) {
2308 g_free (parent_dir);
2309 return;
2310 }
2311
2312 char *full_dir = g_build_filename (worktree, parent_dir, NULL);
2313 SeafStat st;
2314 if (seaf_stat (full_dir, &st) < 0) {
2315 goto out;
2316 }
2317
2318 add_empty_dir_to_index_with_check (istate, parent_dir, &st);
2319
2320 out:
2321 g_free (parent_dir);
2322 g_free (full_dir);
2323 }
2324
2325 static void
try_add_empty_parent_dir_entry_from_wt(const char * worktree,struct index_state * istate,GList * ignore_list,const char * path)2326 try_add_empty_parent_dir_entry_from_wt (const char *worktree,
2327 struct index_state *istate,
2328 GList *ignore_list,
2329 const char *path)
2330 {
2331 if (index_name_exists (istate, path, strlen(path), 0) != NULL)
2332 return;
2333
2334 char *parent_dir = g_path_get_dirname (path);
2335
2336 /* Parent dir is the worktree dir. */
2337 if (strcmp (parent_dir, ".") == 0) {
2338 g_free (parent_dir);
2339 return;
2340 }
2341
2342 char *full_dir = g_build_filename (worktree, parent_dir, NULL);
2343 SeafStat st;
2344 if (seaf_stat (full_dir, &st) < 0) {
2345 goto out;
2346 }
2347
2348 if (is_empty_dir (full_dir, ignore_list)) {
2349 #ifdef WIN32
2350 wchar_t *parent_dir_w = g_utf8_to_utf16 (parent_dir, -1, NULL, NULL, NULL);
2351 wchar_t *pw;
2352 for (pw = parent_dir_w; *pw != L'\0'; ++pw)
2353 if (*pw == L'/')
2354 *pw = L'\\';
2355
2356 wchar_t *long_path = win32_83_path_to_long_path (worktree,
2357 parent_dir_w,
2358 wcslen(parent_dir_w));
2359 g_free (parent_dir_w);
2360 if (!long_path) {
2361 seaf_warning ("Convert %s to long path failed.\n", parent_dir);
2362 goto out;
2363 }
2364
2365 char *utf8_path = g_utf16_to_utf8 (long_path, -1, NULL, NULL, NULL);
2366 if (!utf8_path) {
2367 g_free (long_path);
2368 goto out;
2369 }
2370
2371 char *p;
2372 for (p = utf8_path; *p != 0; ++p)
2373 if (*p == '\\')
2374 *p = '/';
2375 g_free (long_path);
2376
2377 add_empty_dir_to_index (istate, utf8_path, &st);
2378 #else
2379 add_empty_dir_to_index (istate, parent_dir, &st);
2380 #endif
2381 }
2382
2383 out:
2384 g_free (parent_dir);
2385 g_free (full_dir);
2386 }
2387
2388 static void
update_attributes(SeafRepo * repo,struct index_state * istate,const char * worktree,const char * path)2389 update_attributes (SeafRepo *repo,
2390 struct index_state *istate,
2391 const char *worktree,
2392 const char *path)
2393 {
2394 ChangeSet *changeset = repo->changeset;
2395 char *full_path;
2396 struct cache_entry *ce;
2397 SeafStat st;
2398
2399 ce = index_name_exists (istate, path, strlen(path), 0);
2400 if (!ce)
2401 return;
2402
2403 full_path = g_build_filename (worktree, path, NULL);
2404 if (seaf_stat (full_path, &st) < 0) {
2405 seaf_warning ("Failed to stat %s: %s.\n", full_path, strerror(errno));
2406 g_free (full_path);
2407 return;
2408 }
2409
2410 unsigned int new_mode = create_ce_mode (st.st_mode);
2411 if (new_mode != ce->ce_mode || st.st_mtime != ce->ce_mtime.sec) {
2412 ce->ce_mode = new_mode;
2413 ce->ce_mtime.sec = st.st_mtime;
2414 istate->cache_changed = 1;
2415 add_to_changeset (changeset,
2416 DIFF_STATUS_MODIFIED,
2417 ce->sha1,
2418 &st,
2419 repo->email,
2420 path,
2421 NULL);
2422 }
2423 g_free (full_path);
2424 }
2425
2426 #ifdef WIN32
2427 static void
scan_subtree_for_deletion(const char * repo_id,struct index_state * istate,const char * worktree,const char * path,GList * ignore_list,LockedFileSet * fset,gboolean is_readonly,GList ** scanned_dirs,ChangeSet * changeset)2428 scan_subtree_for_deletion (const char *repo_id,
2429 struct index_state *istate,
2430 const char *worktree,
2431 const char *path,
2432 GList *ignore_list,
2433 LockedFileSet *fset,
2434 gboolean is_readonly,
2435 GList **scanned_dirs,
2436 ChangeSet *changeset)
2437 {
2438 wchar_t *path_w = NULL;
2439 wchar_t *dir_w = NULL;
2440 wchar_t *p;
2441 char *dir = NULL;
2442 char *p2;
2443
2444 /* In most file systems, like NTFS, 8.3 format path should contain ~.
2445 * Also note that *~ files are ignored.
2446 */
2447 if (!strchr (path, '~') || path[strlen(path)-1] == '~')
2448 return;
2449
2450 path_w = g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);
2451
2452 for (p = path_w; *p != L'\0'; ++p)
2453 if (*p == L'/')
2454 *p = L'\\';
2455
2456 while (1) {
2457 p = wcsrchr (path_w, L'\\');
2458 if (p)
2459 *p = L'\0';
2460 else
2461 break;
2462
2463 dir_w = win32_83_path_to_long_path (worktree, path_w, wcslen(path_w));
2464 if (dir_w)
2465 break;
2466 }
2467
2468 if (!dir_w)
2469 dir_w = wcsdup(L"");
2470
2471 dir = g_utf16_to_utf8 (dir_w, -1, NULL, NULL, NULL);
2472 if (!dir)
2473 goto out;
2474
2475 for (p2 = dir; *p2 != 0; ++p2)
2476 if (*p2 == '\\')
2477 *p2 = '/';
2478
2479 /* If we've recursively scanned the parent directory, don't need to scan
2480 * any files under it any more.
2481 */
2482 GList *ptr;
2483 char *s, *full_s;
2484 for (ptr = *scanned_dirs; ptr; ptr = ptr->next) {
2485 s = ptr->data;
2486
2487 /* Have scanned from root directory. */
2488 if (s[0] == 0) {
2489 goto out;
2490 }
2491
2492 /* exact match */
2493 if (strcmp (s, path) == 0) {
2494 goto out;
2495 }
2496
2497 /* prefix match. */
2498 full_s = g_strconcat (s, "/", NULL);
2499 if (strncmp (full_s, dir, strlen(full_s)) == 0) {
2500 g_free (full_s);
2501 goto out;
2502 }
2503 g_free (full_s);
2504 }
2505
2506 *scanned_dirs = g_list_prepend (*scanned_dirs, g_strdup(dir));
2507
2508 remove_deleted (istate, worktree, dir, ignore_list, fset,
2509 repo_id, is_readonly, changeset);
2510
2511 /* After remove_deleted(), empty dirs are left not removed in changeset.
2512 * This can be fixed by removing the accurate deleted path. In most cases,
2513 * basename doesn't contain ~, so we can always get the accurate path.
2514 */
2515 /* if (!convertion_failed) { */
2516 /* char *basename = strrchr (path, '/'); */
2517 /* char *deleted_path = NULL; */
2518 /* if (basename) { */
2519 /* deleted_path = g_build_path ("/", dir, basename, NULL); */
2520 /* add_to_changeset (changeset, */
2521 /* DIFF_STATUS_DELETED, */
2522 /* NULL, */
2523 /* NULL, */
2524 /* NULL, */
2525 /* deleted_path, */
2526 /* NULL, */
2527 /* FALSE); */
2528 /* g_free (deleted_path); */
2529 /* } */
2530 /* } */
2531
2532 out:
2533 g_free (path_w);
2534 g_free (dir_w);
2535 g_free (dir);
2536 }
2537 #else
2538 static void
scan_subtree_for_deletion(const char * repo_id,struct index_state * istate,const char * worktree,const char * path,GList * ignore_list,LockedFileSet * fset,gboolean is_readonly,GList ** scanned_dirs,ChangeSet * changeset)2539 scan_subtree_for_deletion (const char *repo_id,
2540 struct index_state *istate,
2541 const char *worktree,
2542 const char *path,
2543 GList *ignore_list,
2544 LockedFileSet *fset,
2545 gboolean is_readonly,
2546 GList **scanned_dirs,
2547 ChangeSet *changeset)
2548 {
2549 }
2550 #endif
2551
2552 /* Return TRUE if the caller should stop processing next event. */
2553 static gboolean
handle_add_files(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,GList * ignore_list,LockedFileSet * fset,WTStatus * status,WTEvent * event,GList ** scanned_dirs,gint64 * total_size)2554 handle_add_files (SeafRepo *repo, struct index_state *istate,
2555 SeafileCrypt *crypt, GList *ignore_list,
2556 LockedFileSet *fset,
2557 WTStatus *status, WTEvent *event,
2558 GList **scanned_dirs, gint64 *total_size)
2559 {
2560 SyncInfo *info;
2561
2562 if (!repo->create_partial_commit) {
2563 /* XXX: We now use remain_files = NULL to signify not creating
2564 * partial commits. It's better to use total_size = NULL for
2565 * that purpose.
2566 */
2567 add_path_to_index (repo, istate, crypt, event->path,
2568 ignore_list, scanned_dirs,
2569 total_size, NULL, NULL);
2570 } else if (!event->remain_files) {
2571 GQueue *remain_files = NULL;
2572 add_path_to_index (repo, istate, crypt, event->path,
2573 ignore_list, scanned_dirs,
2574 total_size, &remain_files, fset);
2575 if (*total_size >= MAX_COMMIT_SIZE) {
2576 seaf_message ("Creating partial commit after adding %s.\n",
2577 event->path);
2578
2579 status->partial_commit = TRUE;
2580
2581 /* An event for a new folder may contain many files.
2582 * If the total_size become larger than 100MB after adding
2583 * some of these files, the remaining file paths will be
2584 * cached in remain files. This way we don't need to scan
2585 * the folder again next time.
2586 */
2587 if (remain_files) {
2588 if (g_queue_get_length (remain_files) == 0) {
2589 g_queue_free (remain_files);
2590 return TRUE;
2591 }
2592
2593 seaf_message ("Remain files for %s.\n", event->path);
2594
2595 /* Cache remaining files in the event structure. */
2596 event->remain_files = remain_files;
2597
2598 pthread_mutex_lock (&status->q_lock);
2599 g_queue_push_head (status->event_q, event);
2600 pthread_mutex_unlock (&status->q_lock);
2601
2602 info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, repo->id);
2603 if (!info->multipart_upload) {
2604 info->multipart_upload = TRUE;
2605 info->total_bytes = *total_size;
2606 }
2607 }
2608
2609 return TRUE;
2610 }
2611 } else {
2612 seaf_message ("Adding remaining files for %s.\n", event->path);
2613
2614 add_remain_files (repo, istate, crypt, event->remain_files,
2615 ignore_list, total_size);
2616 if (g_queue_get_length (event->remain_files) != 0) {
2617 pthread_mutex_lock (&status->q_lock);
2618 g_queue_push_head (status->event_q, event);
2619 pthread_mutex_unlock (&status->q_lock);
2620 return TRUE;
2621 } else {
2622 info = seaf_sync_manager_get_sync_info (seaf->sync_mgr, repo->id);
2623 info->end_multipart_upload = TRUE;
2624 return TRUE;
2625 }
2626 if (*total_size >= MAX_COMMIT_SIZE)
2627 return TRUE;
2628 }
2629
2630 return FALSE;
2631 }
2632
2633 #ifdef __APPLE__
2634
2635 /* struct _WTDirent { */
2636 /* char *dname; */
2637 /* struct stat st; */
2638 /* }; */
2639 /* typedef struct _WTDirent WTDirent; */
2640
2641 /* static gint */
2642 /* compare_wt_dirents (gconstpointer a, gconstpointer b) */
2643 /* { */
2644 /* const WTDirent *dent_a = a, *dent_b = b; */
2645
2646 /* return (strcmp (dent_a->dname, dent_b->dname)); */
2647 /* } */
2648
2649 /* static GList * */
2650 /* get_sorted_wt_dirents (const char *dir_path, const char *full_dir_path, */
2651 /* gboolean *error) */
2652 /* { */
2653 /* GDir *dir; */
2654 /* GError *err = NULL; */
2655 /* const char *name; */
2656 /* char *dname; */
2657 /* char *full_sub_path, *sub_path; */
2658 /* WTDirent *dent; */
2659 /* GList *ret = NULL; */
2660
2661 /* dir = g_dir_open (full_dir_path, 0, &err); */
2662 /* if (!dir) { */
2663 /* seaf_warning ("Failed to open dir %s: %s.\n", full_dir_path, err->message); */
2664 /* *error = TRUE; */
2665 /* return NULL; */
2666 /* } */
2667
2668 /* while ((name = g_dir_read_name(dir)) != NULL) { */
2669 /* dname = g_utf8_normalize (name, -1, G_NORMALIZE_NFC); */
2670 /* sub_path = g_strconcat (dir_path, "/", dname, NULL); */
2671 /* full_sub_path = g_strconcat (full_dir_path, "/", dname, NULL); */
2672
2673 /* dent = g_new0 (WTDirent, 1); */
2674 /* dent->dname = dname; */
2675
2676 /* if (stat (full_sub_path, &dent->st) < 0) { */
2677 /* seaf_warning ("Failed to stat %s: %s.\n", full_sub_path, strerror(errno)); */
2678 /* g_free (dname); */
2679 /* g_free (sub_path); */
2680 /* g_free (full_sub_path); */
2681 /* g_free (dent); */
2682 /* continue; */
2683 /* } */
2684
2685 /* ret = g_list_prepend (ret, dent); */
2686
2687 /* g_free (sub_path); */
2688 /* g_free (full_sub_path); */
2689 /* } */
2690
2691 /* g_dir_close (dir); */
2692
2693 /* ret = g_list_sort (ret, compare_wt_dirents); */
2694 /* return ret; */
2695 /* } */
2696
2697 /* static void */
2698 /* wt_dirent_free (WTDirent *dent) */
2699 /* { */
2700 /* if (!dent) */
2701 /* return; */
2702 /* g_free (dent->dname); */
2703 /* g_free (dent); */
2704 /* } */
2705
2706 /* inline static char * */
2707 /* concat_sub_path (const char *dir, const char *dname) */
2708 /* { */
2709 /* if (dir[0] != 0) */
2710 /* return g_strconcat(dir, "/", dname, NULL); */
2711 /* else */
2712 /* return g_strdup(dname); */
2713 /* } */
2714
2715 /* static int */
2716 /* get_changed_paths_in_folder (SeafRepo *repo, struct index_state *istate, */
2717 /* const char *dir_path, */
2718 /* GList **add, GList **mod, GList **del) */
2719 /* { */
2720 /* char *full_dir_path; */
2721 /* GList *wt_dents = NULL, *index_dents = NULL; */
2722 /* gboolean error = FALSE; */
2723
2724 /* full_dir_path = g_build_filename(repo->worktree, dir_path, NULL); */
2725
2726 /* wt_dents = get_sorted_wt_dirents (dir_path, full_dir_path, &error); */
2727 /* if (error) { */
2728 /* g_free (full_dir_path); */
2729 /* return -1; */
2730 /* } */
2731
2732 /* index_dents = list_dirents_from_index (istate, dir_path); */
2733
2734 /* GList *p; */
2735 /* IndexDirent *dent; */
2736 /* for (p = index_dents; p; p = p->next) { */
2737 /* dent = p->data; */
2738 /* } */
2739
2740 /* GList *p1 = wt_dents, *p2 = index_dents; */
2741 /* WTDirent *dent1; */
2742 /* IndexDirent *dent2; */
2743
2744 /* while (p1 && p2) { */
2745 /* dent1 = p1->data; */
2746 /* dent2 = p2->data; */
2747
2748 /* int rc = strcmp (dent1->dname, dent2->dname); */
2749 /* if (rc == 0) { */
2750 /* if (S_ISREG(dent1->st.st_mode) && !dent2->is_dir) { */
2751 /* if (dent1->st.st_mtime != dent2->ce->ce_mtime.sec) */
2752 /* *mod = g_list_prepend (*mod, concat_sub_path(dir_path, dent1->dname)); */
2753 /* } else if ((S_ISREG(dent1->st.st_mode) && dent2->is_dir) || */
2754 /* (S_ISDIR(dent1->st.st_mode) && !dent2->is_dir)) { */
2755 /* *add = g_list_prepend (*add, concat_sub_path(dir_path, dent1->dname)); */
2756 /* *del = g_list_prepend (*del, concat_sub_path(dir_path, dent1->dname)); */
2757 /* } */
2758 /* p1 = p1->next; */
2759 /* p2 = p2->next; */
2760 /* } else if (rc < 0) { */
2761 /* *add = g_list_prepend (*add, concat_sub_path(dir_path, dent1->dname)); */
2762 /* p1 = p1->next; */
2763 /* } else { */
2764 /* *del = g_list_prepend (*del, concat_sub_path(dir_path, dent2->dname)); */
2765 /* p2 = p2->next; */
2766 /* } */
2767 /* } */
2768
2769 /* while (p1) { */
2770 /* dent1 = p1->data; */
2771 /* *add = g_list_prepend (*add, concat_sub_path(dir_path, dent1->dname)); */
2772 /* p1 = p1->next; */
2773 /* } */
2774
2775 /* while (p2) { */
2776 /* dent2 = p2->data; */
2777 /* *del = g_list_prepend (*del, concat_sub_path(dir_path, dent2->dname)); */
2778 /* p2 = p2->next; */
2779 /* } */
2780
2781 /* g_free (full_dir_path); */
2782 /* g_list_free_full (wt_dents, (GDestroyNotify)wt_dirent_free); */
2783 /* g_list_free_full (index_dents, (GDestroyNotify)index_dirent_free); */
2784 /* return 0; */
2785 /* } */
2786
2787 #endif /* __APPLE__ */
2788
2789 static void
update_active_file(SeafRepo * repo,const char * path,SeafStat * st,struct index_state * istate,gboolean ignored)2790 update_active_file (SeafRepo *repo,
2791 const char *path,
2792 SeafStat *st,
2793 struct index_state *istate,
2794 gboolean ignored)
2795 {
2796 if (ignored) {
2797 seaf_sync_manager_update_active_path (seaf->sync_mgr,
2798 repo->id,
2799 path,
2800 S_IFREG,
2801 SYNC_STATUS_IGNORED,
2802 TRUE);
2803 } else {
2804 SyncStatus status;
2805 gboolean is_writable;
2806
2807 struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);
2808 if (!ce || ie_match_stat(ce, st, 0) != 0)
2809 status = SYNC_STATUS_SYNCING;
2810 else
2811 status = SYNC_STATUS_SYNCED;
2812
2813 is_writable = is_path_writable (repo->id, repo->is_readonly, path);
2814
2815 if (!is_writable && status == SYNC_STATUS_SYNCING)
2816 seaf_sync_manager_delete_active_path (seaf->sync_mgr,
2817 repo->id,
2818 path);
2819 else
2820 seaf_sync_manager_update_active_path (seaf->sync_mgr,
2821 repo->id,
2822 path,
2823 S_IFREG,
2824 status,
2825 TRUE);
2826 }
2827 }
2828
2829 #ifdef WIN32
2830
2831 typedef struct _UpdatePathData {
2832 SeafRepo *repo;
2833 struct index_state *istate;
2834 GList *ignore_list;
2835
2836 const char *parent;
2837 const char *full_parent;
2838 gboolean ignored;
2839 } UpdatePathData;
2840
2841 static void
2842 update_active_path_recursive (SeafRepo *repo,
2843 const char *path,
2844 struct index_state *istate,
2845 GList *ignore_list,
2846 gboolean ignored);
2847
2848 static int
update_active_path_cb(wchar_t * full_parent_w,WIN32_FIND_DATAW * fdata,void * user_data,gboolean * stop)2849 update_active_path_cb (wchar_t *full_parent_w,
2850 WIN32_FIND_DATAW *fdata,
2851 void *user_data,
2852 gboolean *stop)
2853 {
2854 UpdatePathData *upd_data = user_data;
2855 char *dname;
2856 char *path;
2857 gboolean ignored = FALSE;
2858 SeafStat st;
2859
2860 dname = g_utf16_to_utf8 (fdata->cFileName, -1, NULL, NULL, NULL);
2861 if (!dname)
2862 return 0;
2863
2864 path = g_build_path ("/", upd_data->parent, dname, NULL);
2865
2866 if (upd_data->ignored || should_ignore (upd_data->full_parent, dname, upd_data->ignore_list))
2867 ignored = TRUE;
2868
2869 seaf_stat_from_find_data (fdata, &st);
2870
2871 if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
2872 update_active_path_recursive (upd_data->repo,
2873 path,
2874 upd_data->istate,
2875 upd_data->ignore_list,
2876 ignored);
2877 } else {
2878 update_active_file (upd_data->repo,
2879 path,
2880 &st,
2881 upd_data->istate,
2882 ignored);
2883 }
2884
2885 g_free (dname);
2886 g_free (path);
2887
2888 return 0;
2889 }
2890
2891 static void
update_active_path_recursive(SeafRepo * repo,const char * path,struct index_state * istate,GList * ignore_list,gboolean ignored)2892 update_active_path_recursive (SeafRepo *repo,
2893 const char *path,
2894 struct index_state *istate,
2895 GList *ignore_list,
2896 gboolean ignored)
2897 {
2898 char *full_path;
2899 wchar_t *full_path_w;
2900 int ret = 0;
2901 UpdatePathData upd_data;
2902
2903 full_path = g_build_filename (repo->worktree, path, NULL);
2904
2905 memset (&upd_data, 0, sizeof(upd_data));
2906 upd_data.repo = repo;
2907 upd_data.istate = istate;
2908 upd_data.ignore_list = ignore_list;
2909 upd_data.parent = path;
2910 upd_data.full_parent = full_path;
2911 upd_data.ignored = ignored;
2912
2913 full_path_w = win32_long_path (full_path);
2914 ret = traverse_directory_win32 (full_path_w, update_active_path_cb, &upd_data);
2915 g_free (full_path_w);
2916 g_free (full_path);
2917
2918 if (ret < 0)
2919 return;
2920
2921 /* Don't set sync status for read-only paths, since changes to read-only
2922 * files are ignored.
2923 */
2924 if (!is_path_writable (repo->id, repo->is_readonly, path))
2925 return;
2926
2927 /* traverse_directory_win32() returns number of entries in the directory. */
2928 if (ret == 0 && path[0] != 0) {
2929 if (ignored) {
2930 seaf_sync_manager_update_active_path (seaf->sync_mgr,
2931 repo->id,
2932 path,
2933 S_IFDIR,
2934 SYNC_STATUS_IGNORED,
2935 TRUE);
2936 } else {
2937 /* There is no need to update an empty dir. */
2938 SyncStatus status;
2939 struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);
2940 if (!ce)
2941 status = SYNC_STATUS_SYNCING;
2942 else
2943 status = SYNC_STATUS_SYNCED;
2944 seaf_sync_manager_update_active_path (seaf->sync_mgr,
2945 repo->id,
2946 path,
2947 S_IFDIR,
2948 status,
2949 TRUE);
2950 }
2951 }
2952 }
2953
2954 #else
2955
2956 static void
update_active_path_recursive(SeafRepo * repo,const char * path,struct index_state * istate,GList * ignore_list,gboolean ignored)2957 update_active_path_recursive (SeafRepo *repo,
2958 const char *path,
2959 struct index_state *istate,
2960 GList *ignore_list,
2961 gboolean ignored)
2962 {
2963 GDir *dir;
2964 GError *error = NULL;
2965 const char *name;
2966 char *dname;
2967 char *full_path, *full_sub_path, *sub_path;
2968 struct stat st;
2969 gboolean ignore_sub;
2970
2971 full_path = g_build_filename(repo->worktree, path, NULL);
2972
2973 dir = g_dir_open (full_path, 0, &error);
2974 if (!dir) {
2975 seaf_warning ("Failed to open dir %s: %s.\n", full_path, error->message);
2976 g_free (full_path);
2977 return;
2978 }
2979
2980 int n = 0;
2981 while ((name = g_dir_read_name(dir)) != NULL) {
2982 ++n;
2983
2984 dname = g_utf8_normalize (name, -1, G_NORMALIZE_NFC);
2985 sub_path = g_strconcat (path, "/", dname, NULL);
2986 full_sub_path = g_strconcat (full_path, "/", dname, NULL);
2987
2988 ignore_sub = FALSE;
2989 if (ignored || should_ignore(full_path, dname, ignore_list))
2990 ignore_sub = TRUE;
2991
2992 if (stat (full_sub_path, &st) < 0) {
2993 seaf_warning ("Failed to stat %s: %s.\n", full_sub_path, strerror(errno));
2994 g_free (dname);
2995 g_free (sub_path);
2996 g_free (full_sub_path);
2997 continue;
2998 }
2999
3000 if (S_ISDIR(st.st_mode)) {
3001 update_active_path_recursive (repo, sub_path, istate, ignore_list,
3002 ignore_sub);
3003 } else if (S_ISREG(st.st_mode)) {
3004 update_active_file (repo, sub_path, &st, istate,
3005 ignore_sub);
3006 }
3007
3008 g_free (dname);
3009 g_free (sub_path);
3010 g_free (full_sub_path);
3011 }
3012
3013 g_dir_close (dir);
3014
3015 g_free (full_path);
3016
3017 /* Don't set sync status for read-only paths, since changes to read-only
3018 * files are ignored.
3019 */
3020 if (!is_path_writable (repo->id, repo->is_readonly, path))
3021 return;
3022
3023 if (n == 0 && path[0] != 0) {
3024 if (ignored) {
3025 seaf_sync_manager_update_active_path (seaf->sync_mgr,
3026 repo->id,
3027 path,
3028 S_IFDIR,
3029 SYNC_STATUS_IGNORED,
3030 TRUE);
3031 } else {
3032 /* There is no need to update an empty dir. */
3033 SyncStatus status;
3034 struct cache_entry *ce = index_name_exists(istate, path, strlen(path), 0);
3035 if (!ce)
3036 status = SYNC_STATUS_SYNCING;
3037 else
3038 status = SYNC_STATUS_SYNCED;
3039 seaf_sync_manager_update_active_path (seaf->sync_mgr,
3040 repo->id,
3041 path,
3042 S_IFDIR,
3043 status,
3044 TRUE);
3045 }
3046 }
3047 }
3048
3049 #endif /* WIN32 */
3050
3051 static void
process_active_path(SeafRepo * repo,const char * path,struct index_state * istate,GList * ignore_list)3052 process_active_path (SeafRepo *repo, const char *path,
3053 struct index_state *istate, GList *ignore_list)
3054 {
3055 SeafStat st;
3056 gboolean ignored = FALSE;
3057
3058 char *fullpath = g_build_filename (repo->worktree, path, NULL);
3059 if (seaf_stat (fullpath, &st) < 0) {
3060 g_free (fullpath);
3061 return;
3062 }
3063
3064 if (check_full_path_ignore (repo->worktree, path, ignore_list))
3065 ignored = TRUE;
3066
3067 if (S_ISREG(st.st_mode)) {
3068 if (!seaf_filelock_manager_is_file_locked(seaf->filelock_mgr,
3069 repo->id, path)) {
3070 update_active_file (repo, path, &st, istate, ignored);
3071 }
3072 } else {
3073 update_active_path_recursive (repo, path, istate, ignore_list, ignored);
3074 }
3075
3076 g_free (fullpath);
3077 }
3078
3079 #ifdef __APPLE__
3080
3081 /* static void */
3082 /* process_active_folder (SeafRepo *repo, const char *dir, */
3083 /* struct index_state *istate, GList *ignore_list) */
3084 /* { */
3085 /* GList *add = NULL, *mod = NULL, *del = NULL; */
3086 /* GList *p; */
3087 /* char *path; */
3088
3089 /* /\* Delete event will be triggered on the deleted dir too. *\/ */
3090 /* if (!g_file_test (dir, G_FILE_TEST_IS_DIR)) */
3091 /* return; */
3092
3093 /* if (get_changed_paths_in_folder (repo, istate, dir, &add, &mod, &del) < 0) { */
3094 /* seaf_warning ("Failed to get changed paths under %s.\n", dir); */
3095 /* return; */
3096 /* } */
3097
3098 /* for (p = add; p; p = p->next) { */
3099 /* path = p->data; */
3100 /* process_active_path (repo, path, istate, ignore_list); */
3101 /* } */
3102
3103 /* for (p = mod; p; p = p->next) { */
3104 /* path = p->data; */
3105 /* process_active_path (repo, path, istate, ignore_list); */
3106 /* } */
3107
3108 /* g_list_free_full (add, g_free); */
3109 /* g_list_free_full (mod, g_free); */
3110 /* g_list_free_full (del, g_free); */
3111 /* } */
3112
3113 #endif /* __APPLE__ */
3114
3115 static void
update_path_sync_status(SeafRepo * repo,WTStatus * status,struct index_state * istate,GList * ignore_list)3116 update_path_sync_status (SeafRepo *repo, WTStatus *status,
3117 struct index_state *istate, GList *ignore_list)
3118 {
3119 char *path;
3120
3121 while (1) {
3122 pthread_mutex_lock (&status->ap_q_lock);
3123 path = g_queue_pop_head (status->active_paths);
3124 pthread_mutex_unlock (&status->ap_q_lock);
3125
3126 if (!path)
3127 break;
3128
3129 /* #ifdef __APPLE__ */
3130 /* process_active_folder (repo, path, istate, ignore_list); */
3131 /* #else */
3132 process_active_path (repo, path, istate, ignore_list);
3133 /* #endif */
3134
3135 g_free (path);
3136 }
3137 }
3138
3139 /* Excel first writes update to a temporary file and then rename the file to
3140 * xlsx. Unfortunately the temp file dosen't have specific pattern.
3141 * We can only ignore renaming from non xlsx file to xlsx file.
3142 */
3143 static gboolean
ignore_xlsx_update(const char * src_path,const char * dst_path)3144 ignore_xlsx_update (const char *src_path, const char *dst_path)
3145 {
3146 GPatternSpec *pattern = g_pattern_spec_new ("*.xlsx");
3147 int ret = FALSE;
3148
3149 if (!g_pattern_match_string(pattern, src_path) &&
3150 g_pattern_match_string(pattern, dst_path))
3151 ret = TRUE;
3152
3153 g_pattern_spec_free (pattern);
3154 return ret;
3155 }
3156
3157 static gboolean
is_seafile_backup_file(const char * path)3158 is_seafile_backup_file (const char *path)
3159 {
3160 GPatternSpec *pattern = g_pattern_spec_new ("*.sbak");
3161 int ret = FALSE;
3162
3163 if (g_pattern_match_string(pattern, path))
3164 ret = TRUE;
3165
3166 g_pattern_spec_free (pattern);
3167 return ret;
3168 }
3169
3170 static void
handle_rename(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,GList * ignore_list,LockedFileSet * fset,WTEvent * event,GList ** scanned_del_dirs,gint64 * total_size)3171 handle_rename (SeafRepo *repo, struct index_state *istate,
3172 SeafileCrypt *crypt, GList *ignore_list,
3173 LockedFileSet *fset,
3174 WTEvent *event, GList **scanned_del_dirs,
3175 gint64 *total_size)
3176 {
3177 gboolean not_found, src_ignored, dst_ignored;
3178
3179 seaf_sync_manager_delete_active_path (seaf->sync_mgr, repo->id, event->path);
3180
3181 if (!is_path_writable(repo->id,
3182 repo->is_readonly, event->path) ||
3183 !is_path_writable(repo->id,
3184 repo->is_readonly, event->new_path)) {
3185 seaf_debug ("Rename: %s or %s is not writable, ignore.\n",
3186 event->path, event->new_path);
3187 return;
3188 }
3189
3190 if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
3191 repo->id, event->path)) {
3192 seaf_debug ("Rename: %s is locked on server, ignore.\n", event->path);
3193 /* send_sync_error_notification (repo->id, NULL, event->path, */
3194 /* SYNC_ERROR_ID_FILE_LOCKED); */
3195 return;
3196 }
3197
3198 if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
3199 repo->id, event->new_path)) {
3200 seaf_debug ("Rename: %s is locked on server, ignore.\n", event->new_path);
3201 /* send_sync_error_notification (repo->id, NULL, event->new_path, */
3202 /* SYNC_ERROR_ID_FILE_LOCKED); */
3203 return;
3204 }
3205
3206 src_ignored = check_full_path_ignore(repo->worktree, event->path, ignore_list);
3207 dst_ignored = check_full_path_ignore(repo->worktree, event->new_path, ignore_list);
3208
3209 /* If the destination path is ignored, just remove the source path. */
3210 if (dst_ignored) {
3211 if (!src_ignored &&
3212 !is_seafile_backup_file (event->new_path) &&
3213 check_locked_file_before_remove (fset, event->path)) {
3214 not_found = FALSE;
3215 remove_from_index_with_prefix (istate, event->path, ¬_found);
3216 if (not_found)
3217 scan_subtree_for_deletion (repo->id,
3218 istate,
3219 repo->worktree, event->path,
3220 ignore_list, fset,
3221 repo->is_readonly,
3222 scanned_del_dirs,
3223 repo->changeset);
3224
3225 remove_from_changeset (repo->changeset,
3226 DIFF_STATUS_DELETED,
3227 event->path,
3228 FALSE,
3229 NULL);
3230 }
3231 return;
3232 }
3233
3234 /* Now the destination path is not ignored. */
3235
3236 if (!src_ignored && !ignore_xlsx_update (event->path, event->new_path) &&
3237 check_locked_file_before_remove (fset, event->path)) {
3238 not_found = FALSE;
3239 rename_index_entries (istate, event->path, event->new_path, ¬_found,
3240 NULL, NULL);
3241 if (not_found)
3242 scan_subtree_for_deletion (repo->id,
3243 istate,
3244 repo->worktree, event->path,
3245 ignore_list, fset,
3246 repo->is_readonly,
3247 scanned_del_dirs,
3248 repo->changeset);
3249
3250 /* Moving files out of a dir may make it empty. */
3251 try_add_empty_parent_dir_entry_from_wt (repo->worktree,
3252 istate,
3253 ignore_list,
3254 event->path);
3255
3256 add_to_changeset (repo->changeset,
3257 DIFF_STATUS_RENAMED,
3258 NULL,
3259 NULL,
3260 NULL,
3261 event->path,
3262 event->new_path);
3263 }
3264
3265 AddOptions options;
3266 memset (&options, 0, sizeof(options));
3267 options.fset = fset;
3268 options.is_repo_ro = repo->is_readonly;
3269 options.changeset = repo->changeset;
3270
3271 /* We should always scan the destination to compare with the renamed
3272 * index entries. For example, in the following case:
3273 * 1. file a.txt is updated;
3274 * 2. a.txt is moved to test/a.txt;
3275 * If the two operations are executed in a batch, the updated content
3276 * of a.txt won't be committed if we don't scan the destination, because
3277 * when we process the update event, a.txt is already not in its original
3278 * place.
3279 */
3280 add_recursive (repo->id, repo->version, repo->email,
3281 istate, repo->worktree, event->new_path,
3282 crypt, FALSE, ignore_list,
3283 total_size, NULL, &options);
3284 }
3285
3286 #ifdef WIN32
3287
3288 typedef struct FindOfficeData {
3289 const char *lock_file_name;
3290 char *office_file_name;
3291 } FindOfficeData;
3292
3293 static int
find_office_file_cb(wchar_t * parent,WIN32_FIND_DATAW * fdata,void * user_data,gboolean * stop)3294 find_office_file_cb (wchar_t *parent,
3295 WIN32_FIND_DATAW *fdata,
3296 void *user_data,
3297 gboolean *stop)
3298 {
3299 FindOfficeData *data = user_data;
3300 const wchar_t *dname_w = fdata->cFileName;
3301 wchar_t *lock_name_w = NULL;
3302
3303 if (wcslen(dname_w) < 2)
3304 return 0;
3305 if (wcsncmp (dname_w, L"~$", 2) == 0)
3306 return 0;
3307
3308 lock_name_w = g_utf8_to_utf16 (data->lock_file_name,
3309 -1, NULL, NULL, NULL);
3310 /* Skip "~$" at the beginning. */
3311 if (wcscmp (dname_w + 2, lock_name_w) == 0) {
3312 data->office_file_name = g_utf16_to_utf8 (dname_w, -1, NULL, NULL, NULL);
3313 *stop = TRUE;
3314 }
3315 g_free (lock_name_w);
3316
3317 return 0;
3318 }
3319
3320 static gboolean
find_office_file_path(const char * worktree,const char * parent_dir,const char * lock_file_name,char ** office_path)3321 find_office_file_path (const char *worktree,
3322 const char *parent_dir,
3323 const char *lock_file_name,
3324 char **office_path)
3325 {
3326 char *fullpath = NULL;
3327 wchar_t *fullpath_w = NULL;
3328 FindOfficeData data;
3329 gboolean ret = FALSE;
3330
3331 fullpath = g_build_path ("/", worktree, parent_dir, NULL);
3332 fullpath_w = win32_long_path (fullpath);
3333
3334 data.lock_file_name = lock_file_name;
3335 data.office_file_name = NULL;
3336
3337 if (traverse_directory_win32 (fullpath_w, find_office_file_cb, &data) < 0) {
3338 goto out;
3339 }
3340
3341 if (data.office_file_name != NULL) {
3342 *office_path = g_build_path ("/", parent_dir, data.office_file_name, NULL);
3343 ret = TRUE;
3344 }
3345
3346 out:
3347 g_free (fullpath);
3348 g_free (fullpath_w);
3349 return ret;
3350 }
3351
3352 #endif
3353
3354 #ifdef __APPLE__
3355
3356 static gboolean
find_office_file_path(const char * worktree,const char * parent_dir,const char * lock_file_name,char ** office_path)3357 find_office_file_path (const char *worktree,
3358 const char *parent_dir,
3359 const char *lock_file_name,
3360 char **office_path)
3361 {
3362 GDir *dir = NULL;
3363 GError *error = NULL;
3364 char *fullpath = NULL;
3365 const char *dname;
3366 char *dname_nfc = NULL;
3367 char *dname_skip_head = NULL;
3368 gboolean ret = FALSE;
3369
3370 fullpath = g_build_path ("/", worktree, parent_dir, NULL);
3371 dir = g_dir_open (fullpath, 0, &error);
3372 if (error) {
3373 seaf_warning ("Failed to open dir %s: %s.\n", fullpath, error->message);
3374 g_clear_error (&error);
3375 g_free (fullpath);
3376 return ret;
3377 }
3378
3379 while ((dname = g_dir_read_name (dir)) != NULL) {
3380 dname_nfc = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);
3381 if (!dname_nfc)
3382 continue;
3383
3384 if (g_utf8_strlen(dname_nfc, -1) < 2 || strncmp (dname_nfc, "~$", 2) == 0) {
3385 g_free (dname_nfc);
3386 continue;
3387 }
3388
3389 dname_skip_head = g_utf8_find_next_char(g_utf8_find_next_char(dname_nfc, NULL), NULL);
3390
3391 if (g_strcmp0 (dname_skip_head, lock_file_name) == 0) {
3392 *office_path = g_build_path ("/", parent_dir, dname_nfc, NULL);
3393 ret = TRUE;
3394 g_free (dname_nfc);
3395 break;
3396 }
3397
3398 g_free (dname_nfc);
3399 }
3400
3401 g_free (fullpath);
3402 g_dir_close (dir);
3403 return ret;
3404 }
3405
3406 #endif
3407
3408 #if defined WIN32 || defined __APPLE__
3409
3410 static gboolean
is_office_lock_file(const char * worktree,const char * path,char ** office_path)3411 is_office_lock_file (const char *worktree,
3412 const char *path,
3413 char **office_path)
3414 {
3415 gboolean ret;
3416
3417 if (!g_regex_match (office_lock_pattern, path, 0, NULL))
3418 return FALSE;
3419
3420 /* Replace ~$abc.docx with abc.docx */
3421 *office_path = g_regex_replace (office_lock_pattern,
3422 path, -1, 0,
3423 "\\1", 0, NULL);
3424
3425 /* When the filename is long, sometimes the first two characters
3426 in the filename will be directly replaced with ~$.
3427 So if the office_path file doesn't exist, we have to match
3428 against all filenames in this directory, to find the office
3429 file's name.
3430 */
3431 char *fullpath = g_build_path ("/", worktree, *office_path, NULL);
3432 if (seaf_util_exists (fullpath)) {
3433 g_free (fullpath);
3434 return TRUE;
3435 }
3436 g_free (fullpath);
3437
3438 char *lock_file_name = g_path_get_basename(*office_path);
3439 char *parent_dir = g_path_get_dirname(*office_path);
3440 if (strcmp(parent_dir, ".") == 0) {
3441 g_free (parent_dir);
3442 parent_dir = g_strdup("");
3443 }
3444 g_free (*office_path);
3445 *office_path = NULL;
3446
3447 ret = find_office_file_path (worktree, parent_dir, lock_file_name,
3448 office_path);
3449
3450 g_free (lock_file_name);
3451 g_free (parent_dir);
3452 return ret;
3453 }
3454
3455 typedef struct LockOfficeJob {
3456 char repo_id[37];
3457 char *path;
3458 gboolean lock; /* False if unlock */
3459 } LockOfficeJob;
3460
3461 static void
lock_office_job_free(LockOfficeJob * job)3462 lock_office_job_free (LockOfficeJob *job)
3463 {
3464 if (!job)
3465 return;
3466 g_free (job->path);
3467 g_free (job);
3468 }
3469
3470 static void
do_lock_office_file(LockOfficeJob * job)3471 do_lock_office_file (LockOfficeJob *job)
3472 {
3473 SeafRepo *repo;
3474 char *fullpath = NULL;
3475 SeafStat st;
3476
3477 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id);
3478 if (!repo)
3479 return;
3480
3481 fullpath = g_build_path ("/", repo->worktree, job->path, NULL);
3482 if (seaf_stat (fullpath, &st) < 0 || !S_ISREG(st.st_mode)) {
3483 g_free (fullpath);
3484 return;
3485 }
3486 g_free (fullpath);
3487
3488 seaf_message ("Auto lock file %s/%s\n", repo->name, job->path);
3489
3490 int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr,
3491 repo->id, job->path);
3492 if (status != FILE_NOT_LOCKED) {
3493 return;
3494 }
3495
3496 if (http_tx_manager_lock_file (seaf->http_tx_mgr,
3497 repo->effective_host,
3498 repo->use_fileserver_port,
3499 repo->token,
3500 repo->id,
3501 job->path) < 0) {
3502 seaf_warning ("Failed to lock %s in repo %.8s on server.\n",
3503 job->path, repo->id);
3504 return;
3505 }
3506
3507 /* Mark file as locked locally so that the user can see the effect immediately. */
3508 seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo->id, job->path, TRUE);
3509 }
3510
3511 static void
do_unlock_office_file(LockOfficeJob * job)3512 do_unlock_office_file (LockOfficeJob *job)
3513 {
3514 SeafRepo *repo;
3515 char *fullpath = NULL;
3516 SeafStat st;
3517
3518 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, job->repo_id);
3519 if (!repo)
3520 return;
3521
3522 fullpath = g_build_path ("/", repo->worktree, job->path, NULL);
3523 if (seaf_stat (fullpath, &st) < 0 || !S_ISREG(st.st_mode)) {
3524 g_free (fullpath);
3525 return;
3526 }
3527 g_free (fullpath);
3528
3529 seaf_message ("Auto unlock file %s/%s\n", repo->name, job->path);
3530
3531 int status = seaf_filelock_manager_get_lock_status (seaf->filelock_mgr,
3532 repo->id, job->path);
3533 if (status != FILE_LOCKED_BY_ME_AUTO) {
3534 return;
3535 }
3536
3537 if (http_tx_manager_unlock_file (seaf->http_tx_mgr,
3538 repo->effective_host,
3539 repo->use_fileserver_port,
3540 repo->token,
3541 repo->id,
3542 job->path) < 0) {
3543 seaf_warning ("Failed to unlock %s in repo %.8s on server.\n",
3544 job->path, repo->id);
3545 return;
3546 }
3547
3548 /* Mark file as unlocked locally so that the user can see the effect immediately. */
3549 seaf_filelock_manager_mark_file_unlocked (seaf->filelock_mgr, repo->id, job->path);
3550 }
3551
3552 #if 0
3553 static void
3554 unlock_closed_office_files ()
3555 {
3556 GList *locked_files, *ptr;
3557 SeafRepo *repo;
3558 FileLockInfo *info;
3559 LockOfficeJob *job;
3560
3561 locked_files = seaf_filelock_manager_get_auto_locked_files (seaf->filelock_mgr);
3562 for (ptr = locked_files; ptr; ptr = ptr->next) {
3563 info = ptr->data;
3564
3565 seaf_message ("%s %s.\n", info->repo_id, info->path);
3566
3567 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, info->repo_id);
3568 if (!repo)
3569 continue;
3570
3571 seaf_message ("1\n");
3572
3573 if (!do_check_file_locked (info->path, repo->worktree, FALSE)) {
3574 seaf_message ("2\n");
3575
3576 job = g_new0 (LockOfficeJob, 1);
3577 memcpy (job->repo_id, info->repo_id, 36);
3578 job->path = g_strdup(info->path);
3579 do_unlock_office_file (job);
3580 lock_office_job_free (job);
3581 }
3582 }
3583
3584 g_list_free_full (locked_files, (GDestroyNotify)file_lock_info_free);
3585 }
3586 #endif
3587
3588 static void *
lock_office_file_worker(void * vdata)3589 lock_office_file_worker (void *vdata)
3590 {
3591 GAsyncQueue *queue = (GAsyncQueue *)vdata;
3592 LockOfficeJob *job;
3593
3594 /* unlock_closed_office_files (); */
3595
3596 while (1) {
3597 job = g_async_queue_pop (queue);
3598 if (!job)
3599 break;
3600
3601 if (job->lock)
3602 do_lock_office_file (job);
3603 else
3604 do_unlock_office_file (job);
3605
3606 lock_office_job_free (job);
3607 }
3608
3609 return NULL;
3610 }
3611
3612 static void
lock_office_file_on_server(SeafRepo * repo,const char * path)3613 lock_office_file_on_server (SeafRepo *repo, const char *path)
3614 {
3615 LockOfficeJob *job;
3616 GAsyncQueue *queue = seaf->repo_mgr->priv->lock_office_job_queue;
3617
3618 if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, repo->server_url))
3619 return;
3620
3621 job = g_new0 (LockOfficeJob, 1);
3622 memcpy (job->repo_id, repo->id, 36);
3623 job->path = g_strdup(path);
3624 job->lock = TRUE;
3625
3626 g_async_queue_push (queue, job);
3627 }
3628
3629 static void
unlock_office_file_on_server(SeafRepo * repo,const char * path)3630 unlock_office_file_on_server (SeafRepo *repo, const char *path)
3631 {
3632 LockOfficeJob *job;
3633 GAsyncQueue *queue = seaf->repo_mgr->priv->lock_office_job_queue;
3634
3635 if (!seaf_repo_manager_server_is_pro (seaf->repo_mgr, repo->server_url))
3636 return;
3637
3638 job = g_new0 (LockOfficeJob, 1);
3639 memcpy (job->repo_id, repo->id, 36);
3640 job->path = g_strdup(path);
3641 job->lock = FALSE;
3642
3643 g_async_queue_push (queue, job);
3644 }
3645
3646 #endif
3647
3648 static int
apply_worktree_changes_to_index(SeafRepo * repo,struct index_state * istate,SeafileCrypt * crypt,GList * ignore_list,LockedFileSet * fset)3649 apply_worktree_changes_to_index (SeafRepo *repo, struct index_state *istate,
3650 SeafileCrypt *crypt, GList *ignore_list,
3651 LockedFileSet *fset)
3652 {
3653 WTStatus *status;
3654 WTEvent *event, *next_event;
3655 gboolean not_found;
3656 #if defined WIN32 || defined __APPLE__
3657 char *office_path = NULL;
3658 #endif
3659
3660 status = seaf_wt_monitor_get_worktree_status (seaf->wt_monitor, repo->id);
3661 if (!status) {
3662 seaf_warning ("Can't find worktree status for repo %s(%.8s).\n",
3663 repo->name, repo->id);
3664 return -1;
3665 }
3666
3667 update_path_sync_status (repo, status, istate, ignore_list);
3668
3669 GList *scanned_dirs = NULL, *scanned_del_dirs = NULL;
3670
3671 WTEvent *last_event;
3672
3673 pthread_mutex_lock (&status->q_lock);
3674 last_event = g_queue_peek_tail (status->event_q);
3675 pthread_mutex_unlock (&status->q_lock);
3676
3677 if (!last_event) {
3678 seaf_message ("All events are processed for repo %s.\n", repo->id);
3679 status->partial_commit = FALSE;
3680 goto out;
3681 }
3682
3683 gint64 total_size = 0;
3684
3685 while (1) {
3686 pthread_mutex_lock (&status->q_lock);
3687 event = g_queue_pop_head (status->event_q);
3688 next_event = g_queue_peek_head (status->event_q);
3689 pthread_mutex_unlock (&status->q_lock);
3690 if (!event)
3691 break;
3692
3693 /* Scanned dirs list is used to avoid redundant scan of consecutive
3694 CREATE_OR_UPDATE events. When we see other events, we should
3695 clear the list. Otherwise in some cases we'll get wrong result.
3696 For example, the following sequence (run with a script):
3697 1. Add a dir with files
3698 2. Delete the dir with files
3699 3. Add back the same dir again.
3700 */
3701 if (event->ev_type != WT_EVENT_CREATE_OR_UPDATE) {
3702 g_list_free_full (scanned_dirs, g_free);
3703 scanned_dirs = NULL;
3704 }
3705
3706 switch (event->ev_type) {
3707 case WT_EVENT_CREATE_OR_UPDATE:
3708 /* If consecutive CREATE_OR_UPDATE events present
3709 in the event queue, only process the last one.
3710 */
3711 if (next_event &&
3712 next_event->ev_type == event->ev_type &&
3713 strcmp (next_event->path, event->path) == 0)
3714 break;
3715
3716 /* CREATE_OR_UPDATE event tells us the exact path of changed file/dir.
3717 * If the event path is not writable, we don't need to check the paths
3718 * under the event path.
3719 */
3720 if (!is_path_writable(repo->id,
3721 repo->is_readonly, event->path)) {
3722 char *filename = g_path_get_basename (event->path);
3723 if (seaf_repo_manager_is_ignored_hidden_file(filename)) {
3724 g_free (filename);
3725 break;
3726 }
3727 g_free (filename);
3728
3729 char *fullpath = g_build_path(PATH_SEPERATOR, repo->worktree, event->path, NULL);
3730 struct cache_entry *ce = index_name_exists(istate, event->path, strlen(event->path), 0);
3731 SeafStat st;
3732 if (ce != NULL &&
3733 seaf_stat (fullpath, &st) == 0 &&
3734 ce->ce_mtime.sec == st.st_mtime &&
3735 ce->ce_size == st.st_size) {
3736 g_free (fullpath);
3737 break;
3738 }
3739
3740 send_file_sync_error_notification (repo->id, repo->name, event->path,
3741 SYNC_ERROR_ID_UPDATE_TO_READ_ONLY_REPO);
3742 seaf_debug ("%s is not writable, ignore.\n", event->path);
3743
3744 g_free (fullpath);
3745 break;
3746 }
3747
3748 #if defined WIN32 || defined __APPLE__
3749 office_path = NULL;
3750 if (is_office_lock_file (repo->worktree, event->path, &office_path))
3751 lock_office_file_on_server (repo, office_path);
3752 g_free (office_path);
3753 #endif
3754
3755 if (handle_add_files (repo, istate, crypt, ignore_list,
3756 fset,
3757 status, event,
3758 &scanned_dirs, &total_size))
3759 goto out;
3760
3761 break;
3762 case WT_EVENT_SCAN_DIR:
3763 if (handle_add_files (repo, istate, crypt, ignore_list,
3764 fset,
3765 status, event,
3766 &scanned_dirs, &total_size))
3767 goto out;
3768
3769 break;
3770 case WT_EVENT_DELETE:
3771 seaf_sync_manager_delete_active_path (seaf->sync_mgr,
3772 repo->id,
3773 event->path);
3774
3775 #if defined WIN32 || defined __APPLE__
3776 office_path = NULL;
3777 if (is_office_lock_file (repo->worktree, event->path, &office_path))
3778 unlock_office_file_on_server (repo, office_path);
3779 g_free (office_path);
3780 #endif
3781
3782 if (check_full_path_ignore(repo->worktree, event->path, ignore_list))
3783 break;
3784
3785 if (!is_path_writable(repo->id,
3786 repo->is_readonly, event->path)) {
3787 seaf_debug ("%s is not writable, ignore.\n", event->path);
3788 break;
3789 }
3790
3791 if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
3792 repo->id, event->path)) {
3793 seaf_debug ("Delete: %s is locked on server, ignore.\n", event->path);
3794 /* send_sync_error_notification (repo->id, NULL, event->path, */
3795 /* SYNC_ERROR_ID_FILE_LOCKED); */
3796 break;
3797 }
3798
3799 if (check_locked_file_before_remove (fset, event->path)) {
3800 not_found = FALSE;
3801 remove_from_index_with_prefix (istate, event->path, ¬_found);
3802 if (not_found)
3803 scan_subtree_for_deletion (repo->id,
3804 istate,
3805 repo->worktree, event->path,
3806 ignore_list, fset,
3807 repo->is_readonly,
3808 &scanned_del_dirs,
3809 repo->changeset);
3810
3811 remove_from_changeset (repo->changeset,
3812 DIFF_STATUS_DELETED,
3813 event->path,
3814 FALSE,
3815 NULL);
3816
3817 try_add_empty_parent_dir_entry_from_wt (repo->worktree,
3818 istate,
3819 ignore_list,
3820 event->path);
3821 }
3822 break;
3823 case WT_EVENT_RENAME:
3824 handle_rename (repo, istate, crypt, ignore_list, fset, event, &scanned_del_dirs, &total_size);
3825 break;
3826 case WT_EVENT_ATTRIB:
3827 if (!is_path_writable(repo->id,
3828 repo->is_readonly, event->path)) {
3829 seaf_debug ("%s is not writable, ignore.\n", event->path);
3830 break;
3831 }
3832 update_attributes (repo, istate, repo->worktree, event->path);
3833 break;
3834 case WT_EVENT_OVERFLOW:
3835 seaf_warning ("Kernel event queue overflowed, fall back to scan.\n");
3836 scan_worktree_for_changes (istate, repo, crypt, ignore_list, fset);
3837 break;
3838 }
3839
3840 if (event == last_event) {
3841 wt_event_free (event);
3842 seaf_message ("All events are processed for repo %s.\n", repo->id);
3843 status->partial_commit = FALSE;
3844 break;
3845 } else
3846 wt_event_free (event);
3847 }
3848
3849 out:
3850 wt_status_unref (status);
3851 string_list_free (scanned_dirs);
3852 string_list_free (scanned_del_dirs);
3853
3854 return 0;
3855 }
3856
3857 static int
index_add(SeafRepo * repo,struct index_state * istate,gboolean is_force_commit)3858 index_add (SeafRepo *repo, struct index_state *istate, gboolean is_force_commit)
3859 {
3860 SeafileCrypt *crypt = NULL;
3861 LockedFileSet *fset = NULL;
3862 GList *ignore_list = NULL;
3863 int ret = 0;
3864
3865 if (repo->encrypted) {
3866 crypt = seafile_crypt_new (repo->enc_version, repo->enc_key, repo->enc_iv);
3867 }
3868
3869 #if defined WIN32 || defined __APPLE__
3870 if (repo->version > 0)
3871 fset = seaf_repo_manager_get_locked_file_set (seaf->repo_mgr, repo->id);
3872 #endif
3873
3874 ignore_list = seaf_repo_load_ignore_files (repo->worktree);
3875
3876 if (!is_force_commit) {
3877 if (apply_worktree_changes_to_index (repo, istate, crypt, ignore_list, fset) < 0) {
3878 seaf_warning ("Failed to apply worktree changes to index.\n");
3879 ret = -1;
3880 }
3881 } else if (scan_worktree_for_changes (istate, repo, crypt, ignore_list, fset) < 0) {
3882 seaf_warning ("Failed to scan worktree for changes.\n");
3883 ret = -1;
3884 }
3885
3886 seaf_repo_free_ignore_files (ignore_list);
3887
3888 #if defined WIN32 || defined __APPLE__
3889 locked_file_set_free (fset);
3890 #endif
3891
3892 g_free (crypt);
3893
3894 return ret;
3895 }
3896
3897 static int
commit_tree(SeafRepo * repo,const char * root_id,const char * desc,char commit_id[])3898 commit_tree (SeafRepo *repo, const char *root_id,
3899 const char *desc, char commit_id[])
3900 {
3901 SeafCommit *commit;
3902
3903 commit = seaf_commit_new (NULL, repo->id, root_id,
3904 repo->email ? repo->email
3905 : "unknown",
3906 seaf->client_id,
3907 desc, 0);
3908
3909 commit->parent_id = g_strdup (repo->head->commit_id);
3910
3911 /* Add this computer's name to commit. */
3912 commit->device_name = g_strdup(seaf->client_name);
3913 commit->client_version = g_strdup (SEAFILE_CLIENT_VERSION);
3914
3915 seaf_repo_to_commit (repo, commit);
3916
3917 if (seaf_commit_manager_add_commit (seaf->commit_mgr, commit) < 0)
3918 return -1;
3919
3920 seaf_branch_set_commit (repo->head, commit->commit_id);
3921 seaf_branch_manager_update_branch (seaf->branch_mgr, repo->head);
3922
3923 strcpy (commit_id, commit->commit_id);
3924 seaf_commit_unref (commit);
3925
3926 return 0;
3927 }
3928
3929 static gboolean
compare_index_changeset(struct index_state * istate,ChangeSet * changeset)3930 compare_index_changeset (struct index_state *istate, ChangeSet *changeset)
3931 {
3932 struct cache_entry *ce;
3933 int i;
3934 gboolean ret = TRUE;
3935
3936 for (i = 0; i < istate->cache_nr; ++i) {
3937 ce = istate->cache[i];
3938
3939 if (!(ce->ce_flags & CE_ADDED))
3940 continue;
3941
3942 seaf_message ("checking %s in changeset.\n", ce->name);
3943
3944 if (!changeset_check_path (changeset, ce->name,
3945 ce->sha1, ce->ce_mode, ce->ce_mtime.sec))
3946 ret = FALSE;
3947 }
3948
3949 return ret;
3950 }
3951
3952 #if 0
3953 static int
3954 print_index (struct index_state *istate)
3955 {
3956 int i;
3957 struct cache_entry *ce;
3958 char id[41];
3959 seaf_message ("Totally %u entries in index, version %u.\n",
3960 istate->cache_nr, istate->version);
3961 for (i = 0; i < istate->cache_nr; ++i) {
3962 ce = istate->cache[i];
3963 rawdata_to_hex (ce->sha1, id, 20);
3964 seaf_message ("%s, %s, %o, %"G_GINT64_FORMAT", %s, %"G_GINT64_FORMAT", %d\n",
3965 ce->name, id, ce->ce_mode,
3966 ce->ce_mtime.sec, ce->modifier, ce->ce_size, ce_stage(ce));
3967 }
3968
3969 return 0;
3970 }
3971 #endif
3972
3973 char *
seaf_repo_index_commit(SeafRepo * repo,gboolean is_force_commit,gboolean is_initial_commit,GError ** error)3974 seaf_repo_index_commit (SeafRepo *repo,
3975 gboolean is_force_commit,
3976 gboolean is_initial_commit,
3977 GError **error)
3978 {
3979 SeafRepoManager *mgr = repo->manager;
3980 struct index_state istate;
3981 char index_path[SEAF_PATH_MAX];
3982 SeafCommit *head = NULL;
3983 char *new_root_id = NULL;
3984 char commit_id[41];
3985 ChangeSet *changeset = NULL;
3986 GList *diff_results = NULL;
3987 char *desc = NULL;
3988 char *ret = NULL;
3989
3990 if (!check_worktree_common (repo))
3991 return NULL;
3992
3993 memset (&istate, 0, sizeof(istate));
3994 snprintf (index_path, SEAF_PATH_MAX, "%s/%s", mgr->index_dir, repo->id);
3995 if (read_index_from (&istate, index_path, repo->version) < 0) {
3996 seaf_warning ("Failed to load index.\n");
3997 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Internal data structure error");
3998 return NULL;
3999 }
4000
4001 changeset = changeset_new (repo->id);
4002 if (!changeset) {
4003 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Internal data structure error");
4004 goto out;
4005 }
4006
4007 repo->changeset = changeset;
4008
4009 if (index_add (repo, &istate, is_force_commit) < 0) {
4010 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, "Failed to add");
4011 goto out;
4012 }
4013
4014 if (!istate.cache_changed)
4015 goto out;
4016
4017 if (!is_initial_commit && !is_force_commit) {
4018 new_root_id = commit_tree_from_changeset (changeset);
4019 if (!new_root_id) {
4020 seaf_warning ("Create commit tree failed for repo %s\n", repo->id);
4021 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
4022 "Failed to generate commit");
4023 goto out;
4024 }
4025 } else {
4026 char hex[41];
4027 struct cache_tree *it = cache_tree ();
4028 if (cache_tree_update (repo->id, repo->version,
4029 repo->worktree,
4030 it, istate.cache,
4031 istate.cache_nr, 0, 0, commit_trees_cb) < 0) {
4032 seaf_warning ("Failed to build cache tree");
4033 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL,
4034 "Internal data structure error");
4035 cache_tree_free (&it);
4036 goto out;
4037 }
4038 rawdata_to_hex (it->sha1, hex, 20);
4039 new_root_id = g_strdup(hex);
4040 cache_tree_free (&it);
4041 }
4042
4043 head = seaf_commit_manager_get_commit (seaf->commit_mgr,
4044 repo->id, repo->version,
4045 repo->head->commit_id);
4046 if (!head) {
4047 seaf_warning ("Head commit %s for repo %s not found\n",
4048 repo->head->commit_id, repo->id);
4049 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Data corrupt");
4050 goto out;
4051 }
4052
4053 if (strcmp (head->root_id, new_root_id) == 0) {
4054 seaf_message ("No change to the fs tree of repo %s\n", repo->id);
4055 /* If no file modification and addition are missing, and the new root
4056 * id is the same as the old one, skip commiting.
4057 */
4058 if (!is_initial_commit && !is_force_commit)
4059 compare_index_changeset (&istate, changeset);
4060
4061 update_index (&istate, index_path);
4062 goto out;
4063 }
4064
4065 diff_commit_roots (repo->id, repo->version, head->root_id, new_root_id, &diff_results, TRUE);
4066 desc = diff_results_to_description (diff_results);
4067 if (!desc)
4068 desc = g_strdup("");
4069
4070 if (commit_tree (repo, new_root_id, desc, commit_id) < 0) {
4071 seaf_warning ("Failed to save commit file");
4072 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Internal error");
4073 goto out;
4074 }
4075
4076 if (update_index (&istate, index_path) < 0) {
4077 g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_INTERNAL, "Internal error");
4078 goto out;
4079 }
4080
4081 g_signal_emit_by_name (seaf, "repo-committed", repo);
4082
4083 ret = g_strdup(commit_id);
4084
4085 out:
4086 g_free (desc);
4087 seaf_commit_unref (head);
4088 g_free (new_root_id);
4089 changeset_free (changeset);
4090 g_list_free_full (diff_results, (GDestroyNotify)diff_entry_free);
4091 discard_index (&istate);
4092 return ret;
4093 }
4094
4095 #ifdef DEBUG_UNPACK_TREES
4096 static void
print_unpack_result(struct index_state * result)4097 print_unpack_result (struct index_state *result)
4098 {
4099 int i;
4100 struct cache_entry *ce;
4101
4102 for (i = 0; i < result->cache_nr; ++i) {
4103 ce = result->cache[i];
4104 printf ("%s\t", ce->name);
4105 if (ce->ce_flags & CE_UPDATE)
4106 printf ("update/add\n");
4107 else if (ce->ce_flags & CE_WT_REMOVE)
4108 printf ("remove\n");
4109 else
4110 printf ("unchange\n");
4111 }
4112 }
4113
4114 static int
print_index(struct index_state * istate)4115 print_index (struct index_state *istate)
4116 {
4117 printf ("Index timestamp: %d\n", istate->timestamp.sec);
4118
4119 int i;
4120 struct cache_entry *ce;
4121 char id[41];
4122 printf ("Totally %u entries in index.\n", istate->cache_nr);
4123 for (i = 0; i < istate->cache_nr; ++i) {
4124 ce = istate->cache[i];
4125 rawdata_to_hex (ce->sha1, id, 20);
4126 printf ("%s\t%s\t%o\t%d\t%d\n", ce->name, id, ce->ce_mode,
4127 ce->ce_ctime.sec, ce->ce_mtime.sec);
4128 }
4129
4130 return 0;
4131 }
4132 #endif /* DEBUG_UNPACK_TREES */
4133
4134 GList *
seaf_repo_diff(SeafRepo * repo,const char * old,const char * new,int fold_dir_diff,char ** error)4135 seaf_repo_diff (SeafRepo *repo, const char *old, const char *new, int fold_dir_diff, char **error)
4136 {
4137 SeafCommit *c1 = NULL, *c2 = NULL;
4138 int ret = 0;
4139 GList *diff_entries = NULL;
4140
4141 c2 = seaf_commit_manager_get_commit (seaf->commit_mgr,
4142 repo->id, repo->version,
4143 new);
4144 if (!c2) {
4145 *error = g_strdup("Can't find new commit");
4146 return NULL;
4147 }
4148
4149 if (old == NULL || old[0] == '\0') {
4150 if (c2->parent_id && c2->second_parent_id) {
4151 ret = diff_merge (c2, &diff_entries, fold_dir_diff);
4152 seaf_commit_unref (c2);
4153 if (ret < 0) {
4154 *error = g_strdup("Failed to do diff");
4155 g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);
4156 return NULL;
4157 }
4158 return diff_entries;
4159 }
4160
4161 if (!c2->parent_id) {
4162 seaf_commit_unref (c2);
4163 return NULL;
4164 }
4165 c1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
4166 repo->id, repo->version,
4167 c2->parent_id);
4168 } else {
4169 c1 = seaf_commit_manager_get_commit (seaf->commit_mgr,
4170 repo->id, repo->version, old);
4171 }
4172
4173 if (!c1) {
4174 *error = g_strdup("Can't find old commit");
4175 seaf_commit_unref (c2);
4176 return NULL;
4177 }
4178
4179 /* do diff */
4180 ret = diff_commits (c1, c2, &diff_entries, fold_dir_diff);
4181 if (ret < 0) {
4182 g_list_free_full (diff_entries, (GDestroyNotify)diff_entry_free);
4183 diff_entries = NULL;
4184 *error = g_strdup("Failed to do diff");
4185 }
4186
4187 seaf_commit_unref (c1);
4188 seaf_commit_unref (c2);
4189
4190 return diff_entries;
4191 }
4192
4193 int
checkout_empty_dir(const char * worktree,const char * name,gint64 mtime,struct cache_entry * ce,GHashTable * conflict_hash,GHashTable * no_conflict_hash)4194 checkout_empty_dir (const char *worktree,
4195 const char *name,
4196 gint64 mtime,
4197 struct cache_entry *ce,
4198 GHashTable *conflict_hash,
4199 GHashTable *no_conflict_hash)
4200 {
4201 char *path;
4202 gboolean case_conflict = FALSE;
4203
4204 path = build_checkout_path (worktree, name, strlen(name));
4205
4206 if (!path)
4207 return FETCH_CHECKOUT_FAILED;
4208
4209 if (!seaf_util_exists (path) && seaf_util_mkdir (path, 0777) < 0) {
4210 seaf_warning ("Failed to create empty dir %s in checkout.\n", path);
4211 g_free (path);
4212 return FETCH_CHECKOUT_FAILED;
4213 }
4214
4215 if (mtime != 0 && seaf_set_file_time (path, mtime) < 0) {
4216 seaf_warning ("Failed to set mtime for %s.\n", path);
4217 }
4218
4219 if (case_conflict) {
4220 ce->ce_flags |= CE_REMOVE;
4221 g_free (path);
4222 return FETCH_CHECKOUT_SUCCESS;
4223 }
4224
4225 SeafStat st;
4226 seaf_stat (path, &st);
4227 fill_stat_cache_info (ce, &st);
4228
4229 g_free (path);
4230 return FETCH_CHECKOUT_SUCCESS;
4231 }
4232
4233 static struct cache_entry *
cache_entry_from_diff_entry(DiffEntry * de)4234 cache_entry_from_diff_entry (DiffEntry *de)
4235 {
4236 int size, namelen;
4237 struct cache_entry *ce;
4238
4239 namelen = strlen(de->name);
4240 size = cache_entry_size(namelen);
4241 ce = calloc(1, size);
4242 memcpy(ce->name, de->name, namelen);
4243 ce->ce_flags = namelen;
4244
4245 memcpy (ce->sha1, de->sha1, 20);
4246 ce->modifier = g_strdup(de->modifier);
4247 ce->ce_size = de->size;
4248 ce->ce_mtime.sec = de->mtime;
4249
4250 if (S_ISREG(de->mode))
4251 ce->ce_mode = create_ce_mode (de->mode);
4252 else
4253 ce->ce_mode = S_IFDIR;
4254
4255 return ce;
4256 }
4257
4258 #define UPDATE_CACHE_SIZE_LIMIT 100 * (1 << 20) /* 100MB */
4259
4260 typedef struct FileTxData {
4261 char repo_id[37];
4262 int repo_version;
4263 SeafileCrypt *crypt;
4264 HttpTxTask *http_task;
4265 char conflict_head_id[41];
4266 GAsyncQueue *finished_tasks;
4267 } FileTxData;
4268
4269 typedef struct FileTxTask {
4270 char *path;
4271 struct cache_entry *ce;
4272 DiffEntry *de;
4273 gboolean new_ce;
4274 gboolean skip_fetch;
4275
4276 int result;
4277 gboolean no_checkout;
4278 gboolean force_conflict;
4279 } FileTxTask;
4280
4281 static void
file_tx_task_free(FileTxTask * task)4282 file_tx_task_free (FileTxTask *task)
4283 {
4284 if (!task)
4285 return;
4286
4287 g_free (task->path);
4288 g_free (task);
4289 }
4290
4291 static int
fetch_file_http(FileTxData * data,FileTxTask * file_task)4292 fetch_file_http (FileTxData *data, FileTxTask *file_task)
4293 {
4294 int repo_version = data->repo_version;
4295 struct cache_entry *ce = file_task->ce;
4296 DiffEntry *de = file_task->de;
4297 SeafileCrypt *crypt = data->crypt;
4298 char *path = file_task->path;
4299 HttpTxTask *http_task = data->http_task;
4300 SeafStat st;
4301 char file_id[41];
4302 gboolean path_exists = FALSE;
4303
4304 rawdata_to_hex (de->sha1, file_id, 20);
4305
4306 path_exists = (seaf_stat (path, &st) == 0);
4307
4308 if (path_exists && S_ISREG(st.st_mode)) {
4309 if (st.st_mtime == ce->ce_mtime.sec) {
4310 /* Worktree and index are consistent. */
4311 if (memcmp (de->sha1, ce->sha1, 20) == 0) {
4312 seaf_debug ("wt and index are consistent. no need to checkout.\n");
4313 file_task->no_checkout = TRUE;
4314
4315 /* Update mode if necessary. */
4316 if (de->mode != ce->ce_mode) {
4317 #ifndef WIN32
4318 chmod (path, de->mode & ~S_IFMT);
4319 ce->ce_mode = de->mode;
4320 #endif
4321 }
4322
4323 /* Update mtime if necessary. */
4324 if (de->mtime != ce->ce_mtime.sec) {
4325 seaf_set_file_time (path, de->mtime);
4326 ce->ce_mtime.sec = de->mtime;
4327 }
4328
4329 fill_stat_cache_info (ce, &st);
4330
4331 return FETCH_CHECKOUT_SUCCESS;
4332 }
4333 /* otherwise we have to checkout the file. */
4334 } else {
4335 if (compare_file_content (path, &st, de->sha1, crypt, repo_version) == 0) {
4336 /* This happens after the worktree file was updated,
4337 * but the index was not. Just need to update the index.
4338 */
4339 seaf_debug ("update index only.\n");
4340 file_task->no_checkout = TRUE;
4341 fill_stat_cache_info (ce, &st);
4342 return FETCH_CHECKOUT_SUCCESS;
4343 } else {
4344 /* Conflict. The worktree file was updated by the user. */
4345 seaf_message ("File %s is updated by user. "
4346 "Will checkout to conflict file later.\n", path);
4347 file_task->force_conflict = TRUE;
4348 }
4349 }
4350 }
4351
4352 /* Download the blocks of this file. */
4353 int rc;
4354 rc = http_tx_task_download_file_blocks (http_task, file_id);
4355 if (http_task->state == HTTP_TASK_STATE_CANCELED) {
4356 return FETCH_CHECKOUT_CANCELED;
4357 }
4358 if (rc < 0) {
4359 return FETCH_CHECKOUT_TRANSFER_ERROR;
4360 }
4361
4362 return FETCH_CHECKOUT_SUCCESS;
4363 }
4364
4365 static void
fetch_file_thread_func(gpointer data,gpointer user_data)4366 fetch_file_thread_func (gpointer data, gpointer user_data)
4367 {
4368 FileTxTask *task = data;
4369 FileTxData *tx_data = user_data;
4370 GAsyncQueue *finished_tasks = tx_data->finished_tasks;
4371 DiffEntry *de = task->de;
4372 char *repo_id = tx_data->repo_id;
4373 char file_id[41];
4374 gboolean is_clone = tx_data->http_task->is_clone;
4375 int rc = FETCH_CHECKOUT_SUCCESS;
4376
4377 if (task->skip_fetch)
4378 goto out;
4379
4380 rawdata_to_hex (de->sha1, file_id, 20);
4381
4382 /* seaf_message ("Download file %s for repo %s\n", de->name, repo_id); */
4383
4384 if (!is_clone)
4385 seaf_sync_manager_update_active_path (seaf->sync_mgr,
4386 repo_id,
4387 de->name,
4388 de->mode,
4389 SYNC_STATUS_SYNCING,
4390 TRUE);
4391
4392 rc = fetch_file_http (tx_data, task);
4393
4394 /* Even if the file failed to check out, still need to update index.
4395 * But we have to stop after transfer errors.
4396 */
4397 if (rc == FETCH_CHECKOUT_CANCELED) {
4398 seaf_debug ("Transfer canceled.\n");
4399 } else if (rc == FETCH_CHECKOUT_TRANSFER_ERROR) {
4400 seaf_warning ("Transfer failed.\n");
4401 }
4402
4403 out:
4404 task->result = rc;
4405 g_async_queue_push (finished_tasks, task);
4406 }
4407
4408 static int
schedule_file_fetch(GThreadPool * tpool,const char * repo_id,const char * repo_name,const char * worktree,struct index_state * istate,DiffEntry * de,GHashTable * pending_tasks,GHashTable * conflict_hash,GHashTable * no_conflict_hash)4409 schedule_file_fetch (GThreadPool *tpool,
4410 const char *repo_id,
4411 const char *repo_name,
4412 const char *worktree,
4413 struct index_state *istate,
4414 DiffEntry *de,
4415 GHashTable *pending_tasks,
4416 GHashTable *conflict_hash,
4417 GHashTable *no_conflict_hash)
4418 {
4419 struct cache_entry *ce;
4420 gboolean new_ce = FALSE;
4421 gboolean skip_fetch = FALSE;
4422 char *path = NULL;
4423 FileTxTask *file_task;
4424
4425 ce = index_name_exists (istate, de->name, strlen(de->name), 0);
4426 if (!ce) {
4427 ce = cache_entry_from_diff_entry (de);
4428 new_ce = TRUE;
4429 }
4430
4431 IgnoreReason reason;
4432 if (should_ignore_on_checkout (de->name, &reason)) {
4433 seaf_message ("Path %s is invalid on Windows, skip checkout\n",
4434 de->name);
4435 if (reason == IGNORE_REASON_END_SPACE_PERIOD)
4436 send_file_sync_error_notification (repo_id, repo_name, de->name,
4437 SYNC_ERROR_ID_PATH_END_SPACE_PERIOD);
4438 else if (reason == IGNORE_REASON_INVALID_CHARACTER)
4439 send_file_sync_error_notification (repo_id, repo_name, de->name,
4440 SYNC_ERROR_ID_PATH_INVALID_CHARACTER);
4441 skip_fetch = TRUE;
4442 }
4443
4444 if (!skip_fetch) {
4445 path = build_checkout_path (worktree, de->name, strlen(de->name));
4446 if (!path) {
4447 if (new_ce)
4448 cache_entry_free (ce);
4449 return FETCH_CHECKOUT_FAILED;
4450 }
4451 }
4452
4453 file_task = g_new0 (FileTxTask, 1);
4454 file_task->de = de;
4455 file_task->ce = ce;
4456 file_task->path = path;
4457 file_task->new_ce = new_ce;
4458 file_task->skip_fetch = skip_fetch;
4459
4460 if (!g_hash_table_lookup (pending_tasks, de->name)) {
4461 g_hash_table_insert (pending_tasks, g_strdup(de->name), file_task);
4462 g_thread_pool_push (tpool, file_task, NULL);
4463 } else {
4464 file_tx_task_free (file_task);
4465 }
4466
4467 return FETCH_CHECKOUT_SUCCESS;
4468 }
4469
4470 static void
cleanup_file_blocks_http(HttpTxTask * task,const char * file_id)4471 cleanup_file_blocks_http (HttpTxTask *task, const char *file_id)
4472 {
4473 Seafile *file;
4474 int i;
4475 char *block_id;
4476 int *pcnt;
4477
4478 file = seaf_fs_manager_get_seafile (seaf->fs_mgr,
4479 task->repo_id, task->repo_version,
4480 file_id);
4481 if (!file) {
4482 seaf_warning ("Failed to load seafile object %s:%s\n",
4483 task->repo_id, file_id);
4484 return;
4485 }
4486
4487 for (i = 0; i < file->n_blocks; ++i) {
4488 block_id = file->blk_sha1s[i];
4489
4490 pthread_mutex_lock (&task->ref_cnt_lock);
4491
4492 pcnt = g_hash_table_lookup (task->blk_ref_cnts, block_id);
4493 if (pcnt) {
4494 --(*pcnt);
4495 if (*pcnt > 0) {
4496 pthread_mutex_unlock (&task->ref_cnt_lock);
4497 continue;
4498 }
4499 }
4500
4501 seaf_block_manager_remove_block (seaf->block_mgr,
4502 task->repo_id, task->repo_version,
4503 block_id);
4504 g_hash_table_remove (task->blk_ref_cnts, block_id);
4505
4506 pthread_mutex_unlock (&task->ref_cnt_lock);
4507 }
4508
4509 seafile_unref (file);
4510 }
4511
4512 static gboolean
check_path_conflict(const char * path,char ** orig_path)4513 check_path_conflict (const char *path, char **orig_path)
4514 {
4515 gboolean is_conflict = FALSE;
4516 GError *error = NULL;
4517
4518 is_conflict = g_regex_match (conflict_pattern, path, 0, NULL);
4519 if (is_conflict) {
4520 *orig_path = g_regex_replace_literal (conflict_pattern, path, -1,
4521 0, "", 0, &error);
4522 if (!*orig_path)
4523 is_conflict = FALSE;
4524 }
4525
4526 return is_conflict;
4527 }
4528
4529 static int
checkout_file_http(FileTxData * data,FileTxTask * file_task,const char * worktree,GHashTable * conflict_hash,GHashTable * no_conflict_hash,const char * conflict_head_id,LockedFileSet * fset)4530 checkout_file_http (FileTxData *data,
4531 FileTxTask *file_task,
4532 const char *worktree,
4533 GHashTable *conflict_hash,
4534 GHashTable *no_conflict_hash,
4535 const char *conflict_head_id,
4536 LockedFileSet *fset)
4537 {
4538 char *repo_id = data->repo_id;
4539 int repo_version = data->repo_version;
4540 struct cache_entry *ce = file_task->ce;
4541 DiffEntry *de = file_task->de;
4542 SeafileCrypt *crypt = data->crypt;
4543 gboolean no_checkout = file_task->no_checkout;
4544 gboolean force_conflict = file_task->force_conflict;
4545 HttpTxTask *http_task = data->http_task;
4546 gboolean path_exists;
4547 gboolean case_conflict = FALSE;
4548 SeafStat st;
4549 char file_id[41];
4550 gboolean locked_on_server = FALSE;
4551
4552 if (no_checkout)
4553 return FETCH_CHECKOUT_SUCCESS;
4554
4555 if (should_ignore_on_checkout (de->name, NULL))
4556 return FETCH_CHECKOUT_SUCCESS;
4557
4558 rawdata_to_hex (de->sha1, file_id, 20);
4559
4560 locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
4561 repo_id, de->name);
4562
4563 #if defined WIN32 || defined __APPLE__
4564 if (do_check_file_locked (de->name, worktree, locked_on_server)) {
4565 if (!locked_file_set_lookup (fset, de->name))
4566 send_file_sync_error_notification (repo_id, NULL, de->name,
4567 SYNC_ERROR_ID_FILE_LOCKED_BY_APP);
4568
4569 locked_file_set_add_update (fset, de->name, LOCKED_OP_UPDATE,
4570 ce->ce_mtime.sec, file_id);
4571 /* Stay in syncing status if the file is locked. */
4572
4573 return FETCH_CHECKOUT_SUCCESS;
4574 }
4575 #endif
4576
4577 path_exists = (seaf_stat (file_task->path, &st) == 0);
4578
4579 /* The worktree file may have been changed when we're downloading the blocks. */
4580 if (!file_task->new_ce &&
4581 path_exists && S_ISREG(st.st_mode) &&
4582 !force_conflict) {
4583 if (st.st_mtime != ce->ce_mtime.sec) {
4584 seaf_message ("File %s is updated by user. "
4585 "Will checkout to conflict file later.\n", file_task->path);
4586 force_conflict = TRUE;
4587 }
4588 }
4589
4590 /* Temporarily unlock the file if it's locked on server, so that the client
4591 * itself can write to it.
4592 */
4593 if (locked_on_server)
4594 seaf_filelock_manager_unlock_wt_file (seaf->filelock_mgr,
4595 repo_id, de->name);
4596
4597 /* then checkout the file. */
4598 gboolean conflicted = FALSE;
4599 if (seaf_fs_manager_checkout_file (seaf->fs_mgr,
4600 repo_id,
4601 repo_version,
4602 file_id,
4603 file_task->path,
4604 de->mode,
4605 de->mtime,
4606 crypt,
4607 de->name,
4608 conflict_head_id,
4609 force_conflict,
4610 &conflicted,
4611 http_task->email) < 0) {
4612 seaf_warning ("Failed to checkout file %s.\n", file_task->path);
4613
4614 if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
4615 repo_id, de->name))
4616 seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr,
4617 repo_id, de->name);
4618
4619 return FETCH_CHECKOUT_FAILED;
4620 }
4621
4622 if (locked_on_server)
4623 seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr,
4624 repo_id, de->name);
4625
4626 cleanup_file_blocks_http (http_task, file_id);
4627
4628 if (conflicted) {
4629 send_file_sync_error_notification (repo_id, NULL, de->name, SYNC_ERROR_ID_CONFLICT);
4630 } else if (!http_task->is_clone) {
4631 char *orig_path = NULL;
4632 if (check_path_conflict (de->name, &orig_path))
4633 send_file_sync_error_notification (repo_id, NULL, orig_path, SYNC_ERROR_ID_CONFLICT);
4634 g_free (orig_path);
4635 }
4636
4637 /* If case conflict, this file will be checked out to another path.
4638 * Remove the current entry, otherwise it won't be removed later
4639 * since it's timestamp is 0.
4640 */
4641 if (case_conflict)
4642 ce->ce_flags |= CE_REMOVE;
4643
4644 /* finally fill cache_entry info */
4645 /* Only update index if we checked out the file without any error
4646 * or conflicts. The ctime of the entry will remain 0 if error.
4647 */
4648 seaf_stat (file_task->path, &st);
4649 fill_stat_cache_info (ce, &st);
4650
4651 return FETCH_CHECKOUT_SUCCESS;
4652 }
4653
4654 static void
handle_dir_added_de(const char * repo_id,const char * repo_name,const char * worktree,struct index_state * istate,DiffEntry * de,GHashTable * conflict_hash,GHashTable * no_conflict_hash)4655 handle_dir_added_de (const char *repo_id,
4656 const char *repo_name,
4657 const char *worktree,
4658 struct index_state *istate,
4659 DiffEntry *de,
4660 GHashTable *conflict_hash,
4661 GHashTable *no_conflict_hash)
4662 {
4663 seaf_debug ("Checkout empty dir %s.\n", de->name);
4664
4665 struct cache_entry *ce;
4666 gboolean add_ce = FALSE;
4667
4668 ce = index_name_exists (istate, de->name, strlen(de->name), 0);
4669 if (!ce) {
4670 ce = cache_entry_from_diff_entry (de);
4671 add_ce = TRUE;
4672 }
4673
4674 IgnoreReason reason;
4675 if (should_ignore_on_checkout (de->name, &reason)) {
4676 seaf_message ("Path %s is invalid on Windows, skip checkout\n",
4677 de->name);
4678 if (reason == IGNORE_REASON_END_SPACE_PERIOD)
4679 send_file_sync_error_notification (repo_id, repo_name, de->name,
4680 SYNC_ERROR_ID_PATH_END_SPACE_PERIOD);
4681 else if (reason == IGNORE_REASON_INVALID_CHARACTER)
4682 send_file_sync_error_notification (repo_id, repo_name, de->name,
4683 SYNC_ERROR_ID_PATH_INVALID_CHARACTER);
4684 goto update_index;
4685 }
4686
4687 checkout_empty_dir (worktree,
4688 de->name,
4689 de->mtime,
4690 ce,
4691 conflict_hash,
4692 no_conflict_hash);
4693
4694 seaf_sync_manager_update_active_path (seaf->sync_mgr,
4695 repo_id,
4696 de->name,
4697 de->mode,
4698 SYNC_STATUS_SYNCED,
4699 TRUE);
4700
4701 update_index:
4702 if (add_ce) {
4703 if (!(ce->ce_flags & CE_REMOVE)) {
4704 add_index_entry (istate, ce,
4705 (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE));
4706 }
4707 } else
4708 ce->ce_mtime.sec = de->mtime;
4709 }
4710
4711 #define DEFAULT_DOWNLOAD_THREADS 3
4712
4713 static int
download_files_http(const char * repo_id,int repo_version,const char * worktree,struct index_state * istate,const char * index_path,SeafileCrypt * crypt,HttpTxTask * http_task,GList * results,GHashTable * conflict_hash,GHashTable * no_conflict_hash,const char * conflict_head_id,LockedFileSet * fset)4714 download_files_http (const char *repo_id,
4715 int repo_version,
4716 const char *worktree,
4717 struct index_state *istate,
4718 const char *index_path,
4719 SeafileCrypt *crypt,
4720 HttpTxTask *http_task,
4721 GList *results,
4722 GHashTable *conflict_hash,
4723 GHashTable *no_conflict_hash,
4724 const char *conflict_head_id,
4725 LockedFileSet *fset)
4726 {
4727 struct cache_entry *ce;
4728 DiffEntry *de;
4729 gint64 checkout_size = 0;
4730 GThreadPool *tpool;
4731 GAsyncQueue *finished_tasks;
4732 GHashTable *pending_tasks;
4733 GList *ptr;
4734 FileTxTask *task;
4735 int ret = FETCH_CHECKOUT_SUCCESS;
4736
4737 finished_tasks = g_async_queue_new ();
4738
4739 FileTxData data;
4740 memset (&data, 0, sizeof(data));
4741 memcpy (data.repo_id, repo_id, 36);
4742 data.repo_version = repo_version;
4743 data.crypt = crypt;
4744 data.http_task = http_task;
4745 memcpy (data.conflict_head_id, conflict_head_id, 40);
4746 data.finished_tasks = finished_tasks;
4747
4748 tpool = g_thread_pool_new (fetch_file_thread_func, &data,
4749 DEFAULT_DOWNLOAD_THREADS, FALSE, NULL);
4750
4751 pending_tasks = g_hash_table_new_full (g_str_hash, g_str_equal,
4752 g_free, (GDestroyNotify)file_tx_task_free);
4753
4754 for (ptr = results; ptr != NULL; ptr = ptr->next) {
4755 de = ptr->data;
4756
4757 if (de->status == DIFF_STATUS_DIR_ADDED) {
4758 handle_dir_added_de (repo_id, http_task->repo_name, worktree, istate, de,
4759 conflict_hash, no_conflict_hash);
4760 } else if (de->status == DIFF_STATUS_ADDED ||
4761 de->status == DIFF_STATUS_MODIFIED) {
4762 if (FETCH_CHECKOUT_FAILED == schedule_file_fetch (tpool,
4763 repo_id,
4764 http_task->repo_name,
4765 worktree,
4766 istate,
4767 de,
4768 pending_tasks,
4769 conflict_hash,
4770 no_conflict_hash))
4771 continue;
4772 }
4773 }
4774
4775 /* If there is no file need to be downloaded, return immediately. */
4776 if (g_hash_table_size(pending_tasks) == 0) {
4777 if (results != NULL)
4778 update_index (istate, index_path);
4779 goto out;
4780 }
4781
4782 char file_id[41];
4783 while ((task = g_async_queue_pop (finished_tasks)) != NULL) {
4784 ce = task->ce;
4785 de = task->de;
4786
4787 rawdata_to_hex (de->sha1, file_id, 20);
4788 /* seaf_message ("Finished downloading file %s for repo %s\n", */
4789 /* de->name, repo_id); */
4790
4791 if (task->result == FETCH_CHECKOUT_CANCELED ||
4792 task->result == FETCH_CHECKOUT_TRANSFER_ERROR) {
4793 ret = task->result;
4794 if (task->new_ce)
4795 cache_entry_free (task->ce);
4796 http_task->all_stop = TRUE;
4797 goto out;
4798 }
4799
4800 int rc = checkout_file_http (&data, task, worktree,
4801 conflict_hash, no_conflict_hash,
4802 conflict_head_id, fset);
4803
4804 if (!http_task->is_clone) {
4805 SyncStatus status;
4806 if (rc == FETCH_CHECKOUT_FAILED)
4807 status = SYNC_STATUS_ERROR;
4808 else
4809 status = SYNC_STATUS_SYNCED;
4810 seaf_sync_manager_update_active_path (seaf->sync_mgr,
4811 repo_id,
4812 de->name,
4813 de->mode,
4814 status,
4815 TRUE);
4816 }
4817
4818 if (task->new_ce) {
4819 if (!(ce->ce_flags & CE_REMOVE)) {
4820 add_index_entry (istate, task->ce,
4821 (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE));
4822 }
4823 } else {
4824 ce->ce_mtime.sec = de->mtime;
4825 ce->ce_size = de->size;
4826 memcpy (ce->sha1, de->sha1, 20);
4827 if (ce->modifier) g_free (ce->modifier);
4828 ce->modifier = g_strdup(de->modifier);
4829 ce->ce_mode = create_ce_mode (de->mode);
4830 }
4831
4832 g_hash_table_remove (pending_tasks, de->name);
4833
4834 if (g_hash_table_size (pending_tasks) == 0)
4835 break;
4836
4837 /* Save index file to disk after checking out some size of files.
4838 * This way we don't need to re-compare too many files if this
4839 * checkout is interrupted.
4840 */
4841 checkout_size += ce->ce_size;
4842 if (checkout_size >= UPDATE_CACHE_SIZE_LIMIT) {
4843 update_index (istate, index_path);
4844 checkout_size = 0;
4845 }
4846 }
4847
4848 update_index (istate, index_path);
4849
4850 out:
4851 /* Wait until all threads exit.
4852 * This is necessary when the download is canceled or encountered error.
4853 */
4854 g_thread_pool_free (tpool, TRUE, TRUE);
4855
4856 /* Free all pending file task structs. */
4857 g_hash_table_destroy (pending_tasks);
4858
4859 g_async_queue_unref (finished_tasks);
4860
4861 return ret;
4862 }
4863
4864 static gboolean
expand_dir_added_cb(SeafFSManager * mgr,const char * path,SeafDirent * dent,void * user_data,gboolean * stop)4865 expand_dir_added_cb (SeafFSManager *mgr,
4866 const char *path,
4867 SeafDirent *dent,
4868 void *user_data,
4869 gboolean *stop)
4870 {
4871 GList **expanded = user_data;
4872 DiffEntry *de = NULL;
4873 unsigned char sha1[20];
4874
4875 hex_to_rawdata (dent->id, sha1, 20);
4876
4877 if (S_ISDIR(dent->mode) && strcmp(dent->id, EMPTY_SHA1) == 0)
4878 de = diff_entry_new (DIFF_TYPE_COMMITS, DIFF_STATUS_DIR_ADDED, sha1, path);
4879 else if (S_ISREG(dent->mode))
4880 de = diff_entry_new (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED, sha1, path);
4881
4882 if (de) {
4883 de->mtime = dent->mtime;
4884 de->mode = dent->mode;
4885 de->modifier = g_strdup(dent->modifier);
4886 de->size = dent->size;
4887 *expanded = g_list_prepend (*expanded, de);
4888 }
4889
4890 return TRUE;
4891 }
4892
4893 /*
4894 * Expand DIR_ADDED results into multiple ADDED results.
4895 */
4896 static int
expand_diff_results(const char * repo_id,int version,const char * remote_root,const char * local_root,GList ** results)4897 expand_diff_results (const char *repo_id, int version,
4898 const char *remote_root, const char *local_root,
4899 GList **results)
4900 {
4901 GList *ptr, *next;
4902 DiffEntry *de;
4903 char obj_id[41];
4904 GList *expanded = NULL;
4905
4906 ptr = *results;
4907 while (ptr) {
4908 de = ptr->data;
4909
4910 next = ptr->next;
4911
4912 if (de->status == DIFF_STATUS_DIR_ADDED) {
4913 *results = g_list_delete_link (*results, ptr);
4914
4915 rawdata_to_hex (de->sha1, obj_id, 20);
4916 if (seaf_fs_manager_traverse_path (seaf->fs_mgr,
4917 repo_id, version,
4918 remote_root,
4919 de->name,
4920 expand_dir_added_cb,
4921 &expanded) < 0) {
4922 diff_entry_free (de);
4923 goto error;
4924 }
4925 diff_entry_free (de);
4926 }
4927
4928 ptr = next;
4929 }
4930
4931 expanded = g_list_reverse (expanded);
4932 *results = g_list_concat (*results, expanded);
4933
4934 return 0;
4935
4936 error:
4937 g_list_free_full (expanded, (GDestroyNotify)diff_entry_free);
4938 return -1;
4939 }
4940
4941 static int
do_rename_in_worktree(DiffEntry * de,const char * worktree,GHashTable * conflict_hash,GHashTable * no_conflict_hash)4942 do_rename_in_worktree (DiffEntry *de, const char *worktree,
4943 GHashTable *conflict_hash, GHashTable *no_conflict_hash)
4944 {
4945 char *old_path, *new_path;
4946 int ret = 0;
4947
4948 old_path = g_build_filename (worktree, de->name, NULL);
4949
4950 if (seaf_util_exists (old_path)) {
4951 new_path = build_checkout_path (worktree, de->new_name, strlen(de->new_name));
4952 if (!new_path) {
4953 ret = -1;
4954 goto out;
4955 }
4956
4957 if (seaf_util_rename (old_path, new_path) < 0) {
4958 seaf_warning ("Failed to rename %s to %s: %s.\n",
4959 old_path, new_path, strerror(errno));
4960 ret = -1;
4961 }
4962
4963 g_free (new_path);
4964 }
4965
4966 out:
4967 g_free (old_path);
4968 return ret;
4969 }
4970
4971 static gboolean
is_built_in_ignored_file(const char * filename)4972 is_built_in_ignored_file (const char *filename)
4973 {
4974 GPatternSpec **spec = ignore_patterns;
4975
4976 while (*spec) {
4977 if (g_pattern_match_string(*spec, filename))
4978 return TRUE;
4979 spec++;
4980 }
4981
4982 if (!seaf->sync_extra_temp_file) {
4983 spec = office_temp_ignore_patterns;
4984 while (*spec) {
4985 if (g_pattern_match_string(*spec, filename))
4986 return TRUE;
4987 spec++;
4988 }
4989 }
4990
4991 return FALSE;
4992 }
4993
4994 #ifdef WIN32
4995
4996 /*
4997 * @path: path relative to the worktree, utf-8 encoded
4998 * @path_w: absolute path include worktree, utf-16 encoded.
4999 * Return 0 when successfully deleted the folder; otherwise -1.
5000 */
5001 static int
delete_worktree_dir_recursive_win32(struct index_state * istate,const char * path,const wchar_t * path_w)5002 delete_worktree_dir_recursive_win32 (struct index_state *istate,
5003 const char *path,
5004 const wchar_t *path_w)
5005 {
5006 WIN32_FIND_DATAW fdata;
5007 HANDLE handle;
5008 wchar_t *pattern;
5009 wchar_t *sub_path_w;
5010 char *sub_path, *dname;
5011 int path_len_w;
5012 DWORD error;
5013 int ret = 0;
5014 guint64 mtime;
5015 gboolean builtin_ignored = FALSE;
5016
5017 path_len_w = wcslen(path_w);
5018
5019 pattern = g_new0 (wchar_t, (path_len_w + 3));
5020 wcscpy (pattern, path_w);
5021 wcscat (pattern, L"\\*");
5022
5023 handle = FindFirstFileW (pattern, &fdata);
5024 g_free (pattern);
5025
5026 if (handle == INVALID_HANDLE_VALUE) {
5027 seaf_warning ("FindFirstFile failed %s: %lu.\n",
5028 path, GetLastError());
5029 return -1;
5030 }
5031
5032 do {
5033 if (wcscmp (fdata.cFileName, L".") == 0 ||
5034 wcscmp (fdata.cFileName, L"..") == 0)
5035 continue;
5036
5037 dname = g_utf16_to_utf8 (fdata.cFileName, -1, NULL, NULL, NULL);
5038 if (!dname)
5039 continue;
5040
5041 sub_path_w = g_new0 (wchar_t, path_len_w + wcslen(fdata.cFileName) + 2);
5042 wcscpy (sub_path_w, path_w);
5043 wcscat (sub_path_w, L"\\");
5044 wcscat (sub_path_w, fdata.cFileName);
5045
5046 sub_path = g_strconcat (path, "/", dname, NULL);
5047 builtin_ignored = is_built_in_ignored_file(dname);
5048 g_free (dname);
5049
5050 if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
5051 if (delete_worktree_dir_recursive_win32 (istate, sub_path, sub_path_w) < 0) {
5052 ret = -1;
5053 }
5054 } else {
5055 struct cache_entry *ce;
5056 /* Files like .DS_Store and Thumbs.db should be deleted any way. */
5057 if (!builtin_ignored) {
5058 mtime = (guint64)file_time_to_unix_time (&fdata.ftLastWriteTime);
5059 ce = index_name_exists (istate, sub_path, strlen(sub_path), 0);
5060 if (!ce || (!is_eml_file (dname) && ce->ce_mtime.sec != mtime)) {
5061 seaf_message ("File %s is changed, skip deleting it.\n", sub_path);
5062 g_free (sub_path_w);
5063 g_free (sub_path);
5064 ret = -1;
5065 continue;
5066 }
5067 }
5068
5069 if (!DeleteFileW (sub_path_w)) {
5070 error = GetLastError();
5071 seaf_warning ("Failed to delete file %s: %lu.\n",
5072 sub_path, error);
5073 ret = -1;
5074 }
5075 }
5076
5077 g_free (sub_path_w);
5078 g_free (sub_path);
5079 } while (FindNextFileW (handle, &fdata) != 0);
5080
5081 error = GetLastError();
5082 if (error != ERROR_NO_MORE_FILES) {
5083 seaf_warning ("FindNextFile failed %s: %lu.\n",
5084 path, error);
5085 ret = -1;
5086 }
5087
5088 FindClose (handle);
5089
5090 if (ret < 0)
5091 return ret;
5092
5093 int n = 0;
5094 while (!RemoveDirectoryW (path_w)) {
5095 error = GetLastError();
5096 seaf_warning ("Failed to remove dir %s: %lu.\n",
5097 path, error);
5098 if (error != ERROR_DIR_NOT_EMPTY) {
5099 ret = -1;
5100 break;
5101 }
5102 if (++n >= 3) {
5103 ret = -1;
5104 break;
5105 }
5106 /* Sleep 100ms and retry. */
5107 g_usleep (100000);
5108 seaf_warning ("Retry remove dir %s.\n", path);
5109 }
5110
5111 return ret;
5112 }
5113
5114 #else
5115
5116 static int
delete_worktree_dir_recursive(struct index_state * istate,const char * path,const char * full_path)5117 delete_worktree_dir_recursive (struct index_state *istate,
5118 const char *path,
5119 const char *full_path)
5120 {
5121 GDir *dir;
5122 const char *dname;
5123 char *dname_nfc;
5124 GError *error = NULL;
5125 char *sub_path, *full_sub_path;
5126 SeafStat st;
5127 int ret = 0;
5128 gboolean builtin_ignored = FALSE;
5129
5130 dir = g_dir_open (full_path, 0, &error);
5131 if (!dir) {
5132 seaf_warning ("Failed to open dir %s: %s.\n", full_path, error->message);
5133 return -1;
5134 }
5135
5136 while ((dname = g_dir_read_name (dir)) != NULL) {
5137 dname_nfc = g_utf8_normalize (dname, -1, G_NORMALIZE_NFC);
5138 sub_path = g_build_path ("/", path, dname_nfc, NULL);
5139 full_sub_path = g_build_path ("/", full_path, dname_nfc, NULL);
5140 builtin_ignored = is_built_in_ignored_file (dname_nfc);
5141 g_free (dname_nfc);
5142
5143 if (lstat (full_sub_path, &st) < 0) {
5144 seaf_warning ("Failed to stat %s.\n", full_sub_path);
5145 g_free (sub_path);
5146 g_free (full_sub_path);
5147 ret = -1;
5148 continue;
5149 }
5150
5151 if (S_ISDIR(st.st_mode)) {
5152 if (delete_worktree_dir_recursive (istate, sub_path, full_sub_path) < 0)
5153 ret = -1;
5154 } else {
5155 struct cache_entry *ce;
5156 /* Files like .DS_Store and Thumbs.db should be deleted any way. */
5157 if (!builtin_ignored) {
5158 ce = index_name_exists (istate, sub_path, strlen(sub_path), 0);
5159 if (!ce || ce->ce_mtime.sec != st.st_mtime) {
5160 seaf_message ("File %s is changed, skip deleting it.\n", full_sub_path);
5161 g_free (sub_path);
5162 g_free (full_sub_path);
5163 ret = -1;
5164 continue;
5165 }
5166 }
5167
5168 /* Delete all other file types. */
5169 if (seaf_util_unlink (full_sub_path) < 0) {
5170 seaf_warning ("Failed to delete file %s: %s.\n",
5171 full_sub_path, strerror(errno));
5172 ret = -1;
5173 }
5174 }
5175
5176 g_free (sub_path);
5177 g_free (full_sub_path);
5178 }
5179
5180 g_dir_close (dir);
5181
5182 if (ret < 0)
5183 return ret;
5184
5185 if (g_rmdir (full_path) < 0) {
5186 seaf_warning ("Failed to delete dir %s: %s.\n", full_path, strerror(errno));
5187 ret = -1;
5188 }
5189
5190 return ret;
5191 }
5192
5193 #endif /* WIN32 */
5194
5195 #define SEAFILE_RECYCLE_BIN_FOLDER "recycle-bin"
5196
5197 static int
move_dir_to_recycle_bin(const char * dir_path)5198 move_dir_to_recycle_bin (const char *dir_path)
5199 {
5200 char *trash_folder = g_build_path ("/", seaf->worktree_dir, SEAFILE_RECYCLE_BIN_FOLDER, NULL);
5201 if (checkdir_with_mkdir (trash_folder) < 0) {
5202 seaf_warning ("Seafile trash folder %s doesn't exist and cannot be created.\n",
5203 trash_folder);
5204 g_free (trash_folder);
5205 return -1;
5206 }
5207 g_free (trash_folder);
5208
5209 char *basename = g_path_get_basename (dir_path);
5210 char *dst_path = g_build_path ("/", seaf->worktree_dir, SEAFILE_RECYCLE_BIN_FOLDER, basename, NULL);
5211 int ret = 0;
5212
5213 int n;
5214 char *tmp_path;
5215 for (n = 1; n < 10; ++n) {
5216 if (g_file_test (dst_path, G_FILE_TEST_EXISTS)) {
5217 tmp_path = g_strdup_printf ("%s(%d)", dst_path, n);
5218 g_free (dst_path);
5219 dst_path = tmp_path;
5220 continue;
5221 }
5222 break;
5223 }
5224
5225 if (seaf_util_rename (dir_path, dst_path) < 0) {
5226 seaf_warning ("Failed to move %s to Seafile recycle bin %s: %s\n",
5227 dir_path, dst_path, strerror(errno));
5228 ret = -1;
5229 goto out;
5230 }
5231
5232 seaf_message ("Moved folder %s to Seafile recyle bin %s.\n",
5233 dir_path, dst_path);
5234
5235 out:
5236 g_free (basename);
5237 g_free (dst_path);
5238 return ret;
5239 }
5240
5241 static void
delete_worktree_dir(const char * repo_id,const char * repo_name,struct index_state * istate,const char * worktree,const char * path)5242 delete_worktree_dir (const char *repo_id,
5243 const char *repo_name,
5244 struct index_state *istate,
5245 const char *worktree,
5246 const char *path)
5247 {
5248 char *full_path = g_build_path ("/", worktree, path, NULL);
5249
5250 #ifdef WIN32
5251 wchar_t *full_path_w = win32_long_path (full_path);
5252 delete_worktree_dir_recursive_win32 (istate, path, full_path_w);
5253 g_free (full_path_w);
5254 #else
5255 delete_worktree_dir_recursive(istate, path, full_path);
5256 #endif
5257
5258 /* If for some reason the dir cannot be removed, try to move it to a trash folder
5259 * under Seafile folder. Otherwise the removed folder will be created agian on the
5260 * server, which will confuse the users.
5261 */
5262 if (g_file_test (full_path, G_FILE_TEST_EXISTS)) {
5263 if (move_dir_to_recycle_bin (full_path) == 0)
5264 send_file_sync_error_notification (repo_id, repo_name, path,
5265 SYNC_ERROR_ID_REMOVE_UNCOMMITTED_FOLDER);
5266 }
5267
5268 g_free (full_path);
5269 }
5270
5271 static void
update_sync_status(struct cache_entry * ce,void * user_data)5272 update_sync_status (struct cache_entry *ce, void *user_data)
5273 {
5274 char *repo_id = user_data;
5275
5276 seaf_sync_manager_update_active_path (seaf->sync_mgr,
5277 repo_id,
5278 ce->name,
5279 ce->ce_mode,
5280 SYNC_STATUS_SYNCED,
5281 TRUE);
5282 }
5283
5284 #ifdef WIN32
5285 static int
convert_rename_to_checkout(const char * repo_id,int repo_version,const char * root_id,DiffEntry * de,GList ** entries)5286 convert_rename_to_checkout (const char *repo_id,
5287 int repo_version,
5288 const char *root_id,
5289 DiffEntry *de,
5290 GList **entries)
5291 {
5292 if (de->status == DIFF_STATUS_RENAMED) {
5293 char file_id[41];
5294 SeafDirent *dent = NULL;
5295 DiffEntry *new_de = NULL;
5296
5297 rawdata_to_hex (de->sha1, file_id, 20);
5298 dent = seaf_fs_manager_get_dirent_by_path (seaf->fs_mgr,
5299 repo_id,
5300 repo_version,
5301 root_id,
5302 de->new_name,
5303 NULL);
5304 if (!dent) {
5305 seaf_warning ("Failed to find %s in repo %s\n",
5306 de->new_name, repo_id);
5307 return -1;
5308 }
5309
5310 new_de = diff_entry_new (DIFF_TYPE_COMMITS, DIFF_STATUS_ADDED,
5311 de->sha1, de->new_name);
5312 if (new_de) {
5313 new_de->mtime = dent->mtime;
5314 new_de->mode = dent->mode;
5315 new_de->modifier = g_strdup(dent->modifier);
5316 new_de->size = dent->size;
5317 *entries = g_list_prepend (*entries, new_de);
5318 }
5319
5320 seaf_dirent_free (dent);
5321 } else if (de->status == DIFF_STATUS_DIR_RENAMED) {
5322 GList *expanded = NULL;
5323
5324 if (seaf_fs_manager_traverse_path (seaf->fs_mgr,
5325 repo_id, repo_version,
5326 root_id,
5327 de->new_name,
5328 expand_dir_added_cb,
5329 &expanded) < 0) {
5330 g_list_free_full (expanded, (GDestroyNotify)diff_entry_free);
5331 return -1;
5332 }
5333
5334 *entries = g_list_concat (*entries, expanded);
5335 }
5336
5337 return 0;
5338 }
5339 #endif /* WIN32 */
5340
5341 int
seaf_repo_fetch_and_checkout(HttpTxTask * http_task,const char * remote_head_id)5342 seaf_repo_fetch_and_checkout (HttpTxTask *http_task, const char *remote_head_id)
5343 {
5344 char *repo_id;
5345 int repo_version;
5346 gboolean is_clone;
5347 char *worktree;
5348 char *passwd;
5349
5350 SeafRepo *repo = NULL;
5351 SeafBranch *master = NULL;
5352 SeafCommit *remote_head = NULL, *master_head = NULL;
5353 char index_path[SEAF_PATH_MAX];
5354 struct index_state istate;
5355 int ret = FETCH_CHECKOUT_SUCCESS;
5356 GList *results = NULL;
5357 SeafileCrypt *crypt = NULL;
5358 GHashTable *conflict_hash = NULL, *no_conflict_hash = NULL;
5359 GList *ignore_list = NULL;
5360 LockedFileSet *fset = NULL;
5361
5362 repo_id = http_task->repo_id;
5363 repo_version = http_task->repo_version;
5364 is_clone = http_task->is_clone;
5365 worktree = http_task->worktree;
5366 passwd = http_task->passwd;
5367
5368 memset (&istate, 0, sizeof(istate));
5369 snprintf (index_path, SEAF_PATH_MAX, "%s/%s",
5370 seaf->repo_mgr->index_dir, repo_id);
5371 if (read_index_from (&istate, index_path, repo_version) < 0) {
5372 seaf_warning ("Failed to load index.\n");
5373 return FETCH_CHECKOUT_FAILED;
5374 }
5375
5376 if (!is_clone) {
5377 repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id);
5378 if (!repo) {
5379 seaf_warning ("Failed to get repo %.8s.\n", repo_id);
5380 goto out;
5381 }
5382
5383 master = seaf_branch_manager_get_branch (seaf->branch_mgr,
5384 repo_id, "master");
5385 if (!master) {
5386 seaf_warning ("Failed to get master branch for repo %.8s.\n",
5387 repo_id);
5388 ret = FETCH_CHECKOUT_FAILED;
5389 goto out;
5390 }
5391
5392 master_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
5393 repo_id,
5394 repo_version,
5395 master->commit_id);
5396 if (!master_head) {
5397 seaf_warning ("Failed to get master head %s of repo %.8s.\n",
5398 repo_id, master->commit_id);
5399 ret = FETCH_CHECKOUT_FAILED;
5400 goto out;
5401 }
5402 }
5403
5404 if (!is_clone)
5405 worktree = repo->worktree;
5406
5407 remote_head = seaf_commit_manager_get_commit (seaf->commit_mgr,
5408 repo_id,
5409 repo_version,
5410 remote_head_id);
5411 if (!remote_head) {
5412 seaf_warning ("Failed to get remote head %s of repo %.8s.\n",
5413 repo_id, remote_head_id);
5414 ret = FETCH_CHECKOUT_FAILED;
5415 goto out;
5416 }
5417
5418 if (diff_commit_roots (repo_id, repo_version,
5419 master_head ? master_head->root_id : EMPTY_SHA1,
5420 remote_head->root_id,
5421 &results, TRUE) < 0) {
5422 seaf_warning ("Failed to diff for repo %.8s.\n", repo_id);
5423 ret = FETCH_CHECKOUT_FAILED;
5424 goto out;
5425 }
5426
5427 GList *ptr;
5428 DiffEntry *de;
5429
5430 /* Expand DIR_ADDED diff entries. */
5431 if (expand_diff_results (repo_id, repo_version,
5432 remote_head->root_id,
5433 master_head ? master_head->root_id : EMPTY_SHA1,
5434 &results) < 0) {
5435 ret = FETCH_CHECKOUT_FAILED;
5436 goto out;
5437 }
5438
5439 #ifdef WIN32
5440 for (ptr = results; ptr; ptr = ptr->next) {
5441 de = ptr->data;
5442 if (de->status == DIFF_STATUS_DIR_RENAMED ||
5443 de->status == DIFF_STATUS_DIR_DELETED) {
5444 if (do_check_dir_locked (de->name, worktree)) {
5445 seaf_message ("File(s) in dir %s are locked by other program, "
5446 "skip rename/delete.\n", de->name);
5447 send_file_sync_error_notification (repo_id, NULL, de->name,
5448 SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP);
5449 ret = FETCH_CHECKOUT_LOCKED;
5450 goto out;
5451 }
5452 } else if (de->status == DIFF_STATUS_RENAMED) {
5453 gboolean locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
5454 repo_id,
5455 de->name);
5456
5457 if (do_check_file_locked (de->name, worktree, locked_on_server)) {
5458 seaf_message ("File %s is locked by other program, skip rename.\n",
5459 de->name);
5460 send_file_sync_error_notification (repo_id, NULL, de->name,
5461 SYNC_ERROR_ID_FILE_LOCKED_BY_APP);
5462 ret = FETCH_CHECKOUT_LOCKED;
5463 goto out;
5464 }
5465 }
5466 }
5467 #endif
5468
5469 if (remote_head->encrypted) {
5470 if (!is_clone) {
5471 crypt = seafile_crypt_new (repo->enc_version,
5472 repo->enc_key,
5473 repo->enc_iv);
5474 } else {
5475 unsigned char enc_key[32], enc_iv[16];
5476 seafile_decrypt_repo_enc_key (remote_head->enc_version,
5477 passwd,
5478 remote_head->random_key,
5479 remote_head->salt,
5480 enc_key, enc_iv);
5481 crypt = seafile_crypt_new (remote_head->enc_version,
5482 enc_key, enc_iv);
5483 }
5484 }
5485
5486 conflict_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
5487 g_free, g_free);
5488 no_conflict_hash = g_hash_table_new_full (g_str_hash, g_str_equal,
5489 g_free, NULL);
5490
5491 ignore_list = seaf_repo_load_ignore_files (worktree);
5492
5493 struct cache_entry *ce;
5494
5495 #if defined WIN32 || defined __APPLE__
5496 fset = seaf_repo_manager_get_locked_file_set (seaf->repo_mgr, repo_id);
5497 #endif
5498
5499 for (ptr = results; ptr; ptr = ptr->next) {
5500 de = ptr->data;
5501 if (de->status == DIFF_STATUS_DELETED) {
5502 seaf_debug ("Delete file %s.\n", de->name);
5503
5504 if (should_ignore_on_checkout (de->name, NULL)) {
5505 seaf_message ("Path %s is invalid on Windows, skip delete.\n",
5506 de->name);
5507 continue;
5508 }
5509
5510 ce = index_name_exists (&istate, de->name, strlen(de->name), 0);
5511 if (!ce)
5512 continue;
5513
5514 gboolean locked_on_server = seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
5515 repo_id,
5516 de->name);
5517 if (locked_on_server)
5518 seaf_filelock_manager_unlock_wt_file (seaf->filelock_mgr,
5519 repo_id, de->name);
5520
5521 #if defined WIN32 || defined __APPLE__
5522 if (!do_check_file_locked (de->name, worktree, locked_on_server)) {
5523 locked_file_set_remove (fset, de->name, FALSE);
5524 delete_path (worktree, de->name, de->mode, ce->ce_mtime.sec);
5525 } else {
5526 if (!locked_file_set_lookup (fset, de->name))
5527 send_file_sync_error_notification (repo_id, http_task->repo_name, de->name,
5528 SYNC_ERROR_ID_FILE_LOCKED_BY_APP);
5529
5530 locked_file_set_add_update (fset, de->name, LOCKED_OP_DELETE,
5531 ce->ce_mtime.sec, NULL);
5532 }
5533 #else
5534 delete_path (worktree, de->name, de->mode, ce->ce_mtime.sec);
5535 #endif
5536
5537 /* No need to lock wt file again since it's deleted. */
5538
5539 remove_from_index_with_prefix (&istate, de->name, NULL);
5540 try_add_empty_parent_dir_entry (worktree, &istate, de->name);
5541 } else if (de->status == DIFF_STATUS_DIR_DELETED) {
5542 seaf_debug ("Delete dir %s.\n", de->name);
5543
5544 if (should_ignore_on_checkout (de->name, NULL)) {
5545 seaf_message ("Path %s is invalid on Windows, skip delete.\n",
5546 de->name);
5547 continue;
5548 }
5549
5550 /* Nothing to delete. */
5551 if (!master_head || strcmp(master_head->root_id, EMPTY_SHA1) == 0)
5552 continue;
5553
5554 delete_worktree_dir (repo_id, http_task->repo_name, &istate, worktree, de->name);
5555
5556 /* Remove all index entries under this directory */
5557 remove_from_index_with_prefix (&istate, de->name, NULL);
5558
5559 try_add_empty_parent_dir_entry (worktree, &istate, de->name);
5560 }
5561 }
5562
5563 for (ptr = results; ptr; ptr = ptr->next) {
5564 de = ptr->data;
5565 if (de->status == DIFF_STATUS_RENAMED ||
5566 de->status == DIFF_STATUS_DIR_RENAMED) {
5567 seaf_debug ("Rename %s to %s.\n", de->name, de->new_name);
5568
5569 #ifdef WIN32
5570 IgnoreReason reason;
5571 if (should_ignore_on_checkout (de->new_name, &reason)) {
5572 seaf_message ("Path %s is invalid on Windows, skip rename.\n", de->new_name);
5573
5574 if (reason == IGNORE_REASON_END_SPACE_PERIOD)
5575 send_file_sync_error_notification (repo_id, http_task->repo_name,
5576 de->new_name,
5577 SYNC_ERROR_ID_PATH_END_SPACE_PERIOD);
5578 else if (reason == IGNORE_REASON_INVALID_CHARACTER)
5579 send_file_sync_error_notification (repo_id, http_task->repo_name,
5580 de->new_name,
5581 SYNC_ERROR_ID_PATH_INVALID_CHARACTER);
5582 continue;
5583 } else if (should_ignore_on_checkout (de->name, NULL)) {
5584 /* If the server renames an invalid path to a valid path,
5585 * directly checkout the valid path. The checkout will merge
5586 * with any existing files.
5587 */
5588 convert_rename_to_checkout (repo_id, repo_version,
5589 remote_head->root_id,
5590 de, &results);
5591 continue;
5592 }
5593 #endif
5594
5595 if (seaf_filelock_manager_is_file_locked (seaf->filelock_mgr,
5596 repo_id, de->name))
5597 seaf_filelock_manager_unlock_wt_file (seaf->filelock_mgr,
5598 repo_id, de->name);
5599
5600 do_rename_in_worktree (de, worktree, conflict_hash, no_conflict_hash);
5601
5602 /* update_sync_status updates the sync status for each renamed path.
5603 * The renamed file/folder becomes "synced" immediately after rename.
5604 */
5605 if (!is_clone)
5606 rename_index_entries (&istate, de->name, de->new_name, NULL,
5607 update_sync_status, repo_id);
5608 else
5609 rename_index_entries (&istate, de->name, de->new_name, NULL,
5610 NULL, NULL);
5611
5612 /* Moving files out of a dir may make it empty. */
5613 try_add_empty_parent_dir_entry (worktree, &istate, de->name);
5614 }
5615 }
5616
5617 if (istate.cache_changed)
5618 update_index (&istate, index_path);
5619
5620 for (ptr = results; ptr; ptr = ptr->next) {
5621 de = ptr->data;
5622 if (de->status == DIFF_STATUS_ADDED || de->status == DIFF_STATUS_MODIFIED) {
5623 http_task->total_download += de->size;
5624 }
5625 }
5626
5627 ret = download_files_http (repo_id,
5628 repo_version,
5629 worktree,
5630 &istate,
5631 index_path,
5632 crypt,
5633 http_task,
5634 results,
5635 conflict_hash,
5636 no_conflict_hash,
5637 remote_head_id,
5638 fset);
5639
5640 out:
5641 discard_index (&istate);
5642
5643 seaf_branch_unref (master);
5644 seaf_commit_unref (master_head);
5645 seaf_commit_unref (remote_head);
5646
5647 g_list_free_full (results, (GDestroyNotify)diff_entry_free);
5648
5649 g_free (crypt);
5650 if (conflict_hash)
5651 g_hash_table_destroy (conflict_hash);
5652 if (no_conflict_hash)
5653 g_hash_table_destroy (no_conflict_hash);
5654
5655 if (ignore_list)
5656 seaf_repo_free_ignore_files (ignore_list);
5657
5658 #if defined WIN32 || defined __APPLE__
5659 locked_file_set_free (fset);
5660 #endif
5661
5662 return ret;
5663 }
5664
5665 int
seaf_repo_manager_set_repo_worktree(SeafRepoManager * mgr,SeafRepo * repo,const char * worktree)5666 seaf_repo_manager_set_repo_worktree (SeafRepoManager *mgr,
5667 SeafRepo *repo,
5668 const char *worktree)
5669 {
5670 if (g_access(worktree, F_OK) != 0)
5671 return -1;
5672
5673 if (repo->worktree)
5674 g_free (repo->worktree);
5675 repo->worktree = g_strdup(worktree);
5676
5677 if (seaf_repo_manager_set_repo_property (mgr, repo->id,
5678 "worktree",
5679 repo->worktree) < 0)
5680 return -1;
5681
5682 repo->worktree_invalid = FALSE;
5683
5684 return 0;
5685 }
5686
5687 void
seaf_repo_manager_invalidate_repo_worktree(SeafRepoManager * mgr,SeafRepo * repo)5688 seaf_repo_manager_invalidate_repo_worktree (SeafRepoManager *mgr,
5689 SeafRepo *repo)
5690 {
5691 if (repo->worktree_invalid)
5692 return;
5693
5694 repo->worktree_invalid = TRUE;
5695
5696 if (repo->auto_sync && (repo->sync_interval == 0)) {
5697 if (seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id) < 0) {
5698 seaf_warning ("failed to unwatch repo %s.\n", repo->id);
5699 }
5700 }
5701 }
5702
5703 void
seaf_repo_manager_validate_repo_worktree(SeafRepoManager * mgr,SeafRepo * repo)5704 seaf_repo_manager_validate_repo_worktree (SeafRepoManager *mgr,
5705 SeafRepo *repo)
5706 {
5707 if (!repo->worktree_invalid)
5708 return;
5709
5710 repo->worktree_invalid = FALSE;
5711
5712 if (repo->auto_sync && (repo->sync_interval == 0)) {
5713 if (seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree) < 0) {
5714 seaf_warning ("failed to watch repo %s.\n", repo->id);
5715 }
5716 }
5717 }
5718
5719 SeafRepoManager*
seaf_repo_manager_new(SeafileSession * seaf)5720 seaf_repo_manager_new (SeafileSession *seaf)
5721 {
5722 SeafRepoManager *mgr = g_new0 (SeafRepoManager, 1);
5723
5724 mgr->priv = g_new0 (SeafRepoManagerPriv, 1);
5725 mgr->seaf = seaf;
5726 mgr->index_dir = g_build_path (PATH_SEPERATOR, seaf->seaf_dir, INDEX_DIR, NULL);
5727
5728 pthread_mutex_init (&mgr->priv->db_lock, NULL);
5729
5730 mgr->priv->checkout_tasks_hash = g_hash_table_new_full
5731 (g_str_hash, g_str_equal, g_free, g_free);
5732
5733 ignore_patterns = g_new0 (GPatternSpec*, G_N_ELEMENTS(ignore_table));
5734 int i;
5735 for (i = 0; ignore_table[i] != NULL; i++) {
5736 ignore_patterns[i] = g_pattern_spec_new (ignore_table[i]);
5737 }
5738
5739 office_temp_ignore_patterns[0] = g_pattern_spec_new("~$*");
5740 /* for files like ~WRL0001.tmp for docx and *.tmp for xlsx and pptx */
5741 office_temp_ignore_patterns[1] = g_pattern_spec_new("*.tmp");
5742 office_temp_ignore_patterns[2] = g_pattern_spec_new(".~lock*#");
5743 office_temp_ignore_patterns[3] = NULL;
5744
5745 GError *error = NULL;
5746 conflict_pattern = g_regex_new (CONFLICT_PATTERN, 0, 0, &error);
5747 if (error) {
5748 seaf_warning ("Failed to create regex '%s': %s\n",
5749 CONFLICT_PATTERN, error->message);
5750 g_clear_error (&error);
5751 }
5752
5753 office_lock_pattern = g_regex_new (OFFICE_LOCK_PATTERN, 0, 0, &error);
5754 if (error) {
5755 seaf_warning ("Failed to create regex '%s': %s\n",
5756 OFFICE_LOCK_PATTERN, error->message);
5757 g_clear_error (&error);
5758 }
5759
5760 mgr->priv->repo_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
5761
5762 pthread_rwlock_init (&mgr->priv->lock, NULL);
5763
5764 mgr->priv->lock_office_job_queue = g_async_queue_new ();
5765
5766 return mgr;
5767 }
5768
5769 int
seaf_repo_manager_init(SeafRepoManager * mgr)5770 seaf_repo_manager_init (SeafRepoManager *mgr)
5771 {
5772 if (checkdir_with_mkdir (mgr->index_dir) < 0) {
5773 seaf_warning ("Index dir %s does not exist and is unable to create\n",
5774 mgr->index_dir);
5775 return -1;
5776 }
5777
5778 /* Load all the repos into memory on the client side. */
5779 load_repos (mgr, mgr->seaf->seaf_dir);
5780
5781 /* Load folder permissions from db. */
5782 init_folder_perms (mgr);
5783
5784 return 0;
5785 }
5786
5787 static void
watch_repos(SeafRepoManager * mgr)5788 watch_repos (SeafRepoManager *mgr)
5789 {
5790 GHashTableIter iter;
5791 SeafRepo *repo;
5792 gpointer key, value;
5793
5794 g_hash_table_iter_init (&iter, mgr->priv->repo_hash);
5795 while (g_hash_table_iter_next (&iter, &key, &value)) {
5796 repo = value;
5797 if (repo->auto_sync && !repo->worktree_invalid && (repo->sync_interval == 0)) {
5798 if (seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id, repo->worktree) < 0) {
5799 seaf_warning ("failed to watch repo %s.\n", repo->id);
5800 /* If we fail to add watch at the beginning, sync manager
5801 * will periodically check repo status and retry.
5802 */
5803 }
5804 }
5805 }
5806 }
5807
5808 #define REMOVE_OBJECTS_BATCH 1000
5809
5810 static int
remove_store(const char * top_store_dir,const char * store_id,int * count)5811 remove_store (const char *top_store_dir, const char *store_id, int *count)
5812 {
5813 char *obj_dir = NULL;
5814 GDir *dir1, *dir2;
5815 const char *dname1, *dname2;
5816 char *path1, *path2;
5817
5818 obj_dir = g_build_filename (top_store_dir, store_id, NULL);
5819
5820 dir1 = g_dir_open (obj_dir, 0, NULL);
5821 if (!dir1) {
5822 g_free (obj_dir);
5823 return 0;
5824 }
5825
5826 seaf_message ("Removing store %s\n", obj_dir);
5827
5828 while ((dname1 = g_dir_read_name(dir1)) != NULL) {
5829 path1 = g_build_filename (obj_dir, dname1, NULL);
5830
5831 dir2 = g_dir_open (path1, 0, NULL);
5832 if (!dir2) {
5833 seaf_warning ("Failed to open obj dir %s.\n", path1);
5834 g_dir_close (dir1);
5835 g_free (path1);
5836 g_free (obj_dir);
5837 return -1;
5838 }
5839
5840 while ((dname2 = g_dir_read_name(dir2)) != NULL) {
5841 path2 = g_build_filename (path1, dname2, NULL);
5842 g_unlink (path2);
5843
5844 /* To prevent using too much IO, only remove 1000 objects per 5 seconds.
5845 */
5846 if (++(*count) > REMOVE_OBJECTS_BATCH) {
5847 g_usleep (5 * G_USEC_PER_SEC);
5848 *count = 0;
5849 }
5850
5851 g_free (path2);
5852 }
5853 g_dir_close (dir2);
5854
5855 g_rmdir (path1);
5856 g_free (path1);
5857 }
5858
5859 g_dir_close (dir1);
5860 g_rmdir (obj_dir);
5861 g_free (obj_dir);
5862
5863 return 0;
5864 }
5865
5866 static void
cleanup_deleted_stores_by_type(const char * type)5867 cleanup_deleted_stores_by_type (const char *type)
5868 {
5869 char *top_store_dir;
5870 const char *repo_id;
5871
5872 top_store_dir = g_build_filename (seaf->seaf_dir, "deleted_store", type, NULL);
5873
5874 GError *error = NULL;
5875 GDir *dir = g_dir_open (top_store_dir, 0, &error);
5876 if (!dir) {
5877 seaf_warning ("Failed to open store dir %s: %s.\n",
5878 top_store_dir, error->message);
5879 g_free (top_store_dir);
5880 return;
5881 }
5882
5883 int count = 0;
5884 while ((repo_id = g_dir_read_name(dir)) != NULL) {
5885 remove_store (top_store_dir, repo_id, &count);
5886 }
5887
5888 g_free (top_store_dir);
5889 g_dir_close (dir);
5890 }
5891
5892 static void *
cleanup_deleted_stores(void * vdata)5893 cleanup_deleted_stores (void *vdata)
5894 {
5895 while (1) {
5896 cleanup_deleted_stores_by_type ("commits");
5897 cleanup_deleted_stores_by_type ("fs");
5898 cleanup_deleted_stores_by_type ("blocks");
5899 g_usleep (60 * G_USEC_PER_SEC);
5900 }
5901 return NULL;
5902 }
5903
5904 int
seaf_repo_manager_start(SeafRepoManager * mgr)5905 seaf_repo_manager_start (SeafRepoManager *mgr)
5906 {
5907 pthread_t tid;
5908 pthread_attr_t attr;
5909 pthread_attr_init(&attr);
5910 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
5911 int rc;
5912
5913 watch_repos (mgr);
5914
5915 rc = pthread_create (&tid, &attr, cleanup_deleted_stores, NULL);
5916 if (rc != 0) {
5917 seaf_warning ("Failed to start cleanup thread: %s\n", strerror(rc));
5918 }
5919
5920 #if defined WIN32 || defined __APPLE__
5921 rc = pthread_create (&tid, &attr, lock_office_file_worker,
5922 mgr->priv->lock_office_job_queue);
5923 if (rc != 0) {
5924 seaf_warning ("Failed to start lock office file thread: %s\n", strerror(rc));
5925 }
5926 #endif
5927
5928 return 0;
5929 }
5930
5931 SeafRepo*
seaf_repo_manager_create_new_repo(SeafRepoManager * mgr,const char * name,const char * desc)5932 seaf_repo_manager_create_new_repo (SeafRepoManager *mgr,
5933 const char *name,
5934 const char *desc)
5935 {
5936 SeafRepo *repo;
5937 char *repo_id;
5938
5939 repo_id = gen_uuid ();
5940 repo = seaf_repo_new (repo_id, name, desc);
5941 if (!repo) {
5942 g_free (repo_id);
5943 return NULL;
5944 }
5945 g_free (repo_id);
5946
5947 /* we directly create dir because it shouldn't exist */
5948 /* if (seaf_repo_mkdir (repo, base) < 0) { */
5949 /* seaf_repo_free (repo); */
5950 /* goto out; */
5951 /* } */
5952
5953 seaf_repo_manager_add_repo (mgr, repo);
5954 return repo;
5955 }
5956
5957 int
seaf_repo_manager_add_repo(SeafRepoManager * manager,SeafRepo * repo)5958 seaf_repo_manager_add_repo (SeafRepoManager *manager,
5959 SeafRepo *repo)
5960 {
5961 char sql[256];
5962 sqlite3 *db = manager->priv->db;
5963
5964 pthread_mutex_lock (&manager->priv->db_lock);
5965
5966 snprintf (sql, sizeof(sql), "REPLACE INTO Repo VALUES ('%s');", repo->id);
5967 sqlite_query_exec (db, sql);
5968
5969 pthread_mutex_unlock (&manager->priv->db_lock);
5970
5971 /* There may be a "deletion record" for this repo when it was deleted
5972 * last time.
5973 */
5974 seaf_repo_manager_remove_garbage_repo (manager, repo->id);
5975
5976 repo->manager = manager;
5977
5978 if (pthread_rwlock_wrlock (&manager->priv->lock) < 0) {
5979 seaf_warning ("[repo mgr] failed to lock repo cache.\n");
5980 return -1;
5981 }
5982
5983 g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo);
5984
5985 pthread_rwlock_unlock (&manager->priv->lock);
5986
5987 return 0;
5988 }
5989
5990 int
seaf_repo_manager_mark_repo_deleted(SeafRepoManager * mgr,SeafRepo * repo)5991 seaf_repo_manager_mark_repo_deleted (SeafRepoManager *mgr, SeafRepo *repo)
5992 {
5993 char sql[256];
5994
5995 pthread_mutex_lock (&mgr->priv->db_lock);
5996
5997 snprintf (sql, sizeof(sql), "INSERT INTO DeletedRepo VALUES ('%s')",
5998 repo->id);
5999 if (sqlite_query_exec (mgr->priv->db, sql) < 0) {
6000 pthread_mutex_unlock (&mgr->priv->db_lock);
6001 return -1;
6002 }
6003
6004 pthread_mutex_unlock (&mgr->priv->db_lock);
6005
6006 repo->delete_pending = TRUE;
6007
6008 return 0;
6009 }
6010
6011 static gboolean
get_garbage_repo_id(sqlite3_stmt * stmt,void * vid_list)6012 get_garbage_repo_id (sqlite3_stmt *stmt, void *vid_list)
6013 {
6014 GList **ret = vid_list;
6015 char *repo_id;
6016
6017 repo_id = g_strdup((const char *)sqlite3_column_text (stmt, 0));
6018 *ret = g_list_prepend (*ret, repo_id);
6019
6020 return TRUE;
6021 }
6022
6023 GList *
seaf_repo_manager_list_garbage_repos(SeafRepoManager * mgr)6024 seaf_repo_manager_list_garbage_repos (SeafRepoManager *mgr)
6025 {
6026 GList *repo_ids = NULL;
6027
6028 pthread_mutex_lock (&mgr->priv->db_lock);
6029
6030 sqlite_foreach_selected_row (mgr->priv->db,
6031 "SELECT repo_id FROM GarbageRepos",
6032 get_garbage_repo_id, &repo_ids);
6033 pthread_mutex_unlock (&mgr->priv->db_lock);
6034
6035 return repo_ids;
6036 }
6037
6038 void
seaf_repo_manager_remove_garbage_repo(SeafRepoManager * mgr,const char * repo_id)6039 seaf_repo_manager_remove_garbage_repo (SeafRepoManager *mgr, const char *repo_id)
6040 {
6041 char sql[256];
6042
6043 pthread_mutex_lock (&mgr->priv->db_lock);
6044
6045 snprintf (sql, sizeof(sql), "DELETE FROM GarbageRepos WHERE repo_id='%s'",
6046 repo_id);
6047 sqlite_query_exec (mgr->priv->db, sql);
6048
6049 pthread_mutex_unlock (&mgr->priv->db_lock);
6050 }
6051
6052 void
seaf_repo_manager_remove_repo_ondisk(SeafRepoManager * mgr,const char * repo_id,gboolean add_deleted_record)6053 seaf_repo_manager_remove_repo_ondisk (SeafRepoManager *mgr,
6054 const char *repo_id,
6055 gboolean add_deleted_record)
6056 {
6057 char sql[256];
6058
6059 /* We don't need to care about I/O errors here, since we can
6060 * GC any unreferenced repo data later.
6061 */
6062
6063 if (add_deleted_record) {
6064 snprintf (sql, sizeof(sql), "REPLACE INTO GarbageRepos VALUES ('%s')",
6065 repo_id);
6066 if (sqlite_query_exec (mgr->priv->db, sql) < 0)
6067 goto out;
6068 }
6069
6070 /* Once the item in Repo table is deleted, the repo is gone.
6071 * This is the "commit point".
6072 */
6073 pthread_mutex_lock (&mgr->priv->db_lock);
6074
6075 snprintf (sql, sizeof(sql), "DELETE FROM Repo WHERE repo_id = '%s'", repo_id);
6076 if (sqlite_query_exec (mgr->priv->db, sql) < 0)
6077 goto out;
6078
6079 snprintf (sql, sizeof(sql),
6080 "DELETE FROM DeletedRepo WHERE repo_id = '%s'", repo_id);
6081 sqlite_query_exec (mgr->priv->db, sql);
6082
6083 pthread_mutex_unlock (&mgr->priv->db_lock);
6084
6085 /* remove index */
6086 char path[SEAF_PATH_MAX];
6087 snprintf (path, SEAF_PATH_MAX, "%s/%s", mgr->index_dir, repo_id);
6088 seaf_util_unlink (path);
6089
6090 /* remove branch */
6091 GList *p;
6092 GList *branch_list =
6093 seaf_branch_manager_get_branch_list (seaf->branch_mgr, repo_id);
6094 for (p = branch_list; p; p = p->next) {
6095 SeafBranch *b = (SeafBranch *)p->data;
6096 seaf_repo_manager_branch_repo_unmap (mgr, b);
6097 seaf_branch_manager_del_branch (seaf->branch_mgr, repo_id, b->name);
6098 }
6099 seaf_branch_list_free (branch_list);
6100
6101 /* delete repo property firstly */
6102 seaf_repo_manager_del_repo_property (mgr, repo_id);
6103
6104 pthread_mutex_lock (&mgr->priv->db_lock);
6105
6106 snprintf (sql, sizeof(sql), "DELETE FROM RepoPasswd WHERE repo_id = '%s'",
6107 repo_id);
6108 sqlite_query_exec (mgr->priv->db, sql);
6109 snprintf (sql, sizeof(sql), "DELETE FROM RepoKeys WHERE repo_id = '%s'",
6110 repo_id);
6111 sqlite_query_exec (mgr->priv->db, sql);
6112
6113 snprintf (sql, sizeof(sql), "DELETE FROM MergeInfo WHERE repo_id = '%s'",
6114 repo_id);
6115 sqlite_query_exec (mgr->priv->db, sql);
6116
6117 snprintf (sql, sizeof(sql), "DELETE FROM LockedFiles WHERE repo_id = '%s'",
6118 repo_id);
6119 sqlite_query_exec (mgr->priv->db, sql);
6120
6121 snprintf (sql, sizeof(sql), "DELETE FROM FolderUserPerms WHERE repo_id = '%s'",
6122 repo_id);
6123 sqlite_query_exec (mgr->priv->db, sql);
6124
6125 snprintf (sql, sizeof(sql), "DELETE FROM FolderGroupPerms WHERE repo_id = '%s'",
6126 repo_id);
6127 sqlite_query_exec (mgr->priv->db, sql);
6128
6129 snprintf (sql, sizeof(sql), "DELETE FROM FolderPermTimestamp WHERE repo_id = '%s'",
6130 repo_id);
6131 sqlite_query_exec (mgr->priv->db, sql);
6132
6133 seaf_filelock_manager_remove (seaf->filelock_mgr, repo_id);
6134
6135 out:
6136 pthread_mutex_unlock (&mgr->priv->db_lock);
6137 }
6138
6139 static char *
gen_deleted_store_path(const char * type,const char * repo_id)6140 gen_deleted_store_path (const char *type, const char *repo_id)
6141 {
6142 int n = 1;
6143 char *path = NULL;
6144 char *name = NULL;
6145
6146 path = g_build_filename (seaf->deleted_store, type, repo_id, NULL);
6147 while (g_access(path, F_OK) == 0 && n < 10) {
6148 g_free (path);
6149 name = g_strdup_printf ("%s(%d)", repo_id, n);
6150 path = g_build_filename (seaf->deleted_store, type, name, NULL);
6151 g_free (name);
6152 ++n;
6153 }
6154
6155 if (n == 10) {
6156 g_free (path);
6157 return NULL;
6158 }
6159
6160 return path;
6161 }
6162
6163 void
seaf_repo_manager_move_repo_store(SeafRepoManager * mgr,const char * type,const char * repo_id)6164 seaf_repo_manager_move_repo_store (SeafRepoManager *mgr,
6165 const char *type,
6166 const char *repo_id)
6167 {
6168 char *src = NULL;
6169 char *dst = NULL;
6170
6171 src = g_build_filename (seaf->seaf_dir, "storage", type, repo_id, NULL);
6172 dst = gen_deleted_store_path (type, repo_id);
6173 if (dst) {
6174 g_rename (src, dst);
6175 }
6176 g_free (src);
6177 g_free (dst);
6178 }
6179
6180 /* Move commits, fs stores into "deleted_store" directory. */
6181 static void
move_repo_stores(SeafRepoManager * mgr,SeafRepo * repo)6182 move_repo_stores (SeafRepoManager *mgr, SeafRepo *repo)
6183 {
6184 seaf_repo_manager_move_repo_store (mgr, "commits", repo->id);
6185 seaf_repo_manager_move_repo_store (mgr, "fs", repo->id);
6186 }
6187
6188 int
seaf_repo_manager_del_repo(SeafRepoManager * mgr,SeafRepo * repo)6189 seaf_repo_manager_del_repo (SeafRepoManager *mgr,
6190 SeafRepo *repo)
6191 {
6192 seaf_repo_manager_remove_repo_ondisk (mgr, repo->id,
6193 (repo->version > 0) ? TRUE : FALSE);
6194
6195 seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id);
6196
6197 remove_folder_perms (mgr, repo->id);
6198
6199 move_repo_stores (mgr, repo);
6200
6201 if (pthread_rwlock_wrlock (&mgr->priv->lock) < 0) {
6202 seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6203 return -1;
6204 }
6205
6206 g_hash_table_remove (mgr->priv->repo_hash, repo->id);
6207
6208 pthread_rwlock_unlock (&mgr->priv->lock);
6209
6210 seaf_repo_free (repo);
6211
6212 return 0;
6213 }
6214
6215 /*
6216 Return the internal Repo in hashtable. The caller should not free the returned Repo.
6217 */
6218 SeafRepo*
seaf_repo_manager_get_repo(SeafRepoManager * manager,const gchar * id)6219 seaf_repo_manager_get_repo (SeafRepoManager *manager, const gchar *id)
6220 {
6221 SeafRepo *res;
6222
6223 if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
6224 seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6225 return NULL;
6226 }
6227
6228 res = g_hash_table_lookup (manager->priv->repo_hash, id);
6229
6230 pthread_rwlock_unlock (&manager->priv->lock);
6231
6232 if (res && !res->delete_pending)
6233 return res;
6234
6235 return NULL;
6236 }
6237
6238 gboolean
seaf_repo_manager_repo_exists(SeafRepoManager * manager,const gchar * id)6239 seaf_repo_manager_repo_exists (SeafRepoManager *manager, const gchar *id)
6240 {
6241 SeafRepo *res;
6242
6243 if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
6244 seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6245 return FALSE;
6246 }
6247
6248 res = g_hash_table_lookup (manager->priv->repo_hash, id);
6249
6250 pthread_rwlock_unlock (&manager->priv->lock);
6251
6252 if (res && !res->delete_pending)
6253 return TRUE;
6254
6255 return FALSE;
6256 }
6257
6258 static int
save_branch_repo_map(SeafRepoManager * manager,SeafBranch * branch)6259 save_branch_repo_map (SeafRepoManager *manager, SeafBranch *branch)
6260 {
6261 char *sql;
6262 sqlite3 *db = manager->priv->db;
6263
6264 pthread_mutex_lock (&manager->priv->db_lock);
6265
6266 sql = sqlite3_mprintf ("REPLACE INTO RepoBranch VALUES (%Q, %Q)",
6267 branch->repo_id, branch->name);
6268 sqlite_query_exec (db, sql);
6269 sqlite3_free (sql);
6270
6271 pthread_mutex_unlock (&manager->priv->db_lock);
6272
6273 return 0;
6274 }
6275
6276 int
seaf_repo_manager_branch_repo_unmap(SeafRepoManager * manager,SeafBranch * branch)6277 seaf_repo_manager_branch_repo_unmap (SeafRepoManager *manager, SeafBranch *branch)
6278 {
6279 char *sql;
6280 sqlite3 *db = manager->priv->db;
6281
6282 pthread_mutex_lock (&manager->priv->db_lock);
6283
6284 sql = sqlite3_mprintf ("DELETE FROM RepoBranch WHERE branch_name = %Q"
6285 " AND repo_id = %Q",
6286 branch->name, branch->repo_id);
6287 if (sqlite_query_exec (db, sql) < 0) {
6288 seaf_warning ("Unmap branch repo failed\n");
6289 pthread_mutex_unlock (&manager->priv->db_lock);
6290 sqlite3_free (sql);
6291 return -1;
6292 }
6293
6294 sqlite3_free (sql);
6295 pthread_mutex_unlock (&manager->priv->db_lock);
6296
6297 return 0;
6298 }
6299
6300 static void
load_repo_commit(SeafRepoManager * manager,SeafRepo * repo,SeafBranch * branch)6301 load_repo_commit (SeafRepoManager *manager,
6302 SeafRepo *repo,
6303 SeafBranch *branch)
6304 {
6305 SeafCommit *commit;
6306
6307 commit = seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr,
6308 repo->id,
6309 branch->commit_id);
6310 if (!commit) {
6311 seaf_warning ("Commit %s is missing\n", branch->commit_id);
6312 repo->is_corrupted = TRUE;
6313 return;
6314 }
6315
6316 set_head_common (repo, branch);
6317 seaf_repo_from_commit (repo, commit);
6318
6319 seaf_commit_unref (commit);
6320 }
6321
6322 static gboolean
load_keys_cb(sqlite3_stmt * stmt,void * vrepo)6323 load_keys_cb (sqlite3_stmt *stmt, void *vrepo)
6324 {
6325 SeafRepo *repo = vrepo;
6326 const char *key, *iv;
6327
6328 key = (const char *)sqlite3_column_text(stmt, 0);
6329 iv = (const char *)sqlite3_column_text(stmt, 1);
6330
6331 if (repo->enc_version == 1) {
6332 hex_to_rawdata (key, repo->enc_key, 16);
6333 hex_to_rawdata (iv, repo->enc_iv, 16);
6334 } else if (repo->enc_version >= 2) {
6335 hex_to_rawdata (key, repo->enc_key, 32);
6336 hex_to_rawdata (iv, repo->enc_iv, 16);
6337 }
6338
6339 return FALSE;
6340 }
6341
6342 static int
load_repo_passwd(SeafRepoManager * manager,SeafRepo * repo)6343 load_repo_passwd (SeafRepoManager *manager, SeafRepo *repo)
6344 {
6345 sqlite3 *db = manager->priv->db;
6346 char sql[256];
6347 int n;
6348
6349 pthread_mutex_lock (&manager->priv->db_lock);
6350
6351 snprintf (sql, sizeof(sql),
6352 "SELECT key, iv FROM RepoKeys WHERE repo_id='%s'",
6353 repo->id);
6354 n = sqlite_foreach_selected_row (db, sql, load_keys_cb, repo);
6355 if (n < 0) {
6356 pthread_mutex_unlock (&manager->priv->db_lock);
6357 return -1;
6358 }
6359
6360 pthread_mutex_unlock (&manager->priv->db_lock);
6361
6362 return 0;
6363
6364 }
6365
6366 static gboolean
load_property_cb(sqlite3_stmt * stmt,void * pvalue)6367 load_property_cb (sqlite3_stmt *stmt, void *pvalue)
6368 {
6369 char **value = pvalue;
6370
6371 char *v = (char *) sqlite3_column_text (stmt, 0);
6372 *value = g_strdup (v);
6373
6374 /* Only one result. */
6375 return FALSE;
6376 }
6377
6378 static char *
load_repo_property(SeafRepoManager * manager,const char * repo_id,const char * key)6379 load_repo_property (SeafRepoManager *manager,
6380 const char *repo_id,
6381 const char *key)
6382 {
6383 sqlite3 *db = manager->priv->db;
6384 char sql[256];
6385 char *value = NULL;
6386
6387 pthread_mutex_lock (&manager->priv->db_lock);
6388
6389 snprintf(sql, 256, "SELECT value FROM RepoProperty WHERE "
6390 "repo_id='%s' and key='%s'", repo_id, key);
6391 if (sqlite_foreach_selected_row (db, sql, load_property_cb, &value) < 0) {
6392 seaf_warning ("Error read property %s for repo %s.\n", key, repo_id);
6393 pthread_mutex_unlock (&manager->priv->db_lock);
6394 return NULL;
6395 }
6396
6397 pthread_mutex_unlock (&manager->priv->db_lock);
6398
6399 return value;
6400 }
6401
6402 static gboolean
load_branch_cb(sqlite3_stmt * stmt,void * vrepo)6403 load_branch_cb (sqlite3_stmt *stmt, void *vrepo)
6404 {
6405 SeafRepo *repo = vrepo;
6406 SeafRepoManager *manager = repo->manager;
6407
6408 char *branch_name = (char *) sqlite3_column_text (stmt, 0);
6409 SeafBranch *branch =
6410 seaf_branch_manager_get_branch (manager->seaf->branch_mgr,
6411 repo->id, branch_name);
6412 if (branch == NULL) {
6413 seaf_warning ("Broken branch name for repo %s\n", repo->id);
6414 repo->is_corrupted = TRUE;
6415 return FALSE;
6416 }
6417 load_repo_commit (manager, repo, branch);
6418 seaf_branch_unref (branch);
6419
6420 /* Only one result. */
6421 return FALSE;
6422 }
6423
6424 static gboolean
is_wt_repo_name_same(const char * worktree,const char * repo_name)6425 is_wt_repo_name_same (const char *worktree, const char *repo_name)
6426 {
6427 char *basename = g_path_get_basename (worktree);
6428 gboolean ret = FALSE;
6429 ret = (g_strcmp0 (basename, repo_name) == 0);
6430 g_free (basename);
6431 return ret;
6432 }
6433
6434 static SeafRepo *
load_repo(SeafRepoManager * manager,const char * repo_id)6435 load_repo (SeafRepoManager *manager, const char *repo_id)
6436 {
6437 char sql[256];
6438
6439 SeafRepo *repo = seaf_repo_new(repo_id, NULL, NULL);
6440 if (!repo) {
6441 seaf_warning ("[repo mgr] failed to alloc repo.\n");
6442 return NULL;
6443 }
6444
6445 repo->manager = manager;
6446
6447 snprintf(sql, 256, "SELECT branch_name FROM RepoBranch WHERE repo_id='%s'",
6448 repo->id);
6449 if (sqlite_foreach_selected_row (manager->priv->db, sql,
6450 load_branch_cb, repo) < 0) {
6451 seaf_warning ("Error read branch for repo %s.\n", repo->id);
6452 seaf_repo_free (repo);
6453 return NULL;
6454 }
6455
6456 /* If repo head is set but failed to load branch or commit. */
6457 if (repo->is_corrupted) {
6458 seaf_repo_free (repo);
6459 /* remove_repo_ondisk (manager, repo_id); */
6460 return NULL;
6461 }
6462
6463 /* Repo head may be not set if it's just cloned but not checked out yet. */
6464 if (repo->head == NULL) {
6465 /* the repo do not have a head branch, try to load 'master' branch */
6466 SeafBranch *branch =
6467 seaf_branch_manager_get_branch (manager->seaf->branch_mgr,
6468 repo->id, "master");
6469 if (branch != NULL) {
6470 SeafCommit *commit;
6471
6472 commit =
6473 seaf_commit_manager_get_commit_compatible (manager->seaf->commit_mgr,
6474 repo->id,
6475 branch->commit_id);
6476 if (commit) {
6477 seaf_repo_from_commit (repo, commit);
6478 seaf_commit_unref (commit);
6479 } else {
6480 seaf_warning ("[repo-mgr] Can not find commit %s\n",
6481 branch->commit_id);
6482 repo->is_corrupted = TRUE;
6483 }
6484
6485 seaf_branch_unref (branch);
6486 } else {
6487 seaf_warning ("[repo-mgr] Failed to get branch master");
6488 repo->is_corrupted = TRUE;
6489 }
6490 }
6491
6492 if (repo->is_corrupted) {
6493 seaf_repo_free (repo);
6494 /* remove_repo_ondisk (manager, repo_id); */
6495 return NULL;
6496 }
6497
6498 load_repo_passwd (manager, repo);
6499
6500 char *value;
6501
6502 value = load_repo_property (manager, repo->id, REPO_AUTO_SYNC);
6503 if (g_strcmp0(value, "false") == 0) {
6504 repo->auto_sync = 0;
6505 }
6506 g_free (value);
6507
6508 repo->worktree = load_repo_property (manager, repo->id, "worktree");
6509 if (repo->worktree)
6510 repo->worktree_invalid = FALSE;
6511
6512 repo->email = load_repo_property (manager, repo->id, REPO_PROP_EMAIL);
6513 repo->token = load_repo_property (manager, repo->id, REPO_PROP_TOKEN);
6514
6515 /* May be NULL if this property is not set in db. */
6516 repo->server_url = load_repo_property (manager, repo->id, REPO_PROP_SERVER_URL);
6517
6518 if (repo->head != NULL && seaf_repo_check_worktree (repo) < 0) {
6519 if (seafile_session_config_get_allow_invalid_worktree(seaf)) {
6520 seaf_warning ("Worktree for repo \"%s\" is invalid, but still keep it.\n",
6521 repo->name);
6522 repo->worktree_invalid = TRUE;
6523 } else {
6524 seaf_message ("Worktree for repo \"%s\" is invalid, delete it.\n",
6525 repo->name);
6526 seaf_repo_manager_del_repo (manager, repo);
6527 return NULL;
6528 }
6529 }
6530
6531 /* load readonly property */
6532 value = load_repo_property (manager, repo->id, REPO_PROP_IS_READONLY);
6533 if (g_strcmp0(value, "true") == 0)
6534 repo->is_readonly = TRUE;
6535 else
6536 repo->is_readonly = FALSE;
6537 g_free (value);
6538
6539 /* load sync period property */
6540 value = load_repo_property (manager, repo->id, REPO_PROP_SYNC_INTERVAL);
6541 if (value) {
6542 int interval = atoi(value);
6543 if (interval > 0)
6544 repo->sync_interval = interval;
6545 }
6546 g_free (value);
6547
6548 if (repo->worktree) {
6549 gboolean wt_repo_name_same = is_wt_repo_name_same (repo->worktree, repo->name);
6550 value = load_repo_property (manager, repo->id, REPO_SYNC_WORKTREE_NAME);
6551 if (g_strcmp0 (value, "true") == 0) {
6552 /* If need to sync worktree name with library name, update worktree folder name. */
6553 if (!wt_repo_name_same)
6554 update_repo_worktree_name (repo, repo->name, FALSE);
6555 } else {
6556 /* If an existing repo's worktree folder name is the same as repo name, but
6557 * sync_worktree_name property is not set, set it.
6558 */
6559 if (wt_repo_name_same)
6560 save_repo_property (manager, repo->id, REPO_SYNC_WORKTREE_NAME, "true");
6561 }
6562 g_free (value);
6563 }
6564
6565 g_hash_table_insert (manager->priv->repo_hash, g_strdup(repo->id), repo);
6566
6567 return repo;
6568 }
6569
6570 static sqlite3*
open_db(SeafRepoManager * manager,const char * seaf_dir)6571 open_db (SeafRepoManager *manager, const char *seaf_dir)
6572 {
6573 sqlite3 *db;
6574 char *db_path;
6575
6576 db_path = g_build_filename (seaf_dir, "repo.db", NULL);
6577 if (sqlite_open_db (db_path, &db) < 0)
6578 return NULL;
6579 g_free (db_path);
6580 manager->priv->db = db;
6581
6582 char *sql = "CREATE TABLE IF NOT EXISTS Repo (repo_id TEXT PRIMARY KEY);";
6583 sqlite_query_exec (db, sql);
6584
6585 sql = "CREATE TABLE IF NOT EXISTS DeletedRepo (repo_id TEXT PRIMARY KEY);";
6586 sqlite_query_exec (db, sql);
6587
6588 sql = "CREATE TABLE IF NOT EXISTS RepoBranch ("
6589 "repo_id TEXT PRIMARY KEY, branch_name TEXT);";
6590 sqlite_query_exec (db, sql);
6591
6592 sql = "CREATE TABLE IF NOT EXISTS RepoLanToken ("
6593 "repo_id TEXT PRIMARY KEY, token TEXT);";
6594 sqlite_query_exec (db, sql);
6595
6596 sql = "CREATE TABLE IF NOT EXISTS RepoTmpToken ("
6597 "repo_id TEXT, peer_id TEXT, token TEXT, timestamp INTEGER, "
6598 "PRIMARY KEY (repo_id, peer_id));";
6599 sqlite_query_exec (db, sql);
6600
6601 sql = "CREATE TABLE IF NOT EXISTS RepoPasswd "
6602 "(repo_id TEXT PRIMARY KEY, passwd TEXT NOT NULL);";
6603 sqlite_query_exec (db, sql);
6604
6605 sql = "CREATE TABLE IF NOT EXISTS RepoKeys "
6606 "(repo_id TEXT PRIMARY KEY, key TEXT NOT NULL, iv TEXT NOT NULL);";
6607 sqlite_query_exec (db, sql);
6608
6609 sql = "CREATE TABLE IF NOT EXISTS RepoProperty ("
6610 "repo_id TEXT, key TEXT, value TEXT);";
6611 sqlite_query_exec (db, sql);
6612
6613 sql = "CREATE INDEX IF NOT EXISTS RepoIndex ON RepoProperty (repo_id);";
6614 sqlite_query_exec (db, sql);
6615
6616 sql = "CREATE TABLE IF NOT EXISTS MergeInfo ("
6617 "repo_id TEXT PRIMARY KEY, in_merge INTEGER, branch TEXT);";
6618 sqlite_query_exec (db, sql);
6619
6620 sql = "CREATE TABLE IF NOT EXISTS CommonAncestor ("
6621 "repo_id TEXT PRIMARY KEY, ca_id TEXT, head_id TEXT);";
6622 sqlite_query_exec (db, sql);
6623
6624 /* Version 1 repos will be added to this table after deletion.
6625 * GC will scan this table and remove the objects and blocks for the repos.
6626 */
6627 sql = "CREATE TABLE IF NOT EXISTS GarbageRepos (repo_id TEXT PRIMARY KEY);";
6628 sqlite_query_exec (db, sql);
6629
6630 sql = "CREATE TABLE IF NOT EXISTS LockedFiles (repo_id TEXT, path TEXT, "
6631 "operation TEXT, old_mtime INTEGER, file_id TEXT, new_path TEXT, "
6632 "PRIMARY KEY (repo_id, path));";
6633 sqlite_query_exec (db, sql);
6634
6635 sql = "CREATE TABLE IF NOT EXISTS FolderUserPerms ("
6636 "repo_id TEXT, path TEXT, permission TEXT);";
6637 sqlite_query_exec (db, sql);
6638
6639 sql = "CREATE INDEX IF NOT EXISTS folder_user_perms_repo_id_idx "
6640 "ON FolderUserPerms (repo_id);";
6641 sqlite_query_exec (db, sql);
6642
6643 sql = "CREATE TABLE IF NOT EXISTS FolderGroupPerms ("
6644 "repo_id TEXT, path TEXT, permission TEXT);";
6645 sqlite_query_exec (db, sql);
6646
6647 sql = "CREATE INDEX IF NOT EXISTS folder_group_perms_repo_id_idx "
6648 "ON FolderGroupPerms (repo_id);";
6649 sqlite_query_exec (db, sql);
6650
6651 sql = "CREATE TABLE IF NOT EXISTS FolderPermTimestamp ("
6652 "repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));";
6653 sqlite_query_exec (db, sql);
6654
6655 sql = "CREATE TABLE IF NOT EXISTS ServerProperty ("
6656 "server_url TEXT, key TEXT, value TEXT, PRIMARY KEY (server_url, key));";
6657 sqlite_query_exec (db, sql);
6658
6659 sql = "CREATE INDEX IF NOT EXISTS ServerIndex ON ServerProperty (server_url);";
6660 sqlite_query_exec (db, sql);
6661
6662 sql = "CREATE TABLE IF NOT EXISTS FileSyncError ("
6663 "id INTEGER PRIMARY KEY AUTOINCREMENT, repo_id TEXT, repo_name TEXT, "
6664 "path TEXT, err_id INTEGER, timestamp INTEGER);";
6665 sqlite_query_exec (db, sql);
6666
6667 sql = "CREATE INDEX IF NOT EXISTS FileSyncErrorIndex ON FileSyncError (repo_id, path)";
6668 sqlite_query_exec (db, sql);
6669
6670 return db;
6671 }
6672
6673 static gboolean
load_repo_cb(sqlite3_stmt * stmt,void * vmanager)6674 load_repo_cb (sqlite3_stmt *stmt, void *vmanager)
6675 {
6676 SeafRepoManager *manager = vmanager;
6677 const char *repo_id;
6678
6679 repo_id = (const char *) sqlite3_column_text (stmt, 0);
6680
6681 load_repo (manager, repo_id);
6682
6683 return TRUE;
6684 }
6685
6686 static gboolean
remove_deleted_repo(sqlite3_stmt * stmt,void * vmanager)6687 remove_deleted_repo (sqlite3_stmt *stmt, void *vmanager)
6688 {
6689 SeafRepoManager *manager = vmanager;
6690 const char *repo_id;
6691
6692 repo_id = (const char *) sqlite3_column_text (stmt, 0);
6693
6694 seaf_repo_manager_remove_repo_ondisk (manager, repo_id, TRUE);
6695
6696 return TRUE;
6697 }
6698
6699 static void
load_repos(SeafRepoManager * manager,const char * seaf_dir)6700 load_repos (SeafRepoManager *manager, const char *seaf_dir)
6701 {
6702 sqlite3 *db = open_db(manager, seaf_dir);
6703 if (!db) return;
6704
6705 char *sql;
6706
6707 sql = "SELECT repo_id FROM DeletedRepo";
6708 if (sqlite_foreach_selected_row (db, sql, remove_deleted_repo, manager) < 0) {
6709 seaf_warning ("Error removing deleted repos.\n");
6710 return;
6711 }
6712
6713 sql = "SELECT repo_id FROM Repo;";
6714 if (sqlite_foreach_selected_row (db, sql, load_repo_cb, manager) < 0) {
6715 seaf_warning ("Error read repo db.\n");
6716 return;
6717 }
6718 }
6719
6720 static void
save_repo_property(SeafRepoManager * manager,const char * repo_id,const char * key,const char * value)6721 save_repo_property (SeafRepoManager *manager,
6722 const char *repo_id,
6723 const char *key, const char *value)
6724 {
6725 char *sql;
6726 sqlite3 *db = manager->priv->db;
6727
6728 pthread_mutex_lock (&manager->priv->db_lock);
6729
6730 sql = sqlite3_mprintf ("SELECT repo_id FROM RepoProperty WHERE repo_id=%Q AND key=%Q",
6731 repo_id, key);
6732 if (sqlite_check_for_existence(db, sql)) {
6733 sqlite3_free (sql);
6734 sql = sqlite3_mprintf ("UPDATE RepoProperty SET value=%Q"
6735 "WHERE repo_id=%Q and key=%Q",
6736 value, repo_id, key);
6737 sqlite_query_exec (db, sql);
6738 sqlite3_free (sql);
6739 } else {
6740 sqlite3_free (sql);
6741 sql = sqlite3_mprintf ("INSERT INTO RepoProperty VALUES (%Q, %Q, %Q)",
6742 repo_id, key, value);
6743 sqlite_query_exec (db, sql);
6744 sqlite3_free (sql);
6745 }
6746
6747 pthread_mutex_unlock (&manager->priv->db_lock);
6748 }
6749
6750 int
seaf_repo_manager_set_repo_property(SeafRepoManager * manager,const char * repo_id,const char * key,const char * value)6751 seaf_repo_manager_set_repo_property (SeafRepoManager *manager,
6752 const char *repo_id,
6753 const char *key,
6754 const char *value)
6755 {
6756 SeafRepo *repo;
6757
6758 repo = seaf_repo_manager_get_repo (manager, repo_id);
6759 if (!repo)
6760 return -1;
6761
6762 if (strcmp(key, REPO_AUTO_SYNC) == 0) {
6763 if (!seaf->started) {
6764 seaf_message ("System not started, skip setting auto sync value.\n");
6765 return 0;
6766 }
6767
6768 if (g_strcmp0(value, "true") == 0) {
6769 repo->auto_sync = 1;
6770 if (repo->sync_interval == 0)
6771 seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id,
6772 repo->worktree);
6773 repo->last_sync_time = 0;
6774 } else {
6775 repo->auto_sync = 0;
6776 if (repo->sync_interval == 0)
6777 seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);
6778 /* Cancel current sync task if any. */
6779 seaf_sync_manager_cancel_sync_task (seaf->sync_mgr, repo->id);
6780 seaf_sync_manager_remove_active_path_info (seaf->sync_mgr, repo->id);
6781 }
6782 }
6783
6784 if (strcmp(key, REPO_PROP_SYNC_INTERVAL) == 0) {
6785 if (!seaf->started) {
6786 seaf_message ("System not started, skip setting auto sync value.\n");
6787 return 0;
6788 }
6789
6790 int interval = atoi(value);
6791
6792 if (interval > 0) {
6793 repo->sync_interval = interval;
6794 if (repo->auto_sync)
6795 seaf_wt_monitor_unwatch_repo (seaf->wt_monitor, repo->id);
6796 } else {
6797 repo->sync_interval = 0;
6798 if (repo->auto_sync)
6799 seaf_wt_monitor_watch_repo (seaf->wt_monitor, repo->id,
6800 repo->worktree);
6801 }
6802 }
6803
6804 if (strcmp (key, REPO_PROP_SERVER_URL) == 0) {
6805 char *url = canonical_server_url (value);
6806
6807 if (!repo->server_url) {
6808 /* Called from clone-mgr. */
6809 repo->server_url = url;
6810 } else {
6811 g_free (repo->server_url);
6812 repo->server_url = url;
6813
6814 g_free (repo->effective_host);
6815 repo->effective_host = NULL;
6816 }
6817
6818 save_repo_property (manager, repo_id, key, url);
6819 return 0;
6820 }
6821
6822 if (strcmp (key, REPO_PROP_IS_READONLY) == 0) {
6823 if (g_strcmp0 (value, "true") == 0)
6824 repo->is_readonly = TRUE;
6825 else
6826 repo->is_readonly = FALSE;
6827 }
6828
6829 save_repo_property (manager, repo_id, key, value);
6830 return 0;
6831 }
6832
6833 char *
seaf_repo_manager_get_repo_property(SeafRepoManager * manager,const char * repo_id,const char * key)6834 seaf_repo_manager_get_repo_property (SeafRepoManager *manager,
6835 const char *repo_id,
6836 const char *key)
6837 {
6838 return load_repo_property (manager, repo_id, key);
6839 }
6840
6841 static void
seaf_repo_manager_del_repo_property(SeafRepoManager * manager,const char * repo_id)6842 seaf_repo_manager_del_repo_property (SeafRepoManager *manager,
6843 const char *repo_id)
6844 {
6845 char *sql;
6846 sqlite3 *db = manager->priv->db;
6847
6848 pthread_mutex_lock (&manager->priv->db_lock);
6849
6850 sql = sqlite3_mprintf ("DELETE FROM RepoProperty WHERE repo_id = %Q", repo_id);
6851 sqlite_query_exec (db, sql);
6852 sqlite3_free (sql);
6853
6854 pthread_mutex_unlock (&manager->priv->db_lock);
6855 }
6856
6857 static void
seaf_repo_manager_del_repo_property_by_key(SeafRepoManager * manager,const char * repo_id,const char * key)6858 seaf_repo_manager_del_repo_property_by_key (SeafRepoManager *manager,
6859 const char *repo_id,
6860 const char *key)
6861 {
6862 char *sql;
6863 sqlite3 *db = manager->priv->db;
6864
6865 pthread_mutex_lock (&manager->priv->db_lock);
6866
6867 sql = sqlite3_mprintf ("DELETE FROM RepoProperty "
6868 "WHERE repo_id = %Q "
6869 " AND key = %Q", repo_id, key);
6870 sqlite_query_exec (db, sql);
6871 sqlite3_free (sql);
6872
6873 pthread_mutex_unlock (&manager->priv->db_lock);
6874 }
6875
6876 static int
save_repo_enc_info(SeafRepoManager * manager,SeafRepo * repo)6877 save_repo_enc_info (SeafRepoManager *manager,
6878 SeafRepo *repo)
6879 {
6880 sqlite3 *db = manager->priv->db;
6881 char sql[512];
6882 char key[65], iv[33];
6883
6884 if (repo->enc_version == 1) {
6885 rawdata_to_hex (repo->enc_key, key, 16);
6886 rawdata_to_hex (repo->enc_iv, iv, 16);
6887 } else if (repo->enc_version >= 2) {
6888 rawdata_to_hex (repo->enc_key, key, 32);
6889 rawdata_to_hex (repo->enc_iv, iv, 16);
6890 }
6891
6892 snprintf (sql, sizeof(sql), "REPLACE INTO RepoKeys VALUES ('%s', '%s', '%s')",
6893 repo->id, key, iv);
6894 if (sqlite_query_exec (db, sql) < 0)
6895 return -1;
6896
6897 return 0;
6898 }
6899
6900 int
seaf_repo_manager_set_repo_passwd(SeafRepoManager * manager,SeafRepo * repo,const char * passwd)6901 seaf_repo_manager_set_repo_passwd (SeafRepoManager *manager,
6902 SeafRepo *repo,
6903 const char *passwd)
6904 {
6905 int ret;
6906
6907 if (seafile_decrypt_repo_enc_key (repo->enc_version, passwd, repo->random_key,
6908 repo->salt,
6909 repo->enc_key, repo->enc_iv) < 0)
6910 return -1;
6911
6912 pthread_mutex_lock (&manager->priv->db_lock);
6913
6914 ret = save_repo_enc_info (manager, repo);
6915
6916 pthread_mutex_unlock (&manager->priv->db_lock);
6917
6918 return ret;
6919 }
6920
6921 GList*
seaf_repo_manager_get_repo_list(SeafRepoManager * manager,int start,int limit)6922 seaf_repo_manager_get_repo_list (SeafRepoManager *manager, int start, int limit)
6923 {
6924 GList *repo_list = NULL;
6925 GHashTableIter iter;
6926 SeafRepo *repo;
6927 gpointer key, value;
6928
6929 if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
6930 seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6931 return NULL;
6932 }
6933 g_hash_table_iter_init (&iter, manager->priv->repo_hash);
6934
6935 while (g_hash_table_iter_next (&iter, &key, &value)) {
6936 repo = value;
6937 if (!repo->delete_pending)
6938 repo_list = g_list_prepend (repo_list, repo);
6939 }
6940
6941 pthread_rwlock_unlock (&manager->priv->lock);
6942
6943 return repo_list;
6944 }
6945
6946 GList *
seaf_repo_manager_get_repo_id_list_by_server(SeafRepoManager * manager,const char * server_url)6947 seaf_repo_manager_get_repo_id_list_by_server (SeafRepoManager *manager, const char *server_url)
6948 {
6949 GList *repo_id_list = NULL;
6950 GHashTableIter iter;
6951 SeafRepo *repo;
6952 gpointer key, value;
6953
6954 if (pthread_rwlock_rdlock (&manager->priv->lock) < 0) {
6955 seaf_warning ("[repo mgr] failed to lock repo cache.\n");
6956 return NULL;
6957 }
6958 g_hash_table_iter_init (&iter, manager->priv->repo_hash);
6959
6960 while (g_hash_table_iter_next (&iter, &key, &value)) {
6961 repo = value;
6962 if (!repo->delete_pending && g_strcmp0 (repo->server_url, server_url) == 0)
6963 repo_id_list = g_list_prepend (repo_id_list, g_strdup(repo->id));
6964 }
6965
6966 pthread_rwlock_unlock (&manager->priv->lock);
6967
6968 return repo_id_list;
6969 }
6970
6971 int
seaf_repo_manager_set_repo_email(SeafRepoManager * mgr,SeafRepo * repo,const char * email)6972 seaf_repo_manager_set_repo_email (SeafRepoManager *mgr,
6973 SeafRepo *repo,
6974 const char *email)
6975 {
6976 g_free (repo->email);
6977 repo->email = g_strdup(email);
6978
6979 save_repo_property (mgr, repo->id, REPO_PROP_EMAIL, email);
6980 return 0;
6981 }
6982
6983 int
seaf_repo_manager_set_repo_token(SeafRepoManager * manager,SeafRepo * repo,const char * token)6984 seaf_repo_manager_set_repo_token (SeafRepoManager *manager,
6985 SeafRepo *repo,
6986 const char *token)
6987 {
6988 g_free (repo->token);
6989 repo->token = g_strdup(token);
6990
6991 save_repo_property (manager, repo->id, REPO_PROP_TOKEN, token);
6992 return 0;
6993 }
6994
6995
6996 int
seaf_repo_manager_remove_repo_token(SeafRepoManager * manager,SeafRepo * repo)6997 seaf_repo_manager_remove_repo_token (SeafRepoManager *manager,
6998 SeafRepo *repo)
6999 {
7000 g_free (repo->token);
7001 repo->token = NULL;
7002 seaf_repo_manager_del_repo_property_by_key(manager, repo->id, REPO_PROP_TOKEN);
7003 return 0;
7004 }
7005
7006 int
seaf_repo_manager_set_repo_relay_info(SeafRepoManager * mgr,const char * repo_id,const char * relay_addr,const char * relay_port)7007 seaf_repo_manager_set_repo_relay_info (SeafRepoManager *mgr,
7008 const char *repo_id,
7009 const char *relay_addr,
7010 const char *relay_port)
7011 {
7012 save_repo_property (mgr, repo_id, REPO_PROP_RELAY_ADDR, relay_addr);
7013 save_repo_property (mgr, repo_id, REPO_PROP_RELAY_PORT, relay_port);
7014 return 0;
7015 }
7016
7017 void
seaf_repo_manager_get_repo_relay_info(SeafRepoManager * mgr,const char * repo_id,char ** relay_addr,char ** relay_port)7018 seaf_repo_manager_get_repo_relay_info (SeafRepoManager *mgr,
7019 const char *repo_id,
7020 char **relay_addr,
7021 char **relay_port)
7022 {
7023 char *addr, *port;
7024
7025 addr = load_repo_property (mgr, repo_id, REPO_PROP_RELAY_ADDR);
7026 port = load_repo_property (mgr, repo_id, REPO_PROP_RELAY_PORT);
7027
7028 if (relay_addr && addr)
7029 *relay_addr = addr;
7030 if (relay_port && port)
7031 *relay_port = port;
7032 }
7033
7034 static void
update_server_properties(SeafRepoManager * mgr,const char * repo_id,const char * new_server_url)7035 update_server_properties (SeafRepoManager *mgr,
7036 const char *repo_id,
7037 const char *new_server_url)
7038 {
7039 char *old_server_url = NULL;
7040 char *sql = NULL;
7041
7042 old_server_url = seaf_repo_manager_get_repo_property (mgr, repo_id,
7043 REPO_PROP_SERVER_URL);
7044 if (!old_server_url)
7045 return;
7046
7047 pthread_mutex_lock (&mgr->priv->db_lock);
7048
7049 sql = sqlite3_mprintf ("UPDATE ServerProperty SET server_url=%Q WHERE "
7050 "server_url=%Q;", new_server_url, old_server_url);
7051 sqlite_query_exec (mgr->priv->db, sql);
7052
7053 pthread_mutex_unlock (&mgr->priv->db_lock);
7054
7055 sqlite3_free (sql);
7056 g_free (old_server_url);
7057 }
7058
7059 int
seaf_repo_manager_update_repos_server_host(SeafRepoManager * mgr,const char * old_server_url,const char * new_server_url)7060 seaf_repo_manager_update_repos_server_host (SeafRepoManager *mgr,
7061 const char *old_server_url,
7062 const char *new_server_url)
7063 {
7064 GList *ptr, *repos = seaf_repo_manager_get_repo_list (seaf->repo_mgr, 0, -1);
7065 SeafRepo *r;
7066 char *canon_old_server_url = canonical_server_url(old_server_url);
7067 char *canon_new_server_url = canonical_server_url(new_server_url);
7068
7069 for (ptr = repos; ptr; ptr = ptr->next) {
7070 r = ptr->data;
7071
7072 char *server_url = seaf_repo_manager_get_repo_property (seaf->repo_mgr,
7073 r->id,
7074 REPO_PROP_SERVER_URL);
7075
7076 if (g_strcmp0(server_url, canon_old_server_url) == 0) {
7077 /* Update server property before server_url is changed. */
7078 update_server_properties (mgr, r->id, canon_new_server_url);
7079
7080 seaf_repo_manager_set_repo_property (
7081 seaf->repo_mgr, r->id, REPO_PROP_SERVER_URL, canon_new_server_url);
7082 }
7083 g_free (server_url);
7084
7085 }
7086
7087 g_list_free (repos);
7088 g_free (canon_old_server_url);
7089 g_free (canon_new_server_url);
7090
7091 return 0;
7092 }
7093
7094 char *
seaf_repo_manager_get_server_property(SeafRepoManager * mgr,const char * server_url,const char * key)7095 seaf_repo_manager_get_server_property (SeafRepoManager *mgr,
7096 const char *server_url,
7097 const char *key)
7098 {
7099 char *sql = sqlite3_mprintf ("SELECT value FROM ServerProperty WHERE "
7100 "server_url=%Q AND key=%Q;",
7101 server_url, key);
7102 char *value;
7103
7104 pthread_mutex_lock (&mgr->priv->db_lock);
7105
7106 value = sqlite_get_string (mgr->priv->db, sql);
7107
7108 pthread_mutex_unlock (&mgr->priv->db_lock);
7109
7110 sqlite3_free (sql);
7111 return value;
7112 }
7113
7114 int
seaf_repo_manager_set_server_property(SeafRepoManager * mgr,const char * server_url,const char * key,const char * value)7115 seaf_repo_manager_set_server_property (SeafRepoManager *mgr,
7116 const char *server_url,
7117 const char *key,
7118 const char *value)
7119 {
7120 char *sql;
7121 int ret;
7122 char *canon_server_url = canonical_server_url(server_url);
7123
7124 pthread_mutex_lock (&mgr->priv->db_lock);
7125
7126 sql = sqlite3_mprintf ("REPLACE INTO ServerProperty VALUES (%Q, %Q, %Q);",
7127 canon_server_url, key, value);
7128 ret = sqlite_query_exec (mgr->priv->db, sql);
7129
7130 pthread_mutex_unlock (&mgr->priv->db_lock);
7131
7132 sqlite3_free (sql);
7133 g_free (canon_server_url);
7134 return ret;
7135 }
7136
7137 gboolean
seaf_repo_manager_server_is_pro(SeafRepoManager * mgr,const char * server_url)7138 seaf_repo_manager_server_is_pro (SeafRepoManager *mgr,
7139 const char *server_url)
7140 {
7141 gboolean ret = FALSE;
7142
7143 char *is_pro = seaf_repo_manager_get_server_property (seaf->repo_mgr,
7144 server_url,
7145 SERVER_PROP_IS_PRO);
7146 if (is_pro != NULL && strcasecmp (is_pro, "true") == 0)
7147 ret = TRUE;
7148
7149 g_free (is_pro);
7150 return ret;
7151 }
7152
7153 /*
7154 * Read ignored files from ignore.txt
7155 */
seaf_repo_load_ignore_files(const char * worktree)7156 GList *seaf_repo_load_ignore_files (const char *worktree)
7157 {
7158 GList *list = NULL;
7159 SeafStat st;
7160 FILE *fp;
7161 char *full_path, *pattern;
7162 char path[PATH_MAX];
7163
7164 full_path = g_build_path (PATH_SEPERATOR, worktree,
7165 IGNORE_FILE, NULL);
7166 if (seaf_stat (full_path, &st) < 0)
7167 goto error;
7168 if (!S_ISREG(st.st_mode))
7169 goto error;
7170 fp = g_fopen(full_path, "r");
7171 if (fp == NULL)
7172 goto error;
7173
7174 while (fgets(path, PATH_MAX, fp) != NULL) {
7175 /* remove leading and trailing whitespace, including \n \r. */
7176 g_strstrip (path);
7177
7178 /* ignore comment and blank line */
7179 if (path[0] == '#' || path[0] == '\0')
7180 continue;
7181
7182 /* Change 'foo/' to 'foo/ *'. */
7183 if (path[strlen(path)-1] == '/')
7184 pattern = g_strdup_printf("%s/%s*", worktree, path);
7185 else
7186 pattern = g_strdup_printf("%s/%s", worktree, path);
7187
7188 list = g_list_prepend(list, pattern);
7189 }
7190
7191 fclose(fp);
7192 g_free (full_path);
7193 return list;
7194
7195 error:
7196 g_free (full_path);
7197 return NULL;
7198 }
7199
7200 gboolean
seaf_repo_check_ignore_file(GList * ignore_list,const char * fullpath)7201 seaf_repo_check_ignore_file (GList *ignore_list, const char *fullpath)
7202 {
7203 char *str;
7204 SeafStat st;
7205 GPatternSpec *ignore_spec;
7206 GList *p;
7207
7208 str = g_strdup(fullpath);
7209
7210 int rc = seaf_stat(str, &st);
7211 if (rc == 0 && S_ISDIR(st.st_mode)) {
7212 g_free (str);
7213 str = g_strconcat (fullpath, "/", NULL);
7214 }
7215
7216 for (p = ignore_list; p != NULL; p = p->next) {
7217 char *pattern = (char *)p->data;
7218
7219 ignore_spec = g_pattern_spec_new(pattern);
7220 if (g_pattern_match_string(ignore_spec, str)) {
7221 g_free (str);
7222 g_pattern_spec_free(ignore_spec);
7223 return TRUE;
7224 }
7225 g_pattern_spec_free(ignore_spec);
7226 }
7227
7228 g_free (str);
7229 return FALSE;
7230 }
7231
7232 /*
7233 * Free ignored file list
7234 */
seaf_repo_free_ignore_files(GList * ignore_list)7235 void seaf_repo_free_ignore_files (GList *ignore_list)
7236 {
7237 GList *p;
7238
7239 if (ignore_list == NULL)
7240 return;
7241
7242 for (p = ignore_list; p != NULL; p = p->next)
7243 free(p->data);
7244
7245 g_list_free (ignore_list);
7246 }
7247