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

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

Implemented uploading and parsing of zip files

File size: 14.9 KB
Line 
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
19import java.io.File;
20import java.util.zip.*
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 )
44                directory = getUploadDir();
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 )
102                uploadDir = "fileuploads"
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 )
112                directory = getUploadDir()
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 )
122                directory = getUploadDir()
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 )
132                directory = getUploadDir()
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 )
147                directory = getUploadDir()
148
149                println "Moving " + file + " with originalname " + originalFilename + " to " + directory
150
151                try {
152                        if( file.exists() ) {
153                                def newFilename = getUniqueFilename( originalFilename, directory );
154                                file.renameTo( new File( directory, newFilename ) )
155                                return newFilename
156                        } else {
157                                return "";
158                        }
159                } catch(Exception exception) {
160                        throw exception; // return ""
161                }
162        }
163
164        /**
165         * Moves the given uploaded file to the upload directory
166         *
167         * MultipartFile is the class used for uploaded files
168         *
169         * @return Filename given to the file on our system or "" if the moving fails
170         */
171        def String moveFileToUploadDir( org.springframework.web.multipart.MultipartFile file, String originalFilename, File directory = null ) {
172                if( directory == null )
173                directory = getUploadDir()
174
175                try {
176                        def newFilename = getUniqueFilename( originalFilename, directory );
177                        file.transferTo( new File( directory, newFilename ))
178                        return newFilename
179                } catch(Exception exception) {
180                        throw exception; // return ""
181                }
182        }
183
184        /**
185         * Moves the given file to the upload directory.
186         *
187         * @return Filename given to the file on our system or "" if the moving fails
188         */
189        def String moveFileToUploadDir( File file, File dir = null ) {
190                moveFileToUploadDir( file, file.getName(), dir );
191        }
192
193        /**
194         * Moves the given uploaded file to the upload directory
195         *
196         * MultipartFile is the class used for uploaded files
197         *
198         * @return Filename given to the file on our system or "" if the moving fails
199         */
200        def String moveFileToUploadDir( org.springframework.web.multipart.MultipartFile file, File dir = null ) {
201                moveFileToUploadDir( file, file.getOriginalFilename(), dir );
202        }
203
204        /**
205         * Returns a filename that looks like the originalFilename and does not yet
206         * exist in the upload directory.
207         *
208         * @return String filename that does not yet exist in the upload directory
209         */
210        def String getUniqueFilename( String originalFilename, File directory = null ) {
211                if( directory == null )
212                directory = getUploadDir()
213
214                if( fileExists( originalFilename, directory ) ) {
215                        def basename;
216                        def extension;
217
218                        // Split the filename into basename and extension
219                        if( originalFilename.lastIndexOf('.') >= 0 ) {
220                                basename = originalFilename[ 0 .. originalFilename.lastIndexOf('.') - 1 ];
221                                extension = originalFilename[ originalFilename.lastIndexOf('.')..originalFilename.size() - 1];
222                        } else {
223                                basename = originalFilename;
224                                extension = '';
225                        }
226
227                        // Find a filename that does not yet exist
228                        def postfix = 0;
229                        def newFilename = basename + '.copy' + postfix + extension;
230                        while( fileExists( newFilename, directory ) ) {
231                                postfix++;
232                                newFilename = basename + '.copy' + postfix + extension;
233                        }
234
235                        return newFilename;
236                } else {
237                        return originalFilename;
238                }
239        }
240
241
242        /**
243         * Returns the original filename for a file that has been uploaded, but renamed
244         * using the uniqueFilename method
245         *
246         * @return String Original filename
247         */
248        def String originalFilename( String filename ) {
249                def basename;
250                def extension;
251
252                // Split the filename into basename and extension
253                if( filename.lastIndexOf('.') >= 0 ) {
254                        basename = filename[ 0 .. filename.lastIndexOf('.') - 1 ];
255                        extension = filename[ filename.lastIndexOf('.')..filename.size() - 1];
256                } else {
257                        basename = originalFilename;
258                        extension = '';
259                }
260
261                // Search whether this file has been renamed (containing copy#)
262                if( basename.lastIndexOf('.') >= 0 ) {
263                        def copypart = basename[ basename.lastIndexOf('.')+1..basename.size() - 1];
264                        def rest = basename[ 0 .. basename.lastIndexOf('.') - 1 ];
265                        if( copypart ==~ /copy[0-9]+/ ) {
266                                return rest + extension;
267                        } else {
268                                return filename;
269                        }
270                } else {
271                        return filename;
272                }
273        }
274
275        /**
276         * Returns the absolute path for the given pathname. It the pathname is relative, it is taken relative to the web-app directory
277         * @param pathname
278         * @return
279         */
280        private File absolutePath( String pathname ) {
281                if( pathname == null)
282                return null
283
284                // Check if this is an absolute path
285                File f = new File( pathname );
286
287                if( f.isAbsolute() ) {
288                        return f
289                } else {
290                        // Find the absolute path relative to the web-app directory. This code is found on
291                        // http://stackoverflow.com/questions/491067/how-to-find-the-physical-path-of-a-gsp-file-in-a-deployed-grails-application
292                        return ApplicationHolder.application.parentContext.getResource(pathname).getFile()
293                }
294        }
295
296        /**
297         * Removes all files with modified date older than 'age' from the given directory.  This method doesn't recurse into directories.
298         * @param directory             File object of the directory to clean
299         * @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
300         */
301        public void cleanDirectory( File directory = null, long age = 0 ) {
302                if( !directory ) {
303                        directory = getUploadDir();
304                }
305
306                if( !age ) {
307                        age = ConfigurationHolder.config.metagenomics.fileUploadMaxAge
308                }
309
310                // Compute the minimum timestamp a file should have not to be deleted
311                Date today = new Date();
312                long treshold = today.getTime() - age * 1000;
313
314                // Loop through all files in this directory
315                File[] files = directory.listFiles();
316                for (File file : files) {
317                        // Check for the age of the file
318                        if (file.lastModified() < treshold) {
319                                file.delete();
320                        }
321                }
322
323        }
324
325        /**
326         * Determines the file type of a given file, based on the extension.
327         * @param file  File to assess
328         * @return
329         */
330        public String determineFileType( File file ) {
331                if( !file )
332                return null
333
334                // Determine the extension of the file
335                String name = file.getName();
336                if( name.lastIndexOf( '.' ) == -1 ) {
337                        // Without an extension the file type is unknown
338                        return ""
339                }
340
341                String extension = name.substring( name.lastIndexOf( '.' ) + 1 ).toLowerCase();
342
343                switch( extension ) {
344                        case "fasta":
345                        case "fna":
346                        return "fasta";
347                        case "qual":
348                        case "fqa":
349                        return "qual";
350                        case "xls":
351                        return "excel";
352                        case "zip":
353                        return "zip";
354                        case "gz":
355                        return "gzip";
356                        default:
357                        return "";              // meaning 'unknown'
358                }
359        }
360
361
362
363        /**
364         * Determines whether a given file is a parsable zip file
365         * @param filename      Filename to assess
366         * @return
367         */
368        public boolean isZipFile( String filename ) {
369                File f = this.get( filename ) ;
370                switch( this.determineFileType( f ) ) {
371                        case "zip":
372                        case "gzip":
373                        return true;
374                }
375
376                return false;
377        }
378
379        /**
380         * Extracts a given zip file and returns a list of the filenames of the extracted files.
381         *
382         * Files will be extracted in the temp directory, as used by the file service
383         *
384         * @param filename      Filename of the zip file to assess
385         * @return                      List of filenames
386         */
387        public ArrayList extractZipFile( String filename, Closure onProgress ) {
388                File f = this.get( filename );
389
390                if( !f ) {
391                        log.error "No files was given for extracting files."
392                        return []
393                }
394
395                switch( this.determineFileType( f ) ) {
396                        case "zip":
397                        return extractZip( f, onProgress );
398                        case "gzip":
399                        return extractGzip( f, onProgress );
400                        default:
401                        log.error "File given for extracting files is not a valid zip file."
402
403                        return [];
404                }
405        }
406
407        /**
408         * Extracts a ZIP file
409         * @param f             Zipfile to extract
410         * @return              A list of extracted filenames; files can be found in the fileService temporary directory
411         */
412        protected ArrayList extractZip( File f, Closure onProgress ) {
413                return extractZip( f, getUploadDir(), onProgress );
414        }
415
416        /**
417         * Extracts a ZIP file
418         * @param f             Zipfile to extract
419         * @param dir   Directory to extract the files to
420         * @return              A list of extracted filenames
421         */
422        protected ArrayList extractZip( File f, File outdir, Closure onProgress ) {
423                // Create a list of filenames to return later
424                def filenames = [];
425
426                // create a buffer to improve copy performance later.
427                byte[] buffer = new byte[2048];
428
429                // open the zip file stream
430                ZipInputStream stream = new ZipInputStream(new FileInputStream( f ) );
431
432                try
433                {
434                        // now iterate through each item in the stream. The get next
435                        // entry call will return a ZipEntry for each file in the
436                        // stream
437                        ZipEntry entry;
438                        while((entry = stream.getNextEntry()) != null)
439                        {
440                                String s = String.format("Entry: %s len %d added %TD",
441                                entry.getName(), entry.getSize(),
442                                new Date(entry.getTime()));
443                                log.debug s;
444
445                                // Determine a unique filename for this entry
446                                def newfilename = this.getUniqueFilename( entry.getName(), outdir )
447
448                                // Once we get the entry from the stream, the stream is
449                                // positioned read to read the raw data, and we keep
450                                // reading until read returns 0 or less.
451                                File outpath = new File( outdir, newfilename );
452                                FileOutputStream output = null;
453                                try
454                                {
455                                        output = new FileOutputStream(outpath);
456                                        int len = 0;
457                                        while ((len = stream.read(buffer)) > 0)
458                                        {
459                                                output.write(buffer, 0, len);
460                                        }
461
462                                        filenames << newfilename
463                                } catch( Exception e ) {
464                                        // Delete the file if it exists
465                                        if( outpath?.exists() ) {
466                                                outpath.delete();
467                                        }
468                                }
469                                finally
470                                {
471                                        // we must always close the output file
472                                        if(output!=null) output.close();
473                                }
474                               
475                                // Update progress
476                                onProgress( 0, entry.getCompressedSize(), 1, outpath.length())
477                        }
478                }
479                finally
480                {
481                        // we must always close the zip file.
482                        stream.close();
483                }
484               
485                return filenames;
486        }
487
488        /**
489         * Extracts a GZip file
490         * @param f             Zipfile to extract
491         * @return              A list of extracted filenames; files can be found in the fileService temporary directory
492         */
493        protected ArrayList extractGZip( File f, Closure onProgress ) {
494                return extractGZip( f, getUploadDir(), onProgress );
495        }
496
497        /**
498         * Extracts a GZip file
499         * @param f             GZipfile to extract
500         * @param dir   Directory to extract the files to
501         * @return              A list of extracted filenames
502         */
503        protected ArrayList extractGZip( File f, File outdir, Closure onProgress ) {
504                def filenames = [];
505
506                // open the input (compressed) file.
507                FileInputStream stream = new FileInputStream( f );
508                FileOutputStream output = null;
509                File outpath;
510               
511                try
512                {
513                        // open the gziped file to decompress.
514                        GZIPInputStream gzipstream = new GZIPInputStream(stream);
515                        byte[] buffer = new byte[2048];
516
517                        // create the output file without the .gz extension.
518                        String outname = f.getName()[0..f.getName().size()-3];
519                        def newfilename = this.getUniqueFilename( outname, outdir )
520                        outpath = new File( outdir, newfilename );
521                        output = new FileOutputStream(outpath);
522
523                        // and copy it to a new file
524                        int len;
525                        while((len = gzipstream.read(buffer))>0)
526                        {
527                                output.write(buffer, 0, len);
528                        }
529
530                        filenames << newfilename;
531
532                        // Update progress
533                        onProgress( 0, f.length(), 1, outpath.length())
534                } catch( Exception e ) {
535                        // Delete the file if it exists
536                        if( outpath?.exists() ) {
537                                outpath.delete();
538                        }
539                }
540                finally
541                {
542                        // both streams must always be closed.
543                        if(output != null) output.close();
544                        stream.close();
545                }
546
547                return filenames;
548        }
549}
Note: See TracBrowser for help on using the repository browser.