/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ #include "common.h" #include #include "seafile-session.h" #include "filelock-mgr.h" #include "set-perm.h" #include "log.h" #include "db.h" struct _FilelockMgrPriv { GHashTable *repo_locked_files; pthread_mutex_t hash_lock; sqlite3 *db; pthread_mutex_t db_lock; }; typedef struct _FilelockMgrPriv FilelockMgrPriv; typedef struct _LockInfo { int locked_by_me; } LockInfo; /* When a file is locked by me, it can have two reasons: * - Locked by the user manually * - Auto-Locked by Seafile when it detects Office opens the file. */ struct _SeafFilelockManager * seaf_filelock_manager_new (struct _SeafileSession *session) { SeafFilelockManager *mgr = g_new0 (SeafFilelockManager, 1); FilelockMgrPriv *priv = g_new0 (FilelockMgrPriv, 1); mgr->session = session; mgr->priv = priv; priv->repo_locked_files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_destroy); pthread_mutex_init (&priv->hash_lock, NULL); pthread_mutex_init (&priv->db_lock, NULL); return mgr; } static void lock_info_free (LockInfo *info) { g_free (info); } static gboolean load_locked_files (sqlite3_stmt *stmt, void *data) { GHashTable *repo_locked_files = data, *files; const char *repo_id, *path; int locked_by_me; repo_id = (const char *)sqlite3_column_text (stmt, 0); path = (const char *)sqlite3_column_text (stmt, 1); locked_by_me = sqlite3_column_int (stmt, 2); files = g_hash_table_lookup (repo_locked_files, repo_id); if (!files) { files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)lock_info_free); g_hash_table_insert (repo_locked_files, g_strdup(repo_id), files); } char *key = g_strdup(path); LockInfo *info = g_new0 (LockInfo, 1); info->locked_by_me = locked_by_me; g_hash_table_replace (files, key, info); return TRUE; } int seaf_filelock_manager_init (SeafFilelockManager *mgr) { char *db_path; sqlite3 *db; char *sql; db_path = g_build_filename (seaf->seaf_dir, "filelocks.db", NULL); if (sqlite_open_db (db_path, &db) < 0) return -1; g_free (db_path); mgr->priv->db = db; sql = "CREATE TABLE IF NOT EXISTS ServerLockedFiles (" "repo_id TEXT, path TEXT, locked_by_me INTEGER);"; sqlite_query_exec (db, sql); sql = "CREATE INDEX IF NOT EXISTS server_locked_files_repo_id_idx " "ON ServerLockedFiles (repo_id);"; sqlite_query_exec (db, sql); sql = "CREATE TABLE IF NOT EXISTS ServerLockedFilesTimestamp (" "repo_id TEXT, timestamp INTEGER, PRIMARY KEY (repo_id));"; sqlite_query_exec (db, sql); sql = "SELECT repo_id, path, locked_by_me FROM ServerLockedFiles"; pthread_mutex_lock (&mgr->priv->db_lock); pthread_mutex_lock (&mgr->priv->hash_lock); if (sqlite_foreach_selected_row (mgr->priv->db, sql, load_locked_files, mgr->priv->repo_locked_files) < 0) { pthread_mutex_unlock (&mgr->priv->db_lock); pthread_mutex_unlock (&mgr->priv->hash_lock); g_hash_table_destroy (mgr->priv->repo_locked_files); return -1; } pthread_mutex_unlock (&mgr->priv->hash_lock); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } static void init_locks (gpointer key, gpointer value, gpointer user_data) { char *repo_id = user_data; char *path = key; LockInfo *info = value; if (!info->locked_by_me) { seaf_filelock_manager_lock_wt_file (seaf->filelock_mgr, repo_id, path); } } int seaf_filelock_manager_start (SeafFilelockManager *mgr) { GHashTableIter iter; gpointer key, value; char *repo_id; GHashTable *locks; pthread_mutex_lock (&mgr->priv->hash_lock); g_hash_table_iter_init (&iter, mgr->priv->repo_locked_files); while (g_hash_table_iter_next (&iter, &key, &value)) { repo_id = key; locks = value; g_hash_table_foreach (locks, init_locks, repo_id); } pthread_mutex_unlock (&mgr->priv->hash_lock); return 0; } gboolean seaf_filelock_manager_is_file_locked (SeafFilelockManager *mgr, const char *repo_id, const char *path) { gboolean ret; pthread_mutex_lock (&mgr->priv->hash_lock); GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FALSE; } LockInfo *info = g_hash_table_lookup (locks, path); if (!info) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FALSE; } ret = !info->locked_by_me; pthread_mutex_unlock (&mgr->priv->hash_lock); return ret; } gboolean seaf_filelock_manager_is_file_locked_by_me (SeafFilelockManager *mgr, const char *repo_id, const char *path) { gboolean ret; pthread_mutex_lock (&mgr->priv->hash_lock); GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FALSE; } LockInfo *info = g_hash_table_lookup (locks, path); if (!info) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FALSE; } ret = (info->locked_by_me > 0); pthread_mutex_unlock (&mgr->priv->hash_lock); return ret; } int seaf_filelock_manager_get_lock_status (SeafFilelockManager *mgr, const char *repo_id, const char *path) { int ret; pthread_mutex_lock (&mgr->priv->hash_lock); GHashTable *locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FILE_NOT_LOCKED; } LockInfo *info = g_hash_table_lookup (locks, path); if (!info) { pthread_mutex_unlock (&mgr->priv->hash_lock); return FILE_NOT_LOCKED; } if (info->locked_by_me == LOCKED_MANUAL) ret = FILE_LOCKED_BY_ME_MANUAL; else if (info->locked_by_me == LOCKED_AUTO) ret = FILE_LOCKED_BY_ME_AUTO; else ret = FILE_LOCKED_BY_OTHERS; pthread_mutex_unlock (&mgr->priv->hash_lock); return ret; } void seaf_filelock_manager_lock_wt_file (SeafFilelockManager *mgr, const char *repo_id, const char *path) { SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) return; char *fullpath = g_build_filename (repo->worktree, path, NULL); if (seaf_util_exists (fullpath)) seaf_set_path_permission (fullpath, SEAF_PATH_PERM_RO, FALSE); g_free (fullpath); } void seaf_filelock_manager_unlock_wt_file (SeafFilelockManager *mgr, const char *repo_id, const char *path) { SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) return; char *fullpath = g_build_filename (repo->worktree, path, NULL); #ifdef WIN32 if (seaf_util_exists (fullpath)) seaf_unset_path_permission (fullpath, FALSE); #else if (seaf_util_exists (fullpath)) seaf_set_path_permission (fullpath, SEAF_PATH_PERM_RW, FALSE); #endif g_free (fullpath); } static void update_in_memory (SeafFilelockManager *mgr, const char *repo_id, GHashTable *new_locks) { GHashTable *repo_hash = mgr->priv->repo_locked_files; pthread_mutex_lock (&mgr->priv->hash_lock); GHashTable *locks = g_hash_table_lookup (repo_hash, repo_id); if (!locks) { if (g_hash_table_size (new_locks) == 0) { pthread_mutex_unlock (&mgr->priv->hash_lock); return; } locks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)lock_info_free); g_hash_table_insert (repo_hash, g_strdup(repo_id), locks); } GHashTableIter iter; gpointer key, value; gpointer new_key, new_val; char *path; #ifdef WIN32 char *fullpath; #endif LockInfo *info; gboolean exists; int locked_by_me; SeafRepo *repo; repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) { seaf_warning ("Failed to find repo %s\n", repo_id); return; } g_hash_table_iter_init (&iter, locks); while (g_hash_table_iter_next (&iter, &key, &value)) { path = key; info = value; exists = g_hash_table_lookup_extended (new_locks, path, &new_key, &new_val); if (!exists) { #ifdef WIN32 fullpath = g_build_path ("/", repo->worktree, path, NULL); seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath); g_free (fullpath); #endif seaf_filelock_manager_unlock_wt_file (mgr, repo_id, path); g_hash_table_iter_remove (&iter); } else { locked_by_me = (int)(long)new_val; if (!info->locked_by_me && locked_by_me) { #ifdef WIN32 fullpath = g_build_path ("/", repo->worktree, path, NULL); seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath); g_free (fullpath); #endif seaf_filelock_manager_unlock_wt_file (mgr, repo_id, path); info->locked_by_me = locked_by_me; } else if (info->locked_by_me && !locked_by_me) { #ifdef WIN32 fullpath = g_build_path ("/", repo->worktree, path, NULL); seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath); g_free (fullpath); #endif seaf_filelock_manager_lock_wt_file (mgr, repo_id, path); info->locked_by_me = locked_by_me; } } } g_hash_table_iter_init (&iter, new_locks); while (g_hash_table_iter_next (&iter, &new_key, &new_val)) { path = new_key; locked_by_me = (int)(long)new_val; if (!g_hash_table_lookup (locks, path)) { info = g_new0 (LockInfo, 1); info->locked_by_me = locked_by_me; g_hash_table_insert (locks, g_strdup(path), info); #ifdef WIN32 fullpath = g_build_path ("/", repo->worktree, path, NULL); seaf_sync_manager_add_refresh_path (seaf->sync_mgr, fullpath); g_free (fullpath); #endif if (!locked_by_me) { seaf_filelock_manager_lock_wt_file (mgr, repo_id, path); } } } pthread_mutex_unlock (&mgr->priv->hash_lock); } static gint compare_paths (gconstpointer a, gconstpointer b) { const char *patha = a, *pathb = b; return strcmp (patha, pathb); } static int update_db (SeafFilelockManager *mgr, const char *repo_id) { char *sql; sqlite3_stmt *stmt; GHashTable *locks; GList *paths, *ptr; char *path; LockInfo *info; pthread_mutex_lock (&mgr->priv->db_lock); sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove server locked files for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks || g_hash_table_size (locks) == 0) { pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } paths = g_hash_table_get_keys (locks); paths = g_list_sort (paths, compare_paths); sql = "INSERT INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)"; stmt = sqlite_query_prepare (mgr->priv->db, sql); for (ptr = paths; ptr; ptr = ptr->next) { path = ptr->data; info = g_hash_table_lookup (locks, path); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT); sqlite3_bind_int (stmt, 3, info->locked_by_me); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to insert server file lock for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_reset (stmt); sqlite3_clear_bindings (stmt); } sqlite3_finalize (stmt); g_list_free (paths); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_filelock_manager_update (SeafFilelockManager *mgr, const char *repo_id, GHashTable *new_locked_files) { update_in_memory (mgr, repo_id, new_locked_files); int ret = update_db (mgr, repo_id); return ret; } int seaf_filelock_manager_update_timestamp (SeafFilelockManager *mgr, const char *repo_id, gint64 timestamp) { char sql[256]; int ret; snprintf (sql, sizeof(sql), "REPLACE INTO ServerLockedFilesTimestamp VALUES ('%s', %"G_GINT64_FORMAT")", repo_id, timestamp); pthread_mutex_lock (&mgr->priv->db_lock); ret = sqlite_query_exec (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); return ret; } gint64 seaf_filelock_manager_get_timestamp (SeafFilelockManager *mgr, const char *repo_id) { char sql[256]; gint64 ret; sqlite3_snprintf (sizeof(sql), sql, "SELECT timestamp FROM ServerLockedFilesTimestamp WHERE repo_id = '%q'", repo_id); pthread_mutex_lock (&mgr->priv->db_lock); ret = sqlite_get_int64 (mgr->priv->db, sql); pthread_mutex_unlock (&mgr->priv->db_lock); return ret; } int seaf_filelock_manager_remove (SeafFilelockManager *mgr, const char *repo_id) { char *sql; sqlite3_stmt *stmt; pthread_mutex_lock (&mgr->priv->db_lock); sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove server locked files for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); sql = "DELETE FROM ServerLockedFilesTimestamp WHERE repo_id = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove server locked files timestamp for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); pthread_mutex_lock (&mgr->priv->hash_lock); g_hash_table_remove (mgr->priv->repo_locked_files, repo_id); pthread_mutex_unlock (&mgr->priv->hash_lock); return 0; } #ifdef WIN32 static void refresh_locked_path_status (const char *repo_id, const char *path) { SeafRepo *repo = seaf_repo_manager_get_repo (seaf->repo_mgr, repo_id); if (!repo) return; char *fullpath = g_build_path ("/", repo->worktree, path, NULL); seaf_sync_manager_refresh_path (seaf->sync_mgr, fullpath); g_free (fullpath); } #endif static int mark_file_locked_in_db (SeafFilelockManager *mgr, const char *repo_id, const char *path, int locked_by_me) { char *sql; sqlite3_stmt *stmt; pthread_mutex_lock (&mgr->priv->db_lock); sql = "REPLACE INTO ServerLockedFiles (repo_id, path, locked_by_me) VALUES (?, ?, ?)"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT); sqlite3_bind_int (stmt, 3, locked_by_me); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to update server locked files for %.8s: %s.\n", repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_filelock_manager_mark_file_locked (SeafFilelockManager *mgr, const char *repo_id, const char *path, FileLockType type) { GHashTable *locks; LockInfo *info; pthread_mutex_lock (&mgr->priv->hash_lock); locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { locks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)lock_info_free); g_hash_table_insert (mgr->priv->repo_locked_files, g_strdup(repo_id), locks); } info = g_hash_table_lookup (locks, path); if (!info) { info = g_new0 (LockInfo, 1); g_hash_table_insert (locks, g_strdup(path), info); } info->locked_by_me = type; pthread_mutex_unlock (&mgr->priv->hash_lock); #ifdef WIN32 refresh_locked_path_status (repo_id, path); #endif return mark_file_locked_in_db (mgr, repo_id, path, info->locked_by_me); } static int remove_locked_file_from_db (SeafFilelockManager *mgr, const char *repo_id, const char *path) { char *sql; sqlite3_stmt *stmt; pthread_mutex_lock (&mgr->priv->db_lock); sql = "DELETE FROM ServerLockedFiles WHERE repo_id = ? AND path = ?"; stmt = sqlite_query_prepare (mgr->priv->db, sql); sqlite3_bind_text (stmt, 1, repo_id, -1, SQLITE_TRANSIENT); sqlite3_bind_text (stmt, 2, path, -1, SQLITE_TRANSIENT); if (sqlite3_step (stmt) != SQLITE_DONE) { seaf_warning ("Failed to remove locked file %s from %.8s: %s.\n", path, repo_id, sqlite3_errmsg (mgr->priv->db)); sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return -1; } sqlite3_finalize (stmt); pthread_mutex_unlock (&mgr->priv->db_lock); return 0; } int seaf_filelock_manager_mark_file_unlocked (SeafFilelockManager *mgr, const char *repo_id, const char *path) { GHashTable *locks; pthread_mutex_lock (&mgr->priv->hash_lock); locks = g_hash_table_lookup (mgr->priv->repo_locked_files, repo_id); if (!locks) { pthread_mutex_unlock (&mgr->priv->hash_lock); return 0; } g_hash_table_remove (locks, path); pthread_mutex_unlock (&mgr->priv->hash_lock); #ifdef WIN32 refresh_locked_path_status (repo_id, path); #endif return remove_locked_file_from_db (mgr, repo_id, path); } void file_lock_info_free (FileLockInfo *info) { if (!info) return; g_free (info->path); g_free (info); } static gboolean collect_auto_locked_files (sqlite3_stmt *stmt, void *vret) { GList **pret = vret; const char *repo_id, *path; FileLockInfo *info; repo_id = (const char *)sqlite3_column_text (stmt, 0); path = (const char *)sqlite3_column_text (stmt, 1); info = g_new0 (FileLockInfo, 1); memcpy (info->repo_id, repo_id, 36); info->path = g_strdup(path); *pret = g_list_prepend (*pret, info); return TRUE; } GList * seaf_filelock_manager_get_auto_locked_files (SeafFilelockManager *mgr) { char *sql; GList *ret = NULL; pthread_mutex_lock (&mgr->priv->db_lock); sql = sqlite3_mprintf ("SELECT repo_id, path FROM ServerLockedFiles " "WHERE locked_by_me = %d", LOCKED_AUTO); sqlite_foreach_selected_row (mgr->priv->db, sql, collect_auto_locked_files, &ret); pthread_mutex_unlock (&mgr->priv->db_lock); ret = g_list_reverse (ret); sqlite3_free (sql); return ret; } int seaf_filelock_manager_lock_file (SeafFilelockManager *mgr, const char *repo_id, const char *path, FileLockType type) { int ret = -1; ret = seaf_filelock_manager_mark_file_locked (seaf->filelock_mgr, repo_id, path, type); if (ret < 0) { return ret; } if (!type) { seaf_filelock_manager_lock_wt_file (mgr, repo_id, path); } return 0; } int seaf_filelock_manager_unlock_file (SeafFilelockManager *mgr, const char *repo_id, const char *path) { int ret = -1; ret = seaf_filelock_manager_mark_file_unlocked (mgr, repo_id, path); if (ret < 0) { return ret; } seaf_filelock_manager_unlock_wt_file (mgr, repo_id, path); return 0; }