package org.bitseal.controllers;
import java.util.ArrayList;
import org.bitseal.R;
import org.bitseal.core.App;
import org.bitseal.core.BehaviourBitfieldProcessor;
import org.bitseal.core.ObjectProcessor;
import org.bitseal.core.QueueRecordProcessor;
import org.bitseal.data.Address;
import org.bitseal.data.Message;
import org.bitseal.data.Payload;
import org.bitseal.data.Pubkey;
import org.bitseal.data.QueueRecord;
import org.bitseal.database.AddressProvider;
import org.bitseal.database.MessageProvider;
import org.bitseal.database.MessagesTable;
import org.bitseal.database.PayloadProvider;
import org.bitseal.network.NetworkHelper;
import org.bitseal.services.BackgroundService;
import org.bitseal.services.MessageStatusHandler;
import org.bitseal.util.TimeUtils;
import android.util.Log;
/**
* This class coordinates the work of the other "Controller" classes, such as
* SendMessageController.
*
* @author Jonathan Coe
*/
public class TaskController
{
/** Used when broadcasting Intents to the UI so that it can refresh the data it is displaying */
public static final String UI_NOTIFICATION = "uiNotification";
private static final String TAG = "TASK_CONTROLLER";
/**
* Creates a new set of identity data and attempts to disseminate the public part
* of that data to the rest of the Bitmessage network. The identity data consists of
* a pubkey for a given Bitmessage address and a payload for that pubkey which
* can be sent around the network.
*
* @param inputQueueRecord - A QueueRecord object for the task of creating and disseminating
* the identity data
* @param doPOW - A boolean indicating whether or not to do POW for the pubkey
* of the identity we are creating
*
* @return A boolean indicating whether all parts of this task were completed successfully
*/
public boolean createIdentity(QueueRecord inputQueueRecord, boolean doPOW)
{
Log.i(TAG, "TaskController.createIdentity() called");
QueueRecordProcessor queueProc = new QueueRecordProcessor();
// Attempt to retrieve the Address from the database. If it has been deleted by the user
// then we should abort the identity creation process.
Address address = null;
try
{
address = AddressProvider.get(App.getContext()).searchForSingleRecord(inputQueueRecord.getObject0Id());
}
catch (RuntimeException e)
{
Log.e(TAG, "While running TaskController.createIdentity(), the attempt to retrieve the Address object from the database failed.\n"
+ "The identity creation process will therefore be aborted.");
queueProc.deleteQueueRecord(inputQueueRecord);
return false;
}
// Attempt to create the pubkey and pubkey payload for a new identity
Payload pubkeyPayload = null;
try
{
pubkeyPayload = new CreateIdentityController().generatePubkeyData(address, doPOW);
}
catch (RuntimeException runEx)
{
Log.e(TAG, "While running TaskController.createIdentity(), CreateIdentityController.generateIdentityData() threw a RuntimeExecption. \n" +
"The exception message was: " + runEx.getMessage());
queueProc.updateQueueRecordAfterFailure(inputQueueRecord); // Update the QueueRecord to record the failed attempt
return false; // If we failed to create the identity data because of an exception, leave the QueueRecord for that
// task in place so that it can be attempted again later
}
// If we successfully generated the identity data, delete the QueueRecord for that task and
// create a new QueueRecord to disseminate the pubkey of that identity
queueProc.deleteQueueRecord(inputQueueRecord);
QueueRecord newQueueRecord = queueProc.createAndSaveQueueRecord(BackgroundService.TASK_DISSEMINATE_PUBKEY, TimeUtils.getUnixTime(), 0, pubkeyPayload, null, null);
// First check whether an Internet connection is available. If not, the QueueRecord for the
// 'disseminate pubkey' task will be saved (as above) and processed later
if (NetworkHelper.checkInternetAvailability() == true)
{
// Attempt to disseminate the pubkey for the newly generated identity
return disseminatePubkey(newQueueRecord, pubkeyPayload, doPOW);
}
else
{
return false;
}
}
/**
* Attempts to disseminate a pubkey payload to the Bitmessage network.
*
* @param inputQueueRecord - A QueueRecord object for this task
* @param pubkeyPayload - A Payload object containing the pubkey payload to be disseminated
* @param POWDone - A boolean indicating whether or not POW has been done for this pubkey
*
* @return A boolean indicating whether or not the pubkey payload was successfully
* disseminated to the rest of the network
*/
public boolean disseminatePubkey(QueueRecord inputQueueRecord, Payload pubkeyPayload, boolean POWDone)
{
Log.i(TAG, "TaskController.disseminatePubkey() called");
QueueRecordProcessor queueProc = new QueueRecordProcessor();
boolean success;
// Attempt to disseminate the pubkey payload of the new identity
try
{
success = new CreateIdentityController().disseminatePubkey(pubkeyPayload, POWDone);
}
catch (RuntimeException runEx)
{
Log.e(TAG, "While running TaskController.disseminatePubkey(), CreateIdentityController.disseminatePubkey() threw a RuntimeExecption. \n" +
"The exception message was: " + runEx.getMessage());
queueProc.updateQueueRecordAfterFailure(inputQueueRecord); // Update the QueueRecord to record the failed attempt
return false; // If we failed to disseminate the pubkey because of an exception, leave the QueueRecord for this
// task in place so that it can be attempted again later
}
// If we successfully generated the identity data
if (success)
{
// Delete the successfully disseminated payload and the QueueRecord for disseminating it
PayloadProvider.get(App.getContext()).deletePayload(pubkeyPayload);
queueProc.deleteQueueRecord(inputQueueRecord);
return true;
}
else
{
queueProc.updateQueueRecordAfterFailure(inputQueueRecord);
return false;
}
}
/**
* This method takes a scheduled 'send message' task and does all the work
* necessary to send the message.
*
* @param inputQueueRecord - A QueueRecord for the task of sending this message
* @param message - A Message object containing the message to send
* @param doPOW - A boolean indicating whether or not to do POW for this message
* @param msgTimeToLive - The 'time to live' value (in seconds) to be used in sending this message
* @param getpubkeyTimeToLive - The 'time to live' value (in seconds) to be used if we need to create
* a getpubkey object in order to retrieve the destination pubkey for this message
*
* @return A boolean indicating whether or not the entire process of creating and
* sending the message was completed successfully.
*/
public boolean sendMessage(QueueRecord inputQueueRecord, Message message, boolean doPOW, long msgTimeToLive, long getpubkeyTimeToLive)
{
Log.i(TAG, "TaskController.sendMessage() called");
// Attempt to retrieve the pubkey of the address that the message is to be sent to
SendMessageController controller = new SendMessageController();
QueueRecordProcessor queueProc = new QueueRecordProcessor();
Pubkey toPubkey = null;
try
{
Object retrievalResult = null;
// If we have already have getpubkey object created during a previous attempt to retrieve this
// pubkey, pass it to the SendMessageController so it can be reused if necessary
if (inputQueueRecord.getObject1Id() != 0)
{
PayloadProvider payProv = PayloadProvider.get(App.getContext());
Payload getpubkeyPayload = payProv.searchForSingleRecord(inputQueueRecord.getObject1Id());
// Check whether If the getpubkey is still valid (its time to live pay have expired)
boolean getpubkeyValid = new ObjectProcessor().validateObject(getpubkeyPayload.getPayload());
if (getpubkeyValid)
{
// Attempt to retrieve the pubkey using the existing getpubkey object
retrievalResult = controller.retrievePubkey(message, getpubkeyPayload, getpubkeyTimeToLive);
}
else
{
// Delete the old (and no longer valid) getpubkey from the database
payProv.deletePayload(getpubkeyPayload);
// Attempt to retrieve the pubkey by creating and disseminating a new getpubkey object
retrievalResult = controller.retrievePubkey(message, null, getpubkeyTimeToLive);
}
}
else
{
// Attempt to retrieve the pubkey by creating and disseminating a new getpubkey object
retrievalResult = controller.retrievePubkey(message, null, getpubkeyTimeToLive);
}
if (retrievalResult instanceof Payload)
{
// We were unable to retrieve the pubkey, and in response have created a new getpubkey
// object and sent it out to the network. Now we will modify the QueueRecord for this task
// to include a reference to the getpubkey Payload, so that we can reuse it if necessary.
inputQueueRecord.setObject1Type(QueueRecord.QUEUE_RECORD_OBJECT_TYPE_PAYLOAD);
inputQueueRecord.setObject1Id(((Payload) retrievalResult).getId());
queueProc.updateQueueRecord(inputQueueRecord);
return false; // If we failed to retrieve the pubkey, leave the QueueRecord for that
// task in place so that it can be attempted again later
}
else
{
toPubkey = (Pubkey) retrievalResult;
}
}
catch (RuntimeException runEx)
{
Log.e(TAG, "While running TaskController.sendMessage(), SendMessageController.retrieveToPubkey() threw a RuntimeExecption. \n" +
"The exception message was: " + runEx.getMessage());
queueProc.updateQueueRecordAfterFailure(inputQueueRecord); // Update the QueueRecord to record the failed attempt
return false; // If we failed to retrieve the pubkey, leave the QueueRecord for that
// task in place so that it can be attempted again later
}
// If we successfully retrieved the pubkey, delete the 'retrieve pubkey' QueueRecord and create a new one for the
// next stage of this task
queueProc.deleteQueueRecord(inputQueueRecord);
QueueRecord newQueueRecord = queueProc.createAndSaveQueueRecord(BackgroundService.TASK_PROCESS_OUTGOING_MESSAGE, TimeUtils.getUnixTime(), inputQueueRecord.getRecordCount(), message, toPubkey, null);
return processOutgoingMessage(newQueueRecord, message, toPubkey, doPOW, msgTimeToLive);
}
/**
* Processes a Message object, extracting the data from it and using that data to
* create a new msg payload that is ready to be sent out to the Bitmessage network.
*
* @param inputQueueRecord - A QueueRecord object for this task
* @param message - A Message object containing the data of the message that is to be sent
* @param toPubkey - A Pubkey object containing the pubkey data of the destination address
* @param doPOW - A boolean indicating whether or not to do POW for this message
* @param timeToLive - The 'time to live' value (in seconds) to be used in sending this message
*
* @return A boolean indicating whether or not the Message was successfully processed
*/
public boolean processOutgoingMessage (QueueRecord inputQueueRecord, Message message, Pubkey toPubkey, boolean doPOW, long timeToLive)
{
Log.i(TAG, "TaskController.processOutgoingMessage() called");
QueueRecordProcessor queueProc = new QueueRecordProcessor();
// Update the status of this message displayed in the UI
String messageStatus = App.getContext().getString(R.string.message_status_constructing_payload);
MessageStatusHandler.updateMessageStatus(message, messageStatus);
// Attempt to construct the message payload
Payload msgPayload = null;
try
{
msgPayload = new SendMessageController().processOutgoingMessage(message, toPubkey, doPOW, timeToLive);
}
catch (Exception e)
{
Log.e(TAG, "While running TaskController.processOutgoingMessage(), SendMessageController.processOutgoingMessage() threw an Exception. \n" +
"The exception message was: " + e.getMessage());
queueProc.updateQueueRecordAfterFailure(inputQueueRecord); // Update the QueueRecord to record the failed attempt
return false; // If we failed to process the outgoing message, leave the QueueRecord for that
// task in place so that it can be attempted again later
}
// If we successfully created the message payload, delete the 'process outgoing message' QueueRecord and create a new one for the
// next stage of this task
queueProc.deleteQueueRecord(inputQueueRecord);
QueueRecord newQueueRecord = queueProc.createAndSaveQueueRecord(BackgroundService.TASK_DISSEMINATE_MESSAGE, TimeUtils.getUnixTime(), 0, message, msgPayload, toPubkey);
MessageStatusHandler.updateMessageStatus(message, App.getContext().getString(R.string.message_status_sending_message));
// Update the "correspondingPayloadId" field of the Message
message.setMsgPayloadId(msgPayload.getId());
MessageProvider.get(App.getContext()).updateMessage(message);
// First check whether an Internet connection is available. If not, the QueueRecord for the
// 'disseminate message' task will be processed later
if (NetworkHelper.checkInternetAvailability() == true)
{
return disseminateMessage(newQueueRecord, msgPayload, toPubkey, doPOW);
}
else
{
MessageStatusHandler.updateMessageStatus(message, App.getContext().getString(R.string.message_status_waiting_for_connection));
return false;
}
}
/**
* Attempts to disseminate a msg payload to the rest of the Bitmessage network.
*
* @param inputQueueRecord - A QueueRecord object for this task
* @param msgPayload - A Payload object containing the msg payload to be sent
* @param toPubkey - A Pubkey object containing the pubkey data of the destination address
* @param POWDone - A boolean indicating whether or not POW has been done for this message
*
* @return A boolean indicating whether or not the msg payload was successfully disseminated
* to the rest of the Bitmessage network
*/
public boolean disseminateMessage(QueueRecord inputQueueRecord, Payload msgPayload, Pubkey toPubkey, boolean POWDone)
{
Log.i(TAG, "TaskController.disseminateMessage() called");
// Attempt to disseminate the message
boolean success;
try
{
success = new SendMessageController().disseminateMessage(msgPayload, toPubkey, POWDone);
}
catch (RuntimeException runEx)
{
Log.e(TAG, "While running TaskController.disseminateMessage(), SendMessageController.disseminateMessage() threw a RuntimeExecption. \n" +
"The exception message was: " + runEx.getMessage());
new QueueRecordProcessor().updateQueueRecordAfterFailure(inputQueueRecord); // Update the QueueRecord to record the failed attempt
return false; // If we failed to disseminate the message, leave the QueueRecord for that
// task in place so that it can be attempted again later
}
if (success)
{
// Delete the "disseminate message" QueueRecord,
new QueueRecordProcessor().deleteQueueRecord(inputQueueRecord);
// Delete the successfully disseminated payload
PayloadProvider.get(App.getContext()).deletePayload(msgPayload);
// Update the status of the original Message that the payload was derived from
ArrayList<Message> retrievedMessages = MessageProvider.get(App.getContext()).searchMessages(MessagesTable.COLUMN_MSG_PAYLOAD_ID, String.valueOf(msgPayload.getId()));
if (retrievedMessages.size() == 1)
{
Message originalMessage = retrievedMessages.get(0);
// Check whether we should expect an acknowledgement and update the original Message's status accordingly
String messageStatus;
if (BehaviourBitfieldProcessor.checkSendsAcks(toPubkey.getBehaviourBitfield()))
{
messageStatus = App.getContext().getString(R.string.message_status_message_sent);
}
else
{
messageStatus = App.getContext().getString(R.string.message_status_message_sent_no_ack_expected);
}
MessageStatusHandler.updateMessageStatus(originalMessage, messageStatus);
}
else
{
Log.e(TAG, "There should be exactly 1 result from this search. Instead " + retrievedMessages.size() + " records were found");
}
}
return success;
}
/**
* Takes all Address objects saved in the database and checks with servers
* to retrieve any new messages that have been sent to them. If any new messages
* are retrieved, they are processed, and acknowledgements for them are sent. <br><br>
*
* Note that we do NOT create QueueRecords for the 'check for messages and send acks'
* task, because it is a default action that will be carried out regularly anyway. See
* BackgroundService.processTasks().
*/
public void checkForMessagesAndSendAcks()
{
Log.i(TAG, "TaskController.checkForMessagesAndAcks() called");
try
{
// Start the message download thread.
MessageDownloadThread.getInstance().startThread();
}
catch (Exception e)
{
Log.e(TAG, "While running TaskController.checkForMessagesAndSendAcks(), MessageDownloadThread.getInstance().startThread() threw an Exception. \n" +
"The exception message was: " + e.getMessage());
}
try
{
// Start the message processing thread
MessageProcessingThread.getInstance().startThread();
}
catch (Exception e)
{
Log.e(TAG, "While running TaskController.checkForMessagesAndSendAcks(), MessageProcessingThread.getInstance().startThread() threw an Exception. \n" +
"The exception message was: " + e.getMessage());
}
}
/**
* Takes all Pubkeys objects saved in the database and checks whether any
* of them need to be disseminated again. If yes, they are disseminated
* again. <br><br>
*
* Note that we do NOT create QueueRecords for this task, because it is a default action
* that will be carried out regularly anyway. See BackgroundService.processTasks().
*
* @param doPOW - A boolean indicating whether or not to do POW for the updated
* pubkey payload
*/
public void checkIfPubkeyDisseminationIsDue(boolean doPOW)
{
Log.i(TAG, "TaskController.checkIfPubkeyDisseminationIsDue() called");
ArrayList<Address> addressesWithExpiredPubkeys = new ArrayList<Address>();
try
{
addressesWithExpiredPubkeys = new ReDisseminatePubkeysController().checkIfPubkeyDisseminationIsDue();
}
catch (RuntimeException runEx)
{
Log.e(TAG, "While running TaskController.checkIfPubkeyDisseminationIsDue(), ReDisseminatePubkeysController.checkIfPubkeyDisseminationIsDue() threw a RuntimeExecption. \n"
+ "The exception message was: " + runEx.getMessage());
}
if (addressesWithExpiredPubkeys.size() > 0)
{
reDisseminatePubkeys(addressesWithExpiredPubkeys, doPOW);
}
else
{
Log.i(TAG, "None of our pubkeys are due to be re-disseminated");
}
}
/**
* Re-disseminates the Pubkeys of any given Addresses
*
* @param addressesWithExpiredPubkeys - The Addresses which require their pubkeys to
* be regenerated and re-disseminated
* @param doPOW - A boolean indicating whether or not to do POW for the updated
* pubkey payload(s)
*/
public void reDisseminatePubkeys(ArrayList<Address> addressesWithExpiredPubkeys, boolean doPOW)
{
try
{
for (Address a : addressesWithExpiredPubkeys)
{
Payload updatedPayload = new ReDisseminatePubkeysController().regeneratePubkey(a, doPOW);
// Create a new QueueRecord to re-disseminate the pubkey
QueueRecordProcessor queueProc = new QueueRecordProcessor();
QueueRecord queueRecord = queueProc.createAndSaveQueueRecord(BackgroundService.TASK_DISSEMINATE_PUBKEY, TimeUtils.getUnixTime(), 0, updatedPayload, null, null);
// First check whether an Internet connection is available. If not, the QueueRecord for the
// 'disseminate pubkey' task will be saved (as above) and processed later
if (NetworkHelper.checkInternetAvailability() == true)
{
// Attempt to disseminate the pubkey for the newly generated identity
Log.d(TAG, "Re-disseminating the pubkey for address " + a.getAddress());
disseminatePubkey(queueRecord, updatedPayload, doPOW);
}
}
}
catch (RuntimeException runEx)
{
Log.e(TAG, "A RuntimeException was thrown while running TaskController.reDisseminatePubkeys(). \n" +
"The exception message was: " + runEx.getMessage());
}
}
}