use of com.vaklinov.zcashui.DataGatheringThread in project zencash-swing-wallet-ui by ZencashOfficial.
the class MessagingPanel method sendMessage.
// String textToSend - if null, taken from the text area
// MessagingIdentity remoteIdentity - if null selection is taken
private void sendMessage(String textToSend, MessagingIdentity remoteIdentity) throws IOException, WalletCallException, InterruptedException {
boolean sendAnonymously = this.sendAnonymously.isSelected();
boolean sendReturnAddress = false;
boolean updateMessagingIdentityJustBeforeSend = false;
// Make sure contacts are available
if (this.contactList.getNumberOfContacts() <= 0) {
JOptionPane.showMessageDialog(this.parentFrame, "You have no messaging contacts in your contact list. To use messaging\n" + "you need to add at least one contact. You can add a contact by importing\n" + "their messaging identity using the menu item Messaging >> Import contact \n" + "identity.", "No messaging contacts available...", JOptionPane.ERROR_MESSAGE);
return;
}
if ((remoteIdentity == null) && (this.contactList.getSelectedContact() == null)) {
JOptionPane.showMessageDialog(this.parentFrame, "No messaging contact is selected in the contact list (on the right side of the UI).\n" + "In order to send an outgoing message you need to select a contact to send it to!", "No messaging contact is selected...", JOptionPane.ERROR_MESSAGE);
return;
}
// Create a copy of the identity to make sure changes made temporarily to do get reflected until
// storage s updated (such a change may be setting a Z address)
final MessagingIdentity contactIdentity = (remoteIdentity != null) ? remoteIdentity.getCloneCopy() : this.contactList.getSelectedContact().getCloneCopy();
// Make sure contact identity is full (not Unknown with no address to send to)
if (Util.stringIsEmpty(contactIdentity.getSendreceiveaddress())) {
String errroMessage = "The messaging contact selected: " + contactIdentity.getDiplayString() + "\n" + "seems to have no valid Z address for sending and receiving messages. \n";
errroMessage += contactIdentity.isAnonymous() ? ("Since the contact is anonymous this means that the contact intentionally did\n" + "not send his Z address (for replies to be psosible). Message cannot be sent!") : ("Most likely the reason is that this contact's messaging identity is not \n" + "imported yet. Message cannot be sent!");
JOptionPane.showMessageDialog(this.parentFrame, errroMessage, "Selected contact has to Z address to send message to!", JOptionPane.ERROR_MESSAGE);
return;
}
// set for the recipient. Also ask the user if he wishes to send a return address.
if (sendAnonymously) {
// If also no thread ID is set yet...
if (Util.stringIsEmpty(contactIdentity.getThreadID())) {
if (!contactIdentity.isGroup()) {
// Offer the user to send a return address
int reply = JOptionPane.showConfirmDialog(this.parentFrame, "This is the first anomymous message you are sending to contact: \n" + contactIdentity.getDiplayString() + "\n" + "Do you wish to send him your send/receive messaging Z address so\n" + "that the contact may be able to answer your anonymous messages?", "Send return address?", JOptionPane.YES_NO_OPTION);
if (reply == JOptionPane.YES_OPTION) {
sendReturnAddress = true;
}
}
String threadID = UUID.randomUUID().toString();
contactIdentity.setThreadID(threadID);
// will have a thread ID set on the first arriving message!
if (contactIdentity.isGroup() || (!Util.stringIsEmpty(contactIdentity.getSenderidaddress()))) {
updateMessagingIdentityJustBeforeSend = true;
} else {
JOptionPane.showMessageDialog(this.parentFrame, "The contact: " + contactIdentity.getDiplayString() + "\n" + "has no message identification T address. It is not possible to \n" + "send a message!", "Contact has no message identification T address", JOptionPane.ERROR_MESSAGE);
return;
}
}
} else {
// Check to make sure a normal message is not being sent to an anonymous identity
if (contactIdentity.isAnonymous()) {
int reply = JOptionPane.showConfirmDialog(this.parentFrame, "The contact: " + contactIdentity.getDiplayString() + "\n" + "is anonymous. However you are about to send a message to him\n" + "that includes your sender identification T address. Are you sure\n" + "you wish to send him the message?", "Send message releavling your sender identification T address?", JOptionPane.YES_NO_OPTION);
if (reply == JOptionPane.NO_OPTION) {
return;
}
}
}
// Get the text to send as a message
if (textToSend == null) {
textToSend = this.writeMessageTextArea.getText();
}
if (textToSend.length() <= 0) {
JOptionPane.showMessageDialog(this.parentFrame, "You have not written any text for a message to be sent. Please write some text\n" + "in the message text field...", "Message text is empty", JOptionPane.ERROR_MESSAGE);
return;
}
// Make sure there is not another send operation going on - at this time
if ((this.operationStatusTimer != null) || (!this.sendButton.isEnabled())) {
JOptionPane.showMessageDialog(this.parentFrame, "There is currently another message sending operation under way.\n" + "Please wait until the operation is completed...", "Another message sending operation is under way!", JOptionPane.ERROR_MESSAGE);
return;
}
// Disable sending controls, set status.
this.sendButton.setEnabled(false);
this.writeMessageTextArea.setEnabled(false);
// Form the JSON message to be sent
MessagingIdentity ownIdentity = this.messagingStorage.getOwnIdentity();
MessagingOptions msgOptions = this.messagingStorage.getMessagingOptions();
// Check to make sure the sending address has some funds!!!
final double minimumBalance = msgOptions.getAmountToSend() + msgOptions.getTransactionFee();
Double balance = null;
Double unconfirmedBalance = null;
Cursor oldCursor = this.parentFrame.getCursor();
try {
this.parentFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
balance = Double.valueOf(this.clientCaller.getBalanceForAddress(ownIdentity.getSendreceiveaddress()));
unconfirmedBalance = Double.valueOf(this.clientCaller.getUnconfirmedBalanceForAddress(ownIdentity.getSendreceiveaddress()));
} finally {
this.parentFrame.setCursor(oldCursor);
}
if ((balance < minimumBalance) && (unconfirmedBalance < minimumBalance)) {
Log.warning("Sending address has balance: {0} and unconfirmed balance: {1}", balance, unconfirmedBalance);
JOptionPane.showMessageDialog(this.parentFrame, "The Z address used to send/receive messages has insufficient balance: \n" + ownIdentity.getSendreceiveaddress() + "\n" + "You will be redirected to the UI tab for sending ZEN to add some balance to it. You need only\n" + "a small amount e.g. typically 0.1 ZEN is suffucient to send 500 messages. After sending some\n" + "ZEN you need to wait for the transaciton to be confirmed (typically takes 2.5 minutes). It is\n" + "recommended to send ZEN to this Z address in two or more separate transactions (though one \n" + "transaction is sufficient).", "Z address to send/receive messages has insufficient balance...", JOptionPane.ERROR_MESSAGE);
// Restore controls and move to the send cash tab etc.
this.sendButton.setEnabled(true);
this.writeMessageTextArea.setEnabled(true);
sendCashPanel.prepareForSending(ownIdentity.getSendreceiveaddress());
parentTabs.setSelectedIndex(3);
return;
}
if ((balance < minimumBalance) && (unconfirmedBalance >= minimumBalance)) {
Log.warning("Sending address has balance: {0} and unconfirmed balance: {1}", balance, unconfirmedBalance);
JOptionPane.showMessageDialog(this.parentFrame, "The Z address used to send/receive messages has insufficient confirmed balance: \n" + ownIdentity.getSendreceiveaddress() + "\n" + "This usually means that the previous mesasaging transaction is not yet confirmed. You\n" + "need to wait for the transaciton to be confirmed (typically takes 2.5 minutes). This\n" + "problem may be avoided if you send ZEN to this Z address in two or more separate \n" + "transactions (when you supply the ZEN balance to be used for messaging).", "Z address to send/receive messages has insufficient confirmed balance...", JOptionPane.ERROR_MESSAGE);
// Restore controls and move to the send cash tab etc.
this.sendButton.setEnabled(true);
this.writeMessageTextArea.setEnabled(true);
return;
}
String memoString = null;
JsonObject jsonInnerMessage = null;
if (sendAnonymously) {
// Form an anonymous message
jsonInnerMessage = new JsonObject();
jsonInnerMessage.set("ver", 1d);
jsonInnerMessage.set("message", textToSend);
jsonInnerMessage.set("threadid", contactIdentity.getThreadID());
if (sendReturnAddress) {
jsonInnerMessage.set("returnaddress", ownIdentity.getSendreceiveaddress());
}
JsonObject jsonOuterMessage = new JsonObject();
jsonOuterMessage.set("zenmsg", jsonInnerMessage);
memoString = jsonOuterMessage.toString();
} else {
// Sign a HEX encoded message ... to avoid possible UNICODE issues
String signature = this.clientCaller.signMessage(ownIdentity.getSenderidaddress(), Util.encodeHexString(textToSend).toUpperCase());
jsonInnerMessage = new JsonObject();
jsonInnerMessage.set("ver", 1d);
jsonInnerMessage.set("from", ownIdentity.getSenderidaddress());
jsonInnerMessage.set("message", textToSend);
jsonInnerMessage.set("sign", signature);
JsonObject jsonOuterMessage = new JsonObject();
jsonOuterMessage.set("zenmsg", jsonInnerMessage);
memoString = jsonOuterMessage.toString();
}
final JsonObject jsonInnerMessageForFurtherUse = jsonInnerMessage;
// Check the size of the message to be sent, error if it exceeds.
final int maxSendingLength = 512;
int overallSendingLength = memoString.getBytes("UTF-8").length;
if (overallSendingLength > maxSendingLength) {
Log.warning("Text length of exceeding message: {0}", textToSend.length());
int difference = Math.abs(maxSendingLength - overallSendingLength);
// We give exact size and advice on reduction...
JOptionPane.showMessageDialog(this.parentFrame, "The text of the message you have written is too long to be sent. When\n" + "packaged as a memo it comes up to " + overallSendingLength + " bytes (maximum is " + maxSendingLength + " bytes)\n\n" + "Advice: try to reduce the message length by " + difference + " characters. The current\n" + "version of the ZEN messaging protocol supports approximately 330\n" + "characters per message (number is not exact - depends on character\n" + "encoding specifics).", "Message size exceeds currently supported limits...", JOptionPane.ERROR_MESSAGE);
// Restore controls and exit
this.sendButton.setEnabled(true);
this.writeMessageTextArea.setEnabled(true);
return;
}
if (updateMessagingIdentityJustBeforeSend) {
if (!contactIdentity.isGroup()) {
this.messagingStorage.updateContactIdentityForSenderIDAddress(contactIdentity.getSenderidaddress(), contactIdentity);
} else {
this.messagingStorage.updateGroupContactIdentityForSendReceiveAddress(contactIdentity.getSendreceiveaddress(), contactIdentity);
}
}
// Finally send the message
String tempOperationID = null;
try {
tempOperationID = this.clientCaller.sendMessage(ownIdentity.getSendreceiveaddress(), contactIdentity.getSendreceiveaddress(), msgOptions.getAmountToSend(), msgOptions.getTransactionFee(), memoString);
} catch (WalletCallException wce) {
Log.error("Wallet call error in sending message: ", wce);
sendResultLabel.setText("<html><span style=\"font-size:0.8em;\">Send status: " + "<span style=\"color:red;font-weight:bold\">ERROR! </span></span></html>");
JOptionPane.showMessageDialog(MessagingPanel.this.getRootPane().getParent(), "An error occurred upon sending message to contact: " + contactIdentity.getDiplayString() + ". \n" + "Error message is: " + wce.getMessage() + "\n" + "If the problem persists, you may need technical support :( ...\n", "Error in sending message", JOptionPane.ERROR_MESSAGE);
sendMessageProgressBar.setValue(0);
sendButton.setEnabled(true);
writeMessageTextArea.setEnabled(true);
// Exit prematurely
return;
}
final String operationStatusID = tempOperationID;
// Start a data gathering thread specific to the operation being executed - this is done is a separate
// thread since the server responds more slowly during JoinSPlits and this blocks he GUI somewhat.
final DataGatheringThread<Boolean> opFollowingThread = new DataGatheringThread<Boolean>(new DataGatheringThread.DataGatherer<Boolean>() {
public Boolean gatherData() throws Exception {
long start = System.currentTimeMillis();
Boolean result = MessagingPanel.this.clientCaller.isSendingOperationComplete(operationStatusID);
long end = System.currentTimeMillis();
Log.info("Checking for messaging operation " + operationStatusID + " status done in " + (end - start) + "ms.");
return result;
}
}, this.errorReporter, 2000, true);
// Start a timer to update the progress of the operation
this.operationStatusTimer = new Timer(2000, new ActionListener() {
public int operationStatusCounter = 0;
@Override
public void actionPerformed(ActionEvent e) {
try {
Boolean opComplete = opFollowingThread.getLastData();
if ((opComplete != null) && opComplete.booleanValue()) {
// End the special thread used to follow the operation
opFollowingThread.setSuspended(true);
boolean sendWasSuccessful = clientCaller.isCompletedOperationSuccessful(operationStatusID);
if (sendWasSuccessful) {
sendResultLabel.setText("<html><span style=\"font-size:0.8em;\">Send status: " + "<span style=\"color:green;font-weight:bold\">SUCCESSFUL</span></span></html>");
} else {
String errorMessage = clientCaller.getOperationFinalErrorMessage(operationStatusID);
sendResultLabel.setText("<html><span style=\"font-size:0.8em;\">Send status: " + "<span style=\"color:red;font-weight:bold\">ERROR! </span></span></html>");
JOptionPane.showMessageDialog(MessagingPanel.this.getRootPane().getParent(), "An error occurred when sending message to contact: " + contactIdentity.getDiplayString() + ". \n" + "Error message is: " + errorMessage + "\n\n" + "If the problem persists, you may need technical support :( ...\n", "Error in sending message", JOptionPane.ERROR_MESSAGE);
}
// Restore controls etc. final actions - reenable
sendMessageProgressBar.setValue(0);
operationStatusTimer.stop();
operationStatusTimer = null;
sendButton.setEnabled(true);
writeMessageTextArea.setEnabled(true);
// clear message from text area
writeMessageTextArea.setText("");
if (sendWasSuccessful) {
// Save message as outgoing
Message msg = new Message(jsonInnerMessageForFurtherUse);
msg.setTime(new Date());
msg.setDirection(DIRECTION_TYPE.SENT);
// TODO: We can get the transaction ID for outgoing messages but is is probably unnecessary
msg.setTransactionID("");
messagingStorage.writeNewSentMessageForContact(contactIdentity, msg);
}
// Update conversation text pane
displayMessagesForContact(contactIdentity);
} else {
// Update the progress
sendResultLabel.setText("<html><span style=\"font-size:0.8em;\">Send status: " + "<span style=\"color:orange;font-weight:bold\">IN PROGRESS</span></span></html>");
operationStatusCounter += 2;
int progress = 0;
if (operationStatusCounter <= 100) {
progress = operationStatusCounter;
} else {
progress = 100 + (((operationStatusCounter - 100) * 6) / 10);
}
sendMessageProgressBar.setValue(progress);
}
MessagingPanel.this.repaint();
} catch (Exception ex) {
Log.error("Unexpected error in sending message: ", ex);
MessagingPanel.this.errorReporter.reportError(ex);
}
}
});
// End timer operation
operationStatusTimer.setInitialDelay(0);
operationStatusTimer.start();
}
use of com.vaklinov.zcashui.DataGatheringThread in project zencash-swing-wallet-ui by ZencashOfficial.
the class MessagingPanel method collectAndStoreNewReceivedMessagesAndHandleErrors.
private void collectAndStoreNewReceivedMessagesAndHandleErrors() throws Exception {
try {
synchronized (this.messageCollectionMutex) {
// When a large number of messages has been accumulated, this operation partly
// slows down blockchain synchronization. So messages are collected only when
// sync is full.
NetworkAndBlockchainInfo info = this.clientCaller.getNetworkAndBlockchainInfo();
// If more than 60 minutes behind in the blockchain - skip collection
if ((System.currentTimeMillis() - info.lastBlockDate.getTime()) > (60 * 60 * 1000)) {
Log.warning("Current blockchain synchronization date is {0}. Message collection skipped for now!", new Date(info.lastBlockDate.getTime()));
return;
}
// Call it for the own identity
collectAndStoreNewReceivedMessages(null);
// Call it for all existing groups
for (MessagingIdentity id : this.messagingStorage.getContactIdentities(false)) {
if (id.isGroup()) {
collectAndStoreNewReceivedMessages(id);
}
}
}
} catch (Exception e) {
if (Thread.currentThread() instanceof DataGatheringThread) {
if (((DataGatheringThread) Thread.currentThread()).isSuspended()) {
// Just rethrow the exception
throw e;
}
}
Log.error("Unexpected error in gathering received messages (wrapper): ", e);
this.errorReporter.reportError(e);
}
}
Aggregations