/* * This file is part of the MediaWiki extension UploadWizard. * * UploadWizard is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * UploadWizard is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with UploadWizard. If not, see . */ ( function ( mw, uw, $, OO ) { /** * Upload step controller. * * @class * @extends uw.controller.Step * @param {mw.Api} api * @param {Object} config UploadWizard config object. */ uw.controller.Upload = function UWControllerUpload( api, config ) { var step = this; uw.controller.Step.call( this, new uw.ui.Upload( config ) .connect( this, { retry: 'retry' } ), api, config ); this.stepName = 'file'; this.finishState = 'stashed'; this.queue = new uw.ConcurrentQueue( { count: this.config.maxSimultaneousConnections, action: this.transitionOne.bind( this ) } ); this.queue.on( 'complete', this.showNext.bind( this ) ); this.ui.on( 'files-added', function ( files ) { var totalFiles = files.length + step.uploads.length, tooManyFiles = totalFiles > step.config.maxUploads; if ( tooManyFiles ) { step.ui.showTooManyFilesError( totalFiles ); } else { step.addFiles( files ); } } ); }; OO.inheritClass( uw.controller.Upload, uw.controller.Step ); /** * Updates the upload step data when a file is added or removed. */ uw.controller.Upload.prototype.updateFileCounts = function () { var fewerThanMax, haveUploads, max = this.config.maxUploads; haveUploads = this.uploads.length > 0; fewerThanMax = this.uploads.length < max; this.updateProgressBarCount( this.uploads.length ); this.ui.updateFileCounts( haveUploads, fewerThanMax ); }; uw.controller.Upload.prototype.load = function ( uploads ) { var controller = this; uw.controller.Step.prototype.load.call( this, uploads ); this.updateFileCounts(); this.startProgressBar(); // make sure queue is empty before starting this step this.queue.abortExecuting(); if ( uploads.length > 0 ) { /* * If we have uploads already, we'll want to to update the "next" * buttons accordingly. showNext() does that, but relies on upload * state being set correctly. * Since every step overwrites the upload state, we'll need to reset * it to reflect the correct upload success state. * If other files are to be added, the showNext() callback will deal * with new uploads, and still understand the existing files that * we've just reset the state for. */ $.each( uploads, function ( i, upload ) { upload.state = upload.fileKey === undefined ? 'error' : controller.finishState; } ); this.showNext(); } }; uw.controller.Upload.prototype.moveNext = function () { this.removeErrorUploads(); uw.controller.Step.prototype.moveNext.call( this ); }; /** * Starts the upload progress bar. */ uw.controller.Upload.prototype.startProgressBar = function () { this.ui.showProgressBar(); this.progressBar = new mw.GroupProgressBar( this.ui.$progress, this.uploads, [ 'stashed' ], [ 'error' ], 'transportProgress', 'transportWeight' ); this.progressBar.start(); }; /** * Starts progress bar if there's not an existing one. */ uw.controller.Upload.prototype.maybeStartProgressBar = function () { if ( this.progressBarEmptyOrFinished() ) { this.startProgressBar(); } }; /** * Check if there is a vacancy for a new progress bar. * * @return {boolean} */ uw.controller.Upload.prototype.progressBarEmptyOrFinished = function () { return !this.progressBar || this.progressBar.finished === true; }; /** * Update success count on the progress bar. * * @param {number} okCount */ uw.controller.Upload.prototype.updateProgressBarCount = function ( okCount ) { if ( this.progressBar ) { this.progressBar.showCount( okCount ); } }; uw.controller.Upload.prototype.canTransition = function ( upload ) { return ( uw.controller.Step.prototype.canTransition.call( this, upload ) && upload.state === 'new' ); }; /** * Perform this step's changes on one upload. * * @param {mw.UploadWizardUpload} upload * @return {jQuery.Promise} */ uw.controller.Upload.prototype.transitionOne = function ( upload ) { var promise = upload.start(); this.maybeStartProgressBar(); return promise; }; /** * Queue an upload object to be uploaded. * * @param {mw.UploadWizardUpload} upload */ uw.controller.Upload.prototype.queueUpload = function ( upload ) { if ( this.canTransition( upload ) ) { this.queue.addItem( upload ); } }; /** * Kick off the upload processes. */ uw.controller.Upload.prototype.startQueuedUploads = function () { this.queue.startExecuting(); }; uw.controller.Upload.prototype.retry = function () { var controller = this; uw.eventFlowLogger.logEvent( 'retry-uploads-button-clicked' ); $.each( this.uploads, function ( i, upload ) { if ( upload.state === 'error' ) { // reset any uploads in error state back to be shiny & new upload.state = 'new'; upload.ui.clearIndicator(); upload.ui.clearStatus(); // and queue them controller.queueUpload( upload ); } } ); this.startQueuedUploads(); }; /** * Create the upload interface, a handler to transport it to the server, and UI for the upload * itself; and immediately fill it with a file and add it to the list of uploads. * * @param {File} file * @return {UploadWizardUpload|false} The new upload, or false if it can't be added */ uw.controller.Upload.prototype.addFile = function ( file ) { var upload; if ( this.uploads.length >= this.config.maxUploads ) { return false; } upload = new mw.UploadWizardUpload( this, file ); if ( !this.validateFile( upload ) ) { return false; } upload.fileChangedOk(); // attach controller-specific event handlers (they're automatically // bound on load already, but we've only just added these files...) this.bindUploadHandlers( upload ); this.setUploadFilled( upload ); return upload; }; /** * Do everything that needs to be done to start uploading a file. Calls #addFile, then appends * each mw.UploadWizardUploadInterface to the DOM and queues thumbnails to be generated. * * @param {File[]} files */ uw.controller.Upload.prototype.addFiles = function ( files ) { var uploadObj, uploadObjs = [], controller = this; $.each( files, function ( i, file ) { uploadObj = controller.addFile( file ); if ( uploadObj ) { uploadObjs.push( uploadObj ); } } ); this.ui.displayUploads( uploadObjs ); uw.eventFlowLogger.logUploadEvent( 'uploads-added', { quantity: files.length } ); }; /** * Remove an upload from our array of uploads, and the HTML UI * We can remove the HTML UI directly, as jquery will just get the parent. * We need to grep through the array of uploads, since we don't know the current index. * We need to update file counts for obvious reasons. * * @param {mw.UploadWizardUpload} upload */ uw.controller.Upload.prototype.removeUpload = function ( upload ) { uw.controller.Step.prototype.removeUpload.call( this, upload ); this.queue.removeItem( upload ); this.updateFileCounts(); // check all uploads, if they're complete, show the next button this.showNext(); }; /** * When an upload is filled with a real file, accept it in the list of uploads * and set up some other interfaces * * @param {mw.UploadWizardUpload} upload */ uw.controller.Upload.prototype.setUploadFilled = function ( upload ) { this.addUpload( upload ); this.updateFileCounts(); // Start uploads now, no reason to wait--leave the remove button alone this.queueUpload( upload ); this.startQueuedUploads(); }; /** * Checks for file validity. * * @param {mw.UploadWizardUpload} upload * @return {boolean} Error in [code, info] format, or empty [] for no errors */ uw.controller.Upload.prototype.validateFile = function ( upload ) { var extension, i, actualMaxSize = mw.UploadWizard.config.maxMwUploadSize, // Check if filename is acceptable // TODO sanitize filename filename = upload.getFilename(), basename = upload.getBasename(); // check to see if this file has already been selected for upload for ( i = 0; i < this.uploads.length; i++ ) { if ( upload !== this.uploads[ i ] && filename === this.uploads[ i ].getFilename() ) { this.ui.showDuplicateError( filename, basename ); return false; } } // check if the filename is valid upload.setTitle( basename ); if ( !upload.title ) { if ( basename.indexOf( '.' ) === -1 ) { this.ui.showMissingExtensionError( filename ); return false; } else { this.ui.showUnparseableFilenameError( filename ); return false; } } // check if extension is acceptable extension = upload.title.getExtension(); if ( !extension ) { this.ui.showMissingExtensionError( filename ); return false; } if ( mw.UploadWizard.config.fileExtensions !== null && $.inArray( extension.toLowerCase(), mw.UploadWizard.config.fileExtensions ) === -1 ) { this.ui.showBadExtensionError( filename, extension ); return false; } // make sure the file isn't too large // TODO need a way to find the size of the Flickr image if ( upload.file.size ) { upload.transportWeight = upload.file.size; if ( upload.transportWeight > actualMaxSize ) { this.ui.showFileTooLargeError( actualMaxSize, upload.transportWeight ); return false; } } return true; }; }( mediaWiki, mediaWiki.uploadWizard, jQuery, OO ) );