diff --git a/.gitignore b/.gitignore index 83276d7..425bb75 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .project !.gitignore # except for .gitignore .settings +/out *.tmproj diff --git a/README.md b/README.md index 2d3acb2..4bb3f6f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# File-Uploader Plugin (Latest 3.0.9) +# File-Uploader Plugin (Latest 3.1.0) Supported Grails 3.2.0 @@ -54,12 +54,21 @@ fileuploader { allowedExtensions = ["xls"] path = "./web-app/degree-applications" storageTypes = "" + //Explnation about this congiguration is given below + checksum { + calculate = true + algorithm = Algorithm.MD5 + } } userAvatar { maxSize = 1024 * 1024 * 2 //256 kbytes allowedExtensions = ["jpg","jpeg","gif","png"] storageTypes = "CDN" container = "anyContainerName" + checksum { + calculate = false + algorithm = Algorithm.SHA1 + } } logo { maxSize = 1024 * 1024 * 2 //256 kbytes @@ -87,3 +96,26 @@ can be overwritten for group level configuration by setting **expirationPeriod** ``` # Google Default Credentials export GOOGLE_APPLICATION_CREDENTIALS='/path/to/key.json' +4. To Enable checksum checks and generation, define configuration as shown in the sample configuration, +``` + groups { + degreeApplication { // Non CDN files, will be stored in local directory. + maxSize = 1000 * 1024 //256 kbytes + allowedExtensions = ["xls"] + path = "./web-app/degree-applications" + storageTypes = "" + checksum { + calculate = true + algorithm = Algorithm.MD5 + } + } + } + +``` +If first flag is set to true, plugin will generate checksum for the uploaded file and try to find a file from database having same checksum. If any such file is found, then plugin will throw an **DuplicateFileException** exception. +Second flag will tell plugin which algorithm to use to calculate the checksum. Currently, possible choices are, +``` +Algorithm.MD5 and, +Algorithm.SHA1 +``` +Second flag will be ignored if first flag is set to false. By default checksum calculation features is disabled. To enable provide valid configurations. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 55283f1..7a6efcd 100644 --- a/build.gradle +++ b/build.gradle @@ -18,12 +18,12 @@ buildscript { mavenCentral() } dependencies { - classpath "com.causecode.plugins:gradle-code-quality:1.0.0" + classpath "com.causecode.plugins:gradle-code-quality:1.0.1-RC1" classpath "org.grails:grails-gradle-plugin:$grailsVersion" } } -version "3.0.9" +version "3.1.0" group "com.causecode.plugins" apply plugin: "idea" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2567a49..e0f4195 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Nov 28 17:45:47 IST 2017 +#Tue Jan 30 19:18:03 IST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip diff --git a/grails-app/domain/com/causecode/fileuploader/UFile.groovy b/grails-app/domain/com/causecode/fileuploader/UFile.groovy index b9cebe8..57df071 100644 --- a/grails-app/domain/com/causecode/fileuploader/UFile.groovy +++ b/grails-app/domain/com/causecode/fileuploader/UFile.groovy @@ -34,6 +34,12 @@ class UFile implements Serializable { String name String path + //Contains calculated hash value of file content + String checksum + + //Algorithm Used to calculate Checksum + String checksumAlgorithm + UFileType type Date dateCreated @@ -48,12 +54,19 @@ class UFile implements Serializable { name blank: false fileGroup blank: false provider nullable: true + checksum nullable: true + checksumAlgorithm nullable: true dateCreated bindable: false lastUpdated bindable: false } static mapping = { path sqlType: 'text' + + //Index on checksum + checksum index: true + //Index on checksumAlgorithm + checksumAlgorithm index: true } def afterDelete() { @@ -118,6 +131,7 @@ enum UFileType { LOCAL(3) final int id + UFileType(int id) { this.id = id } @@ -137,6 +151,7 @@ enum CDNProvider { LOCAL(4) final int id + CDNProvider(int id) { this.id = id } diff --git a/grails-app/services/com/causecode/fileuploader/FileUploaderService.groovy b/grails-app/services/com/causecode/fileuploader/FileUploaderService.groovy index 56d7c0e..8836a10 100644 --- a/grails-app/services/com/causecode/fileuploader/FileUploaderService.groovy +++ b/grails-app/services/com/causecode/fileuploader/FileUploaderService.groovy @@ -8,18 +8,21 @@ package com.causecode.fileuploader import com.causecode.fileuploader.cdn.CDNFileUploader +import com.causecode.fileuploader.cdn.amazon.AmazonCDNFileUploaderImpl import com.causecode.fileuploader.util.FileUploaderUtils +import com.causecode.fileuploader.util.Time +import com.causecode.fileuploader.util.checksum.ChecksumValidator +import com.causecode.fileuploader.util.checksum.exceptions.DuplicateFileException import com.causecode.util.NucleusUtils import grails.core.GrailsApplication import grails.util.Holders import groovy.io.FileType +import org.apache.commons.validator.UrlValidator import org.springframework.context.MessageSource import org.springframework.dao.DataIntegrityViolationException import org.springframework.web.multipart.MultipartFile + import java.nio.channels.FileChannel -import org.apache.commons.validator.UrlValidator -import com.causecode.fileuploader.cdn.amazon.AmazonCDNFileUploaderImpl -import com.causecode.fileuploader.util.Time /** * A service class for all fileUpload related operations. @@ -72,15 +75,28 @@ class FileUploaderService { * @return */ UFile saveFile(String group, def file, String customFileName = '', Object userInstance = null, Locale locale = null) - throws StorageConfigurationException, UploadFailureException, ProviderNotFoundException { - + throws StorageConfigurationException, UploadFailureException, + ProviderNotFoundException, FileNotFoundException, + DuplicateFileException { Date expireOn long currentTimeMillis = System.currentTimeMillis() CDNProvider cdnProvider UFileType type = UFileType.LOCAL String path - FileGroup fileGroupInstance = new FileGroup(group) + ChecksumValidator checksumValidator = new ChecksumValidator(fileGroupInstance) + + if (checksumValidator.shouldCalculateChecksum()) { + UFile uFileInstance = UFile.findByChecksumAndChecksumAlgorithm(checksumValidator.getChecksum(file), + checksumValidator.algorithm) + if (uFileInstance) { + throw new DuplicateFileException( + "Checksum for file ${file.name} is ${checksumValidator.getChecksum(file)} and " + + "that checksum refers to an existing file ${uFileInstance} on server" + ) + } + } + Map fileData = fileGroupInstance.getFileNameAndExtensions(file, customFileName) if (fileData.empty || !file) { @@ -89,35 +105,27 @@ class FileUploaderService { fileGroupInstance.allowedExtensions(fileData, locale, group) fileGroupInstance.validateFileSize(fileData.fileSize, locale) - // If group specific storage type is not defined then use the common storage type String storageTypes = fileGroupInstance.groupConfig.storageTypes ?: fileGroupInstance.config.storageTypes if (storageTypes == 'CDN') { type = UFileType.CDN_PUBLIC - fileGroupInstance.scopeFileName(userInstance, fileData, group, currentTimeMillis) long expirationPeriod = getExpirationPeriod(group) - File tempFile if (file instanceof File) { - /* No need to transfer a file of type File since its already in a temporary location. - * (Saves resource utilization) - */ + // No need to transfer a file of type File since its already in a temporary location. tempFile = file } else { if (file instanceof MultipartFile) { - tempFile = new File(newTemporaryDirectoryPath + - "${fileData.fileName}.${fileData.fileExtension}") - + tempFile = getTempFilePathForMultipartFile(fileData.fileName, fileData.fileExtension) file.transferTo(tempFile) } } // Delete the temporary file when JVM exited since the base file is not required after upload tempFile.deleteOnExit() - cdnProvider = fileGroupInstance.cdnProvider if (!cdnProvider) { @@ -125,27 +133,33 @@ class FileUploaderService { } expireOn = isPublicGroup(group) ? null : new Date(new Date().time + expirationPeriod * 1000) - path = uploadFileToCloud(fileData, fileGroupInstance, tempFile) - } else { path = fileGroupInstance.getLocalSystemPath(storageTypes, fileData, currentTimeMillis) - - // Move file log.debug "Moving [$fileData.fileName] to [${path}]." moveFile(file, path) } - UFile ufile = new UFile([name: fileData.fileName, size: fileData.fileSize, path: path, type: type, - extension: fileData.fileExtension, expiresOn: expireOn, fileGroup: group, provider: cdnProvider]) - NucleusUtils.save(ufile, true) + UFile ufile = new UFile( + [name : fileData.fileName, size: fileData.fileSize, path: path, type: type, + extension: fileData.fileExtension, expiresOn: expireOn, fileGroup: group, provider: cdnProvider]) + + if (checksumValidator.shouldCalculateChecksum()) { + ufile.checksum = checksumValidator.getChecksum(file) + ufile.checksumAlgorithm = checksumValidator.algorithm + } + NucleusUtils.save(ufile, true) return ufile } + private File getTempFilePathForMultipartFile(String fileName, String fileExtension) { + return new File(newTemporaryDirectoryPath + "${fileName}.${fileExtension}") + } + /** * Method is used to upload file to cloud provider. Then it gets the path of uploaded file - * @params fileData, fileGroupInstance, tempFile + * @params fileData , fileGroupInstance, tempFile * @return path of uploaded file * */ @@ -176,7 +190,7 @@ class FileUploaderService { /** * Method is used to move file from temp directory to another. - * @params fileInstance, path + * @params fileInstance , path * */ void moveFile(def file, String path) { diff --git a/src/main/groovy/com/causecode/fileuploader/StorageException.groovy b/src/main/groovy/com/causecode/fileuploader/StorageException.groovy index 3d87cf7..b5462fc 100644 --- a/src/main/groovy/com/causecode/fileuploader/StorageException.groovy +++ b/src/main/groovy/com/causecode/fileuploader/StorageException.groovy @@ -3,7 +3,7 @@ * All rights reserved. * * Redistribution and use in source and binary forms, with or - * without modification, are not permitted. +* without modification, are not permitted. */ package com.causecode.fileuploader diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/Algorithm.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/Algorithm.groovy new file mode 100644 index 0000000..606e159 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/Algorithm.groovy @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +/** + * Enum to represent Algorithm to be used with hash calculation + * @author Milan Savaliya + * @since 3.1.0 + */ +enum Algorithm { + MD5, + SHA1 +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumConfig.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumConfig.groovy new file mode 100644 index 0000000..1bc255c --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumConfig.groovy @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +/** + * Checksum Config object to take decision weather to calculate checksum/hash or not, and if to calculate then with + * which algorithm + * @author Milan Savaliya + * @since 3.1.0 + */ + +class ChecksumConfig { + boolean calculate = false + Algorithm algorithm = Algorithm.MD5 +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumValidator.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumValidator.groovy new file mode 100644 index 0000000..7d64b88 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/ChecksumValidator.groovy @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +import com.causecode.fileuploader.FileGroup +import com.causecode.fileuploader.util.checksum.beans.FileInputBean +import com.causecode.fileuploader.util.checksum.beans.MultipartFileInputBeanImpl +import com.causecode.fileuploader.util.checksum.beans.SimpleFileInputBeanImpl +import com.causecode.fileuploader.util.checksum.exceptions.UnRecognizedFileTypeException +import org.springframework.web.multipart.MultipartFile + +/** + * Helper class to validate and generate checksum. This class will communicate with service to + * generate checksum + * @author Milan Savaliya + * @since 3.1.0 + */ +class ChecksumValidator { + + /** + * Member variable to store the calculated hash code + */ + private String calculatedChecksum = null + private final ChecksumConfig checksumConfig + private final FileGroup fileGroup + + ChecksumValidator(FileGroup fileGroup) { + this.fileGroup = fileGroup + this.checksumConfig = getChecksumConfig(fileGroup) + } + + /** + * returns if flag to calculate checksum is set or not. + * @return boolean + */ + boolean shouldCalculateChecksum() { + return this.checksumConfig.calculate + } + + /** + * Method calculates the checksum and returns calculated checksum. This method does not recalculate + * checksum on second call. + * This method returns previously calculated checksum in the next subsequent calls. + * @param file + * @return String + * @throws FileNotFoundException + */ + String getChecksum(def file) throws FileNotFoundException { + calculatedChecksum = (calculatedChecksum ?: this.getChecksumForFile(file)) + return calculatedChecksum + } + + /** + * Returns the String representation of the algorithm being used to calculate the checksum + * @return String + */ + String getAlgorithm() { + return this.checksumConfig.algorithm.toString() + } + + /** + * Private method to get the ChecksumConfig object from the given fileGroup object. + * @param fileGroup + * @return ChecksumConfig + */ + private static ChecksumConfig getChecksumConfig(FileGroup fileGroup) { + Map checksumProperties = fileGroup.groupConfig.checksum + if (!checksumProperties) { + return new ChecksumConfig() + } + + boolean calculate = checksumProperties.calculate ?: false + Algorithm algorithm = checksumProperties.algorithm ?: Algorithm.MD5 + return new ChecksumConfig(calculate: calculate, algorithm: algorithm) + } + + /** + * This is actual heart method which generates the checksum and returns its hex string representation + * to the calling end. + * @param file + * @return String + * @throws FileNotFoundException + */ + private String getChecksumForFile(def file) throws FileNotFoundException { + FileInputBean fileInputBean = getFileInputBeanForFile(file) + HashCalculator hashCalculator = new FileHashCalculator(fileInputBean, this.checksumConfig.algorithm) + return hashCalculator.calculateHash() + } + + /** + * Generates the FileInputBean from the given File instance. Currently this method accepts File or + * MultipartFile Instance + * @param file + * @return FileInputBean + * @throws UnRecognizedFileTypeException + */ + private static FileInputBean getFileInputBeanForFile(def file) throws UnRecognizedFileTypeException { + if (file in File) { + return new SimpleFileInputBeanImpl(file) + } else if (file in MultipartFile) { + return new MultipartFileInputBeanImpl(file) + } + + throw new UnRecognizedFileTypeException("${file.class.name} is not recognized for FileInputBean") + } +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/FileHashCalculator.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/FileHashCalculator.groovy new file mode 100644 index 0000000..caca380 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/FileHashCalculator.groovy @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +import com.causecode.fileuploader.util.checksum.beans.FileInputBean +import groovy.util.logging.Slf4j + +import javax.xml.bind.annotation.adapters.HexBinaryAdapter +import java.security.MessageDigest + +/** + * This class is the Utility Class which will be used to calculate the Hash of the File + * This class works on the byte level layer. + * @author Milan Savaliya + * @since 3.1.0 + */ +@Slf4j +class FileHashCalculator implements HashCalculator { + + //FileInputBean instance + private final FileInputBean fileInputBean + + /** + * Constructs FileHashCalculator instance and throws FileNotFoundException If Input File Is null or not exists + * This uses default MD5 Hash Calculating Algorithm + * @param algorithm + * @param fileInputBean + * @throws FileNotFoundException + */ + FileHashCalculator(FileInputBean fileInputBean) throws FileNotFoundException { + this.fileInputBean = fileInputBean + validateInputs() + } + + /** + * Constructs FileHashCalculator with given Algorithm instance + * @param fileInputBean + * @param algorithm + */ + FileHashCalculator(FileInputBean fileInputBean, Algorithm algorithm) { + this(fileInputBean) + this.algorithm = algorithm + } + + /** + * This Method validates inputs. + */ + private void validateInputs() throws FileNotFoundException { + if (!fileInputBean) { + throw new FileNotFoundException('File not found') + } + } + + /** + * This method calculates the hash and returns a hex String representation of Calculated Hash. + * @return String [ Calculated Hash ] + */ + @Override + String calculateHash() { + log.info "Starting checksum calculation For File ${fileInputBean.name}" + MessageDigest messageDigest = MessageDigest.getInstance(this.algorithm.toString()) + String hexHasString = new HexBinaryAdapter().marshal(messageDigest.digest(this.fileInputBean.bytes)) + log.info "Calculated Checksum is:- ${hexHasString}" + + return hexHasString + } +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/HashCalculator.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/HashCalculator.groovy new file mode 100644 index 0000000..b8bd783 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/HashCalculator.groovy @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum + +/** + * Trait Specifying Hash Calculating Behaviour + * @author Milan Savaliya + * @since 3.1.0 + */ +trait HashCalculator { + + /** + * Instance of an Algorithm which would be used to calculate the hashF + * Default is set to the "MD5" + */ + private Algorithm algorithm = Algorithm.MD5 + + /** + * Setter for algorithm + */ + void setAlgorithm(Algorithm algorithm) { + this.algorithm = algorithm + } + + /** + * This method returns an instance of a algorithm which will be used to calculate the checksum + * @return + */ + Algorithm getAlgorithm() { + return this.algorithm + } + + /** + * This method returns an String Representation of the Calculated Hash + * @return + */ + abstract String calculateHash() +} diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/FileInputBean.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/FileInputBean.groovy new file mode 100644 index 0000000..71e3433 --- /dev/null +++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/FileInputBean.groovy @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are not permitted. + */ +package com.causecode.fileuploader.util.checksum.beans + +/** + * File InputBean to work with Hash Calculation + * @author Milan Savaliya + * @since 3.1.0 + */ +interface FileInputBean { + /** + * Return the name of the parameter in the multipart form. + * @return the name of the parameter (never {@code null} or empty) + */ + String getName() + + /** + * Return the original filename in the client's filesystem. + *
This may contain path information depending on the browser used,
+ * but it typically will not with any other than Opera.
+ * @return the original filename, or the empty String if no fileInputBean
+ * has been chosen in the multipart form, or {@code null}
+ * if not defined or not available
+ */
+ String getOriginalFilename()
+
+ /**
+ * Return the content type of the fileInputBean.
+ * @return the content type, or {@code null} if not defined
+ * (or no fileInputBean has been chosen in the multipart form)
+ */
+ String getContentType() throws IOException
+
+ /**
+ * Return whether the uploaded fileInputBean is empty, that is, either no fileInputBean has
+ * been chosen in the multipart form or the chosen fileInputBean has no content.
+ */
+ boolean isEmpty()
+
+ /**
+ * Return the size of the fileInputBean in bytes.
+ * @return the size of the fileInputBean, or 0 if empty
+ */
+ long getSize()
+
+ /**
+ * Return the contents of the fileInputBean as an array of bytes.
+ * @return the contents of the fileInputBean as bytes, or an empty byte array if empty
+ * @throws IOException in case of access errors (if the temporary store fails)
+ */
+ byte[] getBytes() throws IOException
+
+ /**
+ * Return an InputStream to read the contents of the fileInputBean from.
+ * The user is responsible for closing the stream.
+ * @return the contents of the fileInputBean as stream, or an empty stream if empty
+ * @throws IOException in case of access errors (if the temporary store fails)
+ */
+ InputStream getInputStream() throws IOException
+
+ /**
+ * Method to check if file exists on disk or not
+ * @return
+ */
+ boolean isExists()
+}
diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/MultipartFileInputBeanImpl.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/MultipartFileInputBeanImpl.groovy
new file mode 100644
index 0000000..4746a53
--- /dev/null
+++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/MultipartFileInputBeanImpl.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are not permitted.
+ */
+package com.causecode.fileuploader.util.checksum.beans
+
+import org.springframework.web.multipart.MultipartFile
+
+/**
+ * Bean to handle Multipart File Uploads to calculate Hash/Checksum
+ * @author Milan Savaliya
+ * @since 3.1.0
+ */
+class MultipartFileInputBeanImpl implements FileInputBean {
+
+ //Instance of MultipartFile Bean
+ private final MultipartFile multipartFile
+
+ /**
+ * Constructor to instantiate MultipartFileInputBeanImpl instance
+ * @param multipartFile
+ */
+ MultipartFileInputBeanImpl(MultipartFile multipartFile) {
+ this.multipartFile = multipartFile
+ validateInputs()
+ }
+
+ /**
+ * Method checks supplied inputs and throws appropriate exception if input is not in format as expected.
+ */
+ private void validateInputs() throws IllegalArgumentException{
+ if (!this.multipartFile) {
+ throw new IllegalArgumentException('Multipart Instance can not be null')
+ }
+ }
+
+ @Override
+ String getName() {
+ return this.multipartFile.name
+ }
+
+ @Override
+ String getOriginalFilename() {
+ return this.multipartFile.originalFilename
+ }
+
+ @Override
+ String getContentType() {
+ return this.multipartFile.contentType
+ }
+
+ @Override
+ boolean isEmpty() {
+ return this.multipartFile.isEmpty()
+ }
+
+ @Override
+ long getSize() {
+ return this.multipartFile.size
+ }
+
+ @Override
+ byte[] getBytes() throws IOException {
+ return this.multipartFile.bytes
+ }
+
+ @Override
+ InputStream getInputStream() throws IOException {
+ return this.multipartFile.inputStream
+ }
+
+ /**
+ * Whenever There is valid MultipartFile object available, file will surely exists on server. So this method returns
+ * @return
+ */
+ @Override
+ boolean isExists() {
+ return true
+ }
+}
diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/SimpleFileInputBeanImpl.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/SimpleFileInputBeanImpl.groovy
new file mode 100644
index 0000000..8dd0189
--- /dev/null
+++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/beans/SimpleFileInputBeanImpl.groovy
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are not permitted.
+ */
+package com.causecode.fileuploader.util.checksum.beans
+
+/**
+ * Bean to handle java.io.File inputs to calculate hash/checksum
+ * @author Milan Savaliya
+ * @since 3.1.0
+ */
+class SimpleFileInputBeanImpl implements FileInputBean {
+
+ private final File file
+
+ SimpleFileInputBeanImpl(File file) {
+ this.file = file
+ validateInputs()
+ }
+
+ /**
+ * Method validates supplied inputs and throws appropriate exception if inputs are not in proper format.
+ * @throws IllegalArgumentException , FileNotFoundException
+ * */
+ private void validateInputs() throws IllegalArgumentException, FileNotFoundException {
+ if (!this.file) {
+ throw new IllegalArgumentException('File instance can not be null')
+ }
+
+ if (!this.file.exists()) {
+ throw new FileNotFoundException("File with name ${this.file.name} not found")
+ }
+ }
+
+ @Override
+ String getName() {
+ return this.file.name
+ }
+
+ @Override
+ String getOriginalFilename() {
+ return this.file.name
+ }
+
+ @Override
+ String getContentType() throws IOException {
+ String fileName = this.file.name
+ if (fileName.lastIndexOf('.') != -1) {
+ return fileName[fileName.lastIndexOf('.') + 1..fileName.length() - 1]
+ }
+
+ return null
+ }
+
+ @Override
+ boolean isEmpty() {
+ return this.file.size() == 0
+ }
+
+ @Override
+ long getSize() {
+ return this.file.size()
+ }
+
+ @Override
+ byte[] getBytes() throws IOException {
+ return this.file.readBytes()
+ }
+
+ /**
+ * User needs to close returned input stream.
+ * @return InputStream
+ * @throws IOException
+ */
+ @Override
+ InputStream getInputStream() throws IOException {
+ return new FileInputStream(this.file)
+ }
+
+ @Override
+ boolean isExists() {
+ return this.file.exists()
+ }
+}
diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/DuplicateFileException.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/DuplicateFileException.groovy
new file mode 100644
index 0000000..58a767e
--- /dev/null
+++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/DuplicateFileException.groovy
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are not permitted.
+ */
+package com.causecode.fileuploader.util.checksum.exceptions
+
+/**
+ * Exception which will be thrown when calculated checksum with given algorithm founds to be in database
+ * @author Milan Savaliya
+ * @since 3.1.0
+ */
+class DuplicateFileException extends RuntimeException {
+ DuplicateFileException(GString gString) {
+ super(gString.toString())
+ }
+}
diff --git a/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/UnRecognizedFileTypeException.groovy b/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/UnRecognizedFileTypeException.groovy
new file mode 100644
index 0000000..b6f7ae3
--- /dev/null
+++ b/src/main/groovy/com/causecode/fileuploader/util/checksum/exceptions/UnRecognizedFileTypeException.groovy
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2011 - Present, CauseCode Technologies Pvt Ltd, India.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are not permitted.
+ */
+package com.causecode.fileuploader.util.checksum.exceptions
+
+/**
+ * Exception will be thrown when FileType for FileInputBean is not valid
+ * @author Milan Savaliya
+ * @since 3.1.0
+ */
+class UnRecognizedFileTypeException extends RuntimeException {
+ UnRecognizedFileTypeException(String message) {
+ super(message)
+ }
+}
diff --git a/src/test/groovy/com/causecode/fileuploader/FileUploaderServiceSpec.groovy b/src/test/groovy/com/causecode/fileuploader/FileUploaderServiceSpec.groovy
index 50baab1..8d78931 100644
--- a/src/test/groovy/com/causecode/fileuploader/FileUploaderServiceSpec.groovy
+++ b/src/test/groovy/com/causecode/fileuploader/FileUploaderServiceSpec.groovy
@@ -7,6 +7,8 @@
*/
package com.causecode.fileuploader
+import com.causecode.fileuploader.util.checksum.Algorithm
+import com.causecode.fileuploader.util.checksum.exceptions.DuplicateFileException
import grails.buildtestdata.mixin.Build
import grails.test.mixin.TestFor
import grails.test.runtime.DirtiesRuntime
@@ -17,8 +19,8 @@ import org.apache.commons.validator.UrlValidator
import org.grails.plugins.codecs.HTMLCodec
import org.springframework.context.MessageSource
import org.springframework.context.i18n.LocaleContextHolder
-import org.springframework.web.multipart.commons.CommonsMultipartFile
import org.springframework.web.multipart.MultipartFile
+import org.springframework.web.multipart.commons.CommonsMultipartFile
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile
import spock.lang.Unroll
import spock.util.mop.ConfineMetaClassChanges
@@ -150,7 +152,7 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup {
file.delete()
where:
- fileGroup | provider
+ fileGroup | provider
'testAmazon' | CDNProvider.AMAZON
'testGoogle' | CDNProvider.GOOGLE
}
@@ -160,8 +162,8 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup {
File file = getFileInstance('/tmp/test.txt')
1 * googleCDNFileUploaderImplMock.uploadFile(_, _, _, _, _) >> {
- String containerName, File fileToUpload, String fileName, boolean makePublic, long maxAge ->
- throw new UploadFailureException(fileName, containerName, new Throwable())
+ String containerName, File fileToUpload, String fileName, boolean makePublic, long maxAge ->
+ throw new UploadFailureException(fileName, containerName, new Throwable())
}
mockGetProviderInstance('google')
@@ -340,9 +342,12 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup {
File fileInstance = getFileInstance('./temp/test.txt')
and: 'Mocked method'
- service.metaClass.saveFile = { String group, def file, String customFileName = '',
- Object userInstance = null, Locale locale = null ->
- return ufIleInstance
+ service.metaClass.saveFile = {
+ String group,
+ def file,
+ String customFileName = '',
+ Object userInstance = null,
+ Locale locale = null -> return ufIleInstance
}
when: 'cloneFile method is called and uFileInstance is missing'
@@ -368,9 +373,12 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup {
File fileInstance = getFileInstance('/tmp/test.txt')
and: 'Mocked method'
- service.metaClass.saveFile = { String group, def file, String customFileName = '',
- Object userInstance = null, Locale locale = null ->
- return ufIleInstance
+ service.metaClass.saveFile = {
+ String group, def file,
+ String customFileName = '',
+ Object userInstance = null,
+ Locale locale = null ->
+ return ufIleInstance
}
when: 'cloneFile method is called for valid parameters and UFile is not LOCAL type'
@@ -672,9 +680,10 @@ class FileUploaderServiceSpec extends BaseFileUploaderServiceSpecSetup {
UFileMoveHistory uFileMoveHistoryInstance = UFileMoveHistory.build(fromCDN: CDNProvider.RACKSPACE,
toCDN: CDNProvider.GOOGLE, status: MoveStatus.FAILURE, ufile: UFile.build())
- service.metaClass.moveFilesToCDN = { List