diff --git a/Dockerfile b/Dockerfile index 993a74bf..a6a1a2e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,8 @@ RUN apt-get update && \ tesseract-ocr-tha \ tesseract-ocr-tur \ tesseract-ocr-ukr \ - tesseract-ocr-vie && \ + tesseract-ocr-vie \ + tesseract-ocr-sqi && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ mkdir /app && \ cd /app && \ diff --git a/docs-core/src/main/java/com/sismics/docs/core/constant/Constants.java b/docs-core/src/main/java/com/sismics/docs/core/constant/Constants.java index 16de2b85..4121573a 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/constant/Constants.java +++ b/docs-core/src/main/java/com/sismics/docs/core/constant/Constants.java @@ -43,7 +43,7 @@ public class Constants { /** * Supported document languages. */ - public static final List SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld", "tur", "heb", "hun", "fin", "swe", "lav", "dan", "nor", "vie", "ces"); + public static final List SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld", "tur", "heb", "hun", "fin", "swe", "lav", "dan", "nor", "vie", "ces", "sqi"); /** * Base URL environment variable. diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/FileDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/FileDao.java index 92c2c529..b66fbaf8 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/FileDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/FileDao.java @@ -163,6 +163,7 @@ public class FileDao { fileDb.setMimeType(file.getMimeType()); fileDb.setVersionId(file.getVersionId()); fileDb.setLatestVersion(file.isLatestVersion()); + fileDb.setSize(file.getSize()); return file; } @@ -245,4 +246,12 @@ public class FileDao { q.setParameter("versionId", versionId); return q.getResultList(); } + + public List getFilesWithUnknownSize(int limit) { + EntityManager em = ThreadLocalContext.get().getEntityManager(); + TypedQuery q = em.createQuery("select f from File f where f.size = :size and f.deleteDate is null order by f.order asc", File.class); + q.setParameter("size", File.UNKNOWN_SIZE); + q.setMaxResults(limit); + return q.getResultList(); + } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/event/FileDeletedAsyncEvent.java b/docs-core/src/main/java/com/sismics/docs/core/event/FileDeletedAsyncEvent.java index 4a6e769a..34cf0cd0 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/event/FileDeletedAsyncEvent.java +++ b/docs-core/src/main/java/com/sismics/docs/core/event/FileDeletedAsyncEvent.java @@ -13,6 +13,8 @@ public class FileDeletedAsyncEvent extends UserEvent { */ private String fileId; + private Long fileSize; + public String getFileId() { return fileId; } @@ -21,10 +23,19 @@ public class FileDeletedAsyncEvent extends UserEvent { this.fileId = fileId; } + public Long getFileSize() { + return fileSize; + } + + public void setFileSize(Long fileSize) { + this.fileSize = fileSize; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) .add("fileId", fileId) + .add("fileSize", fileSize) .toString(); } -} \ No newline at end of file +} diff --git a/docs-core/src/main/java/com/sismics/docs/core/listener/async/FileDeletedAsyncListener.java b/docs-core/src/main/java/com/sismics/docs/core/listener/async/FileDeletedAsyncListener.java index 54faf5f8..3c6fea3a 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/listener/async/FileDeletedAsyncListener.java +++ b/docs-core/src/main/java/com/sismics/docs/core/listener/async/FileDeletedAsyncListener.java @@ -2,8 +2,11 @@ package com.sismics.docs.core.listener.async; import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; +import com.sismics.docs.core.dao.UserDao; import com.sismics.docs.core.event.FileDeletedAsyncEvent; import com.sismics.docs.core.model.context.AppContext; +import com.sismics.docs.core.model.jpa.File; +import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.util.FileUtil; import com.sismics.docs.core.util.TransactionUtil; import org.slf4j.Logger; @@ -11,7 +14,7 @@ import org.slf4j.LoggerFactory; /** * Listener on file deleted. - * + * * @author bgamard */ public class FileDeletedAsyncListener { @@ -22,7 +25,7 @@ public class FileDeletedAsyncListener { /** * File deleted. - * + * * @param event File deleted event * @throws Exception e */ @@ -32,6 +35,24 @@ public class FileDeletedAsyncListener { if (log.isInfoEnabled()) { log.info("File deleted event: " + event.toString()); } + TransactionUtil.handle(() -> { + // Update the user quota + UserDao userDao = new UserDao(); + User user = userDao.getById(event.getUserId()); + if (user != null) { + Long fileSize = event.getFileSize(); + + if (fileSize.equals(File.UNKNOWN_SIZE)) { + // The file size was not in the database, in this case we need to get from the unencrypted size. + fileSize = FileUtil.getFileSize(event.getFileId(), user); + } + + if (! fileSize.equals(File.UNKNOWN_SIZE)) { + user.setStorageCurrent(user.getStorageCurrent() - fileSize); + userDao.updateQuota(user); + } + } + }); // Delete the file from storage FileUtil.delete(event.getFileId()); diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java b/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java index ed8b9095..bc277038 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/context/AppContext.java @@ -9,6 +9,7 @@ import com.sismics.docs.core.dao.UserDao; import com.sismics.docs.core.listener.async.*; import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.service.FileService; +import com.sismics.docs.core.service.FileSizeService; import com.sismics.docs.core.service.InboxService; import com.sismics.docs.core.util.PdfUtil; import com.sismics.docs.core.util.indexing.IndexingHandler; @@ -65,6 +66,11 @@ public class AppContext { */ private FileService fileService; + /** + * File size service. + */ + private FileSizeService fileSizeService; + /** * Asynchronous executors. */ @@ -102,6 +108,11 @@ public class AppContext { inboxService.startAsync(); inboxService.awaitRunning(); + // Start file size service + fileSizeService = new FileSizeService(); + fileSizeService.startAsync(); + fileSizeService.awaitRunning(); + // Register fonts PdfUtil.registerFonts(); @@ -238,6 +249,10 @@ public class AppContext { fileService.stopAsync(); } + if (fileSizeService != null) { + fileSizeService.stopAsync(); + } + instance = null; } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java index 54d9abc4..c67f85e1 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/File.java @@ -88,6 +88,14 @@ public class File implements Loggable { @Column(name = "FIL_LATESTVERSION_B", nullable = false) private boolean latestVersion; + public static final Long UNKNOWN_SIZE = -1L; + + /** + * Can be {@link File#UNKNOWN_SIZE} if the size has not been stored in the database when the file has been uploaded + */ + @Column(name = "FIL_SIZE_N", nullable = false) + private Long size; + /** * Private key to decrypt the file. * Not saved to database, of course. @@ -204,6 +212,18 @@ public class File implements Loggable { return this; } + /** + * Can return {@link File#UNKNOWN_SIZE} if the file size is not stored in the database. + */ + public Long getSize() { + return size; + } + + public File setSize(Long size) { + this.size = size; + return this; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) diff --git a/docs-core/src/main/java/com/sismics/docs/core/service/FileSizeService.java b/docs-core/src/main/java/com/sismics/docs/core/service/FileSizeService.java new file mode 100644 index 00000000..21cafc63 --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/service/FileSizeService.java @@ -0,0 +1,78 @@ +package com.sismics.docs.core.service; + +import com.google.common.util.concurrent.AbstractScheduledService; +import com.sismics.docs.core.dao.FileDao; +import com.sismics.docs.core.dao.UserDao; +import com.sismics.docs.core.model.jpa.File; +import com.sismics.docs.core.model.jpa.User; +import com.sismics.docs.core.util.FileUtil; +import com.sismics.docs.core.util.TransactionUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Service that retrieve files sizes when they are not in the database. + */ +public class FileSizeService extends AbstractScheduledService { + /** + * Logger. + */ + private static final Logger log = LoggerFactory.getLogger(FileSizeService.class); + + public FileSizeService() { + } + + @Override + protected void startUp() { + log.info("File size service starting up"); + } + + @Override + protected void shutDown() { + log.info("File size service shutting down"); + } + + private static final int BATCH_SIZE = 30; + + @Override + protected void runOneIteration() { + try { + TransactionUtil.handle(() -> { + FileDao fileDao = new FileDao(); + List files = fileDao.getFilesWithUnknownSize(BATCH_SIZE); + for(File file : files) { + processFile(file); + } + if(files.size() < BATCH_SIZE) { + log.info("No more file to process, stopping the service"); + stopAsync(); + } + }); + } catch (Throwable e) { + log.error("Exception during file service iteration", e); + } + } + + void processFile(File file) { + UserDao userDao = new UserDao(); + User user = userDao.getById(file.getUserId()); + if(user == null) { + return; + } + + long fileSize = FileUtil.getFileSize(file.getId(), user); + if(fileSize != File.UNKNOWN_SIZE){ + FileDao fileDao = new FileDao(); + file.setSize(fileSize); + fileDao.update(file); + } + } + + @Override + protected Scheduler scheduler() { + return Scheduler.newFixedDelaySchedule(0, 1, TimeUnit.MINUTES); + } +} diff --git a/docs-core/src/main/java/com/sismics/docs/core/service/InboxService.java b/docs-core/src/main/java/com/sismics/docs/core/service/InboxService.java index 58a0cea3..d3bcdf5b 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/service/InboxService.java +++ b/docs-core/src/main/java/com/sismics/docs/core/service/InboxService.java @@ -1,14 +1,11 @@ package com.sismics.docs.core.service; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import com.google.common.util.concurrent.AbstractScheduledService; import com.sismics.docs.core.constant.ConfigType; import com.sismics.docs.core.dao.TagDao; import com.sismics.docs.core.dao.criteria.TagCriteria; import com.sismics.docs.core.dao.dto.TagDto; import com.sismics.docs.core.event.DocumentCreatedAsyncEvent; -import com.sismics.docs.core.model.jpa.Config; import com.sismics.docs.core.model.jpa.Document; import com.sismics.docs.core.model.jpa.Tag; import com.sismics.docs.core.util.ConfigUtil; diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java index 0598b729..e77e3480 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/FileUtil.java @@ -16,6 +16,9 @@ import com.sismics.util.Scalr; import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.io.InputStreamReaderThread; import com.sismics.util.mime.MimeTypeUtil; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.CountingInputStream; +import org.apache.commons.io.output.NullOutputStream; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +49,7 @@ public class FileUtil { /** * File ID of files currently being processed. */ - private static Set processingFileSet = Collections.synchronizedSet(new HashSet<>()); + private static final Set processingFileSet = Collections.synchronizedSet(new HashSet<>()); /** * Optical character recognition on an image. @@ -149,6 +152,7 @@ public class FileUtil { file.setName(StringUtils.abbreviate(name, 200)); file.setMimeType(mimeType); file.setUserId(userId); + file.setSize(fileSize); // Get files of this document FileDao fileDao = new FileDao(); @@ -240,4 +244,31 @@ public class FileUtil { public static boolean isProcessingFile(String fileId) { return processingFileSet.contains(fileId); } + + /** + * Get the size of a file on disk. + * + * @param fileId the file id + * @param user the file owner + * @return the size or -1 if something went wrong + */ + public static long getFileSize(String fileId, User user) { + // To get the size we copy the decrypted content into a null output stream + // and count the copied byte size. + Path storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId); + if (! Files.exists(storedFile)) { + log.debug("File does not exist " + fileId); + return File.UNKNOWN_SIZE; + } + try (InputStream fileInputStream = Files.newInputStream(storedFile); + InputStream inputStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey()); + CountingInputStream countingInputStream = new CountingInputStream(inputStream); + ) { + IOUtils.copy(countingInputStream, NullOutputStream.NULL_OUTPUT_STREAM); + return countingInputStream.getByteCount(); + } catch (Exception e) { + log.debug("Can't find size of file " + fileId, e); + return File.UNKNOWN_SIZE; + } + } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/authentication/LdapAuthenticationHandler.java b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/LdapAuthenticationHandler.java index f3af472b..65d0afc8 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/authentication/LdapAuthenticationHandler.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/authentication/LdapAuthenticationHandler.java @@ -13,10 +13,9 @@ import org.apache.directory.api.ldap.model.entry.Attribute; import org.apache.directory.api.ldap.model.entry.Entry; import org.apache.directory.api.ldap.model.entry.Value; import org.apache.directory.api.ldap.model.message.SearchScope; -import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory; +import org.apache.directory.ldap.client.api.LdapConnection; import org.apache.directory.ldap.client.api.LdapConnectionConfig; -import org.apache.directory.ldap.client.api.LdapConnectionPool; -import org.apache.directory.ldap.client.api.ValidatingPoolableLdapConnectionFactory; +import org.apache.directory.ldap.client.api.LdapNetworkConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,32 +34,14 @@ public class LdapAuthenticationHandler implements AuthenticationHandler { private static final Logger log = LoggerFactory.getLogger(LdapAuthenticationHandler.class); /** - * LDAP connection pool. + * Get a LDAP connection. + * @return LdapConnection */ - private static LdapConnectionPool pool; - - /** - * Reset the LDAP pool. - */ - public static void reset() { - if (pool != null) { - try { - pool.close(); - } catch (Exception e) { - // NOP - } - } - pool = null; - } - - /** - * Initialize the LDAP pool. - */ - private static void init() { + private LdapConnection getConnection() { ConfigDao configDao = new ConfigDao(); Config ldapEnabled = configDao.getById(ConfigType.LDAP_ENABLED); - if (pool != null || ldapEnabled == null || !Boolean.parseBoolean(ldapEnabled.getValue())) { - return; + if (ldapEnabled == null || !Boolean.parseBoolean(ldapEnabled.getValue())) { + return null; } LdapConnectionConfig config = new LdapConnectionConfig(); @@ -70,25 +51,23 @@ public class LdapAuthenticationHandler implements AuthenticationHandler { config.setName(ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_DN)); config.setCredentials(ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_PASSWORD)); - DefaultLdapConnectionFactory factory = new DefaultLdapConnectionFactory(config); - pool = new LdapConnectionPool(new ValidatingPoolableLdapConnectionFactory(factory), null); + return new LdapNetworkConnection(config); } @Override public User authenticate(String username, String password) { - init(); - if (pool == null) { - return null; - } - // Fetch and authenticate the user Entry userEntry; - try { - EntryCursor cursor = pool.getConnection().search(ConfigUtil.getConfigStringValue(ConfigType.LDAP_BASE_DN), + try (LdapConnection ldapConnection = getConnection()) { + if (ldapConnection == null) { + return null; + } + + EntryCursor cursor = ldapConnection.search(ConfigUtil.getConfigStringValue(ConfigType.LDAP_BASE_DN), ConfigUtil.getConfigStringValue(ConfigType.LDAP_FILTER).replace("USERNAME", username), SearchScope.SUBTREE); if (cursor.next()) { userEntry = cursor.get(); - pool.getConnection().bind(userEntry.getDn(), password); + ldapConnection.bind(userEntry.getDn(), password); } else { // User not found return null; diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/format/FormatHandlerUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/format/FormatHandlerUtil.java index 6a9f8ef5..04abb6d4 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/format/FormatHandlerUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/format/FormatHandlerUtil.java @@ -3,7 +3,6 @@ package com.sismics.docs.core.util.format; import com.google.common.collect.Lists; import com.sismics.util.ClasspathScanner; -import java.lang.reflect.InvocationTargetException; import java.util.List; /** diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/format/TextPlainFormatHandler.java b/docs-core/src/main/java/com/sismics/docs/core/util/format/TextPlainFormatHandler.java index f56b13cd..eb2441c6 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/format/TextPlainFormatHandler.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/format/TextPlainFormatHandler.java @@ -1,6 +1,5 @@ package com.sismics.docs.core.util.format; -import com.google.common.base.Charsets; import com.google.common.io.Closer; import com.lowagie.text.*; import com.lowagie.text.pdf.PdfWriter; diff --git a/docs-core/src/main/resources/config.properties b/docs-core/src/main/resources/config.properties index efbd44c8..435fb302 100644 --- a/docs-core/src/main/resources/config.properties +++ b/docs-core/src/main/resources/config.properties @@ -1 +1 @@ -db.version=28 \ No newline at end of file +db.version=30 diff --git a/docs-core/src/main/resources/db/update/dbupdate-029-0.sql b/docs-core/src/main/resources/db/update/dbupdate-029-0.sql index 78d0f506..6e455d85 100644 --- a/docs-core/src/main/resources/db/update/dbupdate-029-0.sql +++ b/docs-core/src/main/resources/db/update/dbupdate-029-0.sql @@ -1,2 +1,2 @@ -create index IDX_FIL_IDDOC_C ON T_FILE (FIL_IDDOC_C ASC); +alter table T_FILE add column FIL_SIZE_N bigint not null default -1; update T_CONFIG set CFG_VALUE_C = '29' where CFG_ID_C = 'DB_VERSION'; diff --git a/docs-core/src/main/resources/db/update/dbupdate-030-0.sql b/docs-core/src/main/resources/db/update/dbupdate-030-0.sql new file mode 100644 index 00000000..be80c0ef --- /dev/null +++ b/docs-core/src/main/resources/db/update/dbupdate-030-0.sql @@ -0,0 +1,2 @@ +create index IDX_FIL_IDDOC_C ON T_FILE (FIL_IDDOC_C ASC); +update T_CONFIG set CFG_VALUE_C = '30' where CFG_ID_C = 'DB_VERSION'; diff --git a/docs-core/src/test/java/com/sismics/BaseTest.java b/docs-core/src/test/java/com/sismics/BaseTest.java new file mode 100644 index 00000000..384bc164 --- /dev/null +++ b/docs-core/src/test/java/com/sismics/BaseTest.java @@ -0,0 +1,49 @@ +package com.sismics; + +import java.io.InputStream; +import java.net.URL; + +public abstract class BaseTest { + + protected static final String FILE_CSV = "document.csv"; + + protected static final String FILE_DOCX = "document.docx"; + + protected static final String FILE_GIF = "image.gif"; + + protected static final String FILE_JPG = "apollo_portrait.jpg"; + + protected static final Long FILE_JPG_SIZE = 7_907L; + + protected static final String FILE_JPG2 = "apollo_landscape.jpg"; + + protected static final String FILE_MP4 = "video.mp4"; + + protected static final String FILE_ODT = "document.odt"; + + protected static final String FILE_PDF = "udhr.pdf"; + + protected static final String FILE_PDF_ENCRYPTED = "udhr_encrypted.pdf"; + + protected static final String FILE_PDF_SCANNED = "scanned.pdf"; + + protected static final String FILE_PNG = "image.png"; + + protected static final String FILE_PPTX = "apache.pptx"; + + protected static final String FILE_TXT = "document.txt"; + + protected static final String FILE_WEBM = "video.webm"; + + protected static final String FILE_XLSX = "document.xlsx"; + + protected static final String FILE_ZIP = "document.zip"; + + protected static URL getResource(String fileName) { + return ClassLoader.getSystemResource("file/" + fileName); + } + + protected static InputStream getSystemResourceAsStream(String fileName) { + return ClassLoader.getSystemResourceAsStream("file/" + fileName); + } +} diff --git a/docs-core/src/test/java/com/sismics/docs/BaseTransactionalTest.java b/docs-core/src/test/java/com/sismics/docs/BaseTransactionalTest.java index 9fff5fe7..974c0deb 100644 --- a/docs-core/src/test/java/com/sismics/docs/BaseTransactionalTest.java +++ b/docs-core/src/test/java/com/sismics/docs/BaseTransactionalTest.java @@ -1,19 +1,34 @@ package com.sismics.docs; +import com.sismics.BaseTest; +import com.sismics.docs.core.dao.FileDao; +import com.sismics.docs.core.dao.UserDao; +import com.sismics.docs.core.model.jpa.File; +import com.sismics.docs.core.model.jpa.User; +import com.sismics.docs.core.util.DirectoryUtil; +import com.sismics.docs.core.util.EncryptionUtil; import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.jpa.EMF; +import com.sismics.util.mime.MimeType; import org.junit.After; import org.junit.Before; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityTransaction; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import java.io.InputStream; +import java.nio.file.Files; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + /** * Base class of tests with a transactional context. * * @author jtremeaux */ -public abstract class BaseTransactionalTest { +public abstract class BaseTransactionalTest extends BaseTest { @Before public void setUp() throws Exception { // Initialize the entity manager @@ -27,4 +42,32 @@ public abstract class BaseTransactionalTest { @After public void tearDown() throws Exception { } + + protected User createUser(String userName) throws Exception { + UserDao userDao = new UserDao(); + User user = new User(); + user.setUsername(userName); + user.setPassword("12345678"); + user.setEmail("toto@docs.com"); + user.setRoleId("admin"); + user.setStorageQuota(100_000L); + userDao.create(user, userName); + return user; + } + + protected File createFile(User user, long fileSize) throws Exception { + FileDao fileDao = new FileDao(); + try(InputStream inputStream = getSystemResourceAsStream(FILE_JPG)) { + File file = new File(); + file.setId("apollo_portrait"); + file.setUserId(user.getId()); + file.setVersion(0); + file.setMimeType(MimeType.IMAGE_JPEG); + file.setSize(fileSize); + String fileId = fileDao.create(file, user.getId()); + Cipher cipher = EncryptionUtil.getEncryptionCipher(user.getPrivateKey()); + Files.copy(new CipherInputStream(inputStream, cipher), DirectoryUtil.getStorageDirectory().resolve(fileId), REPLACE_EXISTING); + return file; + } + } } diff --git a/docs-core/src/test/java/com/sismics/docs/core/dao/jpa/TestJpa.java b/docs-core/src/test/java/com/sismics/docs/core/dao/jpa/TestJpa.java index 0adbfe35..04c41b32 100644 --- a/docs-core/src/test/java/com/sismics/docs/core/dao/jpa/TestJpa.java +++ b/docs-core/src/test/java/com/sismics/docs/core/dao/jpa/TestJpa.java @@ -18,22 +18,16 @@ public class TestJpa extends BaseTransactionalTest { public void testJpa() throws Exception { // Create a user UserDao userDao = new UserDao(); - User user = new User(); - user.setUsername("username"); - user.setPassword("12345678"); - user.setEmail("toto@docs.com"); - user.setRoleId("admin"); - user.setStorageQuota(10L); - String id = userDao.create(user, "me"); - + User user = createUser("testJpa"); + TransactionUtil.commit(); // Search a user by his ID - user = userDao.getById(id); + user = userDao.getById(user.getId()); Assert.assertNotNull(user); Assert.assertEquals("toto@docs.com", user.getEmail()); // Authenticate using the database - Assert.assertNotNull(new InternalAuthenticationHandler().authenticate("username", "12345678")); + Assert.assertNotNull(new InternalAuthenticationHandler().authenticate("testJpa", "12345678")); } } diff --git a/docs-core/src/test/java/com/sismics/docs/core/listener/async/FileDeletedAsyncListenerTest.java b/docs-core/src/test/java/com/sismics/docs/core/listener/async/FileDeletedAsyncListenerTest.java new file mode 100644 index 00000000..0cd2e6e3 --- /dev/null +++ b/docs-core/src/test/java/com/sismics/docs/core/listener/async/FileDeletedAsyncListenerTest.java @@ -0,0 +1,52 @@ +package com.sismics.docs.core.listener.async; + +import com.sismics.docs.BaseTransactionalTest; +import com.sismics.docs.core.dao.UserDao; +import com.sismics.docs.core.event.FileDeletedAsyncEvent; +import com.sismics.docs.core.model.jpa.File; +import com.sismics.docs.core.model.jpa.User; +import com.sismics.docs.core.util.TransactionUtil; +import org.junit.Assert; +import org.junit.Test; + +public class FileDeletedAsyncListenerTest extends BaseTransactionalTest { + + @Test + public void updateQuotaSizeKnown() throws Exception { + User user = createUser("updateQuotaSizeKnown"); + File file = createFile(user, FILE_JPG_SIZE); + UserDao userDao = new UserDao(); + user = userDao.getById(user.getId()); + user.setStorageCurrent(10_000L); + userDao.updateQuota(user); + + FileDeletedAsyncListener fileDeletedAsyncListener = new FileDeletedAsyncListener(); + TransactionUtil.commit(); + FileDeletedAsyncEvent event = new FileDeletedAsyncEvent(); + event.setFileSize(FILE_JPG_SIZE); + event.setFileId(file.getId()); + event.setUserId(user.getId()); + fileDeletedAsyncListener.on(event); + Assert.assertEquals(userDao.getById(user.getId()).getStorageCurrent(), Long.valueOf(10_000 - FILE_JPG_SIZE)); + } + + @Test + public void updateQuotaSizeUnknown() throws Exception { + User user = createUser("updateQuotaSizeUnknown"); + File file = createFile(user, File.UNKNOWN_SIZE); + UserDao userDao = new UserDao(); + user = userDao.getById(user.getId()); + user.setStorageCurrent(10_000L); + userDao.updateQuota(user); + + FileDeletedAsyncListener fileDeletedAsyncListener = new FileDeletedAsyncListener(); + TransactionUtil.commit(); + FileDeletedAsyncEvent event = new FileDeletedAsyncEvent(); + event.setFileSize(FILE_JPG_SIZE); + event.setFileId(file.getId()); + event.setUserId(user.getId()); + fileDeletedAsyncListener.on(event); + Assert.assertEquals(userDao.getById(user.getId()).getStorageCurrent(), Long.valueOf(10_000 - FILE_JPG_SIZE)); + } + +} diff --git a/docs-core/src/test/java/com/sismics/docs/core/service/TestFileSizeService.java b/docs-core/src/test/java/com/sismics/docs/core/service/TestFileSizeService.java new file mode 100644 index 00000000..f70ba75b --- /dev/null +++ b/docs-core/src/test/java/com/sismics/docs/core/service/TestFileSizeService.java @@ -0,0 +1,22 @@ +package com.sismics.docs.core.service; + +import com.sismics.docs.BaseTransactionalTest; +import com.sismics.docs.core.dao.FileDao; +import com.sismics.docs.core.model.jpa.File; +import com.sismics.docs.core.model.jpa.User; +import org.junit.Assert; +import org.junit.Test; + +public class TestFileSizeService extends BaseTransactionalTest { + + @Test + public void processFileTest() throws Exception { + User user = createUser("processFileTest"); + + FileDao fileDao = new FileDao(); + File file = createFile(user, File.UNKNOWN_SIZE); + FileSizeService fileSizeService = new FileSizeService(); + fileSizeService.processFile(file); + Assert.assertEquals(fileDao.getFile(file.getId()).getSize(), Long.valueOf(FILE_JPG_SIZE)); + } +} diff --git a/docs-core/src/test/java/com/sismics/docs/core/util/TestEncryptUtil.java b/docs-core/src/test/java/com/sismics/docs/core/util/TestEncryptUtil.java index 197626df..ad06efcc 100644 --- a/docs-core/src/test/java/com/sismics/docs/core/util/TestEncryptUtil.java +++ b/docs-core/src/test/java/com/sismics/docs/core/util/TestEncryptUtil.java @@ -2,6 +2,7 @@ package com.sismics.docs.core.util; import com.google.common.base.Strings; import com.google.common.io.ByteStreams; +import com.sismics.BaseTest; import org.junit.Assert; import org.junit.Test; @@ -14,7 +15,7 @@ import java.io.InputStream; * * @author bgamard */ -public class TestEncryptUtil { +public class TestEncryptUtil extends BaseTest { @Test public void generatePrivateKeyTest() { String key = EncryptionUtil.generatePrivateKey(); @@ -31,9 +32,9 @@ public class TestEncryptUtil { // NOP } Cipher cipher = EncryptionUtil.getEncryptionCipher("OnceUponATime"); - InputStream inputStream = new CipherInputStream(this.getClass().getResourceAsStream("/file/udhr.pdf"), cipher); + InputStream inputStream = new CipherInputStream(getSystemResourceAsStream(FILE_PDF), cipher); byte[] encryptedData = ByteStreams.toByteArray(inputStream); - byte[] assertData = ByteStreams.toByteArray(this.getClass().getResourceAsStream("/file/udhr_encrypted.pdf")); + byte[] assertData = ByteStreams.toByteArray(getSystemResourceAsStream(FILE_PDF_ENCRYPTED)); Assert.assertEquals(encryptedData.length, assertData.length); } @@ -41,9 +42,9 @@ public class TestEncryptUtil { @Test public void decryptStreamTest() throws Exception { InputStream inputStream = EncryptionUtil.decryptInputStream( - this.getClass().getResourceAsStream("/file/udhr_encrypted.pdf"), "OnceUponATime"); + getSystemResourceAsStream(FILE_PDF_ENCRYPTED), "OnceUponATime"); byte[] encryptedData = ByteStreams.toByteArray(inputStream); - byte[] assertData = ByteStreams.toByteArray(this.getClass().getResourceAsStream("/file/udhr.pdf")); + byte[] assertData = ByteStreams.toByteArray(getSystemResourceAsStream(FILE_PDF)); Assert.assertEquals(encryptedData.length, assertData.length); } diff --git a/docs-core/src/test/java/com/sismics/docs/core/util/TestFileUtil.java b/docs-core/src/test/java/com/sismics/docs/core/util/TestFileUtil.java index ad1c50ce..c7ccd0d3 100644 --- a/docs-core/src/test/java/com/sismics/docs/core/util/TestFileUtil.java +++ b/docs-core/src/test/java/com/sismics/docs/core/util/TestFileUtil.java @@ -2,6 +2,7 @@ package com.sismics.docs.core.util; import com.google.common.collect.Lists; import com.google.common.io.Resources; +import com.sismics.BaseTest; import com.sismics.docs.core.dao.dto.DocumentDto; import com.sismics.docs.core.model.jpa.File; import com.sismics.docs.core.util.format.*; @@ -23,11 +24,11 @@ import java.util.Date; * * @author bgamard */ -public class TestFileUtil { +public class TestFileUtil extends BaseTest { @Test public void extractContentOpenDocumentTextTest() throws Exception { - Path path = Paths.get(ClassLoader.getSystemResource("file/document.odt").toURI()); - FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "document.odt")); + Path path = Paths.get(getResource(FILE_ODT).toURI()); + FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_ODT)); Assert.assertNotNull(formatHandler); Assert.assertTrue(formatHandler instanceof OdtFormatHandler); String content = formatHandler.extractContent("eng", path); @@ -36,8 +37,8 @@ public class TestFileUtil { @Test public void extractContentOfficeDocumentTest() throws Exception { - Path path = Paths.get(ClassLoader.getSystemResource("file/document.docx").toURI()); - FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "document.docx")); + Path path = Paths.get(getResource(FILE_DOCX).toURI()); + FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_DOCX)); Assert.assertNotNull(formatHandler); Assert.assertTrue(formatHandler instanceof DocxFormatHandler); String content = formatHandler.extractContent("eng", path); @@ -46,8 +47,8 @@ public class TestFileUtil { @Test public void extractContentPowerpointTest() throws Exception { - Path path = Paths.get(ClassLoader.getSystemResource("file/apache.pptx").toURI()); - FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "apache.pptx")); + Path path = Paths.get(getResource(FILE_PPTX).toURI()); + FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_PPTX)); Assert.assertNotNull(formatHandler); Assert.assertTrue(formatHandler instanceof PptxFormatHandler); String content = formatHandler.extractContent("eng", path); @@ -56,8 +57,8 @@ public class TestFileUtil { @Test public void extractContentPdf() throws Exception { - Path path = Paths.get(ClassLoader.getSystemResource("file/udhr.pdf").toURI()); - FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "udhr.pdf")); + Path path = Paths.get(getResource(FILE_PDF).toURI()); + FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_PDF)); Assert.assertNotNull(formatHandler); Assert.assertTrue(formatHandler instanceof PdfFormatHandler); String content = formatHandler.extractContent("eng", path); @@ -66,8 +67,8 @@ public class TestFileUtil { @Test public void extractContentScannedPdf() throws Exception { - Path path = Paths.get(ClassLoader.getSystemResource("file/scanned.pdf").toURI()); - FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "scanned.pdf")); + Path path = Paths.get(getResource("scanned.pdf").toURI()); + FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_PDF_SCANNED)); Assert.assertNotNull(formatHandler); Assert.assertTrue(formatHandler instanceof PdfFormatHandler); String content = formatHandler.extractContent("eng", path); @@ -76,12 +77,12 @@ public class TestFileUtil { @Test public void convertToPdfTest() throws Exception { - try (InputStream inputStream0 = Resources.getResource("file/apollo_landscape.jpg").openStream(); - InputStream inputStream1 = Resources.getResource("file/apollo_portrait.jpg").openStream(); - InputStream inputStream2 = Resources.getResource("file/udhr_encrypted.pdf").openStream(); - InputStream inputStream3 = Resources.getResource("file/document.docx").openStream(); - InputStream inputStream4 = Resources.getResource("file/document.odt").openStream(); - InputStream inputStream5 = Resources.getResource("file/apache.pptx").openStream()) { + try (InputStream inputStream0 = getSystemResourceAsStream(FILE_JPG2); + InputStream inputStream1 = getSystemResourceAsStream(FILE_JPG); + InputStream inputStream2 = getSystemResourceAsStream(FILE_PDF_ENCRYPTED); + InputStream inputStream3 = getSystemResourceAsStream(FILE_DOCX); + InputStream inputStream4 = getSystemResourceAsStream(FILE_ODT); + InputStream inputStream5 = getSystemResourceAsStream(FILE_PPTX)) { // Document DocumentDto documentDto = new DocumentDto(); documentDto.setTitle("My super document 1"); diff --git a/docs-core/src/test/java/com/sismics/util/TestMimeTypeUtil.java b/docs-core/src/test/java/com/sismics/util/TestMimeTypeUtil.java index 3f7d1cfe..b941ef16 100644 --- a/docs-core/src/test/java/com/sismics/util/TestMimeTypeUtil.java +++ b/docs-core/src/test/java/com/sismics/util/TestMimeTypeUtil.java @@ -1,5 +1,6 @@ package com.sismics.util; +import com.sismics.BaseTest; import com.sismics.util.mime.MimeType; import com.sismics.util.mime.MimeTypeUtil; import org.junit.Assert; @@ -13,59 +14,59 @@ import java.nio.file.Paths; * * @author bgamard */ -public class TestMimeTypeUtil { +public class TestMimeTypeUtil extends BaseTest { @Test public void test() throws Exception { // Detect ODT files - Path path = Paths.get(ClassLoader.getSystemResource("file/document.odt").toURI()); - Assert.assertEquals(MimeType.OPEN_DOCUMENT_TEXT, MimeTypeUtil.guessMimeType(path, "document.odt")); + Path path = Paths.get(getResource(FILE_ODT).toURI()); + Assert.assertEquals(MimeType.OPEN_DOCUMENT_TEXT, MimeTypeUtil.guessMimeType(path, FILE_ODT)); // Detect DOCX files - path = Paths.get(ClassLoader.getSystemResource("file/document.docx").toURI()); - Assert.assertEquals(MimeType.OFFICE_DOCUMENT, MimeTypeUtil.guessMimeType(path, "document.odt")); + path = Paths.get(getResource(FILE_DOCX).toURI()); + Assert.assertEquals(MimeType.OFFICE_DOCUMENT, MimeTypeUtil.guessMimeType(path, FILE_ODT)); // Detect PPTX files - path = Paths.get(ClassLoader.getSystemResource("file/apache.pptx").toURI()); - Assert.assertEquals(MimeType.OFFICE_PRESENTATION, MimeTypeUtil.guessMimeType(path, "apache.pptx")); + path = Paths.get(getResource(FILE_PPTX).toURI()); + Assert.assertEquals(MimeType.OFFICE_PRESENTATION, MimeTypeUtil.guessMimeType(path, FILE_PPTX)); // Detect XLSX files - path = Paths.get(ClassLoader.getSystemResource("file/document.xlsx").toURI()); - Assert.assertEquals(MimeType.OFFICE_SHEET, MimeTypeUtil.guessMimeType(path, "document.xlsx")); + path = Paths.get(getResource(FILE_XLSX).toURI()); + Assert.assertEquals(MimeType.OFFICE_SHEET, MimeTypeUtil.guessMimeType(path, FILE_XLSX)); // Detect TXT files - path = Paths.get(ClassLoader.getSystemResource("file/document.txt").toURI()); - Assert.assertEquals(MimeType.TEXT_PLAIN, MimeTypeUtil.guessMimeType(path, "document.txt")); + path = Paths.get(getResource(FILE_TXT).toURI()); + Assert.assertEquals(MimeType.TEXT_PLAIN, MimeTypeUtil.guessMimeType(path, FILE_TXT)); // Detect CSV files - path = Paths.get(ClassLoader.getSystemResource("file/document.csv").toURI()); - Assert.assertEquals(MimeType.TEXT_CSV, MimeTypeUtil.guessMimeType(path, "document.csv")); + path = Paths.get(getResource(FILE_CSV).toURI()); + Assert.assertEquals(MimeType.TEXT_CSV, MimeTypeUtil.guessMimeType(path, FILE_CSV)); // Detect PDF files - path = Paths.get(ClassLoader.getSystemResource("file/udhr.pdf").toURI()); - Assert.assertEquals(MimeType.APPLICATION_PDF, MimeTypeUtil.guessMimeType(path, "udhr.pdf")); + path = Paths.get(getResource(FILE_PDF).toURI()); + Assert.assertEquals(MimeType.APPLICATION_PDF, MimeTypeUtil.guessMimeType(path, FILE_PDF)); // Detect JPEG files - path = Paths.get(ClassLoader.getSystemResource("file/apollo_portrait.jpg").toURI()); - Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(path, "apollo_portrait.jpg")); + path = Paths.get(getResource(FILE_JPG).toURI()); + Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(path, FILE_JPG)); // Detect GIF files - path = Paths.get(ClassLoader.getSystemResource("file/image.gif").toURI()); - Assert.assertEquals(MimeType.IMAGE_GIF, MimeTypeUtil.guessMimeType(path, "image.gif")); + path = Paths.get(getResource(FILE_GIF).toURI()); + Assert.assertEquals(MimeType.IMAGE_GIF, MimeTypeUtil.guessMimeType(path, FILE_GIF)); // Detect PNG files - path = Paths.get(ClassLoader.getSystemResource("file/image.png").toURI()); - Assert.assertEquals(MimeType.IMAGE_PNG, MimeTypeUtil.guessMimeType(path, "image.png")); + path = Paths.get(getResource(FILE_PNG).toURI()); + Assert.assertEquals(MimeType.IMAGE_PNG, MimeTypeUtil.guessMimeType(path, FILE_PNG)); // Detect ZIP files - path = Paths.get(ClassLoader.getSystemResource("file/document.zip").toURI()); - Assert.assertEquals(MimeType.APPLICATION_ZIP, MimeTypeUtil.guessMimeType(path, "document.zip")); + path = Paths.get(getResource(FILE_ZIP).toURI()); + Assert.assertEquals(MimeType.APPLICATION_ZIP, MimeTypeUtil.guessMimeType(path, FILE_ZIP)); // Detect WEBM files - path = Paths.get(ClassLoader.getSystemResource("file/video.webm").toURI()); - Assert.assertEquals(MimeType.VIDEO_WEBM, MimeTypeUtil.guessMimeType(path, "video.webm")); + path = Paths.get(getResource(FILE_WEBM).toURI()); + Assert.assertEquals(MimeType.VIDEO_WEBM, MimeTypeUtil.guessMimeType(path, FILE_WEBM)); // Detect MP4 files - path = Paths.get(ClassLoader.getSystemResource("file/video.mp4").toURI()); - Assert.assertEquals(MimeType.VIDEO_MP4, MimeTypeUtil.guessMimeType(path, "video.mp4")); + path = Paths.get(getResource(FILE_MP4).toURI()); + Assert.assertEquals(MimeType.VIDEO_MP4, MimeTypeUtil.guessMimeType(path, FILE_MP4)); } } diff --git a/docs-core/src/test/java/com/sismics/util/format/TestPdfFormatHandler.java b/docs-core/src/test/java/com/sismics/util/format/TestPdfFormatHandler.java index 7e3b2f00..42f99398 100644 --- a/docs-core/src/test/java/com/sismics/util/format/TestPdfFormatHandler.java +++ b/docs-core/src/test/java/com/sismics/util/format/TestPdfFormatHandler.java @@ -1,5 +1,6 @@ package com.sismics.util.format; +import com.sismics.BaseTest; import com.sismics.docs.core.util.format.PdfFormatHandler; import org.junit.Assert; import org.junit.Test; @@ -11,14 +12,14 @@ import java.nio.file.Paths; * * @author bgamard */ -public class TestPdfFormatHandler { +public class TestPdfFormatHandler extends BaseTest { /** * Test related to https://github.com/sismics/docs/issues/373. */ @Test public void testIssue373() throws Exception { PdfFormatHandler formatHandler = new PdfFormatHandler(); - String content = formatHandler.extractContent("deu", Paths.get(ClassLoader.getSystemResource("file/issue373.pdf").toURI())); + String content = formatHandler.extractContent("deu", Paths.get(getResource("issue373.pdf").toURI())); Assert.assertTrue(content.contains("Aufrechterhaltung")); Assert.assertTrue(content.contains("Außentemperatur")); Assert.assertTrue(content.contains("Grundumsatzmessungen")); diff --git a/docs-web-common/src/main/java/com/sismics/rest/util/RestUtil.java b/docs-web-common/src/main/java/com/sismics/rest/util/RestUtil.java index cd749e53..3a3cae46 100644 --- a/docs-web-common/src/main/java/com/sismics/rest/util/RestUtil.java +++ b/docs-web-common/src/main/java/com/sismics/rest/util/RestUtil.java @@ -8,6 +8,7 @@ import com.sismics.util.JsonUtil; import jakarta.json.Json; import jakarta.json.JsonObjectBuilder; + import java.io.IOException; import java.nio.file.Files; @@ -18,12 +19,15 @@ import java.nio.file.Files; */ public class RestUtil { /** - * Transform a File into its JSON representation + * Transform a File into its JSON representation. + * If the file size it is not stored in the database the size can be wrong + * because the encrypted file size is used. * @param fileDb a file * @return the JSON */ public static JsonObjectBuilder fileToJsonObjectBuilder(File fileDb) { try { + long fileSize = fileDb.getSize().equals(File.UNKNOWN_SIZE) ? Files.size(DirectoryUtil.getStorageDirectory().resolve(fileDb.getId())) : fileDb.getSize(); return Json.createObjectBuilder() .add("id", fileDb.getId()) .add("processing", FileUtil.isProcessingFile(fileDb.getId())) @@ -32,7 +36,7 @@ public class RestUtil { .add("mimetype", fileDb.getMimeType()) .add("document_id", JsonUtil.nullable(fileDb.getDocumentId())) .add("create_date", fileDb.getCreateDate().getTime()) - .add("size", Files.size(DirectoryUtil.getStorageDirectory().resolve(fileDb.getId()))); + .add("size", fileSize); } catch (IOException e) { throw new ServerException("FileError", "Unable to get the size of " + fileDb.getId(), e); } diff --git a/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java b/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java index 4b95f731..96a63244 100644 --- a/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java +++ b/docs-web-common/src/test/java/com/sismics/docs/rest/BaseJerseyTest.java @@ -25,6 +25,9 @@ import jakarta.ws.rs.core.UriBuilder; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Objects; @@ -39,7 +42,9 @@ public abstract class BaseJerseyTest extends JerseyTest { protected static final String FILE_DOCUMENT_ODT = "file/document.odt"; protected static final String FILE_DOCUMENT_TXT = "file/document.txt"; protected static final String FILE_EINSTEIN_ROOSEVELT_LETTER_PNG = "file/Einstein-Roosevelt-letter.png"; + protected static final long FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE = 292641L; protected static final String FILE_PIA_00452_JPG = "file/PIA00452.jpg"; + protected static final long FILE_PIA_00452_JPG_SIZE = 163510L; protected static final String FILE_VIDEO_WEBM = "file/video.webm"; protected static final String FILE_WIKIPEDIA_PDF = "file/wikipedia.pdf"; protected static final String FILE_WIKIPEDIA_ZIP = "file/wikipedia.zip"; diff --git a/docs-web/src/dev/resources/config.properties b/docs-web/src/dev/resources/config.properties index 4c2a4937..37e03ad0 100644 --- a/docs-web/src/dev/resources/config.properties +++ b/docs-web/src/dev/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=28 \ No newline at end of file +db.version=30 diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java index 05908242..bb1e8edd 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AppResource.java @@ -14,7 +14,6 @@ import com.sismics.docs.core.model.jpa.File; import com.sismics.docs.core.service.InboxService; import com.sismics.docs.core.util.ConfigUtil; import com.sismics.docs.core.util.DirectoryUtil; -import com.sismics.docs.core.util.authentication.LdapAuthenticationHandler; import com.sismics.docs.core.util.jpa.PaginatedList; import com.sismics.docs.core.util.jpa.PaginatedLists; import com.sismics.docs.rest.constant.BaseFunction; @@ -27,12 +26,6 @@ import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.log4j.LogCriteria; import com.sismics.util.log4j.LogEntry; import com.sismics.util.log4j.MemoryAppender; -import org.apache.commons.lang3.StringUtils; -import org.apache.log4j.Appender; -import org.apache.log4j.Level; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import jakarta.json.Json; import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObjectBuilder; @@ -40,6 +33,12 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import jakarta.ws.rs.*; import jakarta.ws.rs.core.Response; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Appender; +import org.apache.log4j.Level; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; @@ -854,9 +853,6 @@ public class AppResource extends BaseResource { configDao.update(ConfigType.LDAP_ENABLED, Boolean.FALSE.toString()); } - // Reset the LDAP pool to reconnect with the new configuration - LdapAuthenticationHandler.reset(); - return Response.ok().build(); } } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index ad5d230b..2d897ad8 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -32,7 +32,6 @@ import com.sismics.docs.core.model.jpa.Document; import com.sismics.docs.core.model.jpa.File; import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.util.ConfigUtil; -import com.sismics.docs.core.util.DirectoryUtil; import com.sismics.docs.core.util.DocumentUtil; import com.sismics.docs.core.util.FileUtil; import com.sismics.docs.core.util.MetadataUtil; @@ -1149,29 +1148,15 @@ public class DocumentResource extends BaseResource { // Delete the document documentDao.delete(id, principal.getId()); - long totalSize = 0L; for (File file : fileList) { - // Store the file size to update the quota - java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId()); - try { - totalSize += Files.size(storedFile); - } catch (IOException e) { - // The file doesn't exists on disk, which is weird, but not fatal - } - // Raise file deleted event FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent(); fileDeletedAsyncEvent.setUserId(principal.getId()); fileDeletedAsyncEvent.setFileId(file.getId()); + fileDeletedAsyncEvent.setFileSize(file.getSize()); ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent); } - // Update the user quota - UserDao userDao = new UserDao(); - User user = userDao.getById(principal.getId()); - user.setStorageCurrent(user.getStorageCurrent() - totalSize); - userDao.updateQuota(user); - // Raise a document deleted event DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent(); documentDeletedAsyncEvent.setUserId(principal.getId()); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java index 8f6a4bc6..be5eab74 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java @@ -442,7 +442,7 @@ public class FileResource extends BaseResource { /** * List all versions of a file. * - * @api {get} /file/id/versions Get versions of a file + * @api {get} /file/:id/versions Get versions of a file * @apiName GetFileVersions * @apiGroup File * @apiParam {String} id File ID @@ -522,21 +522,11 @@ public class FileResource extends BaseResource { FileDao fileDao = new FileDao(); fileDao.delete(file.getId(), principal.getId()); - // Update the user quota - UserDao userDao = new UserDao(); - User user = userDao.getById(principal.getId()); - java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id); - try { - user.setStorageCurrent(user.getStorageCurrent() - Files.size(storedFile)); - userDao.updateQuota(user); - } catch (IOException e) { - // The file doesn't exists on disk, which is weird, but not fatal - } - // Raise a new file deleted event FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent(); fileDeletedAsyncEvent.setUserId(principal.getId()); fileDeletedAsyncEvent.setFileId(file.getId()); + fileDeletedAsyncEvent.setFileSize(file.getSize()); ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent); if (file.getDocumentId() != null) { diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java index 6e8a2a1e..9403025b 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/UserResource.java @@ -470,22 +470,8 @@ public class UserResource extends BaseResource { UserDao userDao = new UserDao(); userDao.delete(principal.getName(), principal.getId()); - // Raise deleted events for documents - for (Document document : documentList) { - DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent(); - documentDeletedAsyncEvent.setUserId(principal.getId()); - documentDeletedAsyncEvent.setDocumentId(document.getId()); - ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent); - } - - // Raise deleted events for files (don't bother sending document updated event) - for (File file : fileList) { - FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent(); - fileDeletedAsyncEvent.setUserId(principal.getId()); - fileDeletedAsyncEvent.setFileId(file.getId()); - ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent); - } - + sendDeletionEvents(documentList, fileList); + // Always return OK JsonObjectBuilder response = Json.createObjectBuilder() .add("status", "ok"); @@ -551,23 +537,9 @@ public class UserResource extends BaseResource { // Delete the user userDao.delete(user.getUsername(), principal.getId()); - - // Raise deleted events for documents - for (Document document : documentList) { - DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent(); - documentDeletedAsyncEvent.setUserId(principal.getId()); - documentDeletedAsyncEvent.setDocumentId(document.getId()); - ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent); - } - - // Raise deleted events for files (don't bother sending document updated event) - for (File file : fileList) { - FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent(); - fileDeletedAsyncEvent.setUserId(principal.getId()); - fileDeletedAsyncEvent.setFileId(file.getId()); - ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent); - } - + + sendDeletionEvents(documentList, fileList); + // Always return OK JsonObjectBuilder response = Json.createObjectBuilder() .add("status", "ok"); @@ -1178,4 +1150,29 @@ public class UserResource extends BaseResource { } return null; } + + /** + * Send the events about documents and files being deleted. + * @param documentList A document list + * @param fileList A file list + */ + private void sendDeletionEvents(List documentList, List fileList) { + // Raise deleted events for documents + for (Document document : documentList) { + DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent(); + documentDeletedAsyncEvent.setUserId(principal.getId()); + documentDeletedAsyncEvent.setDocumentId(document.getId()); + ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent); + } + + // Raise deleted events for files (don't bother sending document updated event) + for (File file : fileList) { + FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent(); + fileDeletedAsyncEvent.setUserId(principal.getId()); + fileDeletedAsyncEvent.setFileId(file.getId()); + fileDeletedAsyncEvent.setFileSize(file.getSize()); + ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent); + } + } + } diff --git a/docs-web/src/main/webapp/src/app/docs/app.js b/docs-web/src/main/webapp/src/app/docs/app.js index d2835ad3..62fa84bf 100644 --- a/docs-web/src/main/webapp/src/app/docs/app.js +++ b/docs-web/src/main/webapp/src/app/docs/app.js @@ -429,7 +429,7 @@ angular.module('docs', prefix: 'locale/', suffix: '.json?@build.date@' }) - .registerAvailableLanguageKeys(['en', 'es', 'pt', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW'], { + .registerAvailableLanguageKeys(['en', 'es', 'pt', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW', 'sq_AL'], { 'en_*': 'en', 'es_*': 'es', 'pt_*': 'pt', @@ -547,7 +547,8 @@ angular.module('docs', { key: 'dan', label: 'Dansk' }, { key: 'nor', label: 'Norsk' }, { key: 'vie', label: 'Tiếng Việt' }, - { key: 'ces', label: 'Czech' } + { key: 'ces', label: 'Czech' }, + { key: 'sqi', label: 'Shqip' } ]; }) /** diff --git a/docs-web/src/main/webapp/src/app/share/app.js b/docs-web/src/main/webapp/src/app/share/app.js index d45229fb..8a9c47ea 100644 --- a/docs-web/src/main/webapp/src/app/share/app.js +++ b/docs-web/src/main/webapp/src/app/share/app.js @@ -61,7 +61,7 @@ angular.module('share', prefix: 'locale/', suffix: '.json?@build.date@' }) - .registerAvailableLanguageKeys(['en', 'es', 'pt', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW'], { + .registerAvailableLanguageKeys(['en', 'es', 'pt', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW', 'sq_AL'], { 'en_*': 'en', 'es_*': 'es', 'pt_*': 'pt', diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index 0a16863c..e72be76c 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -192,6 +192,7 @@ Polski 简体中文 繁體中文 + Shqip @@ -207,6 +208,7 @@
  • Polski
  • 简体中文
  • 繁體中文
  • +
  • Shqip
  • diff --git a/docs-web/src/main/webapp/src/locale/angular-locale_sq_AL.js b/docs-web/src/main/webapp/src/locale/angular-locale_sq_AL.js new file mode 100644 index 00000000..b5df89c6 --- /dev/null +++ b/docs-web/src/main/webapp/src/locale/angular-locale_sq_AL.js @@ -0,0 +1,150 @@ +'use strict'; +angular.module("ngLocale", [], ["$provide", function($provide) { +var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; +function getDecimals(n) { + n = n + ''; + var i = n.indexOf('.'); + return (i == -1) ? 0 : n.length - i - 1; +} + +function getVF(n, opt_precision) { + var v = opt_precision; + + if (undefined === v) { + v = Math.min(getDecimals(n), 3); + } + + var base = Math.pow(10, v); + var f = ((n * base) | 0) % base; + return {v: v, f: f}; +} + +$provide.value("$locale", { + "DATETIME_FORMATS": { + "AMPMS": [ + "PD", + "MD" + ], + "DAY": [ + "E Diel", + "E Hënë", + "E Martë", + "E Mërkurë", + "E Enjte", + "E Premte", + "E Shtunë" + ], + "ERANAMES": [ + "Para Krishtit", + "Pas Krishtit" + ], + "ERAS": [ + "p.K.", + "n.K." + ], + "FIRSTDAYOFWEEK": 1, + "MONTH": [ + "Janar", + "Shkurt", + "Mars", + "Prill", + "Maj", + "Qershor", + "Korrik", + "Gusht", + "Shtator", + "Tetor", + "Nëntor", + "Dhjetor" + ], + "SHORTDAY": [ + "Die", + "Hën", + "Mar", + "Mër", + "Enj", + "Pre", + "Sht" + ], + "SHORTMONTH": [ + "Jan", + "Shk", + "Mar", + "Pri", + "Maj", + "Qer", + "Kor", + "Gus", + "Sht", + "Tet", + "Nën", + "Dhj" + ], + "STANDALONEMONTH": [ + "Janar", + "Shkurt", + "Mars", + "Prill", + "Maj", + "Qershor", + "Korrik", + "Gusht", + "Shtator", + "Tetor", + "Nëntor", + "Dhjetor" + ], + "WEEKENDRANGE": [ + 6, + 0 + ], + "fullDate": "EEEE, d MMMM y", + "longDate": "d MMMM y", + "medium": "d MMM y h:mm:ss a", + "mediumDate": "d MMM y", + "mediumTime": "h:mm:ss a", + "short": "yy-MM-dd h:mm a", + "shortDate": "yy-MM-dd", + "shortTime": "h:mm a" + }, + "NUMBER_FORMATS": { + "CURRENCY_SYM": "Lek", + "DECIMAL_SEP": ".", + "GROUP_SEP": ",", + "PATTERNS": [ + { + "gSize": 3, + "lgSize": 3, + "maxFrac": 3, + "minFrac": 0, + "minInt": 1, + "negPre": "-", + "negSuf": "", + "posPre": "", + "posSuf": "" + }, + { + "gSize": 3, + "lgSize": 3, + "maxFrac": 2, + "minFrac": 2, + "minInt": 1, + "negPre": "-\u00a4", + "negSuf": "", + "posPre": "\u00a4", + "posSuf": "" + } + ] + }, + "id": "sq-al", + "localeID": "sq_AL", + "pluralCat": function(n, opt_precision) { + var i = n | 0; + var vf = getVF(n, opt_precision); + if (i == 1 && vf.v == 0) { + return PLURAL_CATEGORY.ONE; + } + return PLURAL_CATEGORY.OTHER; + } +}); +}]); diff --git a/docs-web/src/main/webapp/src/locale/sq_AL.json b/docs-web/src/main/webapp/src/locale/sq_AL.json new file mode 100644 index 00000000..849243fe --- /dev/null +++ b/docs-web/src/main/webapp/src/locale/sq_AL.json @@ -0,0 +1,640 @@ +{ + "login": { + "username": "Emri i përdoruesit", + "password": "Fjalëkalimi", + "validation_code_required": "Kërkohet një kod verifikimi", + "validation_code_title": "Ju keni aktivizuar vërtetimin me dy faktorë në llogarinë tuaj. ", + "validation_code": "Kodi i verifikimit", + "remember_me": "Më kujto mua", + "submit": "Hyni", + "login_as_guest": "Identifikohu si i ftuar", + "login_failed_title": "Identifikimi dështoi", + "login_failed_message": "Emri i përdoruesit ose fjalëkalimi është i pavlefshëm", + "password_lost_btn": "Fjalëkalimi i humbur?", + "password_lost_sent_title": "Email për rivendosjen e fjalëkalimit u dërgua", + "password_lost_sent_message": "Një email është dërguar në {{ username }} për të rivendosur fjalëkalimin tuaj", + "password_lost_error_title": "Gabim i rivendosjes së fjalëkalimit", + "password_lost_error_message": "Nuk mund të dërgohet një email për rivendosjen e fjalëkalimit, ju lutemi kontaktoni administratorin tuaj për një rivendosje manuale" + }, + "passwordlost": { + "title": "Fjalëkalimi ka humbur", + "message": "Ju lutemi shkruani emrin tuaj të përdoruesit për të marrë një lidhje të rivendosjes së fjalëkalimit. ", + "submit": "Rivendos fjalëkalimin tim" + }, + "passwordreset": { + "message": "Ju lutemi shkruani një fjalëkalim të ri", + "submit": "Ndrysho fjalëkalimin tim", + "error_title": "Gabim gjatë ndryshimit të fjalëkalimit tuaj", + "error_message": "Kërkesa juaj për rikuperimin e fjalëkalimit ka skaduar, ju lutemi kërkoni një të re në faqen e hyrjes" + }, + "index": { + "toggle_navigation": "Ndrysho navigimin", + "nav_documents": "Dokumentet", + "nav_tags": "Etiketa", + "nav_users_groups": "Përdoruesit", + "error_info": "{{ count }} gabim i ri{{ count > 1 ? 's' : '' }}", + "logged_as": "I identifikuar si {{ username }}", + "nav_settings": "Cilësimet", + "logout": "Shkyç", + "global_quota_warning": "Paralajmërim! Kuota globale pothuajse arriti në {{ current | number: 0 }}MB ({{ percent | number: 1 }}%) përdoret në {{ total | number: 0 }}MB" + }, + "document": { + "navigation_up": "Ngjitu një nivel", + "toggle_navigation": "Ndrysho navigimin e dosjeve", + "display_mode_list": "Shfaq dokumentet në listë", + "display_mode_grid": "Shfaq dokumentet në rrjet", + "search_simple": "Kërkim i thjeshtë", + "search_fulltext": "Kërkimi i tekstit të plotë", + "search_creator": "Krijuesi", + "search_language": "Gjuhe", + "search_before_date": "Krijuar para kësaj date", + "search_after_date": "Krijuar pas kësaj date", + "search_before_update_date": "Përditësuar përpara kësaj date", + "search_after_update_date": "Përditësuar pas kësaj date", + "search_tags": "Etiketa", + "search_shared": "Vetëm dokumente të përbashkëta", + "search_workflow": "Rrjedha e punës më është caktuar", + "search_clear": "Qartë", + "any_language": "Çdo gjuhë", + "add_document": "Shto një dokument", + "import_eml": "Importo nga një email (format EML)", + "tags": "Etiketa", + "no_tags": "Nuk ka etiketa", + "no_documents": "Asnjë dokument në bazën e të dhënave", + "search": "Kërko", + "search_empty": "Nuk ka ndeshje për \"{{ search }}\"", + "shared": "Të përbashkëta", + "current_step_name": "Hapi aktual", + "title": "Titulli", + "description": "Përshkrim", + "contributors": "Kontribuesit", + "language": "Gjuhe", + "creation_date": "Data e krijimit", + "subject": "Subjekti", + "identifier": "Identifikues", + "publisher": "Botues", + "format": "Formati", + "source": "Burimi", + "type": "Lloji", + "coverage": "Mbulimi", + "rights": "Të drejtat", + "relations": "Marrëdhëniet", + "page_size": "Madhësia e faqes", + "page_size_10": "10 për faqe", + "page_size_20": "20 për faqe", + "page_size_30": "30 për faqe", + "upgrade_quota": "Për të përmirësuar kuotën tuaj, pyesni administratorin tuaj", + "quota": "{{ current | number: 0 }}MB ({{ percent | number: 1 }}%) përdoret në {{ total | number: 0 }}MB", + "count": "{{ count }} dokument{{ count > 1 ? 's' : '' }} gjetur", + "last_updated": "Përditësimi i fundit {{ date | timeAgo: dateFormat }}", + "view": { + "delete_comment_title": "Fshi komentin", + "delete_comment_message": "Dëshiron vërtet ta fshish këtë koment?", + "delete_document_title": "Fshi dokumentin", + "delete_document_message": "Dëshiron vërtet ta fshish këtë dokument?", + "shared_document_title": "Dokument i përbashkët", + "shared_document_message": "Ju mund ta ndani këtë dokument duke dhënë këtë lidhje.
    ", + "not_found": "Dokumenti nuk u gjet", + "forbidden": "Qasja është e ndaluar", + "download_files": "Shkarko skedarët", + "export_pdf": "Eksporto në PDF", + "by_creator": "nga", + "comments": "Komentet", + "no_comments": "Ende nuk ka komente për këtë dokument", + "add_comment": "Shto një koment", + "error_loading_comments": "Gabim gjatë ngarkimit të komenteve", + "workflow_current": "Hapi aktual i rrjedhës së punës", + "workflow_comment": "Shto një koment të rrjedhës së punës", + "workflow_validated_title": "Hapi i rrjedhës së punës u vërtetua", + "workflow_validated_message": "Hapi i rrjedhës së punës është vërtetuar me sukses.", + "content": { + "content": "përmbajtja", + "delete_file_title": "Fshi skedarin", + "delete_file_message": "Dëshiron vërtet ta fshish këtë skedar?", + "upload_pending": "Në pritje...", + "upload_progress": "Po ngarkohet...", + "upload_error": "Gabim ngarkimi", + "upload_error_quota": "Kuota u arrit", + "drop_zone": "Zvarrit", + "add_files": "Shtoni skedarë", + "file_processing_indicator": "Ky skedar është duke u përpunuar. ", + "reprocess_file": "Ripërpunoni këtë skedar", + "upload_new_version": "Ngarko një version të ri", + "open_versions": "Shfaq historikun e versionit", + "display_mode_list": "Shfaq skedarët në listë", + "display_mode_grid": "Shfaq skedarët në rrjet" + }, + "workflow": { + "workflow": "Rrjedha e punës", + "message": "Verifikoni ose vërtetoni dokumentet tuaja me njerëzit e organizatës suaj duke përdorur rrjedhat e punës.", + "workflow_start_label": "Cilin rrjedhë pune të filloni?", + "add_more_workflow": "Shto më shumë flukse pune", + "start_workflow_submit": "Filloni rrjedhën e punës", + "full_name": "{{ name }} filloi më {{ create_date | date }}", + "cancel_workflow": "Anuloni rrjedhën aktuale të punës", + "cancel_workflow_title": "Anuloni rrjedhën e punës", + "cancel_workflow_message": "Dëshiron vërtet të anulosh rrjedhën aktuale të punës?", + "no_workflow": "Nuk mund të filloni asnjë rrjedhë pune në këtë dokument." + }, + "permissions": { + "permissions": "Lejet", + "message": "Lejet mund të aplikohen drejtpërdrejt në këtë dokument, ose mund të vijnë nga etiketa.", + "title": "Lejet për këtë dokument", + "inherited_tags": "Lejet e trashëguara nga etiketat", + "acl_source": "Nga", + "acl_target": "Për", + "acl_permission": "Leja" + }, + "activity": { + "activity": "Aktiviteti", + "message": "Çdo veprim në këtë dokument regjistrohet këtu." + } + }, + "edit": { + "document_edited_with_errors": "Dokumenti u redaktua me sukses, por disa skedarë nuk mund të ngarkohen", + "document_added_with_errors": "Dokumenti u shtua me sukses, por disa skedarë nuk mund të ngarkohen", + "quota_reached": "Kuota u arrit", + "primary_metadata": "Meta të dhënat primare", + "title_placeholder": "Një emër i dhënë burimit", + "description_placeholder": "Një llogari e burimit", + "new_files": "Skedarë të rinj", + "orphan_files": "{{ count }} dosje{{ count > 1 ? 's' : '' }}", + "additional_metadata": "Meta të dhëna shtesë", + "subject_placeholder": "Tema e burimit", + "identifier_placeholder": "Një referencë e paqartë për burimin brenda një konteksti të caktuar", + "publisher_placeholder": "Një subjekt përgjegjës për vënien në dispozicion të burimit", + "format_placeholder": "Formati i skedarit, mediumi fizik ose dimensionet e burimit", + "source_placeholder": "Një burim i lidhur nga i cili rrjedh burimi i përshkruar", + "uploading_files": "Skedarët po ngarkohen..." + }, + "default": { + "upload_pending": "Në pritje...", + "upload_progress": "Po ngarkohet...", + "upload_error": "Gabim ngarkimi", + "upload_error_quota": "Kuota u arrit", + "quick_upload": "Ngarkimi i shpejtë", + "drop_zone": "Zvarrit", + "add_files": "Shtoni skedarë", + "add_new_document": "Shto në dokument të ri", + "latest_activity": "Aktiviteti i fundit", + "footer_sismics": "E punuar me nga Sizmike", + "api_documentation": "Dokumentacioni API", + "feedback": "Na jepni një koment", + "workflow_document_list": "Dokumentet e caktuara për ju", + "select_all": "Selektoj të gjitha", + "select_none": "Zgjidh asnjë" + }, + "pdf": { + "export_title": "Eksporto në PDF", + "export_metadata": "Eksporto të dhëna meta", + "export_comments": "Eksporto komente", + "fit_to_page": "Përshtat imazhin në faqe", + "margin": "Marzhi", + "millimeter": "mm" + }, + "share": { + "title": "Ndani dokumentin", + "message": "Emërtoni ndarjen nëse dëshironi të ndani disa herë të njëjtin dokument.", + "submit": "Shpërndaje" + } + }, + "file": { + "view": { + "previous": "E mëparshme", + "next": "Tjetra", + "not_found": "Skedari nuk u gjet" + }, + "edit": { + "title": "Redakto skedarin", + "name": "Emri i skedarit" + }, + "versions": { + "title": "Historia e versionit", + "filename": "Emri i skedarit", + "mimetype": "Lloji", + "create_date": "Data e krijimit", + "version": "Version" + } + }, + "tag": { + "new_tag": "Etiketë e re", + "search": "Kërko", + "default": { + "title": "Etiketa", + "message_1": "Etiketa janë etiketa të lidhura me dokumentet.", + "message_2": "Një dokument mund të etiketohet me etiketa të shumta dhe një etiketë mund të aplikohet në dokumente të shumta.", + "message_3": "Duke perdorur butonin, ju mund të modifikoni lejet në një etiketë.", + "message_4": "Nëse një etiketë mund të lexohet nga një përdorues ose grup tjetër, dokumentet shoqëruese mund të lexohen gjithashtu nga ata njerëz.", + "message_5": "Për shembull, etiketoni dokumentet e kompanisë suaj me një etiketë Kompania ime dhe shtoni lejen Mund të lexojë në një grup punonjësit" + }, + "edit": { + "delete_tag_title": "Fshi etiketën", + "delete_tag_message": "Dëshiron vërtet ta fshish këtë etiketë?", + "name": "Emri", + "color": "Ngjyrë", + "parent": "Prindi", + "info": "Lejet për këtë etiketë do të zbatohen gjithashtu për dokumentet e etiketuara {{ name }}", + "circular_reference_title": "Referencë rrethore", + "circular_reference_message": "Hierarkia e etiketave prind krijon një lak, ju lutemi zgjidhni një prind tjetër." + } + }, + "group": { + "profile": { + "members": "Anëtarët", + "no_members": "Asnjë anëtar", + "related_links": "Lidhje të ngjashme", + "edit_group": "Redakto {{ name }} grup" + } + }, + "user": { + "profile": { + "groups": "Grupet", + "quota_used": "Kuota e përdorur", + "percent_used": "{{ percent | number: 0 }}% e përdorur", + "related_links": "Lidhje të ngjashme", + "document_created": "Dokumentet e krijuara nga {{ username }}", + "edit_user": "Redakto {{ username }} përdorues" + } + }, + "usergroup": { + "search_groups": "Kërkoni në grupe", + "search_users": "Kërkoni në përdoruesit", + "you": "je ti!", + "default": { + "title": "Përdoruesit", + "message": "Këtu mund të shikoni informacione rreth përdoruesve dhe grupeve." + } + }, + "settings": { + "menu_personal_settings": "Cilësimet personale", + "menu_user_account": "Llogaria e përdoruesit", + "menu_two_factor_auth": "Autentifikimi me dy faktorë", + "menu_opened_sessions": "Seancat e hapura", + "menu_file_importer": "Importuesi i skedarëve në masë", + "menu_general_settings": "Cilësimet e përgjithshme", + "menu_workflow": "Rrjedha e punës", + "menu_users": "Përdoruesit", + "menu_groups": "Grupet", + "menu_vocabularies": "Fjalorët", + "menu_configuration": "Konfigurimi", + "menu_inbox": "Skanimi i kutisë hyrëse", + "menu_ldap": "Autentifikimi LDAP", + "menu_metadata": "Meta të dhëna të personalizuara", + "menu_monitoring": "Monitorimi", + "ldap": { + "title": "Autentifikimi LDAP", + "enabled": "Aktivizo vërtetimin LDAP", + "host": "Emri i hostit LDAP", + "port": "Porta LDAP (389 si parazgjedhje)", + "usessl": "Aktivizo SSL (ldaps)", + "admin_dn": "Admin DN", + "admin_password": "Fjalëkalimi i administratorit", + "base_dn": "Kërkimi bazë DN", + "filter": "Filtri i kërkimit (duhet të përmbajë USERNAME, p.sh. \"(uid=USERNAME)\")", + "default_email": "Email-i i parazgjedhur për përdoruesin LDAP", + "default_storage": "Hapësira ruajtëse e paracaktuar për përdoruesin LDAP", + "saved": "Konfigurimi LDAP u ruajt me sukses" + }, + "user": { + "title": "Menaxhimi i përdoruesve", + "add_user": "Shto një përdorues", + "username": "Emri i përdoruesit", + "create_date": "Krijo datë", + "totp_enabled": "Për këtë llogari është aktivizuar vërtetimi me dy faktorë", + "edit": { + "delete_user_title": "Fshi përdoruesin", + "delete_user_message": "Dëshiron vërtet ta fshish këtë përdorues? ", + "user_used_title": "Përdoruesi në përdorim", + "user_used_message": "Ky përdorues përdoret në rrjedhën e punës \"{{ name }}\"", + "edit_user_failed_title": "Përdoruesi ekziston tashmë", + "edit_user_failed_message": "Ky emër përdoruesi është marrë tashmë nga një përdorues tjetër", + "edit_user_title": "Redakto \"{{ username }}\"", + "add_user_title": "Shto një përdorues", + "username": "Emri i përdoruesit", + "email": "E-mail", + "groups": "Grupet", + "storage_quota": "Kuota e ruajtjes", + "storage_quota_placeholder": "Kuota e hapësirës ruajtëse (në MB)", + "password": "Fjalëkalimi", + "password_confirm": "Fjalëkalimi (konfirmo)", + "disabled": "Përdorues me aftësi të kufizuara", + "password_reset_btn": "Dërgoni një email për rivendosjen e fjalëkalimit te ky përdorues", + "password_lost_sent_title": "Email për rivendosjen e fjalëkalimit u dërgua", + "password_lost_sent_message": "Është dërguar një email për rivendosjen e fjalëkalimit {{ username }}", + "disable_totp_btn": "Çaktivizo vërtetimin me dy faktorë për këtë përdorues", + "disable_totp_title": "Çaktivizo vërtetimin me dy faktorë", + "disable_totp_message": "Jeni i sigurt që dëshironi të çaktivizoni vërtetimin me dy faktorë për këtë përdorues?" + } + }, + "workflow": { + "title": "Konfigurimi i rrjedhës së punës", + "add_workflow": "Shto një rrjedhë pune", + "name": "Emri", + "create_date": "Krijo datë", + "edit": { + "delete_workflow_title": "Fshi fluksin e punës", + "delete_workflow_message": "Dëshiron vërtet ta fshish këtë rrjedhë pune? ", + "edit_workflow_title": "Redakto \"{{ name }}\"", + "add_workflow_title": "Shto një rrjedhë pune", + "name": "Emri", + "name_placeholder": "Emri ose përshkrimi i hapit", + "drag_help": "Zvarrit dhe lësho për të rirenditur hapin", + "type": "Lloji i hapit", + "type_approve": "Mirato", + "type_validate": "Vërtetoni", + "target": "Caktuar për", + "target_help": "Mirato: Pranoni ose refuzoni rishikimin
    Vërteto: Rishikoni dhe vazhdoni rrjedhën e punës", + "add_step": "Shto një hap të rrjedhës së punës", + "actions": "Çfarë ndodh më pas?", + "remove_action": "Hiq veprimin", + "acl_info": "Vetëm përdoruesit dhe grupet e përcaktuara këtu do të mund të fillojnë këtë rrjedhë pune në një dokument" + } + }, + "security": { + "enable_totp": "Aktivizo vërtetimin me dy faktorë", + "enable_totp_message": "Sigurohuni që të keni një aplikacion të përputhshëm me TOTP në telefonin tuaj gati për të shtuar një llogari të re", + "title": "Autentifikimi me dy faktorë", + "message_1": "Autentifikimi me dy faktorë ju lejon të shtoni një shtresë sigurie në tuaj {{ appName }} llogari.
    Përpara se të aktivizoni këtë veçori, sigurohuni që të keni një aplikacion të pajtueshëm me TOTP në telefonin tuaj:", + "message_google_authenticator": "Për Android, iOS dhe Blackberry: Google Authenticator", + "message_duo_mobile": "Për Android dhe iOS: Duo Mobile", + "message_authenticator": "Për Windows Phone: Vërtetuesi", + "message_2": "Këto aplikacione gjenerojnë automatikisht një kod verifikimi që ndryshon pas një periudhe të caktuar kohe.
    Do t'ju kërkohet të vendosni këtë kod verifikimi sa herë që identifikoheni {{ appName }}.", + "secret_key": "Çelësi juaj sekret është: {{ secret }}", + "secret_key_warning": "Konfiguro aplikacionin tënd TOTP në telefonin tënd me këtë çelës sekret tani, nuk do të mund ta qasesh më vonë.", + "totp_enabled_message": "Autentifikimi me dy faktorë është aktivizuar në llogarinë tuaj.
    Sa herë që identifikoheni {{ appName }}, do t'ju kërkohet një kod verifikimi nga aplikacioni i telefonit tuaj të konfiguruar.
    Nëse e humbni telefonin, nuk do të jeni në gjendje të identifikoheni në llogarinë tuaj, por seancat aktive do t'ju lejojnë të rigjeneroni një çelës sekret.", + "disable_totp": { + "disable_totp": "Çaktivizo vërtetimin me dy faktorë", + "message": "Llogaria juaj nuk do të mbrohet më nga vërtetimi me dy faktorë.", + "confirm_password": "Konfirmoni fjalëkalimin tuaj", + "submit": "Çaktivizo vërtetimin me dy faktorë" + }, + "test_totp": "Ju lutemi shkruani kodin e vërtetimit të shfaqur në telefonin tuaj:", + "test_code_success": "Kodi i verifikimit në rregull", + "test_code_fail": "Ky kod nuk është i vlefshëm, ju lutemi kontrolloni dy herë nëse telefoni juaj është i konfiguruar siç duhet ose çaktivizoni vërtetimin me dy faktorë" + }, + "group": { + "title": "Menaxhimi i grupeve", + "add_group": "Shto një grup", + "name": "Emri", + "edit": { + "delete_group_title": "Fshi grupin", + "delete_group_message": "Dëshiron vërtet ta fshish këtë grup?", + "edit_group_failed_title": "Grupi tashmë ekziston", + "edit_group_failed_message": "Ky emër grupi është marrë tashmë nga një grup tjetër", + "group_used_title": "Grupi në përdorim", + "group_used_message": "Ky grup përdoret në rrjedhën e punës \"{{ name }}\"", + "edit_group_title": "Redakto \"{{ name }}\"", + "add_group_title": "Shto një grup", + "name": "Emri", + "parent_group": "Grupi i prindërve", + "search_group": "Kërkoni një grup", + "members": "Anëtarët", + "new_member": "Anëtar i ri", + "search_user": "Kërkoni një përdorues" + } + }, + "account": { + "title": "Llogaria e përdoruesit", + "password": "Fjalëkalimi", + "password_confirm": "Fjalëkalimi (konfirmo)", + "updated": "Llogaria u përditësua me sukses" + }, + "config": { + "title_guest_access": "Qasja e mysafirëve", + "message_guest_access": "Qasja e mysafirëve është një mënyrë ku çdokush mund të hyjë {{ appName }} pa fjalëkalim.
    Ashtu si një përdorues normal, përdoruesi mysafir mund të qaset vetëm në dokumentet e tij dhe ato të aksesueshme përmes lejeve.
    ", + "enable_guest_access": "Aktivizo qasjen e vizitorëve", + "disable_guest_access": "Çaktivizo qasjen e vizitorëve", + "title_theme": "Personalizimi i temës", + "title_general": "Konfigurimi i përgjithshëm", + "default_language": "Gjuha e parazgjedhur për dokumentet e reja", + "application_name": "Emri i aplikacionit", + "main_color": "Ngjyra kryesore", + "custom_css": "CSS e personalizuar", + "custom_css_placeholder": "CSS e personalizuar për t'u shtuar pas fletës kryesore të stilit", + "logo": "Logo (madhësia katrore)", + "background_image": "Imazhi i sfondit", + "uploading_image": "Po ngarkon imazhin...", + "title_smtp": "Konfigurimi i emailit", + "smtp_hostname": "Emri i hostit SMTP", + "smtp_port": "Porta SMTP", + "smtp_from": "E-mail i dërguesit", + "smtp_username": "Emri i përdoruesit SMTP", + "smtp_password": "Fjalëkalimi SMTP", + "smtp_updated": "Konfigurimi SMTP u përditësua me sukses", + "webhooks": "Uebhooks", + "webhooks_explain": "Webhooks do të thirren kur të ndodhë ngjarja e specifikuar. ", + "webhook_event": "Ngjarja", + "webhook_url": "URL", + "webhook_create_date": "Krijo datë", + "webhook_add": "Shto një uebhook" + }, + "metadata": { + "title": "Konfigurimi i personalizuar i meta të dhënave", + "message": "Këtu mund të shtoni meta të dhëna të personalizuara në dokumentet tuaja si një identifikues i brendshëm ose një datë skadimi. ", + "name": "Emri i meta të dhënave", + "type": "Lloji i meta të dhënave" + }, + "inbox": { + "title": "Skanimi i kutisë hyrëse", + "message": "Duke aktivizuar këtë veçori, sistemi do të skanojë kutinë hyrëse të specifikuar çdo minutë i palexuar emailet dhe i importoni automatikisht.
    Pas importimit të një emaili, ai do të shënohet si i lexuar.
    Cilësimet e konfigurimit për Gmail, Outlook.com, Yahoo.", + "enabled": "Aktivizo skanimin e kutisë hyrëse", + "hostname": "Emri i hostit IMAP", + "port": "Porta IMAP (143 ose 993)", + "starttls": "Aktivizo STARTTLS", + "username": "Emri i përdoruesit IMAP", + "password": "Fjalëkalimi IMAP", + "folder": "Dosja IMAP", + "tag": "Etiketa u shtua në dokumentet e importuara", + "test": "Testoni parametrat", + "last_sync": "Sinkronizimi i fundit: {{ data.date | date: 'medium' }}, {{ data.count }} mesazh{{ data.count > 1 ? 's' : '' }} të importuara", + "test_success": "Lidhja me kutinë hyrëse është e suksesshme ({{ count }} i palexuar mesazh{{ count > 1 ? 's' : '' }})", + "test_fail": "Ndodhi një gabim gjatë lidhjes me kutinë hyrëse, ju lutemi kontrolloni parametrat", + "saved": "Konfigurimi IMAP u ruajt me sukses", + "autoTagsEnabled": "Shtoni automatikisht etiketat nga rreshti i subjektit të shënuar me", + "deleteImported": "Fshi mesazhin nga kutia postare pas importimit" + }, + "monitoring": { + "background_tasks": "Detyrat e sfondit", + "queued_tasks": "Aktualisht ka {{ count }} detyrat në radhë.", + "queued_tasks_explain": "Përpunimi i skedarëve, krijimi i miniaturave, përditësimi i indeksit, njohja optike e karaktereve janë detyra në sfond. ", + "server_logs": "Regjistrat e serverit", + "log_date": "Data", + "log_tag": "Etiketë", + "log_message": "Mesazh", + "indexing": "Indeksimi", + "indexing_info": "Nëse vëreni mospërputhje në rezultatet e kërkimit, mund të provoni të bëni një riindeksim të plotë. ", + "start_reindexing": "Filloni riindeksimin e plotë", + "reindexing_started": "Ri-indeksimi filloi, ju lutemi prisni derisa të mos ketë më detyra në sfond." + }, + "session": { + "title": "Seancat e hapura", + "created_date": "Data e krijimit", + "last_connection_date": "Data e fundit e lidhjes", + "user_agent": "Nga", + "current": "Aktuale", + "current_session": "Ky është sesioni aktual", + "clear_message": "Të gjitha pajisjet e tjera të lidhura me këtë llogari do të shkëputen", + "clear": "Pastro të gjitha seancat e tjera" + }, + "vocabulary": { + "title": "Shënimet e fjalorit", + "choose_vocabulary": "Zgjidhni një fjalor për të redaktuar", + "type": "Lloji", + "coverage": "Mbulimi", + "rights": "Të drejtat", + "value": "Vlera", + "order": "Rendit", + "new_entry": "Hyrje e re" + }, + "fileimporter": { + "title": "Importuesi i skedarëve në masë", + "advanced_users": "Për përdoruesit e avancuar!", + "need_intro": "Nëse ju duhet:", + "need_1": "Importoni një direktori skedarësh menjëherë", + "need_2": "Skanoni një drejtori për skedarë të rinj dhe importojini ato", + "line_1": "Shkoni në sismics/docs/releases dhe shkarkoni mjetin e importuesit të skedarëve për sistemin tuaj.", + "line_2": "Ndiq udhëzime këtu për të përdorur këtë mjet.", + "line_3": "Skedarët tuaj do të importohen në dokumente sipas konfigurimit të importuesit të skedarëve.", + "download": "Shkarko", + "instructions": "Udhëzimet" + } + }, + "feedback": { + "title": "Na jepni një koment", + "message": "Ndonjë sugjerim apo pyetje në lidhje me Teedy? ", + "sent_title": "Komentet u dërguan", + "sent_message": "Faleminderit për komentin tuaj! " + }, + "import": { + "title": "Importimi", + "error_quota": "U arrit kufiri i kuotës, kontaktoni administratorin tuaj për të rritur kuotën tuaj", + "error_general": "Ndodhi një gabim gjatë përpjekjes për të importuar skedarin tuaj, ju lutemi sigurohuni që ai është një skedar i vlefshëm EML" + }, + "app_share": { + "403": { + "title": "I pa autorizuar", + "message": "Dokumenti që po përpiqeni të shikoni nuk ndahet më" + }, + "main": "Kërkoni një lidhje të përbashkët të dokumentit për të hyrë në të" + }, + "directive": { + "acledit": { + "acl_target": "Për", + "acl_permission": "Leja", + "add_permission": "Shto një leje", + "search_user_group": "Kërkoni një përdorues ose grup" + }, + "auditlog": { + "log_created": "krijuar", + "log_updated": "përditësuar", + "log_deleted": "fshihet", + "Acl": "ACL", + "Comment": "Komentoni", + "Document": "Dokumenti", + "File": "Skedari", + "Group": "Grupi", + "Route": "Rrjedha e punës", + "RouteModel": "Modeli i rrjedhës së punës", + "Tag": "Etiketë", + "User": "Përdoruesi", + "Webhook": "Uebhook" + }, + "selectrelation": { + "typeahead": "Shkruani një titull dokumenti" + }, + "selecttag": { + "typeahead": "Shkruani një etiketë" + }, + "datepicker": { + "current": "Sot", + "clear": "Qartë", + "close": "U krye" + } + }, + "filter": { + "filesize": { + "mb": "MB", + "kb": "kB" + } + }, + "acl": { + "READ": "Mund të lexojë", + "READWRITE": "Mund të shkruajë", + "WRITE": "Mund të shkruajë", + "USER": "Përdoruesi", + "GROUP": "Grupi", + "SHARE": "Të përbashkëta" + }, + "workflow_type": { + "VALIDATE": "Vleresimi", + "APPROVE": "Miratimi" + }, + "workflow_transition": { + "APPROVED": "Miratuar", + "REJECTED": "Refuzuar", + "VALIDATED": "E vërtetuar" + }, + "validation": { + "required": "E detyrueshme", + "too_short": "Shumë e shkurtër", + "too_long": "Shume gjate", + "email": "Duhet të jetë një e-mail i vlefshëm", + "password_confirm": "Fjalëkalimi dhe konfirmimi i fjalëkalimit duhet të përputhen", + "number": "Numri i kërkuar", + "no_space": "Hapësirat dhe dy pikat nuk lejohen", + "alphanumeric": "Lejohen vetëm shkronja dhe numra" + }, + "action_type": { + "ADD_TAG": "Shto një etiketë", + "REMOVE_TAG": "Hiq një etiketë", + "PROCESS_FILES": "Përpunoni skedarët" + }, + "pagination": { + "previous": "E mëparshme", + "next": "Tjetra", + "first": "Së pari", + "last": "E fundit" + }, + "onboarding": { + "step1": { + "title": "Hera e parë?", + "description": "Nëse është hera juaj e parë në Teedy, klikoni butonin Next, përndryshe mos ngurroni të më mbyllni." + }, + "step2": { + "title": "Dokumentet", + "description": "Teedy është i organizuar në dokumente dhe çdo dokument përmban skedarë të shumtë." + }, + "step3": { + "title": "Skedarët", + "description": "Mund të shtoni skedarë pas krijimit të një dokumenti ose përpara se të përdorni këtë zonë të ngarkimit të shpejtë." + }, + "step4": { + "title": "Kërko", + "description": "Kjo është mënyra kryesore për të gjetur përsëri dokumentet tuaja. " + }, + "step5": { + "title": "Etiketa", + "description": "Dokumentet mund të organizohen në etiketa (të cilat janë si super-dosje). " + } + }, + "yes": "po", + "no": "Nr", + "ok": "Në rregull", + "cancel": "Anulo", + "share": "Shpërndaje", + "unshare": "Shpërndaje", + "close": "Mbylle", + "add": "Shtoni", + "open": "Hapur", + "see": "Shiko", + "save": "Ruaj", + "export": "Eksporto", + "edit": "Redakto", + "delete": "Fshije", + "rename": "Riemërto", + "download": "Shkarko", + "loading": "Po ngarkohet...", + "send": "Dërgo", + "enabled": "Aktivizuar", + "disabled": "I paaftë" +} \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/share.html b/docs-web/src/main/webapp/src/share.html index a97e745e..5bf57f2e 100644 --- a/docs-web/src/main/webapp/src/share.html +++ b/docs-web/src/main/webapp/src/share.html @@ -6,7 +6,7 @@ - + @@ -102,4 +102,4 @@ - \ No newline at end of file + diff --git a/docs-web/src/prod/resources/config.properties b/docs-web/src/prod/resources/config.properties index 75038405..37e03ad0 100644 --- a/docs-web/src/prod/resources/config.properties +++ b/docs-web/src/prod/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=28 +db.version=30 diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java index 93d6488c..43783d8a 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestFileResource.java @@ -105,7 +105,7 @@ public class TestFileResource extends BaseJerseyTest { Assert.assertEquals("PIA00452.jpg", files.getJsonObject(0).getString("name")); Assert.assertEquals("image/jpeg", files.getJsonObject(0).getString("mimetype")); Assert.assertEquals(0, files.getJsonObject(0).getInt("version")); - Assert.assertEquals(163510L, files.getJsonObject(0).getJsonNumber("size").longValue()); + Assert.assertEquals(FILE_PIA_00452_JPG_SIZE, files.getJsonObject(0).getJsonNumber("size").longValue()); Assert.assertEquals(file2Id, files.getJsonObject(1).getString("id")); Assert.assertEquals("PIA00452.jpg", files.getJsonObject(1).getString("name")); Assert.assertEquals(0, files.getJsonObject(1).getInt("version")); @@ -370,7 +370,7 @@ public class TestFileResource extends BaseJerseyTest { .get(); is = (InputStream) response.getEntity(); fileBytes = ByteStreams.toByteArray(is); - Assert.assertEquals(163510, fileBytes.length); + Assert.assertEquals(FILE_PIA_00452_JPG_SIZE, fileBytes.length); // Create another document String document2Id = clientUtil.createDocument(fileOrphanToken); @@ -415,28 +415,19 @@ public class TestFileResource extends BaseJerseyTest { String file1Id = clientUtil.addFileToDocument(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG, fileQuotaToken, null); // Check current quota - JsonObject json = target().path("/user").request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken) - .get(JsonObject.class); - Assert.assertEquals(292641L, json.getJsonNumber("storage_current").longValue()); + Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE, getUserQuota(fileQuotaToken)); // Add a file (292641 bytes large) clientUtil.addFileToDocument(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG, fileQuotaToken, null); // Check current quota - json = target().path("/user").request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken) - .get(JsonObject.class); - Assert.assertEquals(585282L, json.getJsonNumber("storage_current").longValue()); + Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 2, getUserQuota(fileQuotaToken)); // Add a file (292641 bytes large) clientUtil.addFileToDocument(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG, fileQuotaToken, null); // Check current quota - json = target().path("/user").request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken) - .get(JsonObject.class); - Assert.assertEquals(877923L, json.getJsonNumber("storage_current").longValue()); + Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 3, getUserQuota(fileQuotaToken)); // Add a file (292641 bytes large) try { @@ -446,16 +437,13 @@ public class TestFileResource extends BaseJerseyTest { } // Deletes a file - json = target().path("/file/" + file1Id).request() + JsonObject json = target().path("/file/" + file1Id).request() .cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken) .delete(JsonObject.class); Assert.assertEquals("ok", json.getString("status")); // Check current quota - json = target().path("/user").request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken) - .get(JsonObject.class); - Assert.assertEquals(585282L, json.getJsonNumber("storage_current").longValue()); + Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 2, getUserQuota(fileQuotaToken)); // Create a document long create1Date = new Date().getTime(); @@ -472,10 +460,7 @@ public class TestFileResource extends BaseJerseyTest { clientUtil.addFileToDocument(FILE_PIA_00452_JPG, fileQuotaToken, document1Id); // Check current quota - json = target().path("/user").request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken) - .get(JsonObject.class); - Assert.assertEquals(748792, json.getJsonNumber("storage_current").longValue()); + Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 2 + FILE_PIA_00452_JPG_SIZE, getUserQuota(fileQuotaToken)); // Deletes the document json = target().path("/document/" + document1Id).request() @@ -484,9 +469,12 @@ public class TestFileResource extends BaseJerseyTest { Assert.assertEquals("ok", json.getString("status")); // Check current quota - json = target().path("/user").request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken) - .get(JsonObject.class); - Assert.assertEquals(585282L, json.getJsonNumber("storage_current").longValue()); + Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 2, getUserQuota(fileQuotaToken)); + } + + private long getUserQuota(String userToken) { + return target().path("/user").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, userToken) + .get(JsonObject.class).getJsonNumber("storage_current").longValue(); } } diff --git a/pom.xml b/pom.xml index 4c7ff8f6..1994ba03 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ 1.6.14 1.15.4 4.10.0 - 2.1.2 + 2.1.3 3.0.10 5.0.0