Search in sources :

Example 1 with DataGatheringThread

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: &nbsp;" + "<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: &nbsp;" + "<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: &nbsp;" + "<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: &nbsp;" + "<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();
}
Also used : ActionEvent(java.awt.event.ActionEvent) JsonObject(com.eclipsesource.json.JsonObject) Cursor(java.awt.Cursor) URISyntaxException(java.net.URISyntaxException) WalletCallException(com.vaklinov.zcashui.ZCashClientCaller.WalletCallException) IOException(java.io.IOException) Date(java.util.Date) Timer(javax.swing.Timer) ActionListener(java.awt.event.ActionListener) DataGatheringThread(com.vaklinov.zcashui.DataGatheringThread) WalletCallException(com.vaklinov.zcashui.ZCashClientCaller.WalletCallException)

Example 2 with DataGatheringThread

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);
    }
}
Also used : NetworkAndBlockchainInfo(com.vaklinov.zcashui.ZCashClientCaller.NetworkAndBlockchainInfo) DataGatheringThread(com.vaklinov.zcashui.DataGatheringThread) Date(java.util.Date) URISyntaxException(java.net.URISyntaxException) WalletCallException(com.vaklinov.zcashui.ZCashClientCaller.WalletCallException) IOException(java.io.IOException)

Aggregations

DataGatheringThread (com.vaklinov.zcashui.DataGatheringThread)2 WalletCallException (com.vaklinov.zcashui.ZCashClientCaller.WalletCallException)2 IOException (java.io.IOException)2 URISyntaxException (java.net.URISyntaxException)2 Date (java.util.Date)2 JsonObject (com.eclipsesource.json.JsonObject)1 NetworkAndBlockchainInfo (com.vaklinov.zcashui.ZCashClientCaller.NetworkAndBlockchainInfo)1 Cursor (java.awt.Cursor)1 ActionEvent (java.awt.event.ActionEvent)1 ActionListener (java.awt.event.ActionListener)1 Timer (javax.swing.Timer)1