I built a service that can be plugged into multiple controllers to achieve the effect. While I wont publish the whole service, here is a skelleton that should be enough to get you started.
A Grails Flow Upload Service (replace config values with your own)
/** * A Simple service that can handle multipart uploads and recombine them once complete */ @Transactional class FlowUploadService { //Needed to get config values def grailsApplication /** * Parse the incoming Flow Upload Chunk Requests * @param params HTTP Request Parameter Map * @param request HTTP Request * @return */ def handleFlowPartUpload(params, request) { if (!params.flowChunkNumber || !params.flowTotalChunks || !params.flowIdentifier || !params.flowFilename) return [status: 400, upload: null, err: new Exception("invalidParams")] if (request.get) { /** Flow is asking if chunk exists, it might be retrying an upload **/ println "get ${params} ${request}" def status = checkChunk(params) return [status: status, upload: null, err: null] } else if (request.post) /** Flow is handling us a chunk to upload **/ { println "post ${params} ${request}" def file = request.getFile('file') if(file.empty) { return [status: 400, upload: null, err : new Exception('noFile')] } else { def finalFile = null try { saveChunk(params, file) if (params.flowChunkNumber == params.flowTotalChunks) { finalFile = combineChunks(params, file) /** Handle Final Chunk **/ } } catch(Exception e) { e.printStackTrace() return [status: 200, upload: finalFile, err: e] } return [status: 200, upload: finalFile, err : null] } } } private int checkChunk(params) { def destinationPath = grailsApplication.config.yolobe.upload.temp new File(destinationPath).mkdirs() //TODO remove for performance String output = getHashOfChunk(params) def chunkFile = new File(destinationPath + output) return chunkFile.exists()? 200 : 204 } private void saveChunk(params, file) { def destinationPath = grailsApplication.config.yolobe.upload.temp + getHashOfChunk(params) def finalFile = new File(destinationPath) println "uploading chunk $params.flowChunkNumber to $destinationPath" file.transferTo(finalFile) } private def combineChunks(params, file) { //Determine a final random "Date/(HASH)(UUID)" String uuid = UUID.randomUUID().toString() String date = new Date().format('M-d-yyyy') /* def extension = params.flowFilename.lastIndexOf('.').with { it != -1 ? params.flowFilename[0..<it] : params.flowFilename } //optionally preserve extension of final file - could be exploited*/ def fileName = date + File.separator + getHashOfChunk(params) + uuid def destinationPath = grailsApplication.config.yolobe.upload.path + File.separator + fileName def chunks = Integer.parseInt(params.flowTotalChunks) def finalFile = new File(destinationPath) def fileStream = new FileOutputStream(finalFile) println "combining $params.flowTotalChunks chunks to $destinationPath" 1.upto(chunks) { c -> def input = new FileInputStream(grailsApplication.config.yolobe.upload.temp + getHashOfChunk(params, c)) fileStream << input } fileStream.close() [path: destinationPath, name: fileName] } private String getHashOfChunk(params, chunkNo = null) { MessageDigest md = MessageDigest.getInstance("MD5") byte[] md5sum = md.digest(params.flowIdentifier.getBytes()); String output = String.format("%032X", new BigInteger(1, md5sum)) + (chunkNo? chunkNo : params.flowChunkNumber); output } }
No comments:
Post a Comment