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

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

Renamed module to massSequencing

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