Changeset 58


Ignore:
Timestamp:
May 17, 2011, 1:44:07 PM (8 years ago)
Author:
robert@…
Message:

Implemented importing of classifications

Location:
trunk
Files:
18 added
13 edited
1 copied
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/application.properties

    r52 r58  
    11#Grails Metadata file
    2 #Fri Apr 15 14:24:03 CEST 2011
     2#Wed May 11 12:10:59 CEST 2011
    33app.build.display.info=0
     4app.grails.version=1.3.7
     5app.name=massSequencing
     6app.servlet.version=2.4
    47app.version=0.1
     8plugins.famfamfam=1.0.1
     9plugins.hibernate=1.3.7
     10plugins.jquery=1.4.4.1
     11plugins.jquery-ui=1.8.7
    512plugins.tomcat=1.3.7
    6 plugins.jquery=1.4.4.1
    7 plugins.famfamfam=1.0.1
    8 plugins.jquery-ui=1.8.7
    9 app.servlet.version=2.4
    10 plugins.hibernate=1.3.7
    11 app.name=massSequencing
    12 app.grails.version=1.3.7
  • trunk/grails-app/conf/DataSource.groovy

    r52 r58  
    77}
    88hibernate {
     9        jdbc.batch_size = 50
    910    cache.use_second_level_cache = true
    1011    cache.use_query_cache = true
  • trunk/grails-app/controllers/nl/tno/massSequencing/FastaController.groovy

    r49 r58  
    99        def fastaService
    1010        def sessionFactory
    11        
    12         /**************************************************************************
    13          *
    14          * Methods for handling uploaded sequence and quality files
    15          *
    16          *************************************************************************/
    17 
    18         /**
    19          * Shows a screen that processing is done
    20          */
    21         def showProcessScreen = {
    22                 def entityType = params.entityType
    23 
    24                 // Check whether files are given
    25                 def names = params.sequencefiles
    26 
    27                 if( !names ) {
    28                         flash.message = "No files uploaded for processing"
    29                         if( params.entityType && params.id)
    30                                 redirect( controller: params.entityType, action: 'show', 'id': params.id)
    31                         else
    32                                 redirect( url: "" )
    33                                
    34                         return
    35                 }
    36                
    37                 // If only 1 file is uploaded, it is given as String
    38                 ArrayList filenames = []
    39                 if( names instanceof String )
    40                         filenames << names
    41                 else
    42                         names.each { filenames << it }
    43                        
    44                 // Save filenames in session
    45                 session.processFilenames = names;
    46                
    47                 // Check for total size of the files in order to be able
    48                 // to show a progress bar
    49                 long filesize = 0;
    50                 names.each {
    51                         filesize += fileService.get( it )?.length()
    52                 }
    53                 session.processProgress = [
    54                         numFiles: names.size(),
    55                         numBytes: filesize,
    56                         filesProcessed: 0,
    57                         bytesProcessed: 0
    58                 ]
    59                        
    60                 [entityId: params.id, entityType: params.entityType, filenames: names, url: createLink( action: 'showProcessResult', id: params.id, params: [entityType: entityType] ) ]
    61         }
    62        
    63         /**
    64          * Processes uploaded files and tries to combine them with samples
    65          */
    66         def process = {
    67                 def entity
    68                 def assaySamples
    69                
    70                 switch( params.entityType ) {
    71                         case "run":
    72                                 entity = getRun( params.id );
    73                                 assaySamples = entity.assaySamples.findAll { it.assay.study.canRead( session.user ) };
    74                                 break;
    75                         case "assay":
    76                                 entity = getAssay( params.id );
    77                                 assaySamples = entity.assaySamples;
    78                                 break;
    79                         default:
    80                                 response.setStatus( 404, "No controller found" );
    81                                 render "";
    82                                 return;
    83                 }
    84 
    85                 if (!entity) {
    86                         response.setStatus( 404, flash.error )
    87                         render "";
    88                         return
    89                 }
    90 
    91                 // Check whether files are given
    92                 def names = session.processFilenames
    93 
    94                 if( !names ) {
    95                         response.setStatus( 500, "No files uploaded for processing" )
    96                         render "";
    97                         return
    98                 }
    99 
    100                 // If only 1 file is uploaded, it is given as String
    101                 ArrayList filenames = []
    102                 if( names instanceof String )
    103                         filenames << names
    104                 else
    105                         names.each { filenames << it }
    106 
    107                 /* Parses uploaded files, discards files we can not handle
    108                  *
    109                  * [
    110                  *              success: [
    111                  *                      [filename: 'abc.fasta', type: FASTA, numSequences: 190]
    112                  *                      [filename: 'cde.fasta', type: FASTA, numSequences: 140]
    113                  *                      [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]
    114                  *                      [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
    115                  *              ],
    116                  *              failure: [
    117                  *                      [filename: 'testing.xls', message: 'Type not recognized']
    118                  *              ]
    119                  * ]
    120                  *
    121                  * The second parameter is a callback function to update progress indicators
    122                  */
    123                 def httpSession = session;
    124                 def parsedFiles = fastaService.parseFiles( filenames, { files, bytes, totalFiles, totalBytes ->
    125                         httpSession.processProgress.numFiles += totalFiles;
    126                         httpSession.processProgress.numBytes += totalBytes;
    127                         httpSession.processProgress.filesProcessed = files;
    128                         httpSession.processProgress.bytesProcessed = bytes;
    129                 } );
    130                
    131                 // Check which assaySamples to use (only the ones visible to the user)
    132                 assaySamples = assaySamples.findAll { it.assay.study.canWrite( session.user ) }
    133 
    134                 // Match files with samples in the database
    135                 def matchedFiles = fastaService.matchFiles( parsedFiles.success, assaySamples );
    136 
    137                 // Sort files on filename
    138                 matchedFiles.sort { a,b -> a.fasta?.originalfilename <=> b.fasta?.originalfilename }
    139 
    140                 // Saved file matches in session to use them later on
    141                 session.processedFiles = [ parsed: parsedFiles, matched: matchedFiles ];
    142 
    143                 render ""
    144         }
    145        
    146         def getProgress = {
    147                 if( !session.processProgress ) {
    148                         response.setStatus( 500, "No progress information found" );
    149                         render ""
    150                         return
    151                 }
    152                
    153                 render session.processProgress as JSON
    154         }
    155        
    156         /**
    157          * Show result of processing
    158          */
    159         def showProcessResult = {
    160                 // load study with id specified by param.id
    161                 def entity
    162                
    163                 switch( params.entityType ) {
    164                         case "run":
    165                                 entity = getRun( params.id )
    166                                 break;
    167                         case "assay":
    168                                 entity = getAssay( params.id )
    169                                 break;
    170                         default:
    171                                 response.setStatus( 404, "No entity found" );
    172                                 render "";
    173                                 return;
    174                 }
    175 
    176                 if (!entity) {
    177                         response.setStatus( 404, flash.error )
    178                         render "";
    179                         return
    180                 }
    181                
    182                 if( !session.processedFiles ) {
    183                         flash.error = "Processing of files failed. Maybe the session timed out."
    184                         redirect( controller: 'assay', action: 'show', 'id': params.id)
    185                         return
    186                 }
    187                
    188                 [entityType: params.entityType, entity: entity, id: params.id, parsedFiles: session.processedFiles.parsed, matchedFiles: session.processedFiles.matched, selectedRun: params.selectedRun ]
    189         }
    190 
    191         /**
    192          * Returns from the upload wizard without saving the data. The uploaded files are removed
    193          */
    194         def returnWithoutSaving = {
    195                 // Delete all uploaded files from disk
    196                 session.processedFiles?.parsed?.success?.each {
    197                         fileService.delete( it.filename );
    198                 }
    199 
    200                 // Redirect to the correct controller           
    201                 switch( params.entityType ) {
    202                         case "run":
    203                         case "assay":
    204                                 redirect( controller: params.entityType, action: "show", id: params.id );
    205                                 return;
    206                         default:
    207                                 response.setStatus( 404, "No entity found" );
    208                                 render "";
    209                                 return;
    210                 }
    211                
    212                
    213         }
    214        
    215         /**
    216          * Saves processed files to the database, based on the selections made by the user
    217          */
    218         def saveProcessedFiles = {
    219                 // load entity with id specified by param.id
    220                 def entity
    221                
    222                 switch( params.entityType ) {
    223                         case "run":
    224                                 entity = getRun( params.id );
    225                                 break;
    226                         case "assay":
    227                                 entity = getAssay( params.id );
    228                                 break;
    229                         default:
    230                                 response.setStatus( 404, "No entity found" );
    231                                 render "";
    232                                 return;
    233                 }
    234 
    235                 if (!entity) {
    236                         response.setStatus( 404, flash.error )
    237                         render "";
    238                         return
    239                 }
    240 
    241                 // Check whether files are given
    242                 def files = params.file
    243 
    244                 if( !files ) {
    245                         flash.message = "No files were selected."
    246                         redirect( controller: params.entityType, action: 'show', 'id': params.id)
    247                         return
    248                 }
    249 
    250                 File permanentDir = fileService.absolutePath( ConfigurationHolder.config.massSequencing.fileDir )
    251                 int numSuccesful = 0;
    252                 def errors = [];
    253                
    254                 // Loop through all files Those are the numeric elements in the 'files' array
    255                 def digitRE = ~/^\d+$/;
    256                 files.findAll { it.key.matches( digitRE ) }.each { file ->
    257                         def filevalue = file.value;
    258                        
    259                         // Check if the file is selected
    260                         if( filevalue.include == "on" ) {
    261                                 if( fileService.fileExists( filevalue.fasta ) ) {
    262                                         try {
    263                                                 def permanent = fastaService.savePermanent( filevalue.fasta, filevalue.qual, session.processedFiles );
    264                                                
    265                                                 // Save the data into the database
    266                                                 SequenceData sd = new SequenceData();
    267                                                
    268                                                 sd.sequenceFile = permanent.fasta
    269                                                 sd.qualityFile = permanent.qual
    270                                                 sd.numSequences = permanent.numSequences
    271                                                 sd.averageQuality = permanent.avgQuality
    272                                                        
    273                                                 def sample = AssaySample.get( filevalue.assaySample );
    274                                                 if( sample )
    275                                                         sample.addToSequenceData( sd );
    276                                                
    277                                                 if( !sd.validate() ) {
    278                                                         errors << "an error occurred while saving " + filevalue.fasta + ": validation of SequenceData failed.";
    279                                                 } else {
    280                                                         sd.save(flush:true);
    281                                                 }
    282                                                
    283                                                 numSuccesful++;
    284                                         } catch( Exception e ) {
    285                                                 errors << "an error occurred while saving " + filevalue.fasta + ": " + e.getMessage()
    286                                         }
    287                                 }
    288                         } else {
    289                                 // File doesn't need to be included in the system. Delete it also from disk
    290                                 fileService.delete( filevalue.fasta );
    291                         }
    292                 }
    293 
    294                 // Return all files that have not been moved
    295                 session.processedFiles?.parsed?.success?.each {
    296                         fileService.delete( it.filename );
    297                 }
    298                
    299                 // Return a message to the user
    300                 if( numSuccesful == 0 ) {
    301                         flash.error = "None of the files were imported, because "
    302                        
    303                         if( errors.size() > 0 ) {
    304                                 errors.each {
    305                                         flash.error += "<br />- " + it
    306                                 }
    307                         } else {
    308                                 flash.error = "none of the files were selected for import."
    309                         }
    310                 } else {
    311                         flash.message = numSuccesful + " files have been added to the system. "
    312 
    313                         if( errors.size() > 0 ) {
    314                                 flash.error += errors.size() + " errors occurred during import: "
    315                                 errors.each {
    316                                         flash.error += "<br />- " + it
    317                                 }
    318                         }
    319                 }
    320                
    321                 redirect( controller: params.entityType, action: "show", id: params.id )
    322         }
    32311       
    32412        def deleteData = {
  • trunk/grails-app/controllers/nl/tno/massSequencing/files/ImportController.groovy

    r57 r58  
    1 package nl.tno.massSequencing
     1package nl.tno.massSequencing.files
    22
    33import org.codehaus.groovy.grails.commons.ConfigurationHolder
    44import org.hibernate.SessionFactory
    55import grails.converters.*;
    6 
    7 class FastaController {
     6import nl.tno.massSequencing.*
     7
     8class ImportController {
    89        def fileService
    910        def fastaService
     11        def importService
     12        def classificationService
    1013        def sessionFactory
    1114       
    1215        /**************************************************************************
    1316         *
    14          * Methods for handling uploaded sequence and quality files
     17         * Methods for handling uploaded sequence, quality and classification files
    1518         *
    1619         *************************************************************************/
     
    2326
    2427                // Check whether files are given
    25                 def names = params.sequencefiles
     28                def names = params.list( 'sequencefiles' )
    2629
    2730                if( !names ) {
     
    3437                        return
    3538                }
    36                
    37                 // If only 1 file is uploaded, it is given as String
    38                 ArrayList filenames = []
    39                 if( names instanceof String )
    40                         filenames << names
    41                 else
    42                         names.each { filenames << it }
    4339                       
    4440                // Save filenames in session
     
    5147                        filesize += fileService.get( it )?.length()
    5248                }
     49
    5350                session.processProgress = [
    54                         numFiles: names.size(),
    55                         numBytes: filesize,
    56                         filesProcessed: 0,
    57                         bytesProcessed: 0
     51                        stepNum: 1,
     52                        numSteps: 2,
     53                        stepDescription: 'Parsing files',       // Second step is Store classification
     54                       
     55                        stepProgress: 0,
     56                        stepTotal: filesize
    5857                ]
    5958                       
     
    7170                        case "run":
    7271                                entity = getRun( params.id );
    73                                 assaySamples = entity.assaySamples.findAll { it.assay.study.canRead( session.user ) };
     72                                assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) };
    7473                                break;
    7574                        case "assay":
    7675                                entity = getAssay( params.id );
    77                                 assaySamples = entity.assaySamples;
     76                                assaySamples = entity.assaySamples.findAll { it.assay.study.canWrite( session.user ) };
    7877                                break;
    7978                        default:
     
    115114                 *              ],
    116115                 *              failure: [
    117                  *                      [filename: 'testing.xls', message: 'Type not recognized']
     116                 *                      [filename: 'testing.doc', message: 'Type not recognized']
    118117                 *              ]
    119118                 * ]
     
    122121                 */
    123122                def httpSession = session;
    124                 def parsedFiles = fastaService.parseFiles( filenames, { files, bytes, totalFiles, totalBytes ->
    125                         httpSession.processProgress.numFiles += totalFiles;
    126                         httpSession.processProgress.numBytes += totalBytes;
    127                         httpSession.processProgress.filesProcessed = files;
    128                         httpSession.processProgress.bytesProcessed = bytes;
    129                 } );
    130                
    131                 // Check which assaySamples to use (only the ones visible to the user)
    132                 assaySamples = assaySamples.findAll { it.assay.study.canWrite( session.user ) }
    133 
     123                def onProgress = { progress, total ->
     124                        // Update progress
     125                        httpSession.processProgress.stepTotal = total;
     126                        httpSession.processProgress.stepProgress = progress;
     127                }
     128                def newStep = { total, description ->
     129                        // Start a new step
     130                        httpSession.processProgress.stepTotal = total;
     131                        httpSession.processProgress.stepProgress = 0;
     132                       
     133                        httpSession.processProgress.stepDescription = description;
     134                        httpSession.processProgress.stepNum++;
     135                }
     136
     137                def parsedFiles = importService.parseFiles( filenames, onProgress, [progress: 0, total: httpSession.processProgress.stepTotal ], newStep );
     138               
     139                println "Parsed files success: " + parsedFiles.success
     140               
     141                // Determine excel matches from the uploaded files
     142                parsedFiles.success = fastaService.inferExcelMatches( parsedFiles.success );
     143               
     144                // Now check whether a taxonomy and groups file are uploaded. If so send them to the classificationService for
     145                // parsing
     146                def classificationFiles = parsedFiles.success.findAll { it.type == 'groups' || it.type == 'taxonomy' };
     147                def excelFiles = parsedFiles.success.findAll { it.type == 'excel' }
     148                def excelMatches = [:]
     149               
     150                // Find a match between input (mothur) samples and the samples in the system
     151                excelFiles.each { excelFile ->
     152                        if( excelFile.matches && excelFile.matches[ 'mothursamples' ] ) {
     153                                excelFile.matches[ 'mothursamples' ].each { excelMatch ->
     154                                        def foundSample = assaySamples.find { it.sample.name == excelMatch.samplename };
     155                                        if( foundSample )
     156                                                excelMatches[ excelMatch.mothurname ] = foundSample
     157                                }
     158                        }
     159                }
     160               
     161                if( classificationFiles ) {
     162                        parsedFiles.success -= classificationFiles;
     163
     164                        // If no excel matches are found, no classifications can be stored
     165                        if( !excelMatches ) {
     166                                classificationFiles.each {
     167                                        it.success = false
     168                                        it.message = "An excel file which maps the input samples with samples in the system must be provided."
     169                                       
     170                                        parsedFiles.failure << it;
     171                                }
     172                        } else {
     173                                // Set progress to a new step, so the user knows that it will take a while
     174                                long totalSize = classificationFiles.collect { it.filesize }.sum();
     175                                newStep( totalSize, "Storing classification" )
     176                                println "Total size: " + totalSize;
     177                               
     178                                classificationFiles = classificationService.storeClassification( classificationFiles, excelMatches, { bytesProcessed -> httpSession.processProgress.stepProgress += bytesProcessed } );
     179                               
     180                                classificationFiles.each {
     181                                        if( it.success ) {
     182                                                parsedFiles.success << it;
     183                                        } else {
     184                                                parsedFiles.failure << it;
     185                                        }
     186                                }
     187                        }
     188                }
     189               
    134190                // Match files with samples in the database
    135191                def matchedFiles = fastaService.matchFiles( parsedFiles.success, assaySamples );
     
    139195
    140196                // Saved file matches in session to use them later on
    141                 session.processedFiles = [ parsed: parsedFiles, matched: matchedFiles ];
     197                session.processedFiles = [ parsed: parsedFiles,  matched: matchedFiles ];
    142198
    143199                render ""
     
    186242                }
    187243               
    188                 [entityType: params.entityType, entity: entity, id: params.id, parsedFiles: session.processedFiles.parsed, matchedFiles: session.processedFiles.matched, selectedRun: params.selectedRun ]
     244                [entityType: params.entityType, entity: entity, id: params.id, parsedFiles: session.processedFiles.parsed, classificationFiles: session.processedFiles.parsed.success.findAll { it.type == 'taxonomy' }, matchedFiles: session.processedFiles.matched, selectedRun: params.selectedRun ]
    189245        }
    190246
     
    252308                def errors = [];
    253309               
    254                 // Loop through all files Those are the numeric elements in the 'files' array
     310                // Loop through all files. Those are the numeric elements in the 'files' array
    255311                def digitRE = ~/^\d+$/;
    256312                files.findAll { it.key.matches( digitRE ) }.each { file ->
  • trunk/grails-app/domain/nl/tno/massSequencing/AssaySample.groovy

    r52 r58  
    7070        }
    7171
     72        /**
     73         * Returns the number of sequences that have been classified for this sample
     74         * @return
     75         */
     76        public int numClassifiedSequences() {
     77                return Sequence.countByAssaySample( this );
     78        }
     79       
    7280        /**
    7381         * Returns the number of sequence files in the system, belonging to this
     
    260268                return  fwOligo || fwMidName || fwTotalSeq || fwMidSeq || fwPrimerSeq ||
    261269                                revOligo || revMidName || revTotalSeq || revMidSeq || revPrimerSeq ||
    262                                 numFiles() > 0;
     270                                numFiles() > 0 || numClassifiedSequences() > 0;
    263271        }
    264272       
     
    306314                        }
    307315                }
    308 
     316               
     317                // Copy all sequence classifications to the new assaysample
     318                Sequence.executeUpdate( "UPDATE Sequence s SET s.assaySample = :new WHERE s.assaySample = :old ", [ 'old': this, 'new': otherAssaySample ] )
     319       
    309320                // Copy run properties
    310321                if( otherAssaySample.run ) {
     
    340351                }
    341352
     353                // Remove all sequence objects referencing this sequenceData object
     354                Sequence.executeUpdate( "DELETE FROM Sequence s WHERE s.assaySample = ?", [this])
     355               
    342356                resetStats();
    343357                save();
     
    346360        }
    347361
     362       
     363        /**
     364         * Remove data from associations before delete the assaySample itself
     365         */
     366        def beforeDelete = {
     367                deleteSequenceData();
     368               
     369                Classification.executeUpdate( "DELETE FROM Classification c WHERE c.assaySample = ?", [this])
     370        }
    348371}
  • trunk/grails-app/domain/nl/tno/massSequencing/Study.groovy

    r53 r58  
    1 import java.io.StringWriter;
    2 
    31package nl.tno.massSequencing
    42
  • trunk/grails-app/services/nl/tno/massSequencing/FastaService.groovy

    r55 r58  
    99import java.util.zip.*
    1010
    11 class FastaService {
     11class FastaService implements nl.tno.massSequencing.imports.Importer {
    1212        def fileService
    1313        def fuzzySearchService
     
    1515        def excelService
    1616
    17         static transactional = true
    18 
    19         /**
    20          * Parses uploaded files and checks them for FASTA and QUAL files
    21          * @param filenames             List of filenames currently existing in the upload directory
    22          * @param onProgress    Closure to execute when progress indicators should be updated.
    23          *                                              Has 4 parameters: numFilesProcessed, numBytesProcessed that indicate the number
    24          *                                              of files and bytes that have been processed in total. totalFiles, totalBytes indicate the change
    25          *                                              in total number of files and bytes (e.g. should take 1 if a new file is added to the list)
    26          * @param directory             Directory to move the files to
    27          * @return                              Structure with information about the parsed files. The 'success' files are
    28          *                                              moved to the given directory
    29          *
    30          * [
    31          *              success: [
    32          *                      [filename: 'abc.fasta', type: FASTA, numSequences: 190]
    33          *                      [filename: 'cde.fasta', type: FASTA, numSequences: 140]
    34          *                      [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]
    35          *                      [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
    36          *              ],
    37          *              failure: [
    38          *                      [filename: 'testing.xls', type: 'unknown', message: 'Type not recognized']
    39          *              ]
    40          * ]
    41          *
    42          */
    43         def parseFiles( ArrayList filenames, Closure onProgress, File directory = null ) {
    44                 if( filenames.size() == 0 ) {
    45                         return [ success: [], failure: [] ];
    46                 }
    47 
    48                 if( !directory ) {
    49                         directory = fileService.absolutePath( ConfigurationHolder.config.massSequencing.fileDir )
    50                 }
    51 
    52                 def success = [];
    53                 def failure = [];
    54 
    55                 long filesProcessed = 0;
    56                 long bytesProcessed = 0;
    57 
    58                 // Loop through all filenames
    59                 for( int i = 0; i < filenames.size(); i++ ) {
    60                         def filename = filenames[ i ];
    61                        
    62                         if( fileService.isZipFile( filename ) ) {
    63                                 // ZIP files are extracted and appended to the filenames list.
    64                                 def newfiles = fileService.extractZipFile( filename, { files, bytes, totalFiles, totalBytes ->
    65                                         filesProcessed += files;
    66                                         bytesProcessed += bytes;
    67 
    68                                         onProgress( filesProcessed, bytesProcessed, totalFiles, totalBytes );
    69                                 } );
    70                                 if( newfiles ) {
    71                                         newfiles.each {
    72                                                 filenames.add( it );
    73                                         }
    74                                        
    75                                         // Delete the zip file itself
    76                                         fileService.delete( filename );
    77                                 } else {
    78                                         failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: "zip", message: 'Zip file could not be extracted' ];
    79                                 }
    80                         } else {
    81                                 def file = fileService.get( filename );
    82                                 String filetype = fileService.determineFileType( file );
    83 
    84                                 if( !fileTypeValid( filetype ) ) {
    85                                         // If files are not valid for parsing, delete them and return a message to the user
    86                                         fileService.delete(filename);
    87                                         failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: filetype, message: 'File type not accepted' ];
    88                                 } else {
    89                                         try {
    90                                                 def contents = parseFile( file, filetype, { files, bytes ->
    91                                                         filesProcessed += files;
    92                                                         bytesProcessed += bytes;
    93 
    94                                                         onProgress( filesProcessed, bytesProcessed, 0, 0 );
    95                                                 } );
    96                                                
    97                                                 contents.filename = file.getName();
    98                                                 contents.originalfilename = fileService.originalFilename( contents.filename )
    99 
    100                                                 if( contents.success ) {
    101                                                         success << contents;
    102                                                 } else {
    103                                                         fileService.delete(filename);
    104                                                         failure << contents;
    105                                                 }
    106                                         } catch( Exception e ) {
    107                                                 // If anything fails during parsing, return an error message
    108                                                 fileService.delete(filename);
    109                                                 failure << [ filename: filename, originalfilename: fileService.originalFilename( filename ), type: filetype, message: e.getMessage() ];
    110                                         }
    111                                 }
    112                         }
    113                 }
    114 
    115                 return [ 'success': success, 'failure': failure ];
    116         }
    117 
    118         /**
    119          * Matches uploaded fasta and qual files and combines them with the samples they probably belong to
    120          * @param parsedFiles   Parsed files
    121          * [
    122          *              [filename: 'abc.fasta', type: FASTA, numSequences: 190]
    123          *              [filename: 'cde.fasta', type: FASTA, numSequences: 140]
    124          *              [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]
    125          *              [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
    126          *              [filename: 'match.xls', type: EXCEL, matches: [ [ filename: 'abc.fasta', basename: 'abc', sample: 's1' ] ]
    127          * ]
    128          * @param samples               AssaySample objects to which the files should be matched.
    129          * @return                              Structure with information about the matching.
    130          * [
    131          *              [
    132          *                      fasta:  [filename: 'abc.fasta', type: FASTA, numSequences: 190],
    133          *                      qual:   [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38],
    134          *                      feasibleQuals: [
    135          *                              [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38],
    136          *                              [filename: 'def.qual', type: QUAL, numSequences: 190, avgQuality: 21]
    137          *                      ]
    138          *                      sample: AssaySample object     
    139          */
    140         def matchFiles( def parsedFiles, def samples ) {
    141                 def fastas = parsedFiles.findAll { it.type == "fasta" }
    142                 def quals = parsedFiles.findAll { it.type == "qual" }
    143                 def excels = parsedFiles.findAll { it.type == "excel" }
    144                
    145                 samples = samples.toList()
    146                
    147                 // Collect matches from all files
    148                 def matches = [];
    149                 excels.each { m ->
    150                         if( m.matches ) {
    151                                 m.matches.each { matches << it }
    152                         }
    153                 }
    154 
    155                 def files = [];
    156 
    157                 fastas.each { fastaFile ->
    158                         // Remove extension
    159                         def matchWith = fastaFile.originalfilename.substring( 0, fastaFile.originalfilename.lastIndexOf( '.' ) )
    160 
    161                         // Determine feasible quals (based on number of sequences )
    162                         def feasibleQuals = quals.findAll { it.numSequences == fastaFile.numSequences }
    163 
    164                         // Best matching qual file
    165                         def qualIdx = fuzzySearchService.mostSimilarWithIndex( matchWith + '.qual', feasibleQuals.originalfilename );
    166 
    167                         def qual = null
    168                         if( qualIdx != null )
    169                                 qual = feasibleQuals[ qualIdx ];
    170 
    171                         // Best matching sample
    172                         def assaySample = null
    173                         def checked = true;
    174                         if( matches ) {
    175                                 // Match with files from excelsheet
    176                                
    177                                 // First find the best matching filename in the list of matches.
    178                                 def sampleNameIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, matches*.basename, 0.8 );
    179                                
    180                                 // If one is found, use the sample name associated with it to do the matching with samples
    181                                 if( sampleNameIdx != null ) {
    182                                         matchWith = matches[ sampleNameIdx ].sample;
    183                                 } else {
    184                                         // If no match is found in the excel sheet, this sample is unchecked by default
    185                                         checked = false;
    186                                 }
    187                         }
    188                        
    189                         // Match on filenames
    190                         def sampleIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, samples.sample.name );
    191                         if( sampleIdx != null ) {
    192                                 assaySample = samples[ sampleIdx ];
    193                         }
    194 
    195                         files << [
    196                                                 fasta: fastaFile,
    197                                                 feasibleQuals: feasibleQuals,
    198                                                 qual: qual,
    199                                                 assaySample: assaySample,
    200                                                 checked: checked
    201                                         ]
    202                 }
    203 
    204                 return files;
    205 
    206 
    207         }
     17        static transactional = false
    20818
    20919        /**
     
    21323         * @return
    21424         */
    215         protected boolean fileTypeValid( String filetype ) {
     25        public boolean canParseFileType( String filetype ) {
    21626                switch( filetype ) {
    21727                        case "fasta":
    21828                        case "qual":
    219                         case "excel":
    22029                                return true;
    22130                        default:
     
    22534
    22635        /**
    227          * Parses the given file
     36         * Parses the given FASTA or QUAL file
     37         *
    22838         * @param file                  File to parse
    22939         * @param filetype              Type of the given file
    23040         * @param onProgress    Closure to execute when progress indicators should be updated.
    231          *                                              Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
    232          *                                              of files and bytes that have been processed in this file (so the first parameter should
    233          *                                              only be 1 when the file is finished)
     41         *                                              Has 2 parameters: progress that indicates the number of bytes that have been processed. The second parameter determines
     42         *                                              the number of bytes that has to be processed extra after this action (e.g. when a zip file is extracted, the extracted
     43         *                                              files should be added to the total number of bytes to be processed)
     44         * @return                              Structure with information about the parsed files. The 'success' files are
     45         *                                              moved to the given directory
    23446         *
    235          * @return                              List structure. Examples:
     47         * Examples:
     48         *                      [filename: 'abc.fasta', type: FASTA, numSequences: 190]
     49         *                      [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
    23650         *
    237          *   [ success: true, filename: 'abc.fasta', type: 'fasta', numSequences: 200 ]
    238          *   [ success: true, filename: 'abc.qual', type: 'qual', numSequences: 200, avgQuality: 36 ]
    239          *   [ success: false, filename: 'abc.txt', type: 'txt', message: 'Filetype could not be parsed.' ]
    240          */
    241         protected def parseFile( File file, String filetype, Closure onProgress ) {
     51         *                      [filename: 'test.zip', type: ZIP, extraFiles: [newfile1.xls, newfile2.xls, newfile3.xls]]
     52         *
     53         *                      [filename: 'testing.xls', type: 'unknown', message: 'Type not recognized']
     54         *
     55         */
     56
     57        public Map parseFile( File file, String filetype, Closure onProgress ) {
    24258                switch( filetype ) {
    24359                        case "fasta":
     
    24561                        case "qual":
    24662                                return parseQual( file, onProgress );
    247                         case "excel":
    248                                 return parseExcelMatch( file, onProgress );
    24963                        default:
    250                                 onProgress( 1, file.length() );
    25164                                return [ success: false, type: filetype, message: 'Filetype could not be parsed.' ]
    25265                }
     
    290103                                bytesProcessed += line.size();
    291104                                if( bytesProcessed > 1000000 ) {
    292                                         onProgress( 0, bytesProcessed );
     105                                        onProgress( bytesProcessed, 0 );
    293106                                        bytesProcessed = 0;
    294107                                }
     
    298111
    299112                // Update progress and say we're finished
    300                 onProgress( 1, bytesProcessed );
     113                onProgress( bytesProcessed, 0 );
    301114
    302115                log.trace "Finished parsing FASTA " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
     
    388201                                bytesProcessed += line.size();
    389202                                if( bytesProcessed > 1000000 ) {
    390                                         onProgress( 0, bytesProcessed );
     203                                        onProgress( bytesProcessed, 0 );
    391204                                        bytesProcessed = 0;
    392205                                }
     
    396209
    397210                // Update progress and say we're finished
    398                 onProgress( 1, bytesProcessed );
     211                onProgress( bytesProcessed, 0 );
    399212
    400213                log.trace "Finished parsing QUAL " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
     
    403216        }
    404217
     218       
     219        /**
     220        * Matches uploaded fasta and qual files and combines them with the samples they probably belong to
     221        * @param parsedFiles    Parsed files
     222        * [
     223        *               [filename: 'abc.fasta', type: FASTA, numSequences: 190]
     224        *               [filename: 'cde.fasta', type: FASTA, numSequences: 140]
     225        *               [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38]
     226        *               [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
     227        *               [filename: 'match.xls', type: EXCEL, matches: [ [ filename: 'abc.fasta', basename: 'abc', sample: 's1' ] ]
     228        * ]
     229        * @param samples                AssaySample objects to which the files should be matched.
     230        * @return                               Structure with information about the matching.
     231        * [
     232        *               [
     233        *                       fasta:  [filename: 'abc.fasta', type: FASTA, numSequences: 190],
     234        *                       qual:   [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38],
     235        *                       feasibleQuals: [
     236        *                               [filename: 'abc.qual', type: QUAL, numSequences: 190, avgQuality: 38],
     237        *                               [filename: 'def.qual', type: QUAL, numSequences: 190, avgQuality: 21]
     238        *                       ]
     239        *                       sample: AssaySample object
     240        */
     241   def matchFiles( def parsedFiles, def samples ) {
     242           def fastas = parsedFiles.findAll { it.type == "fasta" }
     243           def quals = parsedFiles.findAll { it.type == "qual" }
     244           def excels = parsedFiles.findAll { it.type == "excel" }
     245           
     246           samples = samples.toList()
     247           
     248           // Collect (filename) matches from all excel files
     249           def matches = [];
     250           excels.each { m ->
     251                   if( m.matches && m.matches.filenames ) {
     252                           // [ filename: filename, samplename: GSCFSample, basename: basename ]
     253                           m.matches.filenames.each { matches << it }
     254                   }
     255           }
     256
     257           def files = [];
     258
     259           fastas.each { fastaFile ->
     260                   // Remove extension
     261                   def matchWith = fastaFile.originalfilename.substring( 0, fastaFile.originalfilename.lastIndexOf( '.' ) )
     262
     263                   // Determine feasible quals (based on number of sequences )
     264                   def feasibleQuals = quals.findAll { it.numSequences == fastaFile.numSequences }
     265
     266                   // Best matching qual file
     267                   def qualIdx = fuzzySearchService.mostSimilarWithIndex( matchWith + '.qual', feasibleQuals.originalfilename );
     268
     269                   def qual = null
     270                   if( qualIdx != null )
     271                           qual = feasibleQuals[ qualIdx ];
     272
     273                   // Best matching sample
     274                   def assaySample = null
     275                   def checked = true;
     276                   if( matches ) {
     277                           // Match with files from excelsheet
     278                           
     279                           // First find the best matching filename in the list of matches.
     280                           def sampleNameIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, matches*.basename, 0.8 );
     281                           
     282                           // If one is found, use the sample name associated with it to do the matching with samples
     283                           if( sampleNameIdx != null ) {
     284                                   matchWith = matches[ sampleNameIdx ].samplename;
     285                           } else {
     286                                   // If no match is found in the excel sheet, this sample is unchecked by default
     287                                   checked = false;
     288                           }
     289                   }
     290                   
     291                   // Match on filenames
     292                   def sampleIdx = fuzzySearchService.mostSimilarWithIndex( matchWith, samples.sample.name );
     293                   if( sampleIdx != null ) {
     294                           assaySample = samples[ sampleIdx ];
     295                   }
     296
     297                   files << [
     298                                           fasta: fastaFile,
     299                                           feasibleQuals: feasibleQuals,
     300                                           qual: qual,
     301                                           assaySample: assaySample,
     302                                           checked: checked
     303                                   ]
     304           }
     305
     306           return files;
     307
     308
     309   }
     310
     311       
    405312        /**
    406313         * Parses a given excel file with a match between filenames and samples
     
    417324         *   [ success: false, filename: 'def.xls', type: 'excel', message: 'File is not a valid XLS file' ]
    418325         */
    419         protected def parseExcelMatch( File file, Closure onProgress ) {
    420                 long startTime = System.nanoTime();
    421                 log.trace "Start parsing XLS " + file.getName()
    422 
    423                 def matches = []
    424 
    425                 // Read excel file
    426                 def wb;
    427                 try {
    428                         wb = excelService.open( file );
    429                 } catch( Exception e ) {
    430                         // If an exception occurs, the file can't be opened. Return the error message
    431                         return [ success: false, type: "excel", filename: file.getName(), message: "Excel file could not be opened or parsed."]
    432                 }
    433 
    434                 // Read all data into an array, and the header in a separate array
    435                 def header = excelService.readRow( wb, 0, 0 );
    436                 def data = excelService.readData( wb, 0, 1 );
    437 
    438                 // Check whether (at least) 2 columns are present
    439                 if( header.size() < 2 ) {
    440                         return [ success: false, type: "excel", filename: file.getName(), message: "Excel file must contain at least 2 columns, one with filenames and one with sample names"]
     326        public def inferExcelMatches( parsedFiles ) {
     327                // Loop through all excel files, and determine which (usable) columns are in the file
     328                for( parsedFile in parsedFiles ) {
     329                        if( parsedFile.type == "excel" ) {
     330                               
     331                                def header = parsedFile.header;
     332                                def data = parsedFile.data
     333                               
     334                                // Check whether (at least) 2 columns are present
     335                                if( header.size() < 2 ) {
     336                                        parsedFile.success = false
     337                                        parsedFile.exceldata = []
     338                                        parsedFile.message = "Excel file must contain at least 2 columns, one with filenames and one with sample names"
     339                                        continue;
     340                                }
     341                                               
     342                                // Check the headers to see whether any columns are found
     343                                def columns = [:]
     344                                def matches = [ "filenames": [], "mothursamples": [] ]
     345                               
     346                                header.eachWithIndex { cell, i ->
     347                                        switch( cell?.toLowerCase() ) {
     348                                                case "gscfsample":
     349                                                case "sample":
     350                                                        columns[ "GSCFSample" ] = i; break;
     351                                                case "file":
     352                                                case "filename":
     353                                                        columns[ "filename" ] = i; break;
     354                                                case "mothursample":
     355                                                        columns[ "MothurSample" ] = i; break;
     356                                        }
     357                                }
     358                                               
     359                                // Loop through the data and find matches
     360                                data.each { row ->
     361                                        def filename = columns[ "filename" ] != null ? row[ columns[ "filename" ] ] : null;
     362                                        def GSCFSample = columns[ "GSCFSample" ] != null ? row[ columns[ "GSCFSample" ] ] : null;
     363                                        def mothurSample = columns[ "MothurSample" ] != null ? row[ columns[ "MothurSample" ] ] : null;
     364                                       
     365                                        // Matches only exist if GSCF sample is entered in this row
     366                                        if( GSCFSample && GSCFSample != "null" ) {
     367                                                if( filename && filename != "null" ) {
     368                                                        // Remove extension from the filename, but only if it is
     369                                                        // .fna, .fasta, .qual, .fqa, .xls or .xlsx. Otherwise useful parts of the filename would be removed.
     370                                                        def basename = ( filename =~ /\.(fna|fasta|qual|fqa|xls|xlsx)$/ ).replaceAll( "" );
     371
     372                                                        matches[ "filenames" ] << [ filename: filename, samplename: GSCFSample, basename: basename ];
     373                                                }
     374                                               
     375                                                if( mothurSample && mothurSample != "null" ) {
     376                                                        matches[ "mothursamples" ] << [ samplename: GSCFSample, mothurname: mothurSample ];
     377                                                }
     378                                        }
     379                                }
     380                               
     381                                parsedFile[ 'columns' ] = columns;
     382                                parsedFile[ 'matches' ] = matches;
     383                        }
    441384                }
    442385               
    443                 // Check the headers to see whether the default columns are switched
    444                 def filenameColumn = 0;
    445                 def sampleColumn = 1;
    446                
    447                 header.eachWithIndex { cell, i ->
    448                         switch( cell?.toLowerCase() ) {
    449                                 case "sample":
    450                                         sampleColumn = i; break;
    451                                 case "file":
    452                                 case "filename":
    453                                         filenameColumn = i; break;
    454                         }
    455                 }
    456                
    457                 // If both filenames and samples are found in the same column (happens if only one of the headers is given)
    458                 // an error is given
    459                 if( filenameColumn == sampleColumn )
    460                         return [ success: false, type: "excel", filename: file.getName(), message: "Excel file must contain 'Sample' and 'Filename' headers."]
    461                
    462                 // Loop through the data and create a match
    463                 def maxColumn = Math.max( filenameColumn, sampleColumn );
    464                 data.each { row ->
    465                         if( row.size() >= maxColumn ) {
    466                                 def filename = row[ filenameColumn ];
    467                                 def sample = row[ sampleColumn ];
    468                                
    469                                 if( sample && sample != "null"  && filename && filename != "null" ) {
    470                                         // Remove extension from the filename, but only if it is
    471                                         // .fna, .fasta, .qual, .fqa, .xls or .xlsx. Otherwise useful parts of the filename would be removed.
    472                                         def basename = ( filename =~ /\.(fna|fasta|qual|fqa|xls|xlsx)$/ ).replaceAll( "" );
    473                                        
    474                                         matches << [ filename: filename, sample: sample, basename: basename ];
    475                                 }
    476                         }
    477                 }
    478 
    479                 // Update progress and say we're finished
    480                 onProgress( 1, file.size() );
    481 
    482                 log.trace "Finished parsing XLS " + file.getName() + ": " + ( System.nanoTime() - startTime ) / 1000000L
    483 
    484                 return [ success: true, type: "excel", filename: file.getName(), matches: matches ];
     386                return parsedFiles
    485387        }
    486388
  • trunk/grails-app/services/nl/tno/massSequencing/SampleExcelService.groovy

    r53 r58  
    9393
    9494           // Put the headers on the first row
    95            excelService.writeHeader( wb, [ "Filename", "Sample" ], sheetIndex );
     95           excelService.writeHeader( wb, [ "Filename", "MothurSample", "GSCFSample" ], sheetIndex );
    9696
    9797           // Adding the next lines
    9898           ArrayList data = [];
    9999           sortedSamples.each { assaySample ->
    100                    def rowData = [ "", assaySample.sample.name ];
     100                   def rowData = [ "", "", assaySample.sample.name ];
    101101                   
    102102                   data << rowData;
  • trunk/grails-app/services/nl/tno/massSequencing/files/ExcelService.groovy

    r52 r58  
    11package nl.tno.massSequencing.files
    22
     3import groovy.lang.Closure;
     4
     5import java.io.File;
    36import java.text.DecimalFormat
    47import java.text.Format
    58import java.text.NumberFormat
     9import java.util.Map;
     10
    611import org.apache.poi.xssf.usermodel.XSSFWorkbook;
    712import org.apache.poi.hssf.usermodel.HSSFWorkbook;
     
    1419 *
    1520 */
    16 class ExcelService {
    17     static transactional = true
     21class ExcelService implements nl.tno.massSequencing.imports.Importer {
     22    static transactional = false
    1823       
    1924        /**
     
    254259
    255260        }
     261       
     262       
     263        /**
     264         * Determines whether a file can be processed.
     265         * @param filetype      Filetype of the file
     266         * @see determineFileType()
     267         * @return
     268         */
     269        public boolean canParseFileType( String filetype ) {
     270                switch( filetype ) {
     271                        case "excel":
     272                                return true;
     273                        default:
     274                                return false;
     275                }
     276        }
     277
     278        /**
     279         * Parses the given excel file
     280         * @param file                  File to parse
     281         * @param filetype              Type of the given file (only excel is accepted)
     282         * @param onProgress    Closure to execute when progress indicators should be updated.
     283         *                                              Has 2 parameters: progress that indicates the number of bytes that have been processed. The second parameter determines
     284         *                                              the number of bytes that has to be processed extra after this action (e.g. when a zip file is extracted, the extracted
     285         *                                              files should be added to the total number of bytes to be processed)
     286         * @return                              Structure with information about the parsed files. The 'success' files are
     287         *                                              moved to the given directory
     288         *
     289         * Examples:
     290         *                      [filename: 'abc.fasta', type: FASTA, numSequences: 190]
     291         *                      [filename: 'cde.qual', type: QUAL, numSequences: 140, avgQuality: 29]
     292         *
     293         *                      [filename: 'test.zip', type: ZIP, extraFiles: [newfile1.xls, newfile2.xls, newfile3.xls]]
     294         *
     295         *                      [filename: 'testing.xls', type: 'unknown', message: 'Type not recognized']
     296         *
     297         */
     298        public Map parseFile( File file, String filetype, Closure onProgress ) {
     299                switch( filetype ) {
     300                        case "excel":
     301                                return parseExcel( file, onProgress );
     302                        default:
     303                                return [ success: false, type: filetype, message: 'Filetype could not be parsed.' ]
     304                }
     305        }
     306       
     307        /**
     308        * Parses a given excel file with a match between filenames and samples
     309        * @param file                   File to parse
     310        * @param onProgress             Closure to execute when progress indicators should be updated.
     311        *                                               Has 2 parameters: numFilesProcessed and numBytesProcessed that indicate the number
     312        *                                               of files and bytes that have been processed in this file (so the first parameter should
     313        *                                               only be 1 when the file is finished)
     314        * @return                               List structure. The matches array contains an array of matches between filenames and sample(name)s.
     315        *                                               The extension for all files are removed in the 'basename' parameter, in order to improve matching.
     316        *                                               Examples:
     317        *
     318        *   [ success: true, filename: 'abc.xls', type: 'excel', matches: [ [ filename: 's1.qual', basename: 's1', sample: 'sample a' ], [ filename: 's9.fna', basename: 's9', sample: 'sample b' ] ]
     319        *   [ success: false, filename: 'def.xls', type: 'excel', message: 'File is not a valid XLS file' ]
     320        */
     321   protected def parseExcel( File file, Closure onProgress ) {
     322           long startTime = System.nanoTime();
     323           log.trace "Start parsing XLS " + file.getName()
     324
     325           def matches = []
     326
     327           // Read excel file
     328           def wb;
     329           try {
     330                   wb = open( file );
     331           } catch( Exception e ) {
     332                   // If an exception occurs, the file can't be opened. Return the error message
     333                   return [ success: false, type: "excel", filename: file.getName(), message: "Excel file could not be opened or parsed."]
     334           }
     335
     336           // Read all data into an array, and the header in a separate array
     337           def header = readRow( wb, 0, 0 );
     338           def data = readData( wb, 0, 1 );
     339
     340           // Update progress and return
     341           onProgress( file.size(), 0 );
     342           return [ success: true, type: "excel", filename: file.getName(), header: header, data: data ];
     343   }
     344
     345       
    256346}
  • trunk/grails-app/services/nl/tno/massSequencing/files/FileService.groovy

    r53 r58  
    363363                        case "fasta":
    364364                        case "fna":
    365                                 return "fasta";
     365                                return "fasta";         // FASTA format files (see http://en.wikipedia.org/wiki/FASTA_format)
    366366                        case "qual":
    367                         case "fqa":
    368                                 return "qual";
     367                        case "fqa":     
     368                                return "qual";          // QUAL format files for FASTA (see http://sequence.otago.ac.nz/download/ManualPartC.pdf)
    369369                        case "xls":
    370370                        case "xlsx":
    371                                 return "excel";
    372                         case "zip":
    373                                 return "zip";
     371                                return "excel";         // Excel files for metadata
     372                        case "taxonomy":       
     373                                return "taxonomy";      // Taxonomy files as created by Mothur (www.mothur.org) for classification of sequences
     374                        case "groups": 
     375                                return "groups";        // Mothur file to associate a specific sequence to a sample (www.mothur.org)
     376                        case "zip":     
     377                                return "zip";           // Zip files combining all other files
    374378                        case "gz":
    375                                 return "gzip";
     379                                return "gzip";          // GZip files combining all other files
    376380                        default:
    377381                                return "";              // meaning 'unknown'
     
    395399                        case "zip":
    396400                        case "gzip":
    397                         return true;
     401                                return true;
    398402                }
    399403
     
    419423                switch( this.determineFileType( f ) ) {
    420424                        case "zip":
    421                         return extractZip( f, onProgress );
     425                                return extractZip( f, onProgress );
    422426                        case "gzip":
    423                         return extractGzip( f, onProgress );
     427                                return extractGzip( f, onProgress );
    424428                        default:
    425                         log.error "File given for extracting files is not a valid zip file."
     429                                log.error "File given for extracting files is not a valid zip file."
    426430
    427431                        return [];
     
    498502                               
    499503                                // Update progress
    500                                 onProgress( 0, entry.getCompressedSize(), 1, outpath.length())
     504                                onProgress( entry.getCompressedSize(), outpath.length() )
    501505                        }
    502506                }
     
    555559
    556560                        // Update progress
    557                         onProgress( 0, f.length(), 1, outpath.length())
     561                        onProgress( f.length(), outpath.length())
    558562                } catch( Exception e ) {
    559563                        // Delete the file if it exists
  • trunk/grails-app/views/assay/_addFilesDialog.gsp

    r49 r58  
    22        <h2>Upload sequence files</h2>
    33       
    4         <g:form name="addFiles" controller="fasta" action="showProcessScreen" id="${assay.id}">
     4        <g:form name="addFiles" controller="import" action="showProcessScreen" id="${assay.id}">
    55                <input type="hidden" name="entityType" value="assay" />
    66                <p>
  • trunk/grails-app/views/import/showProcessResult.gsp

    r49 r58  
    2525                </g:else>
    2626        </h1>
     27       
     28        <h2>Sequence files</h2>
    2729        <g:if test="${matchedFiles.size() > 0}">
    2830                <p>
     
    9698        </g:if>
    9799        <g:else>
    98                 <h2>Uploaded files</h2>
    99100                <p>
    100101                        ${parsedFiles.success?.size()} files were successfully uploaded, but no FASTA file could be determined. Please
    101                         upload at least one FASTA file.
     102                        upload at least one FASTA file if sequences should be added.
    102103                </p>
    103104        </g:else>
     105       
     106        <h2>Classification files</h2>
     107        <g:if test="${classificationFiles.size() > 0}">
     108                <p>
     109                        The following files containing sequence classification have been imported.
     110                </p>
     111                <table>
     112                        <thead>
     113                                <tr>
     114                                        <th>File</th>
     115                                        <th># sequences imported</th>
     116                                        <th># sequences discarded</th>
     117                                </tr>
     118                        </thead>
     119                        <g:each in="${classificationFiles}" var="file" status="i">
     120                                <tr>
     121                                        <td>${file.filename}</td>
     122                                        <td>${file.importedSequences}</td>
     123                                        <td>${file.discardedSequences}</td>
     124                                </tr>
     125                        </g:each>
     126                </table>
     127        </g:if>
     128        <g:else>
     129                <p>
     130                        No classification files found in uploaded data.
     131                </p>
     132        </g:else>
     133       
     134       
    104135       
    105136        <g:if test="${parsedFiles.failure?.size()}">
    106137                <h2>Failure</h2>
    107138                <p>
    108                         The following files could not be correctly imported. The system can only interpret FASTA and QUAL files.<br />
     139                        The following files could not be correctly imported. The system can only interpret FASTA, QUAL, GROUPS and TAXONOMY files.<br />
    109140                        If these files were intended to be included into the system, please check the messages below.
    110141                 </p>
     
    120151       
    121152        <p>
    122                 <g:link controller="fasta" action="returnWithoutSaving" params="[entityType: entityType, id: entity.id]">Return to ${entityType}</g:link>       
     153                <g:link controller="import" action="returnWithoutSaving" params="[entityType: entityType, id: entity.id]">Return to ${entityType}</g:link>     
    123154        </p>
    124155</body>
  • trunk/grails-app/views/import/showProcessScreen.gsp

    r34 r58  
    11<html>
    22<head>
    3 <meta name="layout" content="main" />
    4 <title>Processing files | Mass Sequencing | dbNP</title>
     3        <meta name="layout" content="main" />
     4        <title>Processing files | Mass Sequencing | dbNP</title>
    55
    6 <script type="text/javascript">
    7                         $(function() {
    8                                 $( "#wait_dialog" ).dialog({
    9                                         height: 140,
    10                                         width: 400,
    11                                         title: 'Please wait while processing files',
    12                                         modal: true,
    13                                         autoOpen: true,
    14                                         buttons: {},
    15                                         closeOnEscape: false,
    16                                         open: function(event, ui) { $(".ui-dialog-titlebar-close" ).hide(); },
    17                                         resizable: false       
    18                                 });
    19 
    20                                 // Show the progressbar and update the value every second
    21                                 $( "#progressbar" ).progressbar({
    22                                         value: 0
    23                                 });
    24 
    25                                 var progressInterval = setInterval(updateProgress, 250);
    26                                                                
    27                                 // Call processing URL
    28                                 $.ajax({
    29                                   type: 'POST',
    30                                   url: "<g:createLink controller="fasta" action="process" id="${entityId}" />",
    31                                   data: "entityType=${entityType}",
    32                                   success: function(data) {
    33                                          
    34                                           // Stop update progress bar
    35                                           clearInterval( progressInterval );
    36                                            
    37                                           window.location.replace( "${url.encodeAsJavaScript()}" );
    38                                   },
    39                                   error: function(xhr, textStatus, errorThrown) {
    40 
    41                                           // Stop update progress bar (but update it for the last time)
    42                                           updateProgress()
    43                                           clearInterval( progressInterval );
    44 
    45                                           alert( "Error " + xhr.getStatus() + ": " + xhr.getStatusText() );
    46                                           //window.location.replace( "<g:createLink controller="${entityType}" action="show" id="${entityId}" />" );
    47                                 }
    48                                 });
     6        <script type="text/javascript">
     7                $(function() {
     8                        $( "#wait_dialog" ).dialog({
     9                                height: 200,
     10                                width: 400,
     11                                title: 'Please wait while processing files',
     12                                modal: true,
     13                                autoOpen: true,
     14                                buttons: {},
     15                                closeOnEscape: false,
     16                                open: function(event, ui) { $(".ui-dialog-titlebar-close" ).hide(); },
     17                                resizable: false       
    4918                        });
    5019
    51                         function updateProgress() {
    52                               $.ajax({
    53                                     type: "GET",
    54                                     url: "<g:createLink controller="fasta" action="getProgress" />",
    55                                     dataType: "json",
    56                                     success: function(progress) {
    57                                           $("#progressbar").progressbar("value", progress.bytesProcessed / progress.numBytes * 100 );
    58                                                 }
    59                               });
     20                        // Show the progressbar and update the value every second
     21                        $( "#progressbar" ).progressbar({
     22                                value: 0
     23                        });
     24
     25                        var progressInterval = setInterval(updateProgress, 250);
     26                                                       
     27                        // Call processing URL
     28                        $.ajax({
     29                          type: 'POST',
     30                          url: "<g:createLink controller="import" action="process" id="${entityId}" />",
     31                          data: "entityType=${entityType}",
     32                          success: function(data) {
     33                                 
     34                                  // Stop update progress bar
     35                                  clearInterval( progressInterval );
     36                                   
     37                                  window.location.replace( "${url.encodeAsJavaScript()}" );
     38                          },
     39                          error: function(xhr, textStatus, errorThrown) {
     40
     41                                  // Stop update progress bar (but update it for the last time)
     42                                  updateProgress()
     43                                  clearInterval( progressInterval );
     44
     45                                  alert( "Error " + xhr.getStatus() + ": " + xhr.getStatusText() );
     46                                  //window.location.replace( "<g:createLink controller="${entityType}" action="show" id="${entityId}" />" );
    6047                        }
    61                 </script>
    62 <style type="text/css">
    63         #wait_dialog {
    64                 padding: 20px;
    65                 text-align: center;
    66         }
    67        
    68         #progressbar { margin-top: 10px; }
    69 </style>
     48                        });
     49                });
     50
     51                function updateProgress() {
     52                        var stepDiv = $( '#step' );
     53                        $.ajax({
     54                        type: "GET",
     55                                url: "<g:createLink controller="import" action="getProgress" />",
     56                                dataType: "json",
     57                                success: function(progress) {
     58                                        $( '#stepNum', stepDiv ).text( progress.stepNum );
     59                                        $( '#numSteps', stepDiv ).text( progress.numSteps );
     60                                        $( '#stepDescription', stepDiv ).text( progress.stepDescription );
     61
     62                                        stepDiv.show();
     63
     64                                        $("#progressbar").progressbar("value", progress.stepProgress / progress.stepTotal * 100 );
     65                                }
     66                        });
     67                }
     68        </script>
     69        <style type="text/css">
     70                #wait_dialog {
     71                        padding: 20px;
     72                        text-align: center;
     73                }
     74               
     75                #progressbar {
     76                        margin-top: 10px;
     77                }
     78        </style>
    7079</head>
    7180<body>
    72 <div id="wait_dialog">
    73 
    74         <span class="spinner" style="display: inline-block; zoom: 1; *display: inline;"></span>
    75         Please wait while processing your files.
     81        <div id="wait_dialog">
     82                <span class="spinner" style="display: inline-block; zoom: 1; *display: inline;"></span>
     83                <p>Please wait while processing your files.</p>
     84                <p id="step" style="display: none;">
     85                        Step <span id="stepNum"></span> / <span id="numSteps"></span>: <span id="stepDescription"></span>
     86                </p>
    7687       
    77         <div id="progressbar"></div>
     88                <div id="progressbar"></div>
    7889       
    79 </div>
     90        </div>
    8091</body>
    8192</html>
  • trunk/grails-app/views/run/_addFilesDialog.gsp

    r49 r58  
    22        <h2>Upload sequence files</h2>
    33       
    4         <g:form name="addFiles" controller="fasta" action="showProcessScreen" id="${run.id}">
     4        <g:form name="addFiles" controller="import" action="showProcessScreen" id="${run.id}">
    55                <input type="hidden" name="entityType" value="run" />
    66                <p>
    7                         Select sequence (.fna, .fasta) and quality (.fqa, .qual) files to upload. It is possible to zip the files before upload. You can add multiple files, if needed.
     7                        Select sequence (.fna, .fasta), quality (.fqa, .qual) and classification (.groups, .taxonomy) files to upload. It is possible to zip the files before upload. You can add multiple files, if needed.
    88                </p>
    99                <p>
    10                         The filenames should match the sample names of the samples they belong to. It is also possible to provide an
     10                        The filenames should match the sample names of the samples they belong to. You should also provide an
    1111                        excel sheet to describe which file belongs to which sample. The format should be like this <g:link controller="run" action="downloadMatchExcel" id="${run.id}">example</g:link>, with each fasta file appearing once.
    1212                </p>
Note: See TracChangeset for help on using the changeset viewer.