kopia lustrzana https://github.com/OpenDroneMap/NodeODM
				
				
				
			Bug fixes, parallel uploads working
							rodzic
							
								
									5be741bef2
								
							
						
					
					
						commit
						a489a95d6e
					
				|  | @ -107,7 +107,6 @@ class TaskManager{ | |||
|     } | ||||
| 
 | ||||
|     removeStaleUploads(done){ | ||||
|         logger.info("Checking for stale uploads..."); | ||||
|         fs.readdir("tmp", (err, entries) => { | ||||
|             if (err) done(err); | ||||
|             else{ | ||||
|  |  | |||
|  | @ -109,6 +109,8 @@ module.exports = { | |||
|             if (config.testDropUploads){ | ||||
|                 if (Math.random() < 0.5) res.sendStatus(500); | ||||
|                 else next(); | ||||
|             }else{ | ||||
|                 next(); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  |  | |||
|  | @ -54,10 +54,24 @@ | |||
|                         </div> | ||||
|                         <div id="imagesInput" class="form-group" data-bind="visible: mode() === 'file'"> | ||||
|                             <div id="images">Images and GCP File (optional):</div> <button id="btnSelectFiles" class="btn btn-default btn-sm" data-bind="attr: {disabled: uploading()}">Add Files...</button> | ||||
|                             <div data-bind="visible: filesCount()">Selected files: <span data-bind="text: filesCount()"></span></div> | ||||
|                             <div data-bind="visible: filesCount() && !uploading()">Selected files: <span data-bind="text: filesCount()"></span></div> | ||||
|                             <div data-bind="visible: uploading()" class="progress" style="margin-top: 12px;"> | ||||
|                                 <div class="progress-bar progress-bar-success" role="progressbar" data-bind="text: uploadedFiles() + ' / ' + filesCount() + ' files', style: {width: uploadedPercentage()}"> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             <div data-bind="visible: uploading()" style="min-height: 230px;"> | ||||
|                                 <div data-bind="foreach: fileUploadStatus.items"> | ||||
|                                     <div class="progress"> | ||||
|                                         <div class="progress-bar progress-bar-info" role="progressbar" data-bind="text: key() + ': ' + parseInt(value()) + '%', style: {width: value() + '%'}"></div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div id="zipFileInput" class="form-group" data-bind="visible: mode() === 'url'"> | ||||
|                             <label for="zipurl">URL to zip file with Images and GCP File (optional):</label> <input id="zipurl" name="zipurl" class="form-control" type="text" data-bind="attr: {disabled: uploading()}" > | ||||
|                             <div data-bind="visible: uploading()"> | ||||
|                                 Uploading... | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div id="errorBlock" data-bind="visible: error().length > 0, click: dismissError">⚠️ <span data-bind="text: error"></span></div> | ||||
|                         <hr/> | ||||
|  | @ -188,6 +202,7 @@ | |||
|     </script> | ||||
|     <script src="js/vendor/bootstrap.min.js"></script> | ||||
|     <script src="js/vendor/knockout-3.4.0.js"></script> | ||||
|     <script src="js/vendor/ko.observableDictionary.js"></script> | ||||
|     <script src="js/dropzone.js" type="text/javascript"></script> | ||||
|     <script src="js/main.js"></script> | ||||
| </body> | ||||
|  |  | |||
|  | @ -17,11 +17,16 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>. | |||
| */ | ||||
| $(function() { | ||||
|     function App(){ | ||||
|         this.filesCount = ko.observable(0); | ||||
|         this.mode = ko.observable("file"); | ||||
|         this.filesCount = ko.observable(0); | ||||
|         this.error = ko.observable(""); | ||||
|         this.uploading = ko.observable(false); | ||||
|         this.uuid = ko.observable(""); | ||||
|         this.uploadedFiles = ko.observable(0); | ||||
|         this.fileUploadStatus = new ko.observableDictionary({}); | ||||
|         this.uploadedPercentage = ko.pureComputed(function(){ | ||||
|             return ((this.uploadedFiles() / this.filesCount()) * 100.0) + "%"; | ||||
|         }, this); | ||||
|     } | ||||
|     App.prototype.toggleMode = function(){ | ||||
|         if (this.mode() === 'file') this.mode('url'); | ||||
|  | @ -30,6 +35,15 @@ $(function() { | |||
|     App.prototype.dismissError = function(){ | ||||
|         this.error(""); | ||||
|     }; | ||||
|     App.prototype.resetUpload = function(){ | ||||
|         this.filesCount(0); | ||||
|         this.error(""); | ||||
|         this.uploading(false); | ||||
|         this.uuid(""); | ||||
|         this.uploadedFiles(0); | ||||
|         this.fileUploadStatus.removeAll(); | ||||
|         dz.removeAllFiles(true); | ||||
|     }; | ||||
|     App.prototype.startTask = function(){ | ||||
|         var self = this; | ||||
|         this.uploading(true); | ||||
|  | @ -50,14 +64,15 @@ $(function() { | |||
|         } | ||||
| 
 | ||||
|         // Start upload
 | ||||
|         var formData = new FormData(); | ||||
|         formData.append("name", $("#taskName").val()); | ||||
|         formData.append("webhook", $("#webhook").val()); | ||||
|         formData.append("skipPostProcessing", !$("#doPostProcessing").prop('checked')); | ||||
|         formData.append("options", JSON.stringify(optionsModel.getUserOptions())); | ||||
| 
 | ||||
|         if (this.mode() === 'file'){ | ||||
|             if (this.filesCount() > 0){ | ||||
|                 var formData = new FormData(); | ||||
|                 formData.append("name", $("#taskName").val()); | ||||
|                 formData.append("webhook", $("#webhook").val()); | ||||
|                 formData.append("skipPostProcessing", !$("#doPostProcessing").prop('checked')); | ||||
|                 formData.append("options", JSON.stringify(optionsModel.getUserOptions())); | ||||
|                 $.ajax("/task/new/init", { | ||||
|                 $.ajax("/task/new/init?token=" + token, { | ||||
|                     type: "POST", | ||||
|                     data: formData, | ||||
|                     processData: false, | ||||
|  | @ -65,7 +80,6 @@ $(function() { | |||
|                 }).done(function(result){ | ||||
|                     if (result.uuid){ | ||||
|                         self.uuid(result.uuid); | ||||
|                         console.log("Proessing"); | ||||
|                         dz.processQueue(); | ||||
|                     }else{ | ||||
|                         die(result.error || result); | ||||
|  | @ -77,38 +91,33 @@ $(function() { | |||
|                 die("No files selected"); | ||||
|             } | ||||
|         } else if (this.mode() === 'url'){ | ||||
|             // TODO
 | ||||
|             // Handle uploads
 | ||||
|             // $("#images").fileinput({
 | ||||
|             //     uploadUrl: '/task/new?token=' + token,
 | ||||
|             //     showPreview: false,
 | ||||
|             //     allowedFileExtensions: ['jpg', 'jpeg', 'txt', 'zip'],
 | ||||
|             //     elErrorContainer: '#errorBlock',
 | ||||
|             //     showUpload: false,
 | ||||
|             //     uploadAsync: false,
 | ||||
|             //     // ajaxSettings: { headers: { 'set-uuid': '8366b2ad-a608-4cd1-bdcb-c3d84a034623' } },
 | ||||
|             //     uploadExtraData: function() {
 | ||||
|             //         return {
 | ||||
|             //             name: $("#taskName").val(),
 | ||||
|             //             zipurl: $("#zipurl").val(),
 | ||||
|             //             webhook: $("#webhook").val(),
 | ||||
|             //             skipPostProcessing: !$("#doPostProcessing").prop('checked'),
 | ||||
|             //             options: JSON.stringify(optionsModel.getUserOptions())
 | ||||
|             //         };
 | ||||
|             //     }
 | ||||
|             // });
 | ||||
|             this.uploading(true); | ||||
|             formData.append("zipurl", $("#zipurl").val()); | ||||
| 
 | ||||
|             $.ajax("/task/new?token=" + token, { | ||||
|                 type: "POST", | ||||
|                 data: formData, | ||||
|                 processData: false, | ||||
|                 contentType: false | ||||
|             }).done(function(json){ | ||||
|                 if (json.uuid){ | ||||
|                     taskList.add(new Task(json.uuid)); | ||||
|                     self.resetUpload(); | ||||
|                 }else{ | ||||
|                     die(json.error || result); | ||||
|                 } | ||||
|             }).fail(function(){ | ||||
|                 die("Cannot start task. Is the server available and are you connected to the internet?"); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     app = new App(); | ||||
|     ko.applyBindings(app, document.getElementById('app')); | ||||
| 
 | ||||
|     Dropzone.autoDiscover = false; | ||||
| 
 | ||||
|     var dz = new Dropzone("div#images", { | ||||
|         paramName: function(){ return "images"; }, | ||||
|         url : "/task/new/upload/", | ||||
|         parallelUploads: 8, | ||||
|         parallelUploads: 8, // http://blog.olamisan.com/max-parallel-http-connections-in-a-browser max parallel connections
 | ||||
|         uploadMultiple: false, | ||||
|         acceptedFiles: "image/*,text/*", | ||||
|         autoProcessQueue: false, | ||||
|  | @ -120,58 +129,38 @@ $(function() { | |||
|     }); | ||||
| 
 | ||||
|     dz.on("processing", function(file){ | ||||
|         this.options.url = '/task/new/upload/' + app.uuid(); | ||||
|         this.options.url = '/task/new/upload/' + app.uuid() + "?token=" + token; | ||||
|         app.fileUploadStatus.set(file.name, 0); | ||||
|     }) | ||||
|     .on("error", function(file){ | ||||
|         // Retry
 | ||||
|         console.log("Error uploading ", file, " put back in queue..."); | ||||
|         app.error("Upload of " + file.name + " failed, retrying..."); | ||||
|         file.status = Dropzone.QUEUED; | ||||
|         app.fileUploadStatus.remove(file.name); | ||||
|         dz.processQueue(); | ||||
|     }) | ||||
|     .on("totaluploadprogress", function(progress, totalBytes, totalBytesSent){ | ||||
|         // Limit updates since this gets called a lot
 | ||||
|         var now = (new Date()).getTime(); | ||||
| 
 | ||||
|         // Progress 100 is sent multiple times at the end
 | ||||
|         // this makes it so that we update the state only once.
 | ||||
|         if (progress === 100) now = now + 9999999999; | ||||
| 
 | ||||
|     //   if (this.state.upload.lastUpdated + 500 < now){
 | ||||
|         //   this.setUploadState({
 | ||||
|         //     progress, totalBytes, totalBytesSent, lastUpdated: now
 | ||||
|         //   });
 | ||||
|     //   }
 | ||||
|     .on("uploadprogress", function(file, progress){ | ||||
|         app.fileUploadStatus.set(file.name, progress); | ||||
|     }) | ||||
|     .on("addedfiles", function(files){ | ||||
|         app.filesCount(app.filesCount() + files.length); | ||||
|     }) | ||||
|     .on("complete", function(files){ | ||||
|         // Check
 | ||||
|         var failedCount = 0; | ||||
|         for (var i = 0; i < files.length; i++){ | ||||
|             if (files[i].status !== "success"){ | ||||
|                 failedCount++; | ||||
|                 dz.enqueueFile(files[i]); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (failedCount === 0){ | ||||
|              | ||||
|         }else{ | ||||
|             console.log(failedCount, "files failed to upload, retrying..."); | ||||
|             dz.processQueue(); | ||||
|     .on("complete", function(file){ | ||||
|         if (file.status === "success"){ | ||||
|             app.uploadedFiles(app.uploadedFiles() + 1); | ||||
|         } | ||||
|         app.fileUploadStatus.remove(file.name); | ||||
|         dz.processQueue(); | ||||
|     }) | ||||
|     .on("queuecomplete", function(files){ | ||||
|         // Commit
 | ||||
|         $.ajax("/task/new/commit/" + app.uuid(), { | ||||
|         $.ajax("/task/new/commit/" + app.uuid() + "?token=" + token, { | ||||
|             type: "POST", | ||||
|         }).done(function(json){ | ||||
|             if (json.uuid){ | ||||
|                 taskList.add(new Task(json.uuid)); | ||||
|                 dz.removeAllFiles(true); | ||||
|                 app.filesCount(0); | ||||
|                 app.uuid(""); | ||||
|                 app.resetUpload(); | ||||
|             }else{ | ||||
|                 app.error(json.error || json); | ||||
|             } | ||||
|  | @ -185,6 +174,9 @@ $(function() { | |||
|         app.filesCount(0); | ||||
|     }); | ||||
| 
 | ||||
|     app = new App(); | ||||
|     ko.applyBindings(app, document.getElementById('app')); | ||||
| 
 | ||||
|     function query(key) { | ||||
|         key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars
 | ||||
|         var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)")); | ||||
|  |  | |||
|  | @ -0,0 +1,224 @@ | |||
| // Knockout Observable Dictionary
 | ||||
| // (c) James Foster
 | ||||
| // License: MIT (http://www.opensource.org/licenses/mit-license.php)
 | ||||
| 
 | ||||
| (function () { | ||||
|     function DictionaryItem(key, value, dictionary) { | ||||
|         var observableKey = new ko.observable(key); | ||||
| 
 | ||||
|         this.value = new ko.observable(value); | ||||
|         this.key = new ko.computed({ | ||||
|             read: observableKey, | ||||
|             write: function (newKey) { | ||||
|                 var current = observableKey(); | ||||
| 
 | ||||
|                 if (current == newKey) return; | ||||
| 
 | ||||
|                 // no two items are allowed to share the same key.
 | ||||
|                 dictionary.remove(newKey); | ||||
| 
 | ||||
|                 observableKey(newKey); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     ko.observableDictionary = function (dictionary, keySelector, valueSelector) { | ||||
|         var result = {}; | ||||
| 
 | ||||
|         result.items = new ko.observableArray(); | ||||
| 
 | ||||
|         result._wrappers = {}; | ||||
|         result._keySelector = keySelector || function (value, key) { return key; }; | ||||
|         result._valueSelector = valueSelector || function (value) { return value; }; | ||||
| 
 | ||||
|         if (typeof keySelector == 'string') result._keySelector = function (value) { return value[keySelector]; }; | ||||
|         if (typeof valueSelector == 'string') result._valueSelector = function (value) { return value[valueSelector]; }; | ||||
| 
 | ||||
|         ko.utils.extend(result, ko.observableDictionary['fn']); | ||||
| 
 | ||||
|         result.pushAll(dictionary); | ||||
| 
 | ||||
|         return result; | ||||
|     }; | ||||
| 
 | ||||
|     ko.observableDictionary['fn'] = { | ||||
|         remove: function (valueOrPredicate) { | ||||
|             var predicate = valueOrPredicate; | ||||
| 
 | ||||
|             if (valueOrPredicate instanceof DictionaryItem) { | ||||
|                 predicate = function (item) { | ||||
|                     return item.key() === valueOrPredicate.key(); | ||||
|                 }; | ||||
|             } | ||||
|             else if (typeof valueOrPredicate != "function") { | ||||
|                 predicate = function (item) { | ||||
|                     return item.key() === valueOrPredicate; | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             ko.observableArray['fn'].remove.call(this.items, predicate); | ||||
|         }, | ||||
| 
 | ||||
|         push: function (key, value) { | ||||
|             var item = null; | ||||
| 
 | ||||
|             if (key instanceof DictionaryItem) { | ||||
|                 // handle the case where only a DictionaryItem is passed in
 | ||||
|                 item = key; | ||||
|                 value = key.value(); | ||||
|                 key = key.key(); | ||||
|             } | ||||
| 
 | ||||
|             if (value === undefined) { | ||||
|                 value = this._valueSelector(key); | ||||
|                 key = this._keySelector(value); | ||||
|             } | ||||
|             else { | ||||
|                 value = this._valueSelector(value); | ||||
|             } | ||||
| 
 | ||||
|             var current = this.get(key, false); | ||||
|             if (current) { | ||||
|                 // update existing value
 | ||||
|                 current(value); | ||||
|                 return current; | ||||
|             } | ||||
| 
 | ||||
|             if (!item) { | ||||
|                 item = new DictionaryItem(key, value, this); | ||||
|             } | ||||
| 
 | ||||
|             ko.observableArray['fn'].push.call(this.items, item); | ||||
| 
 | ||||
|             return value; | ||||
|         }, | ||||
| 
 | ||||
|         pushAll: function (dictionary) { | ||||
|             var self = this; | ||||
|             var items = self.items(); | ||||
| 
 | ||||
|             if (dictionary instanceof Array) { | ||||
|                 $.each(dictionary, function (index, item) { | ||||
|                     var key = self._keySelector(item, index); | ||||
|                     var value = self._valueSelector(item); | ||||
|                     items.push(new DictionaryItem(key, value, self)); | ||||
|                 }); | ||||
|             } | ||||
|             else { | ||||
|                 for (var prop in dictionary) { | ||||
|                     if (dictionary.hasOwnProperty(prop)) { | ||||
|                         var item = dictionary[prop]; | ||||
|                         var key = self._keySelector(item, prop); | ||||
|                         var value = self._valueSelector(item); | ||||
|                         items.push(new DictionaryItem(key, value, self)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             self.items.valueHasMutated(); | ||||
|         }, | ||||
| 
 | ||||
|         sort: function (method) { | ||||
|             if (method === undefined) { | ||||
|                 method = function (a, b) { | ||||
|                     return defaultComparison(a.key(), b.key()); | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             return ko.observableArray['fn'].sort.call(this.items, method); | ||||
|         }, | ||||
| 
 | ||||
|         indexOf: function (key) { | ||||
|             if (key instanceof DictionaryItem) { | ||||
|                 return ko.observableArray['fn'].indexOf.call(this.items, key); | ||||
|             } | ||||
| 
 | ||||
|             var underlyingArray = this.items(); | ||||
|             for (var index = 0; index < underlyingArray.length; index++) { | ||||
|                 if (underlyingArray[index].key() == key) | ||||
|                     return index; | ||||
|             } | ||||
|             return -1; | ||||
|         }, | ||||
| 
 | ||||
|         get: function (key, wrap) { | ||||
|             if (wrap == false) | ||||
|                 return getValue(key, this.items()); | ||||
| 
 | ||||
|             var wrapper = this._wrappers[key]; | ||||
| 
 | ||||
|             if (wrapper == null) { | ||||
|                 wrapper = this._wrappers[key] = new ko.computed({ | ||||
|                     read: function () { | ||||
|                         var value = getValue(key, this.items()); | ||||
|                         return value ? value() : null; | ||||
|                     }, | ||||
|                     write: function (newValue) { | ||||
|                         var value = getValue(key, this.items()); | ||||
| 
 | ||||
|                         if (value) | ||||
|                             value(newValue); | ||||
|                         else | ||||
|                             this.push(key, newValue); | ||||
|                     } | ||||
|                 }, this); | ||||
|             } | ||||
| 
 | ||||
|             return wrapper; | ||||
|         }, | ||||
| 
 | ||||
|         set: function (key, value) { | ||||
|             return this.push(key, value); | ||||
|         }, | ||||
| 
 | ||||
|         keys: function () { | ||||
|             return ko.utils.arrayMap(this.items(), function (item) { return item.key(); }); | ||||
|         }, | ||||
| 
 | ||||
|         values: function () { | ||||
|             return ko.utils.arrayMap(this.items(), function (item) { return item.value(); }); | ||||
|         }, | ||||
|          | ||||
|         removeAll: function () { | ||||
|             this.items.removeAll(); | ||||
|         }, | ||||
| 
 | ||||
|         toJSON: function () { | ||||
|             var result = {}; | ||||
|             var items = ko.utils.unwrapObservable(this.items); | ||||
| 
 | ||||
|             ko.utils.arrayForEach(items, function (item) { | ||||
|                 var key = ko.utils.unwrapObservable(item.key); | ||||
|                 var value = ko.utils.unwrapObservable(item.value); | ||||
| 
 | ||||
|                 result[key] = value; | ||||
|             }); | ||||
| 
 | ||||
|             return result; | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     function getValue(key, items) { | ||||
|         var found = ko.utils.arrayFirst(items, function (item) { | ||||
|             return item.key() == key; | ||||
|         }); | ||||
|         return found ? found.value : null; | ||||
|     } | ||||
| })(); | ||||
| 
 | ||||
| 
 | ||||
| // Utility methods
 | ||||
| // ---------------------------------------------
 | ||||
| function isNumeric(n) { | ||||
|     return !isNaN(parseFloat(n)) && isFinite(n); | ||||
| } | ||||
| 
 | ||||
| function defaultComparison(a, b) { | ||||
|     if (isNumeric(a) && isNumeric(b)) return a - b; | ||||
| 
 | ||||
|     a = a.toString(); | ||||
|     b = b.toString(); | ||||
| 
 | ||||
|     return a == b ? 0 : (a < b ? -1 : 1); | ||||
| } | ||||
| // ---------------------------------------------
 | ||||
		Ładowanie…
	
		Reference in New Issue
	
	 Piero Toffanin
						Piero Toffanin