diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 684784b607..71dbb110d3 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -390,6 +390,12 @@ def init_sent_popup_menu(self): _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 @@ def loadSent(self, where="", what=""): 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 @@ def on_action_SentClipboard(self): 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 @@ def on_context_menuSent(self, point): 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/class_singleWorker.py b/src/class_singleWorker.py index e76b75a033..beca3a0c49 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -131,7 +131,7 @@ def doPOWForMyV2Pubkey(self, hash): # This function also broadcasts out the pub 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) print '(For pubkey message) Doing proof of work...' initialHash = hashlib.sha512(payload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) + trialValue, nonce = proofofwork.run(target, initialHash, False) print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload @@ -217,7 +217,7 @@ def sendOutOrStoreMyV3Pubkey(self, hash): 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) print '(For pubkey message) Doing proof of work...' initialHash = hashlib.sha512(payload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) + trialValue, nonce = proofofwork.run(target, initialHash, False) print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload @@ -313,7 +313,7 @@ def sendOutOrStoreMyV4Pubkey(self, myAddress): 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) print '(For pubkey message) Doing proof of work...' initialHash = hashlib.sha512(payload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) + trialValue, nonce = proofofwork.run(target, initialHash, False) print '(For pubkey message) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload @@ -425,7 +425,7 @@ def sendBroadcast(self): shared.UISignalQueue.put(('updateSentItemStatusByAckdata', ( ackdata, tr.translateText("MainWindow", "Doing work necessary to send broadcast...")))) initialHash = hashlib.sha512(payload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) + trialValue, nonce = proofofwork.run(target, initialHash, False) print '(For broadcast message) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload @@ -812,7 +812,14 @@ def sendMsg(self): powStartTime = time.time() initialHash = hashlib.sha512(encryptedPayload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) + trialValue, nonce = proofofwork.run(target, initialHash, True) + + #The PoW was cancelled by the user + if ((trialValue==-1) and (nonce==-1)): #if the PoW was Cancelled + sqlExecute('''UPDATE sent SET status='PoW_Cancelled' WHERE ackdata=? ''', ackdata) + shared.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr.translateText("MainWindow", "Problem: The sending was cancelled. %1").arg(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'), localtime(int(time.time()))), 'utf-8'))))) + continue + with shared.printLock: print '(For msg message) Found proof of work', trialValue, 'Nonce:', nonce try: @@ -905,7 +912,7 @@ def requestPubKey(self, toAddress): target = 2 ** 64 / ((len(payload) + shared.networkDefaultPayloadLengthExtraBytes + 8) * shared.networkDefaultProofOfWorkNonceTrialsPerByte) initialHash = hashlib.sha512(payload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) + trialValue, nonce = proofofwork.run(target, initialHash, False) with shared.printLock: print 'Found proof of work', trialValue, 'Nonce:', nonce @@ -940,7 +947,7 @@ def generateFullAckMessage(self, ackdata, toStreamNumber): powStartTime = time.time() initialHash = hashlib.sha512(payload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) + trialValue, nonce = proofofwork.run(target, initialHash, False) with shared.printLock: print '(For ack message) Found proof of work', trialValue, 'Nonce:', nonce try: diff --git a/src/proofofwork.py b/src/proofofwork.py index c26f2e1bb7..88e636c1be 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,34 @@ 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... + 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 b78bbd835e..8d4dda1968 100644 --- a/src/shared.py +++ b/src/shared.py @@ -14,6 +14,7 @@ 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 @@ 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.