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

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

Implemented export possibility and bugfixes

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