LT-239: Handle the error if the last imported file is not dcm file

- Add message label in progress dialog
This commit is contained in:
Aysel Afsar 2016-04-25 19:01:59 -04:00
parent 2a17760ff8
commit dfcb33153f
8 changed files with 246 additions and 170 deletions

View File

@ -33,7 +33,7 @@ Meteor.startup(function() {
"public": { "public": {
"verifyEmail": false, "verifyEmail": false,
"ui": { "ui": {
"studyListFunctionsEnabled": false "studyListFunctionsEnabled": true
} }
} }
//defaultServiceType: 'dimse' //defaultServiceType: 'dimse'

View File

@ -31,7 +31,7 @@ Meteor.startup(function() {
defaultServiceType: 'dicomWeb', defaultServiceType: 'dicomWeb',
public: { public: {
ui: { ui: {
studyListFunctionsEnabled: false studyListFunctionsEnabled: true
} }
} }
//defaultServiceType: 'dimse' //defaultServiceType: 'dimse'

View File

@ -1,9 +1,9 @@
var net = Npm.require('net'), var net = Npm.require('net'),
Socket = net.Socket; Socket = net.Socket;
var DEFAULT_MAX_PACKAGE_SIZE = 32768; var DEFAULT_MAX_PACKAGE_SIZE = 32768;
Connection = function(options) { Connection = function (options) {
EventEmitter.call(this); EventEmitter.call(this);
this.options = Object.assign({ this.options = Object.assign({
maxPackageSize: C.DEFAULT_MAX_PACKAGE_SIZE, maxPackageSize: C.DEFAULT_MAX_PACKAGE_SIZE,
@ -22,142 +22,152 @@ Connection = function(options) {
util.inherits(Connection, EventEmitter); util.inherits(Connection, EventEmitter);
var StoreHandle = function() { var StoreHandle = function () {
EventEmitter.call(this); EventEmitter.call(this);
} }
util.inherits(StoreHandle, EventEmitter); util.inherits(StoreHandle, EventEmitter);
Connection.prototype.addPeer = function(options) { Connection.prototype.addPeer = function (options) {
if (!options.aeTitle || !options.host || !options.port) { if (!options.aeTitle || !options.host || !options.port) {
return false; 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;
} }
} this.peers[options.aeTitle] = {
if (options.server) { host: options.host, port: options.port
//start listening };
var server = net.createServer(); if (options.default) {
server.listen(options.port, options.host, function(){ if (options.server) {
console.log("listening on %j", this.address()); this.defaultServer = options.aeTitle;
}); } else {
server.on('error', function(err){ this.defaultPeer = options.aeTitle;
console.log("server error %j", err); }
}); }
var o = this; if (options.server) {
server.on('connection', function(nativeSocket) { //start listening
//incoming connections var server = net.createServer();
var socket = new CSocket(nativeSocket, o.options); server.listen(options.port, options.host, function () {
o.addSocket(options.aeTitle, socket); console.log("listening on %j", this.address());
//close server on close socket
socket.on('close', function(){
server.close();
}); });
}); 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]) { if (!aeTitle || !this.peers[aeTitle]) {
throw "No such peer"; throw "No such peer";
} }
return this.peers[aeTitle]; 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) { Connection.prototype._sendFile = function (socket, sHandle, file, maxSend, metaLength, list) {
var contexts = {}, read = 0, length = fileList.length, toSend = [], self = this, handle = new StoreHandle(); var fileNameText = typeof file.file == 'string' ? file.file : 'buffer';
fileList.forEach(function(bufferOrFile){ console.log("Sending file " + fileNameText);
var fileNameText = typeof bufferOrFile == 'string' ? bufferOrFile : 'buffer'; var useContext = socket.getContextByUID(file.context), self = this;
DicomMessage.readMetaHeader(bufferOrFile, function(err, metaMessage, metaLength) {
read++; PDU.generatePDatas(useContext.id, file.file, maxSend, null, metaLength, function (err, handle) {
if (err) { if (err) {
handle.emit('file', err, fileNameText); console.log('Error while sending file');
return; return;
} }
console.log('Dicom file ' + (typeof bufferOrFile == 'string' ? bufferOrFile : 'buffer') + ' found'); var processNext = function () {
var syntax = metaMessage.getValue(0x00020010), var next = list.shift();
sopClassUID = metaMessage.getValue(0x00020002), if (next)
instanceUID = metaMessage.getValue(0x00020003); self._sendFile(socket, sHandle, next, maxSend, metaLength, list);
else
socket.release();
};
if (!contexts[sopClassUID]) { var store = socket.storeInstance(useContext.abstractSyntax, file.uid);
contexts[sopClassUID] = []; handle.on('pdv', function (pdv) {
} socket.sendPData(pdv);
if (syntax && contexts[sopClassUID].indexOf(syntax) == -1) { });
contexts[sopClassUID].push(syntax); handle.on('error', function (err) {
} sHandle.emit('file', err, fileNameText);
toSend.push({file : bufferOrFile, context : sopClassUID, uid : instanceUID}); processNext();
});
if (read == length) { store.on('response', function (msg) {
var useContexts = []; var statusText = msg.getStatus().toString(16);
for (var context in contexts) { console.log('STORE reponse with status', statusText);
var useSyntaxes = contexts[context]; var error = null;
if (useSyntaxes.length > 0) { if (msg.failure()) {
useContexts.push({context : context, syntaxes : contexts[context]}); error = new Error(statusText);
} else {
throw "No syntax specified for context " + context;
} }
} sHandle.emit('file', error, fileNameText);
processNext();
});
});
};
self.associate({ Connection.prototype.storeInstances = function (fileList) {
contexts : useContexts var contexts = {}, read = 0, length = fileList.length, toSend = [], self = this, handle = new StoreHandle();
}, function(ac) { var lastProcessedMetaLength;
var maxSend = ac.getMaxSize(), next = toSend.shift(); fileList.forEach(function (bufferOrFile) {
self._sendFile(this, handle, next, maxSend, metaLength, toSend); 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; 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]; var rq = this.messages[messageId];
if (rq.listener[2]) { 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; var allClosed = true;
for (var i in o.peerSockets) { for (var i in o.peerSockets) {
if (Object.keys(o.peerSockets[ae]).length > 0) { if (Object.keys(o.peerSockets[ae]).length > 0) {
allClosed = false; allClosed = false;
break; break;
} }
} }
return allClosed; return allClosed;
}; };
Connection.prototype.addSocket = function(ae, socket) { Connection.prototype.addSocket = function (ae, socket) {
if (!this.peerSockets[ae]) { if (!this.peerSockets[ae]) {
this.peerSockets[ae] = {}; this.peerSockets[ae] = {};
} }
this.peerSockets[ae][socket.id] = socket; this.peerSockets[ae][socket.id] = socket;
var o = this; var o = this;
socket.on("close", function() { socket.on("close", function () {
if (o.peerSockets[ae][this.id]) { if (o.peerSockets[ae][this.id]) {
delete 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, var hostAE = options.hostAE ? options.hostAE : this.defaultPeer,
sourceAE = options.sourceAE ? options.sourceAE : this.defaultServer; sourceAE = options.sourceAE ? options.sourceAE : this.defaultServer;
if (!hostAE || !sourceAE) { 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(); var peerInfo = this.selectPeer(hostAE), nativeSocket = new Socket();
@ -216,18 +226,18 @@ Connection.prototype.associate = function(options, callback) {
socket.setCallingAE(sourceAE); socket.setCallingAE(sourceAE);
nativeSocket.connect({ nativeSocket.connect({
host : peerInfo.host, port : peerInfo.port host: peerInfo.host, port: peerInfo.port
}, function(){ }, function () {
//connected //connected
o.addSocket(hostAE, socket); o.addSocket(hostAE, socket);
if (options.contexts) { if (options.contexts) {
socket.setPresentationContexts(options.contexts); socket.setPresentationContexts(options.contexts);
} else { } else {
throw "Contexts must be specified"; throw "Contexts must be specified";
} }
socket.associate(); socket.associate();
}); });
return socket; return socket;

View File

@ -9,7 +9,9 @@
{{progressStatus}} {{progressStatus}}
</div> </div>
</div> </div>
<div class="message">{{progressMessage}}</div> <div id="message">
{{{progressMessage}}}
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,9 +1,24 @@
progressDialog = { 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'); $('#progressDialog').css('display', 'block');
}, },
/**
* Updates the value of the progress dialog
* @param {int} numberOfCompleted
*/
'update': function(numberOfCompleted) { 'update': function(numberOfCompleted) {
var progressDialogSettings = Session.get("progressDialogSettings"); var progressDialogSettings = Session.get("progressDialogSettings");
progressDialogSettings.numberOfCompleted = numberOfCompleted; progressDialogSettings.numberOfCompleted = numberOfCompleted;
@ -14,13 +29,29 @@ progressDialog = {
progressDialog.close(); progressDialog.close();
} }
}, },
/**
* Closes the progress dialog
*/
'close': function() { 'close': function() {
Session.set("progressDialogSettings", { title: "", numberOfCompleted: 0, numberOfTotal: 1 }); // Reset progressDialogSettings session
resetDialogSettingsSession();
// Close dialog
$('#progressDialog').css('display', 'none'); $('#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"); var progressDialogSettings = Session.get("progressDialogSettings");
progressDialogSettings.message = message; if (!messageParams.messageType || messageParams.messageType == '') {
messageParams.messageType = 'info';
}
progressDialogSettings.messageParams = messageParams;
Session.set("progressDialogSettings", progressDialogSettings); Session.set("progressDialogSettings", progressDialogSettings);
} }
}; };
@ -31,7 +62,7 @@ Template.progressDialog.helpers({
return Session.get("progressDialogSettings").title; return Session.get("progressDialogSettings").title;
} }
return ""; return "Progress:";
}, },
'progressStatus': function() { 'progressStatus': function() {
var numberOfCompleted = 0; var numberOfCompleted = 0;
@ -48,9 +79,22 @@ Template.progressDialog.helpers({
}, },
'progressMessage': function() { 'progressMessage': function() {
var progressDialogSettings = Session.get("progressDialogSettings"); var progressDialogSettings = Session.get("progressDialogSettings");
if (progressDialogSettings && progressDialogSettings.message) { var messageParams = progressDialogSettings && progressDialogSettings.messageParams || false;
return progressDialogSettings.message; if (messageParams && messageParams.message) {
return '<span class="label label-'+messageParams.messageType+'">'+messageParams.message+'</span>';
} }
return; return;
} }
}); });
// Resets progressDialogSettings
function resetDialogSettingsSession() {
Session.set("progressDialogSettings",
{
title: 'Progress',
numberOfCompleted: 0,
numberOfTotal: 0,
messageParams: {message: null, messageType: 'info'}
}
);
}

View File

@ -7,7 +7,7 @@
right: 0 right: 0
margin: auto margin: auto
width: 20% width: 20%
height: 15% height: 20%
z-index: 100 z-index: 100
border-radius: 5px border-radius: 5px
padding: 10px 20px 10px 20px padding: 10px 20px 10px 20px
@ -16,3 +16,10 @@
.dialogContent .dialogContent
margin-bottom: 20px margin-bottom: 20px
#message
width: 100%
span
display: inline-block;
width: 100%

View File

@ -10,11 +10,13 @@ importStudies = function(filesToImport, importCallback) {
numberOfFilesUploaded: 0, numberOfFilesUploaded: 0,
numberOfFilesFailed: 0 numberOfFilesFailed: 0
}; };
var numberOfFilesToUpload = filesToImport.length; var numberOfFilesToUpload = filesToImport.length;
var studiesToImport = []; var studiesToImport = [];
progressDialog.show({
progressDialog.show("Uploading Files...", numberOfFilesToUpload); title: "Uploading Files...",
numberOfCompleted: 0,
numberOfTotal: numberOfFilesToUpload
});
// Upload files to the server // Upload files to the server
filesToImport.forEach(function(fileToUpload) { filesToImport.forEach(function(fileToUpload) {
@ -70,7 +72,11 @@ function importStudiesInternal(studiesToImport, importCallback) {
var numberOfStudiesToImport = studiesToImport.length; 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 // Create/Insert a new study import status item
Meteor.call("createStudyImportStatus", function(err, studyImportStatusId) { Meteor.call("createStudyImportStatus", function(err, studyImportStatusId) {
@ -81,7 +87,7 @@ function importStudiesInternal(studiesToImport, importCallback) {
return; return;
} }
// Handle when it is updated // Handle when StudyImportStatus collection is updated
StudyImportStatus.find(studyImportStatusId).observe({ StudyImportStatus.find(studyImportStatusId).observe({
changed: function(studyImportStatus) { changed: function(studyImportStatus) {
if (!studyImportStatus) { if (!studyImportStatus) {
@ -90,26 +96,27 @@ function importStudiesInternal(studiesToImport, importCallback) {
var numberOfStudiesProcessedToImport = studyImportStatus.numberOfStudiesImported + studyImportStatus.numberOfStudiesFailed; var numberOfStudiesProcessedToImport = studyImportStatus.numberOfStudiesImported + studyImportStatus.numberOfStudiesFailed;
// Show failed message in the dialog // Show number of imported files
if (studyImportStatus.numberOfStudiesFailed > 0) { var successMessage = 'Imported '+studyImportStatus.numberOfStudiesImported+' of '+numberOfStudiesToImport;
var failMessage = "Failed to import " + studyImportStatus.numberOfStudiesFailed + " of " + numberOfStudiesToImport + " files"; progressDialog.setMessage({
progressDialog.setMessage(failMessage); message: successMessage,
} messageType: 'success'
});
progressDialog.update(numberOfStudiesProcessedToImport); 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) { if (numberOfStudiesProcessedToImport == numberOfStudiesToImport) {
// The entire import operation is completed, so remove the study import status item // The entire import operation is completed, so remove the study import status item
Meteor.call("removeStudyImportStatus", studyImportStatus._id); 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 // Let the caller know that import operation is completed
if (importCallback) { if (importCallback) {
importCallback(); importCallback();

View File

@ -6,6 +6,12 @@ Template.worklistToolbar.events({
}); });
importStudies(selectedFiles); importStudies(selectedFiles);
},
'click #btnImport': function(e) {
// Reset file input
var fileInput = e.currentTarget;
$(fileInput).val("");
} }
}); });