diff --git a/LesionTracker/defaultSettings.js b/LesionTracker/defaultSettings.js index f891e57cd..c3843d55f 100644 --- a/LesionTracker/defaultSettings.js +++ b/LesionTracker/defaultSettings.js @@ -33,7 +33,7 @@ Meteor.startup(function() { "public": { "verifyEmail": false, "ui": { - "studyListFunctionsEnabled": false + "studyListFunctionsEnabled": true } } //defaultServiceType: 'dimse' diff --git a/OHIFViewer/defaultSettings.js b/OHIFViewer/defaultSettings.js index b86fd1991..845e3a9f4 100644 --- a/OHIFViewer/defaultSettings.js +++ b/OHIFViewer/defaultSettings.js @@ -31,7 +31,7 @@ Meteor.startup(function() { defaultServiceType: 'dicomWeb', public: { ui: { - studyListFunctionsEnabled: false + studyListFunctionsEnabled: true } } //defaultServiceType: 'dimse' diff --git a/Packages/dimseservice/server/Connection.js b/Packages/dimseservice/server/Connection.js index 06bb5fc1a..1133e825b 100755 --- a/Packages/dimseservice/server/Connection.js +++ b/Packages/dimseservice/server/Connection.js @@ -1,9 +1,9 @@ -var net = Npm.require('net'), +var net = Npm.require('net'), Socket = net.Socket; var DEFAULT_MAX_PACKAGE_SIZE = 32768; -Connection = function(options) { +Connection = function (options) { EventEmitter.call(this); this.options = Object.assign({ maxPackageSize: C.DEFAULT_MAX_PACKAGE_SIZE, @@ -22,142 +22,152 @@ Connection = function(options) { util.inherits(Connection, EventEmitter); -var StoreHandle = function() { +var StoreHandle = function () { EventEmitter.call(this); } util.inherits(StoreHandle, EventEmitter); -Connection.prototype.addPeer = function(options) { - if (!options.aeTitle || !options.host || !options.port) { - return false; - } - this.peers[options.aeTitle] = { - host : options.host, port : options.port - }; - if (options.default) { - if (options.server) { - this.defaultServer = options.aeTitle; - } else { - this.defaultPeer = options.aeTitle; +Connection.prototype.addPeer = function (options) { + if (!options.aeTitle || !options.host || !options.port) { + return false; } - } - if (options.server) { - //start listening - var server = net.createServer(); - server.listen(options.port, options.host, function(){ - console.log("listening on %j", this.address()); - }); - server.on('error', function(err){ - console.log("server error %j", err); - }); - var o = this; - server.on('connection', function(nativeSocket) { - //incoming connections - var socket = new CSocket(nativeSocket, o.options); - o.addSocket(options.aeTitle, socket); - - //close server on close socket - socket.on('close', function(){ - server.close(); + this.peers[options.aeTitle] = { + host: options.host, port: options.port + }; + if (options.default) { + if (options.server) { + this.defaultServer = options.aeTitle; + } else { + this.defaultPeer = options.aeTitle; + } + } + if (options.server) { + //start listening + var server = net.createServer(); + server.listen(options.port, options.host, function () { + console.log("listening on %j", this.address()); }); - }); - } + server.on('error', function (err) { + console.log("server error %j", err); + }); + var o = this; + server.on('connection', function (nativeSocket) { + //incoming connections + var socket = new CSocket(nativeSocket, o.options); + o.addSocket(options.aeTitle, socket); + + //close server on close socket + socket.on('close', function () { + server.close(); + }); + }); + } }; -Connection.prototype.selectPeer = function(aeTitle) { +Connection.prototype.selectPeer = function (aeTitle) { if (!aeTitle || !this.peers[aeTitle]) { - throw "No such peer"; + throw "No such peer"; } return this.peers[aeTitle]; -} - -Connection.prototype._sendFile = function(socket, sHandle, file, maxSend, metaLength, list) { - var fileNameText = typeof file.file == 'string' ? file.file : 'buffer'; - console.log("Sending file " + fileNameText); - var useContext = socket.getContextByUID(file.context), self = this; - - PDU.generatePDatas(useContext.id, file.file, maxSend, null, metaLength, function(err, handle) { - if (err) { - console.log('Error while sending file'); - return; - } - var processNext = function() { - var next = list.shift(); - if (next) - self._sendFile(socket, sHandle, next, maxSend, metaLength, list); - else - socket.release(); - }; - - var store = socket.storeInstance(useContext.abstractSyntax, file.uid); - handle.on('pdv', function(pdv) { - socket.sendPData(pdv); - }); - handle.on('error', function(err) { - sHandle.emit('file', err, fileNameText); - processNext(); - }); - store.on('response', function(msg) { - var statusText = msg.getStatus().toString(16); - console.log('STORE reponse with status', statusText); - var error = null; - if (msg.failure()) { - error = new Error(statusText); - } - sHandle.emit('file', error, fileNameText); - processNext(); - }); - }); }; -Connection.prototype.storeInstances = function(fileList) { - var contexts = {}, read = 0, length = fileList.length, toSend = [], self = this, handle = new StoreHandle(); - fileList.forEach(function(bufferOrFile){ - var fileNameText = typeof bufferOrFile == 'string' ? bufferOrFile : 'buffer'; - DicomMessage.readMetaHeader(bufferOrFile, function(err, metaMessage, metaLength) { - read++; +Connection.prototype._sendFile = function (socket, sHandle, file, maxSend, metaLength, list) { + var fileNameText = typeof file.file == 'string' ? file.file : 'buffer'; + console.log("Sending file " + fileNameText); + var useContext = socket.getContextByUID(file.context), self = this; + + PDU.generatePDatas(useContext.id, file.file, maxSend, null, metaLength, function (err, handle) { if (err) { - handle.emit('file', err, fileNameText); - return; + console.log('Error while sending file'); + return; } - console.log('Dicom file ' + (typeof bufferOrFile == 'string' ? bufferOrFile : 'buffer') + ' found'); - var syntax = metaMessage.getValue(0x00020010), - sopClassUID = metaMessage.getValue(0x00020002), - instanceUID = metaMessage.getValue(0x00020003); + var processNext = function () { + var next = list.shift(); + if (next) + self._sendFile(socket, sHandle, next, maxSend, metaLength, list); + else + socket.release(); + }; - if (!contexts[sopClassUID]) { - contexts[sopClassUID] = []; - } - if (syntax && contexts[sopClassUID].indexOf(syntax) == -1) { - contexts[sopClassUID].push(syntax); - } - toSend.push({file : bufferOrFile, context : sopClassUID, uid : instanceUID}); - - if (read == length) { - var useContexts = []; - for (var context in contexts) { - var useSyntaxes = contexts[context]; - if (useSyntaxes.length > 0) { - useContexts.push({context : context, syntaxes : contexts[context]}); - } else { - throw "No syntax specified for context " + context; + var store = socket.storeInstance(useContext.abstractSyntax, file.uid); + handle.on('pdv', function (pdv) { + socket.sendPData(pdv); + }); + handle.on('error', function (err) { + sHandle.emit('file', err, fileNameText); + processNext(); + }); + store.on('response', function (msg) { + var statusText = msg.getStatus().toString(16); + console.log('STORE reponse with status', statusText); + var error = null; + if (msg.failure()) { + error = new Error(statusText); } - } + sHandle.emit('file', error, fileNameText); + processNext(); + }); + }); +}; - self.associate({ - contexts : useContexts - }, function(ac) { - var maxSend = ac.getMaxSize(), next = toSend.shift(); - self._sendFile(this, handle, next, maxSend, metaLength, toSend); +Connection.prototype.storeInstances = function (fileList) { + var contexts = {}, read = 0, length = fileList.length, toSend = [], self = this, handle = new StoreHandle(); + var lastProcessedMetaLength; + fileList.forEach(function (bufferOrFile) { + var fileNameText = typeof bufferOrFile == 'string' ? bufferOrFile : 'buffer'; + DicomMessage.readMetaHeader(bufferOrFile, function (err, metaMessage, metaLength) { + read++; + if (err) { + handle.emit('file', err, fileNameText); + if (read == length && toSend.length > 0 && lastProcessedMetaLength) { + sendProcessedFiles(self, contexts, toSend, handle, lastProcessedMetaLength); + } + return; + } + console.log('Dicom file ' + (typeof bufferOrFile == 'string' ? bufferOrFile : 'buffer') + ' found'); + lastProcessedMetaLength = metaLength; + var syntax = metaMessage.getValue(0x00020010), + sopClassUID = metaMessage.getValue(0x00020002), + instanceUID = metaMessage.getValue(0x00020003); - }); - } - }); + if (!contexts[sopClassUID]) { + contexts[sopClassUID] = []; + } + if (syntax && contexts[sopClassUID].indexOf(syntax) == -1) { + contexts[sopClassUID].push(syntax); + } + toSend.push({file: bufferOrFile, context: sopClassUID, uid: instanceUID}); + + if (read == length) { + sendProcessedFiles(self, contexts, toSend, handle, metaLength); + } + }); }); return handle; }; -Connection.prototype.storeResponse = function(messageId, msg) { +// Starts to send dcm files +sendProcessedFiles = function (self, contexts, toSend, handle, metaLength) { + var useContexts = []; + for (var context in contexts) { + var useSyntaxes = contexts[context]; + if (useSyntaxes.length > 0) { + useContexts.push({context: context, syntaxes: contexts[context]}); + } else { + throw "No syntax specified for context " + context; + } + } + + self.associate({ + contexts: useContexts + }, function (ac) { + var maxSend = ac.getMaxSize(), next = toSend.shift(); + self._sendFile(this, handle, next, maxSend, metaLength, toSend); + + }); +}; + +Connection.prototype.storeResponse = function (messageId, msg) { var rq = this.messages[messageId]; if (rq.listener[2]) { @@ -175,36 +185,36 @@ Connection.prototype.storeResponse = function(messageId, msg) { } }; -Connection.prototype.allClosed = function() { +Connection.prototype.allClosed = function () { var allClosed = true; for (var i in o.peerSockets) { - if (Object.keys(o.peerSockets[ae]).length > 0) { - allClosed = false; - break; - } + if (Object.keys(o.peerSockets[ae]).length > 0) { + allClosed = false; + break; + } } return allClosed; }; -Connection.prototype.addSocket = function(ae, socket) { +Connection.prototype.addSocket = function (ae, socket) { if (!this.peerSockets[ae]) { - this.peerSockets[ae] = {}; + this.peerSockets[ae] = {}; } this.peerSockets[ae][socket.id] = socket; var o = this; - socket.on("close", function() { - if (o.peerSockets[ae][this.id]) { - delete o.peerSockets[ae][this.id]; - } + socket.on("close", function () { + if (o.peerSockets[ae][this.id]) { + delete o.peerSockets[ae][this.id]; + } }); }; -Connection.prototype.associate = function(options, callback) { +Connection.prototype.associate = function (options, callback) { var hostAE = options.hostAE ? options.hostAE : this.defaultPeer, sourceAE = options.sourceAE ? options.sourceAE : this.defaultServer; if (!hostAE || !sourceAE) { - throw "Peers not provided or no defaults in settings"; + throw "Peers not provided or no defaults in settings"; } var peerInfo = this.selectPeer(hostAE), nativeSocket = new Socket(); @@ -216,18 +226,18 @@ Connection.prototype.associate = function(options, callback) { socket.setCallingAE(sourceAE); nativeSocket.connect({ - host : peerInfo.host, port : peerInfo.port - }, function(){ - //connected - o.addSocket(hostAE, socket); + host: peerInfo.host, port: peerInfo.port + }, function () { + //connected + o.addSocket(hostAE, socket); - if (options.contexts) { - socket.setPresentationContexts(options.contexts); - } else { - throw "Contexts must be specified"; - } + if (options.contexts) { + socket.setPresentationContexts(options.contexts); + } else { + throw "Contexts must be specified"; + } - socket.associate(); + socket.associate(); }); return socket; diff --git a/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.html b/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.html index e3bc409d6..b65684fc7 100644 --- a/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.html +++ b/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.html @@ -9,7 +9,9 @@ {{progressStatus}} -
{{progressMessage}}
+
+ {{{progressMessage}}} +
\ No newline at end of file diff --git a/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.js b/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.js index 1dcf92900..1413f15da 100644 --- a/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.js +++ b/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.js @@ -1,9 +1,24 @@ progressDialog = { - 'show': function(title, numberOfTotal) { - Session.set("progressDialogSettings", { title: title, numberOfCompleted: 0, numberOfTotal: numberOfTotal }); - + /** + * Shows progress dialog + * @param {Object} settings - Settings used by progress dialog + * @param {string} settings.title + * @param {int} settings.numberOfCompleted - The value of progress dialog + * @param {int} settings.numberOfTotal - The max value of progress dialog + * @param {Object} [settings.messageParams] - Show a message in progress dialog by setting message and messageType + * @param {string} [settings.messageParams.message="Progress"] - The message will be shown in the progress dialog + * @param {string} [settings.messageParams.messageType="info"] - Sets ui, accepts bootstrap predefined classes such as success, info, warning, danger + */ + 'show': function(settings) { + // Set dialog settings + Session.set("progressDialogSettings", settings); $('#progressDialog').css('display', 'block'); }, + + /** + * Updates the value of the progress dialog + * @param {int} numberOfCompleted + */ 'update': function(numberOfCompleted) { var progressDialogSettings = Session.get("progressDialogSettings"); progressDialogSettings.numberOfCompleted = numberOfCompleted; @@ -14,13 +29,29 @@ progressDialog = { progressDialog.close(); } }, + + /** + * Closes the progress dialog + */ 'close': function() { - Session.set("progressDialogSettings", { title: "", numberOfCompleted: 0, numberOfTotal: 1 }); + // Reset progressDialogSettings session + resetDialogSettingsSession(); + + // Close dialog $('#progressDialog').css('display', 'none'); }, - 'setMessage': function(message) { + /** + * Shows a message in the progress dialog + * @param {Object} messageParams + * @param {string} messageParams.message + * @param {string} messageParams.messageType + */ + 'setMessage': function(messageParams) { var progressDialogSettings = Session.get("progressDialogSettings"); - progressDialogSettings.message = message; + if (!messageParams.messageType || messageParams.messageType == '') { + messageParams.messageType = 'info'; + } + progressDialogSettings.messageParams = messageParams; Session.set("progressDialogSettings", progressDialogSettings); } }; @@ -31,7 +62,7 @@ Template.progressDialog.helpers({ return Session.get("progressDialogSettings").title; } - return ""; + return "Progress:"; }, 'progressStatus': function() { var numberOfCompleted = 0; @@ -48,9 +79,22 @@ Template.progressDialog.helpers({ }, 'progressMessage': function() { var progressDialogSettings = Session.get("progressDialogSettings"); - if (progressDialogSettings && progressDialogSettings.message) { - return progressDialogSettings.message; + var messageParams = progressDialogSettings && progressDialogSettings.messageParams || false; + if (messageParams && messageParams.message) { + return ''+messageParams.message+''; } return; } }); + +// Resets progressDialogSettings +function resetDialogSettingsSession() { + Session.set("progressDialogSettings", + { + title: 'Progress', + numberOfCompleted: 0, + numberOfTotal: 0, + messageParams: {message: null, messageType: 'info'} + } + ); +} diff --git a/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.styl b/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.styl index 6aa0bd6b7..312fa4ae5 100644 --- a/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.styl +++ b/Packages/viewerbase/client/components/basic/progressDialog/progressDialog.styl @@ -7,7 +7,7 @@ right: 0 margin: auto width: 20% - height: 15% + height: 20% z-index: 100 border-radius: 5px padding: 10px 20px 10px 20px @@ -16,3 +16,10 @@ .dialogContent margin-bottom: 20px + + #message + width: 100% + + span + display: inline-block; + width: 100% diff --git a/Packages/viewerbase/lib/importStudies.js b/Packages/viewerbase/lib/importStudies.js index 1d6106fbb..d29cdaa15 100644 --- a/Packages/viewerbase/lib/importStudies.js +++ b/Packages/viewerbase/lib/importStudies.js @@ -10,11 +10,13 @@ importStudies = function(filesToImport, importCallback) { numberOfFilesUploaded: 0, numberOfFilesFailed: 0 }; - var numberOfFilesToUpload = filesToImport.length; var studiesToImport = []; - - progressDialog.show("Uploading Files...", numberOfFilesToUpload); + progressDialog.show({ + title: "Uploading Files...", + numberOfCompleted: 0, + numberOfTotal: numberOfFilesToUpload + }); // Upload files to the server filesToImport.forEach(function(fileToUpload) { @@ -70,7 +72,11 @@ function importStudiesInternal(studiesToImport, importCallback) { var numberOfStudiesToImport = studiesToImport.length; - progressDialog.show("Importing Studies...", numberOfStudiesToImport); + progressDialog.show({ + title: "Importing Studies...", + numberOfCompleted: 0, + numberOfTotal: numberOfStudiesToImport + }); // Create/Insert a new study import status item Meteor.call("createStudyImportStatus", function(err, studyImportStatusId) { @@ -81,7 +87,7 @@ function importStudiesInternal(studiesToImport, importCallback) { return; } - // Handle when it is updated + // Handle when StudyImportStatus collection is updated StudyImportStatus.find(studyImportStatusId).observe({ changed: function(studyImportStatus) { if (!studyImportStatus) { @@ -90,26 +96,27 @@ function importStudiesInternal(studiesToImport, importCallback) { var numberOfStudiesProcessedToImport = studyImportStatus.numberOfStudiesImported + studyImportStatus.numberOfStudiesFailed; - // Show failed message in the dialog - if (studyImportStatus.numberOfStudiesFailed > 0) { - var failMessage = "Failed to import " + studyImportStatus.numberOfStudiesFailed + " of " + numberOfStudiesToImport + " files"; - progressDialog.setMessage(failMessage); - } - + // Show number of imported files + var successMessage = 'Imported '+studyImportStatus.numberOfStudiesImported+' of '+numberOfStudiesToImport; + progressDialog.setMessage({ + message: successMessage, + messageType: 'success' + }); progressDialog.update(numberOfStudiesProcessedToImport); + // Show number of failed files if there is at least one failed file + if (studyImportStatus.numberOfStudiesFailed > 0) { + var successMessage = 'Failed '+studyImportStatus.numberOfStudiesFailed+' of '+numberOfStudiesToImport; + progressDialog.setMessage({ + message: successMessage, + messageType: 'warning' + }); + } + if (numberOfStudiesProcessedToImport == numberOfStudiesToImport) { // The entire import operation is completed, so remove the study import status item Meteor.call("removeStudyImportStatus", studyImportStatus._id); - if (studyImportStatus.numberOfStudiesFailed > 0) { - //TODO: Some files failed to import, so let user know - // Update progress dialog message - var failMessage = "Failed to import " + studyImportStatus.numberOfStudiesFailed + " of " + numberOfStudiesToImport + " files"; - progressDialog.setMessage(failMessage); - console.log(failMessage); - } - // Let the caller know that import operation is completed if (importCallback) { importCallback(); diff --git a/Packages/worklist/client/components/worklistToolbar/worklistToolbar.js b/Packages/worklist/client/components/worklistToolbar/worklistToolbar.js index 0b959a913..d2c4b6d2c 100644 --- a/Packages/worklist/client/components/worklistToolbar/worklistToolbar.js +++ b/Packages/worklist/client/components/worklistToolbar/worklistToolbar.js @@ -6,6 +6,12 @@ Template.worklistToolbar.events({ }); importStudies(selectedFiles); + }, + + 'click #btnImport': function(e) { + // Reset file input + var fileInput = e.currentTarget; + $(fileInput).val(""); } });