Closes #166: global quota

This commit is contained in:
Benjamin Gamard 2017-11-20 20:34:29 +01:00
parent 66f781b716
commit fb75bafe96
9 changed files with 71 additions and 30 deletions

View File

@ -46,23 +46,28 @@ public class Constants {
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor"); public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor");
/** /**
* Base URL environnement variable. * Base URL environment variable.
*/ */
public static final String BASE_URL_ENV = "DOCS_BASE_URL"; public static final String BASE_URL_ENV = "DOCS_BASE_URL";
/** /**
* Default language environnement variable. * Default language environment variable.
*/ */
public static final String DEFAULT_LANGUAGE_ENV = "DOCS_DEFAULT_LANGUAGE"; public static final String DEFAULT_LANGUAGE_ENV = "DOCS_DEFAULT_LANGUAGE";
/** /**
* SMTP configuration environnement variables. * SMTP configuration environment variables.
*/ */
public static final String SMTP_HOSTNAME_ENV = "DOCS_SMTP_HOSTNAME"; public static final String SMTP_HOSTNAME_ENV = "DOCS_SMTP_HOSTNAME";
public static final String SMTP_PORT_ENV = "DOCS_SMTP_PORT"; public static final String SMTP_PORT_ENV = "DOCS_SMTP_PORT";
public static final String SMTP_USERNAME_ENV = "DOCS_SMTP_USERNAME"; public static final String SMTP_USERNAME_ENV = "DOCS_SMTP_USERNAME";
public static final String SMTP_PASSWORD_ENV = "DOCS_SMTP_PASSWORD"; public static final String SMTP_PASSWORD_ENV = "DOCS_SMTP_PASSWORD";
/**
* Global quota environment variable.
*/
public static final String GLOBAL_QUOTA_ENV = "DOCS_GLOBAL_QUOTA";
/** /**
* Expiration time of the password recovery in hours. * Expiration time of the password recovery in hours.
*/ */

View File

