source: trunk/grails-app/services/nl/tno/metagenomics/files/FileService.groovy @ 13

Last change on this file since 13 was 13, checked in by robert@…, 9 years ago

Improved user interface and implemented basic export functionality

File size: 15.1 KB
RevLine 
[2]1/**
2 * FileService Service
3 *
4 * Contains methods for file uploads
5 *
6 * @author      Robert Horlings
7 * @since       20100521
8 * @package     dbnp.studycapturing
9 *
10 * Revision information:
11 * $Rev$
12 * $Author$
13 * $Date$
14 */
15package nl.tno.metagenomics.files
16
17import org.codehaus.groovy.grails.commons.ApplicationHolder
18import org.codehaus.groovy.grails.commons.ConfigurationHolder
[5]19import java.io.File;
20import java.util.zip.*
[2]21
22/**
23 * Convenience methods for uploading and handling files
24 * @author robert@isdat.nl
25 *
26 */
27class FileService implements Serializable {
28        // ApplicationContext applicationContext
29
30        // Must be false, since the webflow can't use a transactional service. See
31        // http://www.grails.org/WebFlow for more information
32        static transactional = false
33
34        /**
35         * Handles the upload of a file using the ajax upload method
36         * @param value                 The value entered into the new filed. May be a File object of an uploaded file or a string containing a filename
37         * @param currentFile   Filename of the current file in the database.
38         * @param directory             Path to the directory where the file should move to. See absolutePath() method for more information.
39         *                                              Defaults to the uploadDir from configuration
40         * @return      Filename of the uploaded file
41         */
42        def String handleUploadedFile( def value, String currentFile, File directory = null) {
43                if( directory == null )
[5]44                directory = getUploadDir();
[2]45
46                // If NULL is given or "*deleted*", the field value is emptied and the old file is removed
47                // If an empty string is given, the field value is kept as was
48                // If a file is given, it is moved to the right directory. Old files are deleted. If
49                //   the file does not exist, the field is kept
50                // If a string is given, it is supposed to be a file in the upload directory. If
51                //   it is different from the old one, the old one is deleted. If the file does not
52                //   exist, the old one is kept.
53
54                if (value == null || ( value.class == String && value == '*deleted*' ) ) {
55                        // If NULL is given, the field value is emptied and the old file is removed
56                        value = "";
57                        if (currentFile) {
58                                delete(currentFile, directory)
59                        }
60                } else if (value.class == File) {
61                        // a file was given. Attempt to move it to the upload directory, and
62                        // afterwards, store the filename. If the file doesn't exist
63                        // or can't be moved, "" is returned
64                        value = moveFileToUploadDir(value, directory);
65
66                        if (value) {
67                                if (currentFile) {
68                                        delete(currentFile, directory)
69                                }
70                        } else {
71                                value = currentFile;
72                        }
73                } else if (value == "") {
74                        value = currentFile;
75                } else {
76                        if (value != currentFile) {
77                                if (fileExists(value, directory)) {
78                                        // When a FILE field is filled, and a new file is set
79                                        // the existing file should be deleted
80                                        if (currentFile) {
81                                                delete(currentFile, directory)
82                                        }
83                                } else {
84                                        // If the file does not exist, the field is kept
85                                        value = currentFile;
86                                }
87                        }
88                }
89
90                return value as String;
91        }
92
93        /**
94         * Returns the directory for uploading files. Makes it easy to change the
95         * path to the directory, if needed
96         */
97        def File getUploadDir() {
98                // Find the file upload directory name from the configuration
99                String uploadDir = ConfigurationHolder.config.metagenomics.fileUploadDir
100
101                if( !uploadDir )
[13]102                        uploadDir = "fileuploads"
[2]103
104                return absolutePath( uploadDir );
105        }
106
107        /**
108         * Returns as File object to a given file
109         */
110        def File get( String filename, File directory = null ) {
111                if( directory == null )
[5]112                directory = getUploadDir()
[2]113
114                return new File( directory, filename );
115        }
116
117        /**
118         * Check whether the given file exists in the upload directory
119         */
120        def boolean fileExists( String filename, File directory = null ) {
121                if( directory == null )
[5]122                directory = getUploadDir()
[2]123
124                return new File( directory, filename ).exists();
125        }
126
127        /**
128         * Deletes a file in the upload dir, if it exists
129         */
130        def boolean delete( String filename, File directory = null ) {
131                if( directory == null )
[5]132                directory = getUploadDir()
[2]133
134                def f = new File( directory, filename );
135                if( f.exists() ) {
136                        f.delete();
137                }
138        }
139
140        /**
141         * Moves the given file to the upload directory.
142         *
143         * @return Filename given to the file on our system or "" if the moving fails
144         */
145        def String moveFileToUploadDir( File file, String originalFilename, File directory = null ) {
146                if( directory == null )
[5]147                directory = getUploadDir()
[2]148
149                try {
150                        if( file.exists() ) {
151                                def newFilename = getUniqueFilename( originalFilename, directory );
152                                file.renameTo( new File( directory, newFilename ) )
153                                return newFilename
154                        } else {
155                                return "";
156                        }
157                } catch(Exception exception) {
158                        throw exception; // return ""
159                }
160        }
161
162        /**
163         * Moves the given uploaded file to the upload directory
164         *
165         * MultipartFile is the class used for uploaded files
166         *
167         * @return Filename given to the file on our system or "" if the moving fails
168         */
169        def String moveFileToUploadDir( org.springframework.web.multipart.MultipartFile file, String originalFilename, File directory = null ) {
170                if( directory == null )
[5]171                directory = getUploadDir()
[2]172
173                try {
174                        def newFilename = getUniqueFilename( originalFilename, directory );
175                        file.transferTo( new File( directory, newFilename ))
176                        return newFilename
177                } catch(Exception exception) {
178                        throw exception; // return ""
179                }
180        }
181
182        /**
183         * Moves the given file to the upload directory.
184         *
185         * @return Filename given to the file on our system or "" if the moving fails
186         */
187        def String moveFileToUploadDir( File file, File dir = null ) {
188                moveFileToUploadDir( file, file.getName(), dir );
189        }
190
191        /**
192         * Moves the given uploaded file to the upload directory
193         *
194         * MultipartFile is the class used for uploaded files
195         *
196         * @return Filename given to the file on our system or "" if the moving fails
197         */
198        def String moveFileToUploadDir( org.springframework.web.multipart.MultipartFile file, File dir = null ) {
199                moveFileToUploadDir( file, file.getOriginalFilename(), dir );
200        }
201
202        /**
203         * Returns a filename that looks like the originalFilename and does not yet
204         * exist in the upload directory.
205         *
206         * @return String filename that does not yet exist in the upload directory
207         */
208        def String getUniqueFilename( String originalFilename, File directory = null ) {
209                if( directory == null )
[5]210                directory = getUploadDir()
[2]211
212                if( fileExists( originalFilename, directory ) ) {
213                        def basename;
214                        def extension;
215
216                        // Split the filename into basename and extension
217                        if( originalFilename.lastIndexOf('.') >= 0 ) {
218                                basename = originalFilename[ 0 .. originalFilename.lastIndexOf('.') - 1 ];
219                                extension = originalFilename[ originalFilename.lastIndexOf('.')..originalFilename.size() - 1];
220                        } else {
221                                basename = originalFilename;
222                                extension = '';
223                        }
224
225                        // Find a filename that does not yet exist
226                        def postfix = 0;
227                        def newFilename = basename + '.copy' + postfix + extension;
228                        while( fileExists( newFilename, directory ) ) {
229                                postfix++;
230                                newFilename = basename + '.copy' + postfix + extension;
231                        }
232
233                        return newFilename;
234                } else {
235                        return originalFilename;
236                }
237        }
238
239        /**
240         * Returns the original filename for a file that has been uploaded, but renamed
241         * using the uniqueFilename method
242         *
243         * @return String Original filename
244         */
245        def String originalFilename( String filename ) {
246                def basename;
247                def extension;
248
249                // Split the filename into basename and extension
250                if( filename.lastIndexOf('.') >= 0 ) {
251                        basename = filename[ 0 .. filename.lastIndexOf('.') - 1 ];
252                        extension = filename[ filename.lastIndexOf('.')..filename.size() - 1];
253                } else {
254                        basename = originalFilename;
255                        extension = '';
256                }
257
258                // Search whether this file has been renamed (containing copy#)
259                if( basename.lastIndexOf('.') >= 0 ) {
260                        def copypart = basename[ basename.lastIndexOf('.')+1..basename.size() - 1];
261                        def rest = basename[ 0 .. basename.lastIndexOf('.') - 1 ];
262                        if( copypart ==~ /copy[0-9]+/ ) {
263                                return rest + extension;
264                        } else {
265                                return filename;
266                        }
267                } else {
268                        return filename;
269                }
270        }
[5]271
[2]272        /**
[12]273         * Returns the absolute path for the given pathname. If the pathname is relative, it is taken relative to the web-app directory
[2]274         * @param pathname
275         * @return
276         */
277        private File absolutePath( String pathname ) {
278                if( pathname == null)
[5]279                return null
[2]280
281                // Check if this is an absolute path
282                File f = new File( pathname );
283
284                if( f.isAbsolute() ) {
285                        return f
286                } else {
287                        // Find the absolute path relative to the web-app directory. This code is found on
288                        // http://stackoverflow.com/questions/491067/how-to-find-the-physical-path-of-a-gsp-file-in-a-deployed-grails-application
289                        return ApplicationHolder.application.parentContext.getResource(pathname).getFile()
290                }
291        }
292
293        /**
294         * Removes all files with modified date older than 'age' from the given directory.  This method doesn't recurse into directories.
295         * @param directory             File object of the directory to clean
296         * @param age                   Maximum age of the files that should be kept in seconds. If you want to delete all files older than 1 hour, enter 3600
297         */
298        public void cleanDirectory( File directory = null, long age = 0 ) {
299                if( !directory ) {
300                        directory = getUploadDir();
301                }
302
303                if( !age ) {
304                        age = ConfigurationHolder.config.metagenomics.fileUploadMaxAge
305                }
[5]306
[2]307                // Compute the minimum timestamp a file should have not to be deleted
308                Date today = new Date();
309                long treshold = today.getTime() - age * 1000;
310
311                // Loop through all files in this directory
312                File[] files = directory.listFiles();
313                for (File file : files) {
314                        // Check for the age of the file
315                        if (file.lastModified() < treshold) {
316                                file.delete();
317                        }
318                }
319
320        }
321
[5]322        /**
323         * Determines the file type of a given file, based on the extension.
324         * @param file  File to assess
325         * @return
326         */
327        public String determineFileType( File file ) {
328                if( !file )
329                return null
330
331                // Determine the extension of the file
332                String name = file.getName();
333                if( name.lastIndexOf( '.' ) == -1 ) {
334                        // Without an extension the file type is unknown
335                        return ""
336                }
337
338                String extension = name.substring( name.lastIndexOf( '.' ) + 1 ).toLowerCase();
339
340                switch( extension ) {
341                        case "fasta":
342                        case "fna":
343                        return "fasta";
344                        case "qual":
345                        case "fqa":
346                        return "qual";
347                        case "xls":
348                        return "excel";
349                        case "zip":
350                        return "zip";
351                        case "gz":
352                        return "gzip";
353                        default:
354                        return "";              // meaning 'unknown'
355                }
356        }
357
[12]358        /**********************************************************************************
359         *
360         * Methods for handling zip files
361         *
362         **********************************************************************************/
363       
[5]364        /**
365         * Determines whether a given file is a parsable zip file
366         * @param filename      Filename to assess
367         * @return
368         */
369        public boolean isZipFile( String filename ) {
370                File f = this.get( filename ) ;
371                switch( this.determineFileType( f ) ) {
372                        case "zip":
373                        case "gzip":
374                        return true;
375                }
376
377                return false;
378        }
379
380        /**
381         * Extracts a given zip file and returns a list of the filenames of the extracted files.
382         *
383         * Files will be extracted in the temp directory, as used by the file service
384         *
385         * @param filename      Filename of the zip file to assess
386         * @return                      List of filenames
387         */
388        public ArrayList extractZipFile( String filename, Closure onProgress ) {
389                File f = this.get( filename );
390
391                if( !f ) {
392                        log.error "No files was given for extracting files."
393                        return []
394                }
395
396                switch( this.determineFileType( f ) ) {
397                        case "zip":
398                        return extractZip( f, onProgress );
399                        case "gzip":
400                        return extractGzip( f, onProgress );
401                        default:
402                        log.error "File given for extracting files is not a valid zip file."
403
404                        return [];
405                }
406        }
407
408        /**
409         * Extracts a ZIP file
410         * @param f             Zipfile to extract
411         * @return              A list of extracted filenames; files can be found in the fileService temporary directory
412         */
413        protected ArrayList extractZip( File f, Closure onProgress ) {
414                return extractZip( f, getUploadDir(), onProgress );
415        }
416
417        /**
418         * Extracts a ZIP file
419         * @param f             Zipfile to extract
420         * @param dir   Directory to extract the files to
421         * @return              A list of extracted filenames
422         */
423        protected ArrayList extractZip( File f, File outdir, Closure onProgress ) {
424                // Create a list of filenames to return later
425                def filenames = [];
426
427                // create a buffer to improve copy performance later.
428                byte[] buffer = new byte[2048];
429
430                // open the zip file stream
431                ZipInputStream stream = new ZipInputStream(new FileInputStream( f ) );
432
433                try
434                {
435                        // now iterate through each item in the stream. The get next
436                        // entry call will return a ZipEntry for each file in the
437                        // stream
438                        ZipEntry entry;
439                        while((entry = stream.getNextEntry()) != null)
440                        {
441                                String s = String.format("Entry: %s len %d added %TD",
442                                entry.getName(), entry.getSize(),
443                                new Date(entry.getTime()));
444                                log.debug s;
445
446                                // Determine a unique filename for this entry
447                                def newfilename = this.getUniqueFilename( entry.getName(), outdir )
448
449                                // Once we get the entry from the stream, the stream is
450                                // positioned read to read the raw data, and we keep
451                                // reading until read returns 0 or less.
452                                File outpath = new File( outdir, newfilename );
453                                FileOutputStream output = null;
454                                try
455                                {
456                                        output = new FileOutputStream(outpath);
457                                        int len = 0;
458                                        while ((len = stream.read(buffer)) > 0)
459                                        {
460                                                output.write(buffer, 0, len);
461                                        }
462
463                                        filenames << newfilename
464                                } catch( Exception e ) {
465                                        // Delete the file if it exists
466                                        if( outpath?.exists() ) {
467                                                outpath.delete();
468                                        }
469                                }
470                                finally
471                                {
472                                        // we must always close the output file
473                                        if(output!=null) output.close();
474                                }
475                               
476                                // Update progress
477                                onProgress( 0, entry.getCompressedSize(), 1, outpath.length())
478                        }
479                }
480                finally
481                {
482                        // we must always close the zip file.
483                        stream.close();
484                }
485               
486                return filenames;
487        }
488
489        /**
490         * Extracts a GZip file
491         * @param f             Zipfile to extract
492         * @return              A list of extracted filenames; files can be found in the fileService temporary directory
493         */
494        protected ArrayList extractGZip( File f, Closure onProgress ) {
495                return extractGZip( f, getUploadDir(), onProgress );
496        }
497
498        /**
499         * Extracts a GZip file
500         * @param f             GZipfile to extract
501         * @param dir   Directory to extract the files to
502         * @return              A list of extracted filenames
503         */
504        protected ArrayList extractGZip( File f, File outdir, Closure onProgress ) {
505                def filenames = [];
506
507                // open the input (compressed) file.
508                FileInputStream stream = new FileInputStream( f );
509                FileOutputStream output = null;
510                File outpath;
511               
512                try
513                {
514                        // open the gziped file to decompress.
515                        GZIPInputStream gzipstream = new GZIPInputStream(stream);
516                        byte[] buffer = new byte[2048];
517
518                        // create the output file without the .gz extension.
519                        String outname = f.getName()[0..f.getName().size()-3];
520                        def newfilename = this.getUniqueFilename( outname, outdir )
521                        outpath = new File( outdir, newfilename );
522                        output = new FileOutputStream(outpath);
523
524                        // and copy it to a new file
525                        int len;
526                        while((len = gzipstream.read(buffer))>0)
527                        {
528                                output.write(buffer, 0, len);
529                        }
530
531                        filenames << newfilename;
532
533                        // Update progress
534                        onProgress( 0, f.length(), 1, outpath.length())
535                } catch( Exception e ) {
536                        // Delete the file if it exists
537                        if( outpath?.exists() ) {
538                                outpath.delete();
539                        }
540                }
541                finally
542                {
543                        // both streams must always be closed.
544                        if(output != null) output.close();
545                        stream.close();
546                }
547
548                return filenames;
549        }
[2]550}
Note: See TracBrowser for help on using the repository browser.