Added menu option to delete all trashed messages
This commit is contained in:
parent
3ee9b4ea6e
commit
3b9c5885ea
|
@ -391,7 +391,7 @@ class receiveDataThread(threading.Thread):
|
||||||
print '(concerning', self.HOST + ')', 'number of objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave is now', len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave)
|
print '(concerning', self.HOST + ')', 'number of objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave is now', len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave)
|
||||||
shared.printLock.release()
|
shared.printLock.release()
|
||||||
try:
|
try:
|
||||||
del numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST]
|
del numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST] #this data structure is maintained so that we can keep track of how many total objects, across all connections, are currently outstanding. If it goes too high it can indicate that we are under attack by multiple nodes working together.
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
break
|
break
|
||||||
|
@ -400,14 +400,14 @@ class receiveDataThread(threading.Thread):
|
||||||
print '(concerning', self.HOST + ')', 'number of objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave is now', len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave)
|
print '(concerning', self.HOST + ')', 'number of objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave is now', len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave)
|
||||||
shared.printLock.release()
|
shared.printLock.release()
|
||||||
try:
|
try:
|
||||||
del numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST]
|
del numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST] #this data structure is maintained so that we can keep track of how many total objects, across all connections, are currently outstanding. If it goes too high it can indicate that we are under attack by multiple nodes working together.
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 0:
|
if len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 0:
|
||||||
shared.printLock.acquire()
|
shared.printLock.acquire()
|
||||||
print '(concerning', self.HOST + ')', 'number of objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave is now', len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave)
|
print '(concerning', self.HOST + ')', 'number of objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave is now', len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave)
|
||||||
shared.printLock.release()
|
shared.printLock.release()
|
||||||
numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST] = len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave)
|
numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer[self.HOST] = len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) #this data structure is maintained so that we can keep track of how many total objects, across all connections, are currently outstanding. If it goes too high it can indicate that we are under attack by multiple nodes working together.
|
||||||
if len(self.ackDataThatWeHaveYetToSend) > 0:
|
if len(self.ackDataThatWeHaveYetToSend) > 0:
|
||||||
self.data = self.ackDataThatWeHaveYetToSend.pop()
|
self.data = self.ackDataThatWeHaveYetToSend.pop()
|
||||||
self.processData()
|
self.processData()
|
||||||
|
@ -1507,17 +1507,12 @@ class receiveDataThread(threading.Thread):
|
||||||
for key, value in numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer.items():
|
for key, value in numberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHavePerPeer.items():
|
||||||
totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave += value
|
totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave += value
|
||||||
print 'totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave = ', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave
|
print 'totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave = ', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave
|
||||||
"""if totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave > 200000:
|
|
||||||
shared.printLock.acquire()
|
|
||||||
print 'We already have', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave, 'items yet to retrieve from peers. Ignoring this inv message.'
|
|
||||||
shared.printLock.release()
|
|
||||||
return"""
|
|
||||||
numberOfItemsInInv, lengthOfVarint = decodeVarint(data[:10])
|
numberOfItemsInInv, lengthOfVarint = decodeVarint(data[:10])
|
||||||
if len(data) < lengthOfVarint + (numberOfItemsInInv * 32):
|
if len(data) < lengthOfVarint + (numberOfItemsInInv * 32):
|
||||||
print 'inv message doesn\'t contain enough data. Ignoring.'
|
print 'inv message doesn\'t contain enough data. Ignoring.'
|
||||||
return
|
return
|
||||||
if numberOfItemsInInv == 1: #we'll just request this data from the person who advertised the object.
|
if numberOfItemsInInv == 1: #we'll just request this data from the person who advertised the object.
|
||||||
if totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave > 200000 and len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 1000:
|
if totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave > 200000 and len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 1000: #inv flooding attack mitigation
|
||||||
shared.printLock.acquire()
|
shared.printLock.acquire()
|
||||||
print 'We already have', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave, 'items yet to retrieve from peers and over 1000 from this node in particular. Ignoring this inv message.'
|
print 'We already have', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave, 'items yet to retrieve from peers and over 1000 from this node in particular. Ignoring this inv message.'
|
||||||
shared.printLock.release()
|
shared.printLock.release()
|
||||||
|
@ -1535,7 +1530,7 @@ class receiveDataThread(threading.Thread):
|
||||||
print 'inv message lists', numberOfItemsInInv, 'objects.'
|
print 'inv message lists', numberOfItemsInInv, 'objects.'
|
||||||
for i in range(numberOfItemsInInv): #upon finishing dealing with an incoming message, the receiveDataThread will request a random object from the peer. This way if we get multiple inv messages from multiple peers which list mostly the same objects, we will make getdata requests for different random objects from the various peers.
|
for i in range(numberOfItemsInInv): #upon finishing dealing with an incoming message, the receiveDataThread will request a random object from the peer. This way if we get multiple inv messages from multiple peers which list mostly the same objects, we will make getdata requests for different random objects from the various peers.
|
||||||
if len(data[lengthOfVarint+(32*i):32+lengthOfVarint+(32*i)]) == 32: #The length of an inventory hash should be 32. If it isn't 32 then the remote node is either badly programmed or behaving nefariously.
|
if len(data[lengthOfVarint+(32*i):32+lengthOfVarint+(32*i)]) == 32: #The length of an inventory hash should be 32. If it isn't 32 then the remote node is either badly programmed or behaving nefariously.
|
||||||
if totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave > 200000 and len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 1000:
|
if totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave > 200000 and len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave) > 1000: #inv flooding attack mitigation
|
||||||
shared.printLock.acquire()
|
shared.printLock.acquire()
|
||||||
print 'We already have', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave, 'items yet to retrieve from peers and over',len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave),'from this node in particular. Ignoring the rest of this inv message.'
|
print 'We already have', totalNumberOfObjectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave, 'items yet to retrieve from peers and over',len(self.objectsThatWeHaveYetToCheckAndSeeWhetherWeAlreadyHave),'from this node in particular. Ignoring the rest of this inv message.'
|
||||||
shared.printLock.release()
|
shared.printLock.release()
|
||||||
|
@ -3977,7 +3972,7 @@ if __name__ == "__main__":
|
||||||
shared.config.set('bitmessagesettings','messagesencrypted','false')
|
shared.config.set('bitmessagesettings','messagesencrypted','false')
|
||||||
shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte',str(shared.networkDefaultProofOfWorkNonceTrialsPerByte))
|
shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte',str(shared.networkDefaultProofOfWorkNonceTrialsPerByte))
|
||||||
shared.config.set('bitmessagesettings','defaultpayloadlengthextrabytes',str(shared.networkDefaultPayloadLengthExtraBytes))
|
shared.config.set('bitmessagesettings','defaultpayloadlengthextrabytes',str(shared.networkDefaultPayloadLengthExtraBytes))
|
||||||
shared.config.set('bitmessagesettings','minimizeonclose','true')
|
shared.config.set('bitmessagesettings','minimizeonclose','false')
|
||||||
|
|
||||||
if storeConfigFilesInSameDirectoryAsProgramByDefault:
|
if storeConfigFilesInSameDirectoryAsProgramByDefault:
|
||||||
#Just use the same directory as the program and forget about the appdata folder
|
#Just use the same directory as the program and forget about the appdata folder
|
||||||
|
|
|
@ -87,6 +87,7 @@ class MyForm(QtGui.QMainWindow):
|
||||||
#FILE MENU and other buttons
|
#FILE MENU and other buttons
|
||||||
QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL("triggered()"), self.quit)
|
QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL("triggered()"), self.quit)
|
||||||
QtCore.QObject.connect(self.ui.actionManageKeys, QtCore.SIGNAL("triggered()"), self.click_actionManageKeys)
|
QtCore.QObject.connect(self.ui.actionManageKeys, QtCore.SIGNAL("triggered()"), self.click_actionManageKeys)
|
||||||
|
QtCore.QObject.connect(self.ui.actionDeleteAllTrashedMessages, QtCore.SIGNAL("triggered()"), self.click_actionDeleteAllTrashedMessages)
|
||||||
QtCore.QObject.connect(self.ui.actionRegenerateDeterministicAddresses, QtCore.SIGNAL("triggered()"), self.click_actionRegenerateDeterministicAddresses)
|
QtCore.QObject.connect(self.ui.actionRegenerateDeterministicAddresses, QtCore.SIGNAL("triggered()"), self.click_actionRegenerateDeterministicAddresses)
|
||||||
QtCore.QObject.connect(self.ui.pushButtonNewAddress, QtCore.SIGNAL("clicked()"), self.click_NewAddressDialog)
|
QtCore.QObject.connect(self.ui.pushButtonNewAddress, QtCore.SIGNAL("clicked()"), self.click_NewAddressDialog)
|
||||||
QtCore.QObject.connect(self.ui.comboBoxSendFrom, QtCore.SIGNAL("activated(int)"),self.redrawLabelFrom)
|
QtCore.QObject.connect(self.ui.comboBoxSendFrom, QtCore.SIGNAL("activated(int)"),self.redrawLabelFrom)
|
||||||
|
@ -768,6 +769,21 @@ class MyForm(QtGui.QMainWindow):
|
||||||
if reply == QtGui.QMessageBox.Yes:
|
if reply == QtGui.QMessageBox.Yes:
|
||||||
self.openKeysFile()
|
self.openKeysFile()
|
||||||
|
|
||||||
|
def click_actionDeleteAllTrashedMessages(self):
|
||||||
|
if QtGui.QMessageBox.question(self, 'Delete trash?',"Are you sure you want to delete all trashed messages?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
|
||||||
|
return
|
||||||
|
shared.sqlLock.acquire()
|
||||||
|
shared.sqlSubmitQueue.put('''delete from inbox where folder='trash' ''')
|
||||||
|
shared.sqlSubmitQueue.put('')
|
||||||
|
shared.sqlReturnQueue.get()
|
||||||
|
shared.sqlSubmitQueue.put('''delete from sent where folder='trash' ''')
|
||||||
|
shared.sqlSubmitQueue.put('')
|
||||||
|
shared.sqlReturnQueue.get()
|
||||||
|
shared.sqlSubmitQueue.put('commit') #Commit takes no parameters
|
||||||
|
shared.sqlSubmitQueue.put('vacuum')
|
||||||
|
shared.sqlSubmitQueue.put('')
|
||||||
|
shared.sqlLock.release()
|
||||||
|
|
||||||
def click_actionRegenerateDeterministicAddresses(self):
|
def click_actionRegenerateDeterministicAddresses(self):
|
||||||
self.regenerateAddressesDialogInstance = regenerateAddressesDialog(self)
|
self.regenerateAddressesDialogInstance = regenerateAddressesDialog(self)
|
||||||
if self.regenerateAddressesDialogInstance.exec_():
|
if self.regenerateAddressesDialogInstance.exec_():
|
||||||
|
@ -1206,24 +1222,6 @@ class MyForm(QtGui.QMainWindow):
|
||||||
self.ui.comboBoxSendFrom.setCurrentIndex(0)
|
self.ui.comboBoxSendFrom.setCurrentIndex(0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""def connectObjectToSignals(self,object):
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewInboxMessage)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewSentMessage)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("updateSentItemStatusByHash(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByHash)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByAckdata)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("updateNetworkStatusTab()"), self.updateNetworkStatusTab)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("incrementNumberOfMessagesProcessed()"), self.incrementNumberOfMessagesProcessed)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("incrementNumberOfPubkeysProcessed()"), self.incrementNumberOfPubkeysProcessed)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("incrementNumberOfBroadcastsProcessed()"), self.incrementNumberOfBroadcastsProcessed)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("setStatusIcon(PyQt_PyObject)"), self.setStatusIcon)
|
|
||||||
|
|
||||||
#This function exists because of the API. The API thread starts an address generator thread and must somehow connect the address generator's signals to the QApplication thread. This function is used to connect the slots and signals.
|
|
||||||
def connectObjectToAddressGeneratorSignals(self,object):
|
|
||||||
QtCore.QObject.connect(object, SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable)
|
|
||||||
QtCore.QObject.connect(object, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar)"""
|
|
||||||
|
|
||||||
#This function is called by the processmsg function when that function receives a message to an address that is acting as a pseudo-mailing-list. The message will be broadcast out. This function puts the message on the 'Sent' tab.
|
#This function is called by the processmsg function when that function receives a message to an address that is acting as a pseudo-mailing-list. The message will be broadcast out. This function puts the message on the 'Sent' tab.
|
||||||
def displayNewSentMessage(self,toAddress,toLabel,fromAddress,subject,message,ackdata):
|
def displayNewSentMessage(self,toAddress,toLabel,fromAddress,subject,message,ackdata):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Form implementation generated from reading ui file 'bitmessageui.ui'
|
# Form implementation generated from reading ui file 'bitmessageui.ui'
|
||||||
#
|
#
|
||||||
# Created: Fri May 24 16:22:22 2013
|
# Created: Tue May 28 16:22:12 2013
|
||||||
# by: PyQt4 UI code generator 4.9.4
|
# by: PyQt4 UI code generator 4.9.4
|
||||||
#
|
#
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
@ -412,7 +412,10 @@ class Ui_MainWindow(object):
|
||||||
self.actionSettings.setObjectName(_fromUtf8("actionSettings"))
|
self.actionSettings.setObjectName(_fromUtf8("actionSettings"))
|
||||||
self.actionRegenerateDeterministicAddresses = QtGui.QAction(MainWindow)
|
self.actionRegenerateDeterministicAddresses = QtGui.QAction(MainWindow)
|
||||||
self.actionRegenerateDeterministicAddresses.setObjectName(_fromUtf8("actionRegenerateDeterministicAddresses"))
|
self.actionRegenerateDeterministicAddresses.setObjectName(_fromUtf8("actionRegenerateDeterministicAddresses"))
|
||||||
|
self.actionDeleteAllTrashedMessages = QtGui.QAction(MainWindow)
|
||||||
|
self.actionDeleteAllTrashedMessages.setObjectName(_fromUtf8("actionDeleteAllTrashedMessages"))
|
||||||
self.menuFile.addAction(self.actionManageKeys)
|
self.menuFile.addAction(self.actionManageKeys)
|
||||||
|
self.menuFile.addAction(self.actionDeleteAllTrashedMessages)
|
||||||
self.menuFile.addAction(self.actionRegenerateDeterministicAddresses)
|
self.menuFile.addAction(self.actionRegenerateDeterministicAddresses)
|
||||||
self.menuFile.addAction(self.actionExit)
|
self.menuFile.addAction(self.actionExit)
|
||||||
self.menuSettings.addAction(self.actionSettings)
|
self.menuSettings.addAction(self.actionSettings)
|
||||||
|
@ -544,5 +547,6 @@ class Ui_MainWindow(object):
|
||||||
self.actionAbout.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionAbout.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.actionRegenerateDeterministicAddresses.setText(QtGui.QApplication.translate("MainWindow", "Regenerate deterministic addresses", None, QtGui.QApplication.UnicodeUTF8))
|
self.actionRegenerateDeterministicAddresses.setText(QtGui.QApplication.translate("MainWindow", "Regenerate deterministic addresses", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.actionDeleteAllTrashedMessages.setText(QtGui.QApplication.translate("MainWindow", "Delete all trashed messages", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
import bitmessage_icons_rc
|
import bitmessage_icons_rc
|
||||||
|
|
|
@ -933,6 +933,7 @@ p, li { white-space: pre-wrap; }
|
||||||
<string>File</string>
|
<string>File</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionManageKeys"/>
|
<addaction name="actionManageKeys"/>
|
||||||
|
<addaction name="actionDeleteAllTrashedMessages"/>
|
||||||
<addaction name="actionRegenerateDeterministicAddresses"/>
|
<addaction name="actionRegenerateDeterministicAddresses"/>
|
||||||
<addaction name="actionExit"/>
|
<addaction name="actionExit"/>
|
||||||
</widget>
|
</widget>
|
||||||
|
@ -1002,6 +1003,11 @@ p, li { white-space: pre-wrap; }
|
||||||
<string>Regenerate deterministic addresses</string>
|
<string>Regenerate deterministic addresses</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionDeleteAllTrashedMessages">
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete all trashed messages</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>tabWidget</tabstop>
|
<tabstop>tabWidget</tabstop>
|
||||||
|
|
|
@ -107,12 +107,12 @@ def vacuum():
|
||||||
#takeInboxMessagesOutOfTrash()
|
#takeInboxMessagesOutOfTrash()
|
||||||
#takeSentMessagesOutOfTrash()
|
#takeSentMessagesOutOfTrash()
|
||||||
#markAllInboxMessagesAsUnread()
|
#markAllInboxMessagesAsUnread()
|
||||||
#readInbox()
|
readInbox()
|
||||||
#readSent()
|
#readSent()
|
||||||
#readPubkeys()
|
#readPubkeys()
|
||||||
#readSubscriptions()
|
#readSubscriptions()
|
||||||
#readInventory()
|
#readInventory()
|
||||||
vacuum() #will defragment and clean empty space from the messages.dat file.
|
#vacuum() #will defragment and clean empty space from the messages.dat file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user