mirror of
https://github.com/haiwen/seafile-server.git
synced 2025-05-31 11:05:30 +00:00
605 lines
18 KiB
C
605 lines
18 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
|
|
#include "common.h"
|
|
#define DEBUG_FLAG SEAFILE_DEBUG_OTHER
|
|
#include "log.h"
|
|
#include "utils.h"
|
|
|
|
#include "seafile-session.h"
|
|
#include "seaf-db.h"
|
|
#include "quota-mgr.h"
|
|
#include "seaf-utils.h"
|
|
|
|
#define KB 1000L
|
|
#define MB 1000000L
|
|
#define GB 1000000000L
|
|
#define TB 1000000000000L
|
|
|
|
static gint64
|
|
get_default_quota (SeafCfgManager *mgr)
|
|
{
|
|
char *quota_str;
|
|
char *end;
|
|
gint64 quota_int;
|
|
gint64 multiplier = GB;
|
|
gint64 quota;
|
|
|
|
quota_str = seaf_cfg_manager_get_config_string (mgr, "quota", "default");
|
|
if (!quota_str)
|
|
return INFINITE_QUOTA;
|
|
|
|
quota_int = strtoll (quota_str, &end, 10);
|
|
if (quota_int == LLONG_MIN || quota_int == LLONG_MAX) {
|
|
seaf_warning ("Default quota value out of range. Use unlimited.\n");
|
|
quota = INFINITE_QUOTA;
|
|
goto out;
|
|
}
|
|
|
|
if (*end != '\0') {
|
|
if (strcasecmp(end, "kb") == 0 || strcasecmp(end, "k") == 0)
|
|
multiplier = KB;
|
|
else if (strcasecmp(end, "mb") == 0 || strcasecmp(end, "m") == 0)
|
|
multiplier = MB;
|
|
else if (strcasecmp(end, "gb") == 0 || strcasecmp(end, "g") == 0)
|
|
multiplier = GB;
|
|
else if (strcasecmp(end, "tb") == 0 || strcasecmp(end, "t") == 0)
|
|
multiplier = TB;
|
|
else {
|
|
seaf_warning ("Invalid default quota format %s. Use unlimited.\n", quota_str);
|
|
quota = INFINITE_QUOTA;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
quota = quota_int * multiplier;
|
|
|
|
out:
|
|
g_free (quota_str);
|
|
return quota;
|
|
}
|
|
|
|
SeafQuotaManager *
|
|
seaf_quota_manager_new (struct _SeafileSession *session)
|
|
{
|
|
SeafQuotaManager *mgr = g_new0 (SeafQuotaManager, 1);
|
|
if (!mgr)
|
|
return NULL;
|
|
mgr->session = session;
|
|
|
|
mgr->calc_share_usage = g_key_file_get_boolean (session->config,
|
|
"quota", "calc_share_usage",
|
|
NULL);
|
|
|
|
return mgr;
|
|
}
|
|
|
|
int
|
|
seaf_quota_manager_init (SeafQuotaManager *mgr)
|
|
{
|
|
|
|
if (!mgr->session->create_tables && seaf_db_type (mgr->session->db) != SEAF_DB_TYPE_PGSQL)
|
|
return 0;
|
|
|
|
SeafDB *db = mgr->session->db;
|
|
const char *sql;
|
|
|
|
switch (seaf_db_type(db)) {
|
|
case SEAF_DB_TYPE_PGSQL:
|
|
sql = "CREATE TABLE IF NOT EXISTS UserQuota (\"user\" VARCHAR(255) PRIMARY KEY,"
|
|
"quota BIGINT)";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (\"user\" VARCHAR(255) PRIMARY KEY,"
|
|
"quota BIGINT)";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS OrgQuota (org_id INTEGER PRIMARY KEY,"
|
|
"quota BIGINT)";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (org_id INTEGER,"
|
|
"\"user\" VARCHAR(255), quota BIGINT, PRIMARY KEY (org_id, \"user\"))";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
break;
|
|
case SEAF_DB_TYPE_SQLITE:
|
|
sql = "CREATE TABLE IF NOT EXISTS UserQuota (user VARCHAR(255) PRIMARY KEY,"
|
|
"quota BIGINT)";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (user VARCHAR(255) PRIMARY KEY,"
|
|
"quota BIGINT)";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS OrgQuota (org_id INTEGER PRIMARY KEY,"
|
|
"quota BIGINT)";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (org_id INTEGER,"
|
|
"user VARCHAR(255), quota BIGINT, PRIMARY KEY (org_id, user))";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
break;
|
|
case SEAF_DB_TYPE_MYSQL:
|
|
sql = "CREATE TABLE IF NOT EXISTS UserQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
|
|
"user VARCHAR(255),"
|
|
"quota BIGINT, UNIQUE INDEX(user)) ENGINE=INNODB";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
|
|
"user VARCHAR(255),"
|
|
"quota BIGINT, UNIQUE INDEX(user)) ENGINE=INNODB";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS OrgQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
|
|
"org_id INTEGER,"
|
|
"quota BIGINT, UNIQUE INDEX(org_id)) ENGINE=INNODB";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
|
|
"org_id INTEGER,"
|
|
"user VARCHAR(255), quota BIGINT, UNIQUE INDEX(org_id, user))"
|
|
"ENGINE=INNODB";
|
|
if (seaf_db_query (db, sql) < 0)
|
|
return -1;
|
|
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
seaf_quota_manager_set_user_quota (SeafQuotaManager *mgr,
|
|
const char *user,
|
|
gint64 quota)
|
|
{
|
|
SeafDB *db = mgr->session->db;
|
|
if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
|
|
gboolean exists, err;
|
|
int rc;
|
|
|
|
exists = seaf_db_statement_exists (db,
|
|
"SELECT 1 FROM UserQuota WHERE \"user\"=?",
|
|
&err, 1, "string", user);
|
|
if (err)
|
|
return -1;
|
|
|
|
if (exists)
|
|
rc = seaf_db_statement_query (db,
|
|
"UPDATE UserQuota SET quota=? "
|
|
"WHERE \"user\"=?",
|
|
2, "int64", quota, "string", user);
|
|
else
|
|
rc = seaf_db_statement_query (db,
|
|
"INSERT INTO UserQuota (\"user\", quota) VALUES "
|
|
"(?, ?)",
|
|
2, "string", user, "int64", quota);
|
|
return rc;
|
|
} else {
|
|
int rc;
|
|
rc = seaf_db_statement_query (db,
|
|
"REPLACE INTO UserQuota (user, quota) VALUES (?, ?)",
|
|
2, "string", user, "int64", quota);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
gint64
|
|
seaf_quota_manager_get_user_quota (SeafQuotaManager *mgr,
|
|
const char *user)
|
|
{
|
|
char *sql;
|
|
gint64 quota;
|
|
|
|
if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL)
|
|
sql = "SELECT quota FROM UserQuota WHERE user=?";
|
|
else
|
|
sql = "SELECT quota FROM UserQuota WHERE \"user\"=?";
|
|
|
|
quota = seaf_db_statement_get_int64 (mgr->session->db, sql,
|
|
1, "string", user);
|
|
if (quota <= 0)
|
|
quota = get_default_quota (seaf->cfg_mgr);
|
|
|
|
return quota;
|
|
}
|
|
|
|
int
|
|
seaf_quota_manager_set_org_quota (SeafQuotaManager *mgr,
|
|
int org_id,
|
|
gint64 quota)
|
|
{
|
|
SeafDB *db = mgr->session->db;
|
|
|
|
if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
|
|
gboolean exists, err;
|
|
int rc;
|
|
|
|
exists = seaf_db_statement_exists (db,
|
|
"SELECT 1 FROM OrgQuota WHERE org_id=?",
|
|
&err, 1, "int", org_id);
|
|
if (err)
|
|
return -1;
|
|
|
|
if (exists)
|
|
rc = seaf_db_statement_query (db,
|
|
"UPDATE OrgQuota SET quota=? WHERE org_id=?",
|
|
2, "int64", quota, "int", org_id);
|
|
else
|
|
rc = seaf_db_statement_query (db,
|
|
"INSERT INTO OrgQuota (org_id, quota) VALUES (?, ?)",
|
|
2, "int", org_id, "int64", quota);
|
|
return rc;
|
|
} else {
|
|
int rc = seaf_db_statement_query (db,
|
|
"REPLACE INTO OrgQuota (org_id, quota) VALUES (?, ?)",
|
|
2, "int", org_id, "int64", quota);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
gint64
|
|
seaf_quota_manager_get_org_quota (SeafQuotaManager *mgr,
|
|
int org_id)
|
|
{
|
|
char *sql;
|
|
gint64 quota;
|
|
|
|
sql = "SELECT quota FROM OrgQuota WHERE org_id=?";
|
|
quota = seaf_db_statement_get_int64 (mgr->session->db, sql, 1, "int", org_id);
|
|
if (quota <= 0)
|
|
quota = get_default_quota (seaf->cfg_mgr);
|
|
|
|
return quota;
|
|
}
|
|
|
|
int
|
|
seaf_quota_manager_set_org_user_quota (SeafQuotaManager *mgr,
|
|
int org_id,
|
|
const char *user,
|
|
gint64 quota)
|
|
{
|
|
SeafDB *db = mgr->session->db;
|
|
int rc;
|
|
|
|
if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
|
|
gboolean exists, err;
|
|
|
|
exists = seaf_db_statement_exists (db,
|
|
"SELECT 1 FROM OrgUserQuota "
|
|
"WHERE org_id=? AND \"user\"=?",
|
|
&err, 2, "int", org_id, "string", user);
|
|
if (err)
|
|
return -1;
|
|
|
|
if (exists)
|
|
rc = seaf_db_statement_query (db,
|
|
"UPDATE OrgUserQuota SET quota=?"
|
|
" WHERE org_id=? AND \"user\"=?",
|
|
3, "int64", quota, "int", org_id,
|
|
"string", user);
|
|
else
|
|
rc = seaf_db_statement_query (db,
|
|
"INSERT INTO OrgUserQuota (org_id, \"user\", quota) VALUES "
|
|
"(?, ?, ?)",
|
|
3, "int", org_id, "string", user,
|
|
"int64", quota);
|
|
return rc;
|
|
} else {
|
|
rc = seaf_db_statement_query (db,
|
|
"REPLACE INTO OrgUserQuota (org_id, user, quota) VALUES (?, ?, ?)",
|
|
3, "int", org_id, "string", user, "int64", quota);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
gint64
|
|
seaf_quota_manager_get_org_user_quota (SeafQuotaManager *mgr,
|
|
int org_id,
|
|
const char *user)
|
|
{
|
|
char *sql;
|
|
gint64 quota;
|
|
|
|
if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL)
|
|
sql = "SELECT quota FROM OrgUserQuota WHERE org_id=? AND user=?";
|
|
else
|
|
sql = "SELECT quota FROM OrgUserQuota WHERE org_id=? AND \"user\"=?";
|
|
|
|
quota = seaf_db_statement_get_int64 (mgr->session->db, sql,
|
|
2, "int", org_id, "string", user);
|
|
/* return org quota if per user quota is not set. */
|
|
if (quota <= 0)
|
|
quota = seaf_quota_manager_get_org_quota (mgr, org_id);
|
|
|
|
return quota;
|
|
}
|
|
|
|
static void
|
|
count_group_members (GHashTable *user_hash, GList *members)
|
|
{
|
|
GList *p;
|
|
CcnetGroupUser *user;
|
|
const char *user_name;
|
|
int dummy;
|
|
|
|
for (p = members; p; p = p->next) {
|
|
user = p->data;
|
|
user_name = ccnet_group_user_get_user_name (user);
|
|
g_hash_table_insert (user_hash, g_strdup(user_name), &dummy);
|
|
/* seaf_debug ("Shared to %s.\n", user_name); */
|
|
g_object_unref (user);
|
|
}
|
|
|
|
g_list_free (members);
|
|
}
|
|
|
|
static gint
|
|
get_num_shared_to (const char *user, const char *repo_id)
|
|
{
|
|
GHashTable *user_hash;
|
|
int dummy;
|
|
GList *personal = NULL, *groups = NULL, *members = NULL, *p;
|
|
gint n_shared_to = -1;
|
|
|
|
/* seaf_debug ("Computing share usage for repo %s.\n", repo_id); */
|
|
|
|
/* If a repo is shared to both a user and a group, and that user is also
|
|
* a member of the group, we don't want to count that user twice.
|
|
* This also applies to two groups with overlapped members.
|
|
* So we have to use a hash table to filter out duplicated users.
|
|
*/
|
|
user_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
|
|
/* First count personal share */
|
|
personal = seaf_share_manager_list_shared_to (seaf->share_mgr, user, repo_id);
|
|
for (p = personal; p; p = p->next) {
|
|
char *email = p->data;
|
|
g_hash_table_insert (user_hash, g_strdup(email), &dummy);
|
|
/* seaf_debug ("Shared to %s.\n", email); */
|
|
}
|
|
|
|
/* Then groups... */
|
|
groups = seaf_repo_manager_get_groups_by_repo (seaf->repo_mgr,
|
|
repo_id, NULL);
|
|
for (p = groups; p; p = p->next) {
|
|
members = ccnet_group_manager_get_group_members (seaf->group_mgr, (int)(long)p->data, -1, -1, NULL);
|
|
if (!members) {
|
|
seaf_warning ("Cannot get member list for groupd %d.\n", (int)(long)p->data);
|
|
goto out;
|
|
}
|
|
|
|
count_group_members (user_hash, members);
|
|
}
|
|
|
|
/* Remove myself if i'm in a group. */
|
|
g_hash_table_remove (user_hash, user);
|
|
|
|
n_shared_to = g_hash_table_size(user_hash);
|
|
/* seaf_debug ("n_shared_to = %u.\n", n_shared_to); */
|
|
|
|
out:
|
|
g_hash_table_destroy (user_hash);
|
|
string_list_free (personal);
|
|
g_list_free (groups);
|
|
|
|
return n_shared_to;
|
|
}
|
|
|
|
int
|
|
seaf_quota_manager_check_quota_with_delta (SeafQuotaManager *mgr,
|
|
const char *repo_id,
|
|
gint64 delta)
|
|
{
|
|
SeafVirtRepo *vinfo;
|
|
const char *r_repo_id = repo_id;
|
|
char *user = NULL;
|
|
gint64 quota, usage;
|
|
int ret = 0;
|
|
|
|
/* If it's a virtual repo, check quota to origin repo. */
|
|
vinfo = seaf_repo_manager_get_virtual_repo_info (seaf->repo_mgr, repo_id);
|
|
if (vinfo)
|
|
r_repo_id = vinfo->origin_repo_id;
|
|
|
|
user = seaf_repo_manager_get_repo_owner (seaf->repo_mgr, r_repo_id);
|
|
if (user != NULL) {
|
|
if (g_strrstr (user, "dtable@seafile") != NULL)
|
|
goto out;
|
|
quota = seaf_quota_manager_get_user_quota (mgr, user);
|
|
} else {
|
|
seaf_warning ("Repo %s has no owner.\n", r_repo_id);
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (quota == INFINITE_QUOTA)
|
|
goto out;
|
|
|
|
usage = seaf_quota_manager_get_user_usage (mgr, user);
|
|
if (usage < 0) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (delta != 0) {
|
|
usage += delta;
|
|
}
|
|
if (usage >= quota) {
|
|
ret = 1;
|
|
}
|
|
|
|
out:
|
|
seaf_virtual_repo_info_free (vinfo);
|
|
g_free (user);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
seaf_quota_manager_check_quota (SeafQuotaManager *mgr,
|
|
const char *repo_id)
|
|
{
|
|
int ret = seaf_quota_manager_check_quota_with_delta (mgr, repo_id, 0);
|
|
|
|
if (ret == 1) {
|
|
return -1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
gint64
|
|
seaf_quota_manager_get_user_usage (SeafQuotaManager *mgr, const char *user)
|
|
{
|
|
char *sql;
|
|
|
|
sql = "SELECT SUM(size) FROM "
|
|
"RepoOwner o LEFT JOIN VirtualRepo v ON o.repo_id=v.repo_id, "
|
|
"RepoSize WHERE "
|
|
"owner_id=? AND o.repo_id=RepoSize.repo_id "
|
|
"AND v.repo_id IS NULL";
|
|
|
|
return seaf_db_statement_get_int64 (mgr->session->db, sql,
|
|
1, "string", user);
|
|
|
|
/* Add size of repos in trash. */
|
|
/* sql = "SELECT size FROM RepoTrash WHERE owner_id = ?"; */
|
|
/* if (seaf_db_statement_foreach_row (mgr->session->db, sql, */
|
|
/* get_total_size, &total, */
|
|
/* 1, "string", user) < 0) */
|
|
/* return -1; */
|
|
}
|
|
|
|
static gint64
|
|
repo_share_usage (const char *user, const char *repo_id)
|
|
{
|
|
gint n_shared_to = get_num_shared_to (user, repo_id);
|
|
if (n_shared_to < 0) {
|
|
return -1;
|
|
} else if (n_shared_to == 0) {
|
|
return 0;
|
|
}
|
|
|
|
gint64 size = seaf_repo_manager_get_repo_size (seaf->repo_mgr, repo_id);
|
|
if (size < 0) {
|
|
seaf_warning ("Cannot get size of repo %s.\n", repo_id);
|
|
return -1;
|
|
}
|
|
|
|
/* share_usage = repo_size * n_shared_to */
|
|
gint64 usage = size * n_shared_to;
|
|
|
|
return usage;
|
|
}
|
|
|
|
gint64
|
|
seaf_quota_manager_get_user_share_usage (SeafQuotaManager *mgr,
|
|
const char *user)
|
|
{
|
|
GList *repos, *p;
|
|
char *repo_id;
|
|
gint64 total = 0, per_repo;
|
|
|
|
repos = seaf_repo_manager_get_repo_ids_by_owner (seaf->repo_mgr, user);
|
|
|
|
for (p = repos; p != NULL; p = p->next) {
|
|
repo_id = p->data;
|
|
per_repo = repo_share_usage (user, repo_id);
|
|
if (per_repo < 0) {
|
|
seaf_warning ("Failed to get repo %s share usage.\n", repo_id);
|
|
string_list_free (repos);
|
|
return -1;
|
|
}
|
|
|
|
total += per_repo;
|
|
}
|
|
|
|
string_list_free (repos);
|
|
return total;
|
|
}
|
|
|
|
gint64
|
|
seaf_quota_manager_get_org_usage (SeafQuotaManager *mgr, int org_id)
|
|
{
|
|
char *sql;
|
|
|
|
sql = "SELECT SUM(size) FROM OrgRepo, RepoSize WHERE "
|
|
"org_id=? AND OrgRepo.repo_id=RepoSize.repo_id";
|
|
|
|
return seaf_db_statement_get_int64 (mgr->session->db, sql,
|
|
1, "int", org_id);
|
|
}
|
|
|
|
gint64
|
|
seaf_quota_manager_get_org_user_usage (SeafQuotaManager *mgr,
|
|
int org_id,
|
|
const char *user)
|
|
{
|
|
char *sql;
|
|
|
|
sql = "SELECT SUM(size) FROM OrgRepo, RepoSize WHERE "
|
|
"org_id=? AND user = ? AND OrgRepo.repo_id=RepoSize.repo_id";
|
|
|
|
return seaf_db_statement_get_int64 (mgr->session->db, sql,
|
|
2, "int", org_id, "string", user);
|
|
}
|
|
|
|
static gboolean
|
|
collect_user_and_usage (SeafDBRow *row, void *data)
|
|
{
|
|
GList **p = data;
|
|
const char *user;
|
|
gint64 usage;
|
|
|
|
user = seaf_db_row_get_column_text (row, 0);
|
|
usage = seaf_db_row_get_column_int64 (row, 1);
|
|
|
|
if (!user)
|
|
return TRUE;
|
|
|
|
SeafileUserQuotaUsage *user_usage= g_object_new (SEAFILE_TYPE_USER_QUOTA_USAGE,
|
|
"user", user,
|
|
"usage", usage,
|
|
NULL);
|
|
if (!user_usage)
|
|
return FALSE;
|
|
|
|
*p = g_list_prepend (*p, user_usage);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GList *
|
|
seaf_repo_quota_manager_list_user_quota_usage (SeafQuotaManager *mgr)
|
|
{
|
|
GList *ret = NULL;
|
|
char *sql = NULL;
|
|
|
|
sql = "SELECT owner_id,SUM(size) FROM "
|
|
"RepoOwner o LEFT JOIN VirtualRepo v ON o.repo_id=v.repo_id, "
|
|
"RepoSize WHERE "
|
|
"o.repo_id=RepoSize.repo_id "
|
|
"AND v.repo_id IS NULL "
|
|
"GROUP BY owner_id";
|
|
|
|
if (seaf_db_statement_foreach_row (mgr->session->db, sql,
|
|
collect_user_and_usage,
|
|
&ret, 0) < 0) {
|
|
g_list_free_full (ret, g_object_unref);
|
|
return NULL;
|
|
}
|
|
|
|
return g_list_reverse (ret);
|
|
}
|