if(int(time.time())-timeLastSeen)>172800andlen(knownNodes[self.streamNumber])>1000:# for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the knownNodes data-structure.
knownNodesLock.acquire()
delknownNodes[self.streamNumber][HOST]
knownNodesLock.release()
print'deleting ',HOST,'from knownNodes because it is more than 48 hours old and we could not connect to it.'
if(int(time.time())-timeLastSeen)>172800andlen(knownNodes[self.streamNumber])>1000:# for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the knownNodes data-structure.
knownNodesLock.acquire()
delknownNodes[self.streamNumber][HOST]
knownNodesLock.release()
print'deleting ',HOST,'from knownNodes because it is more than 48 hours old and we could not connect to it.'
exceptException,err:
print'An exception has occurred in the outgoingSynSender thread that was not caught by other exception types:',err
@ -323,7 +327,9 @@ class receiveDataThread(QThread):
#print 'message checksum is correct'
#The time we've last seen this node is obviously right now since we just received valid data from it. So update the knownNodes list so that other peers can be made aware of its existance.
ifself.initiatedConnection:#The remote port is only something we should share with others if it is the remote node's incoming port (rather than some random operating-system-assigned outgoing port).
ifself.payloadLength<=180000000:#If the size of the message is greater than 180MB, ignore it. (I get memory errors when processing messages much larger than this though it is concievable that this value will have to be lowered if some systems are less tolarant of large messages.)
remoteCommand=self.data[4:16]
printLock.acquire()
@ -401,7 +407,7 @@ class receiveDataThread(QThread):
#Notice that I have divided the averageProofOfWorkNonceTrialsPerByte by two. This makes the POW requirement easier. This gives us wiggle-room: if we decide that we want to make the POW easier, the change won't obsolete old clients because they already expect a lower POW. If we decide that the current work done by clients feels approperate then we can remove this division by 2 and make the requirement match what is actually done by a sending node. If we want to raise the POW requirement then old nodes will HAVE to upgrade no matter what.
print'The embedded time in this broadcast message is too old. Ignoring message.'
return
ifself.payloadLength<66:#todo: When version 1 addresses are completely abandoned, this should be changed to 180
iflen(data)<180:
print'The payload length of this broadcast packet is unreasonably low. Someone is probably trying funny business. Ignoring message.'
return
inventoryLock.acquire()
@ -533,11 +539,11 @@ class receiveDataThread(QThread):
self.processbroadcast(data)#When this function returns, we will have either successfully processed this broadcast because we are interested in it, ignored it because we aren't interested in it, or found problem with the broadcast that warranted ignoring it.
# Let us now set lengthOfTimeWeShouldUseToProcessThisMessage. If we haven't used the specified amount of time, we shall sleep. These values are mostly the same values used for msg messages although broadcast messages are processed faster.
ifself.payloadLength>100000000:#Size is greater than 100 megabytes
iflen(data)>100000000:#Size is greater than 100 megabytes
@ -623,6 +629,7 @@ class receiveDataThread(QThread):
sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
sqlLock.release()
workerQueue.put(('newpubkey',(sendersAddressVersion,sendersStream,ripe.digest())))#This will check to see whether we happen to be awaiting this pubkey in order to send a message. If we are, it will do the POW and send it.
@ -654,6 +661,7 @@ class receiveDataThread(QThread):
sqlSubmitQueue.put('''INSERT INTO inbox VALUES (?,?,?,?,?,?,?)''')
@ -714,11 +722,11 @@ class receiveDataThread(QThread):
self.processmsg(readPosition,data)#When this function returns, we will have either successfully processed the message bound for us, ignored it because it isn't bound for us, or found problem with the message that warranted ignoring it.
# Let us now set lengthOfTimeWeShouldUseToProcessThisMessage. If we haven't used the specified amount of time, we shall sleep. These values are based on test timings and you may change them at-will.
ifself.payloadLength>100000000:#Size is greater than 100 megabytes
iflen(data)>100000000:#Size is greater than 100 megabytes
lengthOfTimeWeShouldUseToProcessThisMessage=100#seconds. Actual length of time it took my computer to decrypt and verify the signature of a 100 MB message: 3.7 seconds.
elifself.payloadLength>10000000:#Between 100 and 10 megabytes
eliflen(data)>10000000:#Between 100 and 10 megabytes
lengthOfTimeWeShouldUseToProcessThisMessage=20#seconds. Actual length of time it took my computer to decrypt and verify the signature of a 10 MB message: 0.53 seconds. Actual length of time it takes in practice when processing a real message: 1.44 seconds.
elifself.payloadLength>1000000:#Between 10 and 1 megabyte
eliflen(data)>1000000:#Between 10 and 1 megabyte
lengthOfTimeWeShouldUseToProcessThisMessage=3#seconds. Actual length of time it took my computer to decrypt and verify the signature of a 1 MB message: 0.18 seconds. Actual length of time it takes in practice when processing a real message: 0.30 seconds.
else:#Less than 1 megabyte
lengthOfTimeWeShouldUseToProcessThisMessage=.6#seconds. Actual length of time it took my computer to decrypt and verify the signature of a 100 KB message: 0.15 seconds. Actual length of time it takes in practice when processing a real message: 0.25 seconds.
@ -749,6 +757,7 @@ class receiveDataThread(QThread):
sqlSubmitQueue.put('UPDATE sent SET status=? WHERE ackdata=?')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
sqlLock.release()
self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),encryptedData[readPosition:],'Acknowledgement of the message received just now.')
return
@ -850,6 +859,7 @@ class receiveDataThread(QThread):
sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
sqlLock.release()
workerQueue.put(('newpubkey',(sendersAddressVersionNumber,sendersStreamNumber,ripe.digest())))#This will check to see whether we happen to be awaiting this pubkey in order to send a message. If we are, it will do the POW and send it.
blockMessage=False#Gets set to True if the user shouldn't see the message according to black or white lists.
@ -923,6 +933,7 @@ class receiveDataThread(QThread):
sqlSubmitQueue.put('''INSERT INTO inbox VALUES (?,?,?,?,?,?,?)''')
message='Message ostensibly from '+fromAddress+':\n\n'+body
fromAddress=toAddress#The fromAddress for the broadcast is the toAddress (my address) for the msg message we are currently processing.
message=strftime("%a, %Y-%m-%d%H:%M:%S UTC",gmtime())+'Message ostensibly from '+fromAddress+':\n\n'+body
fromAddress=toAddress#The fromAddress for the broadcast that we are about to send is the toAddress (my address) for the msg message we are currently processing.
ackdata=OpenSSL.rand(32)#We don't actually need the ackdata for acknowledgement since this is a broadcast message but we can use it to update the user interface when the POW is done generating.
toAddress='[Broadcast subscribers]'
ripe=''
@ -954,25 +965,13 @@ class receiveDataThread(QThread):
sqlSubmitQueue.put('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?)''')
#Now let's consider sending the acknowledgement. We'll need to make sure that our client will properly process the ackData; if the packet is malformed, we could clear out self.data and an attacker could use that behavior to determine that we were capable of decoding this message.
ackDataValidThusFar=True
iflen(ackData)<24:
print'The length of ackData is unreasonably short. Not sending ackData.'
ackDataValidThusFar=False
elifackData[0:4]!='\xe9\xbe\xb4\xd9':
print'Ackdata magic bytes were wrong. Not sending ackData.'
ackDataValidThusFar=False
ifackDataValidThusFar:
ackDataPayloadLength,=unpack('>L',ackData[16:20])
iflen(ackData)-24!=ackDataPayloadLength:
print'ackData payload length doesn\'t match the payload length specified in the header. Not sending ackdata.'
ackDataValidThusFar=False
ifackDataValidThusFar:
ifself.isAckDataValid(ackData):
print'ackData is valid. Will process it.'
self.ackDataThatWeHaveYetToSend.append(ackData)#When we have processed all data, the processData function will pop the ackData out and process it as if it is a message received from our peer.
#Display timing data
@ -986,6 +985,21 @@ class receiveDataThread(QThread):
print'Average time for all message decryption successes since startup:',sum/len(successfullyDecryptMessageTimings)
printLock.release()
defisAckDataValid(self,ackData):
iflen(ackData)<24:
print'The length of ackData is unreasonably short. Not sending ackData.'
returnFalse
ifackData[0:4]!='\xe9\xbe\xb4\xd9':
print'Ackdata magic bytes were wrong. Not sending ackData.'
returnFalse
ackDataPayloadLength,=unpack('>L',ackData[16:20])
iflen(ackData)-24!=ackDataPayloadLength:
print'ackData payload length doesn\'t match the payload length specified in the header. Not sending ackdata.'
print'addr message does not contain enough data. Ignoring.'
return
@ -1401,10 +1408,14 @@ class receiveDataThread(QThread):
continue
timeSomeoneElseReceivedMessageFromThisNode,=unpack('>I',data[lengthOfNumberOfAddresses+(34*i):4+lengthOfNumberOfAddresses+(34*i)])#This is the 'time' value in the received addr message.
ifrecaddrStreamnotinknownNodes:#knownNodes is a dictionary of dictionaries with one outer dictionary for each stream. If the outer stream dictionary doesn't exist yet then we must make it.
iflen(knownNodes[recaddrStream])<20000andtimeSomeoneElseReceivedMessageFromThisNode>(int(time.time())-10800)andtimeSomeoneElseReceivedMessageFromThisNode<(int(time.time())+10800):#If we have more than 20000 nodes in our list already then just forget about adding more. Also, make sure that the time that someone else received a message from this node is within three hours from now.
@ -1412,12 +1423,16 @@ class receiveDataThread(QThread):
else:
PORT,timeLastReceivedMessageFromThisNode=knownNodes[recaddrStream][hostFromAddrMessage]#PORT in this case is either the port we used to connect to the remote node, or the port that was specified by someone else in a past addr message.
print'Strange occurance: The port specified in an addr message',str(recaddrPort),'does not match the port',str(PORT),'that this program (or some other peer) used to connect to it',str(hostFromAddrMessage),'. Perhaps they changed their port or are using a strange NAT configuration.'
ifneedToWriteKnownNodesToDisk:#Runs if any nodes were new to us. Also, share those nodes with our peers.
'''The singleCleaner class is a timer-driven thread that cleans data structures to free memory, resends messages when a remote node doesn't respond, and sends pong messages to keep connections alive if the network isn't busy.
@ -2016,12 +2037,13 @@ class singleCleaner(QThread):
self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"Doing housekeeping (Flushing inventory in memory to disk...)")
broadcastToSendDataQueues((0,'pong','no data'))#commands the sendData threads to send out a pong message if they haven't sent anything else in the last five minutes. The socket timeout-time is 10 minutes.
@ -2040,6 +2062,7 @@ class singleCleaner(QThread):
sqlSubmitQueue.put('''DELETE FROM pubkeys WHERE time<? AND usedpersonally='no'''')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
t=()
sqlSubmitQueue.put('''select toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, status, pubkeyretrynumber, msgretrynumber FROM sent WHERE ((status='findingpubkey' OR status='sentmessage') AND folder='sent') ''')#If the message's folder='trash' then we'll ignore it.
@ -2060,7 +2083,6 @@ class singleCleaner(QThread):
sqlSubmitQueue.put('''UPDATE sent SET lastactiontime=?, pubkeyretrynumber=? WHERE toripe=?''')
print'It has been a long time and we haven\'t heard an acknowledgement to our msg. Sending again.'
@ -2068,9 +2090,9 @@ class singleCleaner(QThread):
sqlSubmitQueue.put('''UPDATE sent SET lastactiontime=?, msgretrynumber=?, status=? WHERE ackdata=?''')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
#self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Message sent again because the acknowledgement was never received. ' + strftime(config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))))
workerQueue.put(('sendmessage',toaddress))
self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),"Doing work necessary to again attempt to deliver a message...")
sqlSubmitQueue.put('commit')
sqlLock.release()
@ -2095,7 +2117,7 @@ class singleWorker(QThread):
sqlSubmitQueue.put((toripe,))
queryreturn=sqlReturnQueue.get()
sqlLock.release()
ifqueryreturn!='':#If we have the pubkey then send the message otherwise put the hash in the neededPubkeys data structure so that we will pay attention to it if it comes over the wire.
ifqueryreturn!=[]:#If we have the pubkey then send the message otherwise put the hash in the neededPubkeys data structure so that we will pay attention to it if it comes over the wire.
self.sendMsg(toripe)
else:
neededPubkeys[toripe]=0
@ -2186,7 +2208,7 @@ class singleWorker(QThread):
myAddress=addressInKeysFile
break
embeddedTime=int(time.time())+random.randrange(-300,300)#the current time plus or minus five minutes
embeddedTime=int(time.time()+random.randrange(-300,300))#the current time plus or minus five minutes
payload=pack('>I',(embeddedTime))
payload+=encodeVarint(addressVersionNumber)#Address version number
payload+=encodeVarint(streamNumber)
@ -2226,6 +2248,7 @@ class singleWorker(QThread):
sqlSubmitQueue.put('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''')
self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Broadcast sent at '+strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))))
self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Broadcast sent on '+unicode(strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))),'utf-8'))
#Update the status of the message in the 'sent' table to have a 'broadcastsent' status
sqlLock.acquire()
@ -2306,69 +2329,8 @@ class singleWorker(QThread):
sqlSubmitQueue.put('UPDATE sent SET status=?, lastactiontime=? WHERE fromaddress=? AND subject=? AND message=? AND status=?')
sqlSubmitQueue.put(t)
queryreturn=sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
sqlLock.release()
"""elif addressVersionNumber == 1: #This whole section can be taken out soon because we aren't supporting v1 addresses for much longer.
messageToTransmit='\x02'#message encoding type
messageToTransmit+=encodeVarint(len('Subject:'+subject+'\n'+'Body:'+body))#Type 2 is simple UTF-8 message encoding.
self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Broadcast sent at '+strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))))
#Update the status of the message in the 'sent' table to have a 'broadcastsent' status
self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Message sent. Waiting on acknowledgement. Sent on '+ strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))))
self.emit(SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"),ackdata,'Message sent. Waiting on acknowledgement. Sent on '+unicode(strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))),'utf-8'))
self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),'Broacasting the public key request. This program will auto-retry if they are offline.')
self.emit(SIGNAL("updateSentItemStatusByHash(PyQt_PyObject,PyQt_PyObject)"),ripe,'Sending public key request. Waiting for reply. Requested at '+ strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))))
self.emit(SIGNAL("updateSentItemStatusByHash(PyQt_PyObject,PyQt_PyObject)"),ripe,'Sending public key request. Waiting for reply. Requested at '+unicode(strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))),'utf-8'))
@ -3037,6 +2969,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
sqlSubmitQueue.put('''UPDATE inbox SET folder='trash' WHERE msgid=?''')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
sqlLock.release()
apiSignalQueue.put(('updateStatusBar','Per API: Trashed message (assuming message existed). UI not updated.'))
return'Trashed message (assuming message existed). UI not updated. To double check, run getAllInboxMessages to see that the message disappeared, or restart Bitmessage and look in the normal Bitmessage GUI.'
@ -3097,6 +3030,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
sqlSubmitQueue.put('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?)''')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
sqlLock.release()
toLabel=''
@ -3157,6 +3091,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
sqlSubmitQueue.put('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?)''')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
sqlLock.release()
toLabel='[Broadcast subscribers]'
@ -3430,9 +3365,12 @@ class MyForm(QtGui.QMainWindow):
self.actionAddSenderToAddressBook=self.ui.inboxContextMenuToolbar.addAction("Add sender to your Address Book",self.on_action_InboxAddSenderToAddressBook)
self.actionTrashInboxMessage=self.ui.inboxContextMenuToolbar.addAction("Move to Trash",self.on_action_InboxTrash)
self.actionForceHtml=self.ui.inboxContextMenuToolbar.addAction("View as Richtext",self.on_action_InboxMessageForceHtml)
@ -3651,21 +3590,22 @@ class MyForm(QtGui.QMainWindow):
ifstatus=='findingpubkey':
newItem=myTableWidgetItem('Waiting on their public key. Will request it again soon.')
elifstatus=='sentmessage':
newItem=myTableWidgetItem('Message sent. Waiting on acknowledgement. Sent at '+ strftime(config.get('bitmessagesettings','timeformat'),localtime(lastactiontime)))
newItem=myTableWidgetItem('Message sent. Waiting on acknowledgement. Sent at '+unicode(strftime(config.get('bitmessagesettings','timeformat'),localtime(lastactiontime)),'utf-8'))
elifstatus=='doingpow':
newItem=myTableWidgetItem('Need to do work to send message. Work is queued.')
elifstatus=='ackreceived':
newItem=myTableWidgetItem('Acknowledgement of the message received '+ strftime(config.get('bitmessagesettings','timeformat'),localtime(int(lastactiontime))))
newItem=myTableWidgetItem('Acknowledgement of the message received '+unicode(strftime(config.get('bitmessagesettings','timeformat'),localtime(int(lastactiontime))),'utf-8'))
elifstatus=='broadcastpending':
newItem=myTableWidgetItem('Doing the work necessary to send broadcast...')
elifstatus=='broadcastsent':
newItem=myTableWidgetItem('Broadcast on '+ strftime(config.get('bitmessagesettings','timeformat'),localtime(int(lastactiontime))))
newItem=myTableWidgetItem('Broadcast on '+unicode(strftime(config.get('bitmessagesettings','timeformat'),localtime(int(lastactiontime))),'utf-8'))
self.ui.labelStartupTime.setText('Since startup on '+ strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))))
self.ui.labelStartupTime.setText('Since startup on '+unicode(strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))),'utf-8'))
self.numberOfMessagesProcessed=0
self.numberOfBroadcastsProcessed=0
self.numberOfPubkeysProcessed=0
@ -3895,7 +3835,8 @@ class MyForm(QtGui.QMainWindow):
#newItem = QtGui.QTableWidgetItem('Doing work necessary to send broadcast...'+strftime(config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))))
#newItem = QtGui.QTableWidgetItem('Doing work necessary to send broadcast...'+ unicode(strftime(config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8'))
newItem=myTableWidgetItem('Work is queued.')
newItem.setData(Qt.UserRole,QByteArray(ackdata))
newItem.setData(33,int(time.time()))
@ -4218,6 +4162,7 @@ class MyForm(QtGui.QMainWindow):
#newItem = QtGui.QTableWidgetItem('Doing work necessary to send broadcast...'+strftime(config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))))
newItem=myTableWidgetItem('Work is queued. '+strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))))
#newItem = QtGui.QTableWidgetItem('Doing work necessary to send broadcast...'+ unicode(strftime(config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8'))
newItem=myTableWidgetItem('Work is queued. '+unicode(strftime(config.get('bitmessagesettings','timeformat'),localtime(int(time.time()))),'utf-8'))
sqlSubmitQueue.put('''INSERT INTO subscriptions VALUES (?,?,?)''')
sqlSubmitQueue.put(t)
queryreturn=sqlReturnQueue.get()
sqlSubmitQueue.put('commit')
sqlLock.release()
self.rerenderInboxFromLabels()
self.reloadBroadcastSendersForWhichImWatching()
@ -4460,9 +4411,11 @@ class MyForm(QtGui.QMainWindow):
withopen('keys.dat','wb')asconfigfile:
config.write(configfile)
#Write the knownnodes.dat file to disk in the new location
knownNodesLock.acquire()
output=open('knownnodes.dat','wb')
pickle.dump(knownNodes,output)
output.close()
knownNodesLock.release()
os.remove(appdata+'keys.dat')
os.remove(appdata+'knownnodes.dat')
appdata=''
@ -4477,9 +4430,11 @@ class MyForm(QtGui.QMainWindow):
withopen(appdata+'keys.dat','wb')asconfigfile:
config.write(configfile)
#Write the knownnodes.dat file to disk in the new location
knownNodesLock.acquire()
output=open(appdata+'knownnodes.dat','wb')
pickle.dump(knownNodes,output)
output.close()
knownNodesLock.release()
os.remove('keys.dat')
os.remove('knownnodes.dat')
QMessageBox.about(self,"Restart","Bitmessage has moved most of your config files to the application data directory but you must restart Bitmessage to move the last file (the file which holds messages).")
@ -4521,12 +4476,14 @@ class MyForm(QtGui.QMainWindow):
#sqlSubmitQueue.put('''delete from inbox where msgid=?''')
sqlSubmitQueue.put('''UPDATE inbox SET folder='trash' WHERE msgid=?''')
sqlSubmitQueue.put(t)
sqlReturnQueue.get()
sqlLock.release()
self.ui.textEditInboxMessage.setText("")
self.ui.tableWidgetInbox.removeRow(currentRow)
self.statusBar().showMessage('Moved item to trash. There is no user interface to view your trash, but it is still on disk if you are desperate to get it back.')
self.statusBar().showMessage('Moved items to trash. There is no user interface to view your trash, but it is still on disk if you are desperate to get it back.')
@ -4717,7 +4691,8 @@ class MyForm(QtGui.QMainWindow):
sqlLock.release()
self.ui.textEditSentMessage.setPlainText("")
self.ui.tableWidgetSent.removeRow(currentRow)
self.statusBar().showMessage('Moved item to trash. There is no user interface to view your trash, but it is still on disk if you are desperate to get it back.')
self.statusBar().showMessage('Moved items to trash. There is no user interface to view your trash, but it is still on disk if you are desperate to get it back.')