diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 684784b6..fd41a0be 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -390,6 +390,12 @@ class MyForm(QtGui.QMainWindow): _translate( "MainWindow", "Copy destination address to clipboard"), self.on_action_SentClipboard) + self.actionCancelPoW = self.ui.sentContextMenuToolbar.addAction( + _translate( + "MainWindow", "Cancel sending"), self.on_action_CancelPoW) + self.actionContinuePoW = self.ui.sentContextMenuToolbar.addAction( + _translate( + "MainWindow", "Continue sending"), self.on_action_ContinuePoW) self.actionForceSend = self.ui.sentContextMenuToolbar.addAction( _translate( "MainWindow", "Force send"), self.on_action_ForceSend) @@ -849,6 +855,9 @@ class MyForm(QtGui.QMainWindow): elif status == 'toodifficult': statusText = _translate("MainWindow", "Problem: The work demanded by the recipient is more difficult than you are willing to do. %1").arg( unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(lastactiontime)),'utf-8')) + elif status == 'PoW_Cancelled': + statusText = _translate("MainWindow", "Problem: The sending was cancelled. %1").arg( + unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(lastactiontime)),'utf-8')) elif status == 'badkey': statusText = _translate("MainWindow", "Problem: The recipient\'s encryption key is no good. Could not encrypt message. %1").arg( unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(lastactiontime)),'utf-8')) @@ -2718,6 +2727,31 @@ class MyForm(QtGui.QMainWindow): clipboard = QtGui.QApplication.clipboard() clipboard.setText(str(addressAtCurrentRow)) + def on_action_CancelPoW(self): + #The user wants to cancel the sending + if shared.PoWQueue.empty() == True: #The PoW calculation finished + QMessageBox.about(self, _translate("MainWindow", "PoW Finished"), _translate( + "MainWindow", "The message has already been sent!")) + else: #The PoW calculation is still in progress + if shared.PoWQueue.get() == 'PoW_Single_Thread': #It is the single threaded version + QMessageBox.about(self, _translate("MainWindow", "PoW Cancelled"), _translate( + "MainWindow", "The sending was cancelled!")) + else: #The multi threaded version is different, because of the possible overhead of the termination of the threads. + QMessageBox.about(self, _translate("MainWindow", "PoW Cancellation"), _translate( + "MainWindow", "The cancellation of sending can take up to a few seconds because of the multi-threaded environment. If the message is not be sent during this period it will be cancelled!")) + time.sleep(0.2) # We need to give some time to the PoW process to terminate and change the status of the message + self.loadSent() + + def on_action_ContinuePoW(self): + #Continue sending of a message if it was cancelled by the user + currentRow = self.ui.tableWidgetSent.selectedIndexes()[0].row() + ackdataToContinue = str(self.ui.tableWidgetSent.item( + currentRow, 3).data(Qt.UserRole).toPyObject()) + sqlExecute('''UPDATE sent SET status='msgqueued' WHERE status='PoW_Cancelled' AND ackdata=?''', ackdataToContinue) + self.statusBar().showMessage(_translate( + "MainWindow", "The sending will be continued.")) + shared.workerQueue.put(('sendmessage', '')) + # Group of functions for the Address Book dialog box def on_action_AddressBookNew(self): self.click_pushButtonAddAddressBook() @@ -3060,6 +3094,12 @@ class MyForm(QtGui.QMainWindow): status, = row if status == 'toodifficult': self.popMenuSent.addAction(self.actionForceSend) + if status == 'PoW_Cancelled': + self.popMenuSent.addSeparator() + self.popMenuSent.addAction(self.actionContinuePoW) + if (status == 'doingmsgpow') and (shared.PoWQueue.empty() == False): #The PoW of a message is calculating + self.popMenuSent.addSeparator() + self.popMenuSent.addAction(self.actionCancelPoW) self.popMenuSent.exec_(self.ui.tableWidgetSent.mapToGlobal(point)) def inboxSearchLineEditPressed(self): diff --git a/src/proofofwork.py b/src/proofofwork.py index c26f2e1b..2d31e2f9 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -1,4 +1,4 @@ -#import shared +import shared #import time #from multiprocessing import Pool, cpu_count import hashlib @@ -30,16 +30,19 @@ def _pool_worker(nonce, initialHash, target, pool_size): trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) return [trialValue, nonce] -def _doSafePoW(target, initialHash): +def _doSafePoW(target, initialHash, cancellable): nonce = 0 trialValue = float('inf') while trialValue > target: + if (shared.PoWQueue.empty() == True) and cancellable: #If the PoW is cancellable it can be interrupted + return [-1,-1] #Special value for differentiation nonce += 1 trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) + + if cancellable: shared.PoWQueue.get() #If the PoW is cancellable we need to communicate its end to the UI return [trialValue, nonce] -def _doFastPoW(target, initialHash): - import shared +def _doFastPoW(target, initialHash, cancellable): import time from multiprocessing import Pool, cpu_count try: @@ -62,16 +65,33 @@ def _doFastPoW(target, initialHash): while True: time.sleep(10) # Don't let this thread return here; it will return nothing and cause an exception in bitmessagemain.py return + if (shared.PoWQueue.empty() == True) and cancellable: #If the PoW is cancellable it can be interrupted + pool.terminate() + pool.join() #Wait for the workers to exit... + return [-1, -1] #Special value for differentiation for i in range(pool_size): if result[i].ready(): result = result[i].get() pool.terminate() pool.join() #Wait for the workers to exit... - return result[0], result[1] + if cancellable: shared.PoWQueue.get() #If the PoW is cancellable we need to communicate its end to the UI + return result[0], result[1] time.sleep(0.2) -def run(target, initialHash): +def run(target, initialHash, cancellable): + import time + #Only message PoW calculations are cancellable, Key requests are not. + if cancellable: + #If the PoW is cancellable we need to communicate its beginning to the UI + if frozen == "macosx_app" or not frozen: + shared.PoWQueue.put('PoW_Single_Thread') + else: + shared.PoWQueue.put('PoW_Multi_Thread') + while shared.PoWQueue.empty() == True: #Necessary to wait because of the interprocess/interthread communication + time.sleep(0.1) + if frozen == "macosx_app" or not frozen: - return _doFastPoW(target, initialHash) + return _doFastPoW(target, initialHash, cancellable) else: - return _doSafePoW(target, initialHash) + return _doSafePoW(target, initialHash, cancellable) + diff --git a/src/shared.py b/src/shared.py index b78bbd83..8d4dda19 100644 --- a/src/shared.py +++ b/src/shared.py @@ -14,6 +14,7 @@ import ConfigParser import os import pickle import Queue +from multiprocessing import Queue as MQueue #A Multiproccessing Queue is necessary for the PoW cancellation. import random import socket import sys @@ -39,6 +40,7 @@ broadcastSendersForWhichImWatching = {} workerQueue = Queue.Queue() UISignalQueue = Queue.Queue() addressGeneratorQueue = Queue.Queue() +PoWQueue = MQueue() #Multithreaded queue for interprocess communication. It is used for the PoW calculation knownNodesLock = threading.Lock() knownNodes = {} sendDataQueues = [] #each sendData thread puts its queue in this list.