source: trunk/grails-app/services/nl/tno/massSequencing/files/FileService.groovy

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

Implemented addition of logfiles to sequence data

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