--- univention-management-console/src/univention/management/console/protocol/modserver.py (Revision 31967) +++ univention-management-console/src/univention/management/console/protocol/modserver.py (Arbeitskopie) @@ -100,6 +100,7 @@ def _timed_out( self ): MODULE.info( "Commiting suicide" ) + self.__handler.destroy() self.exit() sys.exit( 0 ) --- univention-management-console/src/univention/management/console/modules/__init__.py (Revision 31967) +++ univention-management-console/src/univention/management/console/modules/__init__.py (Arbeitskopie) @@ -87,6 +87,11 @@ that passes the base configuration to the module process''' pass + def destroy( self ): + '''this function is invoked before before the module process is + exiting.''' + pass + def execute( self, method, request ): self.__requests[ request.id ] = ( request, method ) --- univention-management-console-frontend/umc/widgets/StandbyMixin.js (Revision 31967) +++ univention-management-console-frontend/umc/widgets/StandbyMixin.js (Arbeitskopie) @@ -41,12 +41,8 @@ standbyOpacity: 0.75, - uninitialize: function() { - this.inherited(arguments); + _lastContent: null, - this._standbyWidget.destroy(); - }, - buildRendering: function() { this.inherited(arguments); @@ -62,26 +58,48 @@ this._standbyWidget.startup(); }, + _cleanUp: function() { + if (this._lastContent && this._lastContent.declaredClass && this._lastContent.domNode) { + // we got a widget as last element, remove it from the DOM + try { + this._standbyWidget._textNode.removeChild(this._lastContent.domNode); + } + catch(e) { + console.log('Could remove standby widget from DOM:', e); + } + this._lastContent = null; + } + }, + _updateContent: function(content) { // type check of the content if (dojo.isString(content)) { // string + this._cleanUp(); this._standbyWidget.set('text', content); this._standbyWidget.set('centerIndicator', 'text'); } else if (dojo.isObject(content) && content.declaredClass && content.domNode) { // widget - this._standbyWidget.set('text', ''); - this._standbyWidget.set('centerIndicator', 'text'); + if (!this._lastContent || this._lastContent != content) { + // we only need to add a new widget to the DOM + this._cleanUp(); + this._standbyWidget.set('text', ''); + this._standbyWidget.set('centerIndicator', 'text'); - // hook the given widget to the text node - dojo.place(content.domNode, this._standbyWidget._textNode); - content.startup(); + // hook the given widget to the text node + dojo.place(content.domNode, this._standbyWidget._textNode); + content.startup(); + } } else { // set default image + this._cleanUp(); this._standbyWidget.set('centerIndicator', 'image'); } + + // cache the widget + this._lastContent = content; }, standby: function(/*Boolean*/ doStandby, /*mixed?*/ content) { --- univention-management-console-frontend/umc/widgets/Uploader.js (Revision 31967) +++ univention-management-console-frontend/umc/widgets/Uploader.js (Arbeitskopie) @@ -106,7 +106,7 @@ buildRendering: function() { this.inherited(arguments); - + this._uploader = new dojox.form.Uploader({ url: '/umcp/upload' + (this.command ? '/' + this.command : ''), label: this.buttonLabel, @@ -131,7 +131,7 @@ this.addChild(this._clearButton); } }, - + postCreate: function() { this.inherited(arguments); @@ -144,24 +144,37 @@ }, this); if (!allOk) { umc.dialog.alert(this._('File cannot be uploaded, its maximum size may be %.1f MB.', this.maxSize / 1048576.0)); + this._uploader.reset(); } else { - this._updateLabel(); - this._uploader.upload({ - iframe: (this._uploader.uploadType === 'iframe') ? true : false - }); + dojo.when(this.canUpload(data[0]), dojo.hitch(this, function(doUpload) { + if (!doUpload) { + // upload canceled + this._uploader.reset(); + return; + } + + // perform the upload + this._updateLabel(); + this._uploader.upload({ + iframe: (this._uploader.uploadType === 'iframe') ? true : false + }); + this.onUploadStarted(data[0]); + })); } }); // hook for showing the progress - /*this.connect(this._uploader, 'onProgress', function(data) { - console.log('onProgress:', dojo.toJson(data)); - this._updateLabel(data.percent); - });*/ + this.connect(this._uploader, 'onProgress', 'onProgress'); // notification as soon as the file has been uploaded this.connect(this._uploader, 'onComplete', function(data) { - this.set('data', data.result[0]); + if (data && dojo.isArray(data.result)) { + this.set('data', data.result[0]); + } + else { + this.set('data', null); + } this.onUploaded(this.data); this._resetLabel(); }); @@ -199,6 +212,9 @@ }, _resetLabel: function() { + if (!this._uploader.button) { + return; + } this.set('disabled', false); this.set('buttonLabel', this._origButtonLabel); this._uploader.reset(); @@ -213,11 +229,17 @@ }, _setButtonLabelAttr: function(newVal) { + if (!this._uploader.button) { + return; + } this.buttonLabel = newVal; this._uploader.button.set('label', newVal); }, _setDisabledAttr: function(newVal) { + if (!this._uploader.button) { + return; + } this._uploader.set('disabled', newVal); dojo.style(this._uploader.button.domNode, 'display', 'inline-block'); }, @@ -226,10 +248,28 @@ return this._uploader.get('disabled'); }, + canUpload: function(fileInfo) { + // summary: + // Before uploading a file, this function is called to make sure + // that the given filename is valid. Return boolean or dojo.Deferred. + // fileInfo: Object + // Info object for the requested file, contains properties 'name', + // 'size', 'type'. + return true; + }, + + onUploadStarted: function(fileInfo) { + // event stub + }, + onUploaded: function(data) { // event stub }, + onProgress: function(data) { + // event stub + }, + onChange: function(data) { // event stub }, --- univention-management-console-frontend/umc/widgets/MultiUploader.js (Revision 0) +++ univention-management-console-frontend/umc/widgets/MultiUploader.js (Revision 0) @@ -0,0 +1,325 @@ +/* + * Copyright 2011 Univention GmbH + * + * http://www.univention.de/ + * + * All rights reserved. + * + * The source code of this program is made available + * under the terms of the GNU Affero General Public License version 3 + * (GNU AGPL V3) as published by the Free Software Foundation. + * + * Binary versions of this program provided by Univention to you as + * well as other copyrighted, protected or trademarked materials like + * Logos, graphics, fonts, specific documentations and configurations, + * cryptographic keys etc. are subject to a license agreement between + * you and Univention and not subject to the GNU AGPL V3. + * + * In the case you use this program under the terms of the GNU AGPL V3, + * the program is provided 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public + * License with the Debian GNU/Linux or Univention distribution in file + * /usr/share/common-licenses/AGPL-3; if not, see + * . + */ +/*global dojo dijit dojox umc console */ + +dojo.provide("umc.widgets.MultiUploader"); + +dojo.require("umc.widgets.ContainerWidget"); +dojo.require("umc.i18n"); +dojo.require("umc.widgets.Button"); +dojo.require("umc.widgets.Uploader"); +dojo.require("umc.widgets.MultiSelect"); +dojo.require("umc.widgets.ProgressInfo"); +dojo.require("umc.tools"); +dojo.require("umc.dialog"); + +dojo.declare("umc.widgets.MultiUploader", [ umc.widgets.ContainerWidget, umc.widgets._FormWidgetMixin, umc.i18n.Mixin ], { + 'class': 'umcMultiUploader', + + i18nClass: 'umc.app', + + // command: String + // The UMCP command to which the data shall be uploaded. + // If not given, the data is sent to umcp/upload which will return the + // file content encoded as base64. + command: '', + + // buttonLabel: String + // The label that is displayed on the upload button. + buttonLabel: 'Upload', + + // value: String[] + // 'value' contains an array of the uploaded file names. + value: [], + + // maxSize: Number + // A size limit for the uploaded file. + maxSize: 524288, + + // make sure that no sizeClass is being set + sizeClass: null, + + // this form element should always be valid + valid: true, + + /*===== + // state: String + // Specifies in which state the widget is: + // 'Complete' -> default, + // 'Incomplete' -> uploads are not completed yet + state: 'Complete', + =====*/ + + // internal reference to the MultiSelect widget containing all filenames + _files: null, + + // internal reference to the current Uploader widget + _uploader: null, + + // internal reference to the progress bar + _progressBar: null, + + // button for removing entries + _removeButton: null, + + // container for the upload and remove buttons + _container: null, + + // internal reference to the currently uploading files + _uploadingFiles: [], + + constructor: function() { + this.buttonLabel = this._('Upload'); + this._uploadingFiles = []; + }, + + buildRendering: function() { + this.inherited(arguments); + + // MultiSelect widget for displaying the file list + this._files = new umc.widgets.MultiSelect({ + style: 'width: 50em' + }); + this.addChild(this._files); + + this._createProgressBar(); + + // prepare remove button and container for upload/remove buttons + this._container = new umc.widgets.ContainerWidget({}); + this._container.addChild(new umc.widgets.Button({ + label: this._('Remove'), + iconClass: 'umcIconDelete', + onClick: dojo.hitch(this, '_removeFiles'), + style: 'float: right;' + })); + this.addChild(this._container); + + // add the uploader button + this._addUploader(); + }, + + destroy: function() { + this.inherited(arguments); + + if (this._progressBar) { + // destroy the old progress bar + this._progressBar.destroyRecursive(); + } + }, + + _getStateAttr: function() { + return this._uploadingFiles.length ? 'Incomplete' : 'Complete'; + }, + + _setValueAttr: function(newVal) { + this._files.set('staticValues', newVal); + }, + + _getValueAttr: function() { + return this._files.get('staticValues'); + }, + + _setButtonLabelAttr: function(newVal) { + this.buttonLabel = newVal; + this._uploader.set('buttonLabel', newVal); + }, + + _setDisabledAttr: function(newVal) { + this._files.set('disabled', newVal); + this._uploader.set('disabled', newVal); + }, + + _getDisabledAttr: function() { + return this._files.get('disabled'); + }, + + _removeFiles: function() { + var selectedFiles = this._files.get('value'); + if (!selectedFiles.length) { + return; + } + + // make sure we may remove the selected items + dojo.when(this.canRemove(selectedFiles), dojo.hitch(this, function(doUpload) { + if (!doUpload) { + // removal canceled + return; + } + + // remove items + var files = this.get('value'); + files = dojo.filter(files, function(ifile) { + return dojo.indexOf(selectedFiles, ifile) < 0; + }); + this.set('value', files); + })); + }, + + _createProgressBar: function() { + if (this._progressBar) { + // destroy the old progress bar + this._progressBar.destroyRecursive(); + } + + // create progress bar for displaying the upload information + this._progressBar = new umc.widgets.ProgressInfo({ + maximum: 1 + }); + this._progressBar._progressBar.set('style', 'min-width: 30em;'); + this._progressBar.updateTitle(this._('No upload in progress')); + }, + + _updateProgress: function() { + if (!this._progressBar) { + return; + } + + var currentVal = 0; + var nDone = 0; + dojo.forEach(this._uploadingFiles, function(ifile) { + nDone += ifile.done || 0; + currentVal += ifile.done ? 1.0 : ifile.decimal || 0; + }); + currentVal = Math.min(currentVal / this._uploadingFiles.length, 0.99); + if (!this._uploadingFiles.length || nDone == this._uploadingFiles.length) { + // all uploads are finished + this._progressBar.update(1, '', this._('Uploads finished')); + } + else { + this._progressBar.update(currentVal, '', this._('Uploading... %d of %d files remaining.', this._uploadingFiles.length - nDone, this._uploadingFiles.length)); + } + }, + + _addUploader: function() { + // create a new Uploader widget + this._uploader = new umc.widgets.Uploader({ + showClearButton: false, + buttonLabel: this.buttonLabel, + command: this.command, + maxSize: this.maxSize, + canUpload: this.canUpload, + style: 'float: left;' + }); + this._container.addChild(this._uploader); + + // register events + var uploader = this._uploader; + var startedSignal = this.connect(uploader, 'onUploadStarted', function(file) { + //console.log('### onUploadStarted:', dojo.toJson(file)); + this.disconnect(startedSignal); + + // add current file to the list of uploading items + if (!this._uploadingFiles.length) { + // first file being uploaded -> show the standby animation + //this._createProgressBar(); + this._files.standby(true, this._progressBar); + } + this._uploadingFiles.push(file); + this._updateProgress(); + + var progressSignal = this.connect(uploader, 'onProgress', function(info) { + // update progress information + //console.log('### onProgress:', dojo.toJson(info)); + dojo.mixin(file, info); + this._updateProgress(); + }); + var uploadSignal = this.connect(uploader, 'onUploaded', function() { + // disconnect events + //console.log('### onUploaded'); + this.disconnect(progressSignal); + this.disconnect(uploadSignal); + + // update progress information + file.done = true; + this._updateProgress(); + + // remove Uploader widget from container + this.removeChild(uploader); + uploader.destroyRecursive(); + + // when all files are uploaded, update the internal list of files + var allDone = true; + dojo.forEach(this._uploadingFiles, function(ifile) { + allDone = allDone && ifile.done; + }); + if (allDone) { + // add files to internal list of files + this._files.standby(false); + var vals = this.get('value'); + dojo.forEach(this._uploadingFiles, function(ifile) { + //console.log('### adding:', ifile.name); + vals.unshift(ifile.name); + }); + this.set('value', vals); + + // clear the list of uploading files + this._uploadingFiles = []; + } + }); + + // hide uploader widget and add a new one + dojo.style(uploader.domNode, { + width: '0', + overflow: 'hidden' + }); + this._addUploader(); + }); + }, + + canUpload: function(fileInfo) { + // summary: + // Before uploading a file, this function is called to make sure + // that the given filename is valid. Return boolean or dojo.Deferred. + // fileInfo: Object + // Info object for the requested file, contains properties 'name', + // 'size', 'type'. + return true; + }, + + canRemove: function(filenames) { + // summary: + // Before removing a files from the current list, this function + // is called to make sure that the given file may be removed. + // Return boolean or dojo.Deferred. + // filenames: String[] + // List of filenames. + return true; + }, + + onUploaded: function(data) { + // event stub + }, + + onChange: function(data) { + // event stub + } +}); + + +