@ -1,19 +1,5 @@
package com.sismics.docs.core.dao.jpa; package com.sismics.docs.core.dao.jpa;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import org.mindrot.jbcrypt.BCrypt;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType; import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria; import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
@ -24,6 +10,13 @@ import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil; import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.context.ThreadLocalContext;
import org.mindrot.jbcrypt.BCrypt;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.sql.Timestamp;
import java.util.*;
/** /**
* User DAO. * User DAO.
@ -59,7 +52,7 @@ public class UserDao {
* @param user User to create * @param user User to create
* @param userId User ID * @param userId User ID
* @return User ID * @return User ID
* @throws Exception * @throws Exception e
*/ */
public String create(User user, String userId) throws Exception { public String create(User user, String userId) throws Exception {
// Create the user UUID // Create the user UUID
@ -116,9 +109,8 @@ public class UserDao {
* Updates a user's quota. * Updates a user's quota.
* *
* @param user User to update * @param user User to update
* @return Updated user
*/ */
public User updateQuota(User user) { public void updateQuota(User user) {
EntityManager em = ThreadLocalContext.get().getEntityManager(); EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user // Get the user
@ -128,8 +120,6 @@ public class UserDao {
// Update the user // Update the user
userFromDb.setStorageQuota(user.getStorageQuota()); userFromDb.setStorageQuota(user.getStorageQuota());
return user;
} }
/** /**
@ -298,4 +288,15 @@ public class UserDao {
} }
return userDtoList; return userDtoList;
} }
/**
* Returns the global storage used by all users.
*
* @return Current global storage
*/
public long getGlobalStorageCurrent() {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query query = em.createNativeQuery("select sum(u.USE_STORAGECURRENT_N) from T_USER u where u.USE_DELETEDATE_D is null");
return ((Number) query.getSingleResult()).longValue();
}
} }

View File

@ -63,6 +63,8 @@ public class AppResource extends BaseResource {
* @apiSuccess {Boolean} guest_login True if guest login is enabled * @apiSuccess {Boolean} guest_login True if guest login is enabled
* @apiSuccess {String} total_memory Allocated JVM memory (in bytes) * @apiSuccess {String} total_memory Allocated JVM memory (in bytes)
* @apiSuccess {String} free_memory Free JVM memory (in bytes) * @apiSuccess {String} free_memory Free JVM memory (in bytes)
* @apiSuccess {String} global_storage_current Global storage currently used (in bytes)
* @apiSuccess {String} global_storage_quota Maximum global storage (in bytes)
* @apiPermission none * @apiPermission none
* @apiVersion 1.5.0 * @apiVersion 1.5.0
* *
@ -74,14 +76,24 @@ public class AppResource extends BaseResource {
String currentVersion = configBundle.getString("api.current_version"); String currentVersion = configBundle.getString("api.current_version");
String minVersion = configBundle.getString("api.min_version"); String minVersion = configBundle.getString("api.min_version");
Boolean guestLogin = ConfigUtil.getConfigBooleanValue(ConfigType.GUEST_LOGIN); Boolean guestLogin = ConfigUtil.getConfigBooleanValue(ConfigType.GUEST_LOGIN);
UserDao userDao = new UserDao();
String globalQuotaStr = System.getenv(Constants.GLOBAL_QUOTA_ENV);
long globalQuota = 0;
if (!Strings.isNullOrEmpty(globalQuotaStr)) {
globalQuota = Long.valueOf(globalQuotaStr);
}
JsonObjectBuilder response = Json.createObjectBuilder() JsonObjectBuilder response = Json.createObjectBuilder()
.add("current_version", currentVersion.replace("-SNAPSHOT", "")) .add("current_version", currentVersion.replace("-SNAPSHOT", ""))
.add("min_version", minVersion) .add("min_version", minVersion)
.add("guest_login", guestLogin) .add("guest_login", guestLogin)
.add("total_memory", Runtime.getRuntime().totalMemory()) .add("total_memory", Runtime.getRuntime().totalMemory())
.add("free_memory", Runtime.getRuntime().freeMemory()); .add("free_memory", Runtime.getRuntime().freeMemory())
.add("global_storage_current", userDao.getGlobalStorageCurrent());
if (globalQuota > 0) {
response.add("global_storage_quota", globalQuota);
}
return Response.ok().entity(response.build()).build(); return Response.ok().entity(response.build()).build();
} }

View File

@ -3,6 +3,7 @@ package com.sismics.docs.rest.resource;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.dao.jpa.DocumentDao; import com.sismics.docs.core.dao.jpa.DocumentDao;
@ -14,7 +15,10 @@ import com.sismics.docs.core.event.FileCreatedAsyncEvent;
import com.sismics.docs.core.event.FileDeletedAsyncEvent; import com.sismics.docs.core.event.FileDeletedAsyncEvent;
import com.sismics.docs.core.model.jpa.File; import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.*; import com.sismics.docs.core.util.DirectoryUtil;
import com.sismics.docs.core.util.EncryptionUtil;
import com.sismics.docs.core.util.FileUtil;
import com.sismics.docs.core.util.PdfUtil;
import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException; import com.sismics.rest.exception.ServerException;
@ -34,7 +38,9 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.StreamingOutput;
import java.io.*; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -130,11 +136,21 @@ public class FileResource extends BaseResource {
throw new ServerException("ErrorGuessMime", "Error guessing mime type", e); throw new ServerException("ErrorGuessMime", "Error guessing mime type", e);
} }
// Validate quota // Validate user quota
if (user.getStorageCurrent() + fileSize > user.getStorageQuota()) { if (user.getStorageCurrent() + fileSize > user.getStorageQuota()) {
throw new ClientException("QuotaReached", "Quota limit reached"); throw new ClientException("QuotaReached", "Quota limit reached");
} }
// Validate global quota
String globalStorageQuotaStr = System.getenv(Constants.GLOBAL_QUOTA_ENV);
if (!Strings.isNullOrEmpty(globalStorageQuotaStr)) {
long globalStorageQuota = Long.valueOf(globalStorageQuotaStr);
long globalStorageCurrent = userDao.getGlobalStorageCurrent();
if (globalStorageCurrent + fileSize > globalStorageQuota) {
throw new ClientException("QuotaReached", "Global quota limit reached");
}
}
try { try {
// Get files of this document // Get files of this document
FileDao fileDao = new FileDao(); FileDao fileDao = new FileDao();

View File

@ -77,7 +77,7 @@ angular.module('docs').controller('DocumentDefault', function($scope, $rootScope
}) })
.error(function (data) { .error(function (data) {
newfile.status = $translate.instant('document.default.upload_error'); newfile.status = $translate.instant('document.default.upload_error');
if (data.type == 'QuotaReached') { if (data.type === 'QuotaReached') {
newfile.status += ' - ' + $translate.instant('document.default.upload_error_quota'); newfile.status += ' - ' + $translate.instant('document.default.upload_error_quota');
} }
}); });

View File

@ -157,6 +157,10 @@
<div class="row" ng-controller="Footer"> <div class="row" ng-controller="Footer">
<div class="col-md-12 footer text-center text-muted"> <div class="col-md-12 footer text-center text-muted">
<div class="alert alert-danger" ng-show="app.global_storage_quota && app.global_storage_quota * 0.8 < app.global_storage_current"
translate="index.global_quota_warning"
translate-values="{ current: app.global_storage_current / 1000000, percent: app.global_storage_current / app.global_storage_quota * 100, total: app.global_storage_quota / 1000000 }">
</div>
<ul class="list-inline"> <ul class="list-inline">
<li uib-dropdown class="dropdown"> <li uib-dropdown class="dropdown">
<a href uib-dropdown-toggle> <a href uib-dropdown-toggle>

View File

@ -35,7 +35,8 @@
"error_info": "{{ count }} new error{{ count > 1 ? 's' : '' }}", "error_info": "{{ count }} new error{{ count > 1 ? 's' : '' }}",
"logged_as": "Logged in as {{ username }}", "logged_as": "Logged in as {{ username }}",
"nav_settings": "Settings", "nav_settings": "Settings",
"logout": "Logout" "logout": "Logout",
"global_quota_warning": "<strong>Warning!</strong> Global quota almost reached at {{ current | number: 0 }}MB ({{ percent | number: 1 }}%) used on {{ total | number: 0 }}MB"
}, },
"document": { "document": {
"search_simple": "Simple search", "search_simple": "Simple search",

View File

@ -36,6 +36,7 @@ public class TestAppResource extends BaseJerseyTest {
Long totalMemory = json.getJsonNumber("total_memory").longValue(); Long totalMemory = json.getJsonNumber("total_memory").longValue();
Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory); Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory);
Assert.assertFalse(json.getBoolean("guest_login")); Assert.assertFalse(json.getBoolean("guest_login"));
Assert.assertTrue(json.containsKey("global_storage_current"));
// Rebuild Lucene index // Rebuild Lucene index
Response response = target().path("/app/batch/reindex").request() Response response = target().path("/app/batch/reindex").request()

View File

@ -9,3 +9,4 @@ log4j.logger.com.sismics=INFO
log4j.logger.com.sismics.util.jpa=ERROR log4j.logger.com.sismics.util.jpa=ERROR
log4j.logger.org.hibernate=ERROR log4j.logger.org.hibernate=ERROR
log4j.logger.org.apache.pdfbox=INFO log4j.logger.org.apache.pdfbox=INFO
log4j.logger.com.mchange=ERROR