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

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

Removed mass sample editing (to prevent the edit tags screen opening very slowly). Also added the possibility to add an excel file which matches sequence files to samples (when uploading) (#13). Finally added some 'return false' to onClick events, when dialogs were opened, to prevent the browser from scrolling to the top.

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                        case "xlsx":
369                                return "excel";
370                        case "zip":
371                                return "zip";
372                        case "gz":
373                                return "gzip";
374                        default:
375                                return "";              // meaning 'unknown'
376                }
377        }
378
379        /**********************************************************************************
380         *
381         * Methods for handling zip files
382         *
383         **********************************************************************************/
384       
385        /**
386         * Determines whether a given file is a parsable zip file
387         * @param filename      Filename to assess
388         * @return
389         */
390        public boolean isZipFile( String filename ) {
391                File f = this.get( filename ) ;
392                switch( this.determineFileType( f ) ) {
393                        case "zip":
394                        case "gzip":
395                        return true;
396                }
397
398                return false;
399        }
400
401        /**
402         * Extracts a given zip file and returns a list of the filenames of the extracted files.
403         *
404         * Files will be extracted in the temp directory, as used by the file service
405         *
406         * @param filename      Filename of the zip file to assess
407         * @return                      List of filenames
408         */
409        public ArrayList extractZipFile( String filename, Closure onProgress ) {
410                File f = this.get( filename );
411
412                if( !f ) {
413                        log.error "No files was given for extracting files."
414                        return []
415                }
416
417                switch( this.determineFileType( f ) ) {
418                        case "zip":
419                        return extractZip( f, onProgress );
420                        case "gzip":
421                        return extractGzip( f, onProgress );
422                        default:
423                        log.error "File given for extracting files is not a valid zip file."
424
425                        return [];
426                }
427        }
428
429        /**
430         * Extracts a ZIP file
431         * @param f             Zipfile to extract
432         * @return              A list of extracted filenames; files can be found in the fileService temporary directory
433         */
434        protected ArrayList extractZip( File f, Closure onProgress ) {
435                return extractZip( f, getUploadDir(), onProgress );
436        }
437
438        /**
439         * Extracts a ZIP file
440         * @param f             Zipfile to extract
441         * @param dir   Directory to extract the files to
442         * @return              A list of extracted filenames
443         */
444        protected ArrayList extractZip( File f, File outdir, Closure onProgress ) {
445                // Create a list of filenames to return later
446                def filenames = [];
447
448                // create a buffer to improve copy performance later.
449                byte[] buffer = new byte[2048];
450
451                // open the zip file stream
452                ZipInputStream stream = new ZipInputStream(new FileInputStream( f ) );
453
454                try
455                {
456                        // now iterate through each item in the stream. The get next
457                        // entry call will return a ZipEntry for each file in the
458                        // stream
459                        ZipEntry entry;
460                        while((entry = stream.getNextEntry()) != null)
461                        {
462                                String s = String.format("Entry: %s len %d added %TD",
463                                entry.getName(), entry.getSize(),
464                                new Date(entry.getTime()));
465                                log.debug s;
466
467                                // Determine a unique filename for this entry
468                                def newfilename = this.getUniqueFilename( entry.getName(), outdir )
469
470                                // Once we get the entry from the stream, the stream is
471                                // positioned read to read the raw data, and we keep
472                                // reading until read returns 0 or less.
473                                File outpath = new File( outdir, newfilename );
474                                FileOutputStream output = null;
475                                try
476                                {
477                                        output = new FileOutputStream(outpath);
478                                        int len = 0;
479                                        while ((len = stream.read(buffer)) > 0)
480                                        {
481                                                output.write(buffer, 0, len);
482                                        }
483
484                                        filenames << newfilename
485                                } catch( Exception e ) {
486                                        // Delete the file if it exists
487                                        if( outpath?.exists() ) {
488                                                outpath.delete();
489                                        }
490                                }
491                                finally
492                                {
493                                        // we must always close the output file
494                                        if(output!=null) output.close();
495                                }
496                               
497                                // Update progress
498                                onProgress( 0, entry.getCompressedSize(), 1, outpath.length())
499                        }
500                }
501                finally
502                {
503                        // we must always close the zip file.
504                        stream.close();
505                }
506               
507                return filenames;
508        }
509
510        /**
511         * Extracts a GZip file
512         * @param f             Zipfile to extract
513         * @return              A list of extracted filenames; files can be found in the fileService temporary directory
514         */
515        protected ArrayList extractGZip( File f, Closure onProgress ) {
516                return extractGZip( f, getUploadDir(), onProgress );
517        }
518
519        /**
520         * Extracts a GZip file
521         * @param f             GZipfile to extract
522         * @param dir   Directory to extract the files to
523         * @return              A list of extracted filenames
524         */
525        protected ArrayList extractGZip( File f, File outdir, Closure onProgress ) {
526                def filenames = [];
527
528                // open the input (compressed) file.
529                FileInputStream stream = new FileInputStream( f );
530                FileOutputStream output = null;
531                File outpath;
532               
533                try
534                {
535                        // open the gziped file to decompress.
536                        GZIPInputStream gzipstream = new GZIPInputStream(stream);
537                        byte[] buffer = new byte[2048];
538
539                        // create the output file without the .gz extension.
540                        String outname = f.getName()[0..f.getName().size()-3];
541                        def newfilename = this.getUniqueFilename( outname, outdir )
542                        outpath = new File( outdir, newfilename );
543                        output = new FileOutputStream(outpath);
544
545                        // and copy it to a new file
546                        int len;
547                        while((len = gzipstream.read(buffer))>0)
548                        {
549                                output.write(buffer, 0, len);
550                        }
551
552                        filenames << newfilename;
553
554                        // Update progress
555                        onProgress( 0, f.length(), 1, outpath.length())
556                } catch( Exception e ) {
557                        // Delete the file if it exists
558                        if( outpath?.exists() ) {
559                                outpath.delete();
560                        }
561                }
562                finally
563                {
564                        // both streams must always be closed.
565                        if(output != null) output.close();
566                        stream.close();
567                }
568
569                return filenames;
570        }
571}
Note: See TracBrowser for help on using the repository browser.