From f83b636bc049208954ae99171e6ec34b17278c13 Mon Sep 17 00:00:00 2001 From: Jordan Hall Date: Sun, 11 Aug 2013 00:02:38 +0100 Subject: [PATCH 1/2] Converted print statement in class_sqlThread to logger calls --- src/class_sqlThread.py | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index e07850e4..5bdec02d 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -59,11 +59,10 @@ class sqlThread(threading.Thread): self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( int(time.time()),)) self.conn.commit() - print 'Created messages database file' + logger.info('Created messages database file') except Exception as err: if str(err) == 'table inbox already exists': - with shared.printLock: - print 'Database file already exists.' + logger.debug('Database file already exists.') else: sys.stderr.write( @@ -145,13 +144,13 @@ class sqlThread(threading.Thread): self.cur.execute(item, parameters) if self.cur.fetchall() == []: # The settings table doesn't exist. We need to make it. - print 'In messages.dat database, creating new \'settings\' table.' + logger.debug('In messages.dat database, creating new \'settings\' table.') self.cur.execute( '''CREATE TABLE settings (key text, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' ) self.cur.execute( '''INSERT INTO settings VALUES('version','1')''') self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( int(time.time()),)) - print 'In messages.dat database, removing an obsolete field from the pubkeys table.' + logger.debug('In messages.dat database, removing an obsolete field from the pubkeys table.') self.cur.execute( '''CREATE TEMPORARY TABLE pubkeys_backup(hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE);''') self.cur.execute( @@ -162,17 +161,17 @@ class sqlThread(threading.Thread): self.cur.execute( '''INSERT INTO pubkeys SELECT hash, transmitdata, time, usedpersonally FROM pubkeys_backup;''') self.cur.execute( '''DROP TABLE pubkeys_backup;''') - print 'Deleting all pubkeys from inventory. They will be redownloaded and then saved with the correct times.' + logger.debug('Deleting all pubkeys from inventory. They will be redownloaded and then saved with the correct times.') self.cur.execute( '''delete from inventory where objecttype = 'pubkey';''') - print 'replacing Bitmessage announcements mailing list with a new one.' + logger.debug('replacing Bitmessage announcements mailing list with a new one.') self.cur.execute( '''delete from subscriptions where address='BM-BbkPSZbzPwpVcYZpU4yHwf9ZPEapN5Zx' ''') self.cur.execute( '''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') - print 'Commiting.' + logger.debug('Commiting.') self.conn.commit() - print 'Vacuuming message.dat. You might notice that the file size gets much smaller.' + logger.debug('Vacuuming message.dat. You might notice that the file size gets much smaller.') self.cur.execute( ''' VACUUM ''') # After code refactoring, the possible status values for sent messages @@ -203,11 +202,11 @@ class sqlThread(threading.Thread): self.cur.execute('''DELETE FROM pubkeys WHERE hash='1234' ''') self.conn.commit() if transmitdata == '': - sys.stderr.write('Problem: The version of SQLite you have cannot store Null values. Please download and install the latest revision of your version of Python (for example, the latest Python 2.7 revision) and try again.\n') - sys.stderr.write('PyBitmessage will now exit very abruptly. You may now see threading errors related to this abrupt exit but the problem you need to solve is related to SQLite.\n\n') + logger.fatal('Problem: The version of SQLite you have cannot store Null values. Please download and install the latest revision of your version of Python (for example, the latest Python 2.7 revision) and try again.\n') + logger.fatal('PyBitmessage will now exit very abruptly. You may now see threading errors related to this abrupt exit but the problem you need to solve is related to SQLite.\n\n') os._exit(0) except Exception as err: - print err + logger.error(err) # Let us check to see the last time we vaccumed the messages.dat file. # If it has been more than a month let's do it now. @@ -218,7 +217,7 @@ class sqlThread(threading.Thread): for row in queryreturn: value, = row if int(value) < int(time.time()) - 2592000: - print 'It has been a long time since the messages.dat file has been vacuumed. Vacuuming now...' + logger.info('It has been a long time since the messages.dat file has been vacuumed. Vacuuming now...') self.cur.execute( ''' VACUUM ''') item = '''update settings set value=? WHERE key='lastvacuumtime';''' parameters = (int(time.time()),) @@ -230,13 +229,11 @@ class sqlThread(threading.Thread): self.conn.commit() elif item == 'exit': self.conn.close() - with shared.printLock: - print 'sqlThread exiting gracefully.' + logger.info('sqlThread exiting gracefully.') return elif item == 'movemessagstoprog': - with shared.printLock: - print 'the sqlThread is moving the messages.dat file to the local program directory.' + logger.debug('the sqlThread is moving the messages.dat file to the local program directory.') self.conn.commit() self.conn.close() @@ -246,8 +243,7 @@ class sqlThread(threading.Thread): self.conn.text_factory = str self.cur = self.conn.cursor() elif item == 'movemessagstoappdata': - with shared.printLock: - print 'the sqlThread is moving the messages.dat file to the Appdata folder.' + logger.debug('the sqlThread is moving the messages.dat file to the Appdata folder.') self.conn.commit() self.conn.close() @@ -268,10 +264,8 @@ class sqlThread(threading.Thread): try: self.cur.execute(item, parameters) except Exception as err: - with shared.printLock: - sys.stderr.write('\nMajor error occurred when trying to execute a SQL statement within the sqlThread. Please tell Atheros about this error message or post it in the forum! Error occurred while trying to execute statement: "' + str( - item) + '" Here are the parameters; you might want to censor this data with asterisks (***) as it can contain private information: ' + str(repr(parameters)) + '\nHere is the actual error message thrown by the sqlThread: ' + str(err) + '\n') - sys.stderr.write('This program shall now abruptly exit!\n') + logger.fatal('Major error occurred when trying to execute a SQL statement within the sqlThread. Please tell Atheros about this error message or post it in the forum! Error occurred while trying to execute statement: "%s" Here are the parameters; you might want to censor this data with asterisks (***) as it can contain private information: %s. Here is the actual error message thrown by the sqlThread: %s', str(item), str(repr(parameters)), str(err)) + logger.fatal('This program shall now abruptly exit!') os._exit(0) From 9d3a0a160fe152e0d4978dbd95b05e909c4c5ef5 Mon Sep 17 00:00:00 2001 From: Jordan Hall Date: Sun, 11 Aug 2013 00:08:48 +0100 Subject: [PATCH 2/2] Merging with master and resolving conflicts --- src/bitmessagemain.py | 114 ++++++++++++++++++++++++++++-- src/bitmessageqt/__init__.py | 5 ++ src/bitmessageqt/bitmessageui.py | 8 +-- src/bitmessageqt/bitmessageui.ui | 10 +-- src/bitmessageqt/newchandialog.py | 13 ++-- src/bitmessageqt/newchandialog.ui | 12 +++- src/bitmessageqt/settings.py | 60 ++++++++-------- src/bitmessageqt/settings.ui | 75 +++++++++++--------- src/class_singleCleaner.py | 4 +- src/class_singleWorker.py | 14 ++++ src/class_sqlThread.py | 19 ++++- src/defaultKnownNodes.py | 8 +-- src/message_data_reader.py | 15 +--- src/shared.py | 27 +++---- 14 files changed, 269 insertions(+), 115 deletions(-) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index b39bdc96..cae8daeb 100644 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -738,6 +738,115 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): data += json.dumps({'label':label.encode('base64'), 'address': address, 'enabled': enabled == 1}, indent=4, separators=(',',': ')) data += ']}' return data + elif method == 'disseminatePreEncryptedMsg': + # The device issuing this command to PyBitmessage supplies a msg object that has + # already been encrypted and had the necessary proof of work done for it to be + # disseminated to the rest of the Bitmessage network. PyBitmessage accepts this msg + # object and sends it out to the rest of the Bitmessage network as if it had generated the + # message itself. Please do not yet add this to the api doc. + if len(params) != 1: + return 'API Error 0000: I need 1 parameter!' + encryptedPayload, = params + encryptedPayload = encryptedPayload.decode('hex') + toStreamNumber = decodeVarint(encryptedPayload[16:26])[0] + inventoryHash = calculateInventoryHash(encryptedPayload) + objectType = 'msg' + shared.inventory[inventoryHash] = ( + objectType, toStreamNumber, encryptedPayload, int(time.time())) + with shared.printLock: + print 'Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', inventoryHash.encode('hex') + shared.broadcastToSendDataQueues(( + toStreamNumber, 'sendinv', inventoryHash)) + elif method == 'disseminatePubkey': + # The device issuing this command to PyBitmessage supplies a pubkey object that has + # already had the necessary proof of work done for it to be disseminated to the rest of the + # Bitmessage network. PyBitmessage accepts this pubkey object and sends it out to the + # rest of the Bitmessage network as if it had generated the pubkey object itself. Please + # do not yet add this to the api doc. + if len(params) != 1: + return 'API Error 0000: I need 1 parameter!' + payload, = params + payload = payload.decode('hex') + pubkeyReadPosition = 8 # bypass the nonce + if payload[pubkeyReadPosition:pubkeyReadPosition+4] == '\x00\x00\x00\x00': # if this pubkey uses 8 byte time + pubkeyReadPosition += 8 + else: + pubkeyReadPosition += 4 + addressVersion, addressVersionLength = decodeVarint(payload[pubkeyReadPosition:pubkeyReadPosition+10]) + pubkeyReadPosition += addressVersionLength + pubkeyStreamNumber = decodeVarint(payload[pubkeyReadPosition:pubkeyReadPosition+10])[0] + inventoryHash = calculateInventoryHash(payload) + objectType = 'pubkey' + shared.inventory[inventoryHash] = ( + objectType, pubkeyStreamNumber, payload, int(time.time())) + with shared.printLock: + print 'broadcasting inv within API command disseminatePubkey with hash:', inventoryHash.encode('hex') + shared.broadcastToSendDataQueues(( + streamNumber, 'sendinv', inventoryHash)) + elif method == 'getMessageDataByDestinationHash': + # Method will eventually be used by a particular Android app to + # select relevant messages. Do not yet add this to the api + # doc. + + if len(params) != 1: + return 'API Error 0000: I need 1 parameter!' + requestedHash, = params + if len(requestedHash) != 40: + return 'API Error 0019: The length of hash should be 20 bytes (encoded in hex thus 40 characters).' + requestedHash = requestedHash.decode('hex') + + # This is not a particularly commonly used API function. Before we + # use it we'll need to fill out a field in our inventory database + # which is blank by default (first20bytesofencryptedmessage). + parameters = '' + with shared.sqlLock: + shared.sqlSubmitQueue.put('''SELECT hash, payload FROM inventory WHERE first20bytesofencryptedmessage = '' and objecttype = 'msg' ; ''') + shared.sqlSubmitQueue.put(parameters) + queryreturn = shared.sqlReturnQueue.get() + + for row in queryreturn: + hash, payload = row + readPosition = 16 # Nonce length + time length + readPosition += decodeVarint(payload[readPosition:readPosition+10])[1] # Stream Number length + t = (payload[readPosition:readPosition+20],hash) + shared.sqlSubmitQueue.put('''UPDATE inventory SET first20bytesofencryptedmessage=? WHERE hash=?; ''') + shared.sqlSubmitQueue.put(t) + shared.sqlReturnQueue.get() + + parameters = (requestedHash,) + with shared.sqlLock: + shared.sqlSubmitQueue.put('commit') + shared.sqlSubmitQueue.put('''SELECT payload FROM inventory WHERE first20bytesofencryptedmessage = ?''') + shared.sqlSubmitQueue.put(parameters) + queryreturn = shared.sqlReturnQueue.get() + data = '{"receivedMessageDatas":[' + for row in queryreturn: + payload, = row + if len(data) > 25: + data += ',' + data += json.dumps({'data':payload.encode('hex')}, indent=4, separators=(',', ': ')) + data += ']}' + return data + elif method == 'getPubkeyByHash': + # Method will eventually be used by a particular Android app to + # retrieve pubkeys. Please do not yet add this to the api docs. + if len(params) != 1: + return 'API Error 0000: I need 1 parameter!' + requestedHash, = params + if len(requestedHash) != 40: + return 'API Error 0019: The length of hash should be 20 bytes (encoded in hex thus 40 characters).' + requestedHash = requestedHash.decode('hex') + parameters = (requestedHash,) + with shared.sqlLock: + shared.sqlSubmitQueue.put('''SELECT transmitdata FROM pubkeys WHERE hash = ? ; ''') + shared.sqlSubmitQueue.put(parameters) + queryreturn = shared.sqlReturnQueue.get() + data = '{"pubkey":[' + for row in queryreturn: + transmitdata, = row + data += json.dumps({'data':transmitdata.encode('hex')}, indent=4, separators=(',', ': ')) + data += ']}' + return data elif method == 'clientStatus': return '{ "networkConnections" : "%s" }' % str(len(shared.connectedHostsList)) else: @@ -757,11 +866,8 @@ class singleAPI(threading.Thread): se.register_introspection_functions() se.serve_forever() +# This is a list of current connections (the thread pointers at least) selfInitiatedConnections = {} - # This is a list of current connections (the thread pointers at least) - - - if shared.useVeryEasyProofOfWorkForTesting: diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 5dd4c0b5..396d0b6e 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -2000,6 +2000,8 @@ class MyForm(QtGui.QMainWindow): self.settingsDialogInstance.ui.checkBoxShowTrayNotifications.isChecked())) shared.config.set('bitmessagesettings', 'startintray', str( self.settingsDialogInstance.ui.checkBoxStartInTray.isChecked())) + shared.config.set('bitmessagesettings', 'willinglysendtomobile', str( + self.settingsDialogInstance.ui.checkBoxWillinglySendToMobile.isChecked())) if int(shared.config.get('bitmessagesettings', 'port')) != int(self.settingsDialogInstance.ui.lineEditTCPPort.text()): if not shared.safeConfigGetBoolean('bitmessagesettings', 'dontconnect'): QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate( @@ -2996,6 +2998,8 @@ class settingsDialog(QtGui.QDialog): shared.config.getboolean('bitmessagesettings', 'showtraynotifications')) self.ui.checkBoxStartInTray.setChecked( shared.config.getboolean('bitmessagesettings', 'startintray')) + self.ui.checkBoxWillinglySendToMobile.setChecked( + shared.safeConfigGetBoolean('bitmessagesettings', 'willinglysendtomobile')) if shared.appdata == '': self.ui.checkBoxPortableMode.setChecked(True) if 'darwin' in sys.platform: @@ -3290,6 +3294,7 @@ def run(): try: translator.load("translations/bitmessage_" + str(locale.getdefaultlocale()[0])) + #translator.load("translations/bitmessage_fr_BE") # test French except: # The above is not compatible with all versions of OSX. translator.load("translations/bitmessage_en_US") # Default to english. diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index d5388182..54a3ee6f 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'bitmessageui.ui' # -# Created: Thu Aug 1 00:22:41 2013 +# Created: Fri Aug 9 14:17:50 2013 # by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! @@ -26,7 +26,7 @@ except AttributeError: class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) - MainWindow.resize(775, 598) + MainWindow.resize(795, 580) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-24px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) @@ -422,7 +422,7 @@ class Ui_MainWindow(object): self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtGui.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 775, 21)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 795, 27)) self.menubar.setObjectName(_fromUtf8("menubar")) self.menuFile = QtGui.QMenu(self.menubar) self.menuFile.setObjectName(_fromUtf8("menuFile")) @@ -539,7 +539,7 @@ class Ui_MainWindow(object): self.textEditMessage.setHtml(_translate("MainWindow", "\n" "\n" +"\n" "


", None)) self.label.setText(_translate("MainWindow", "To:", None)) self.label_2.setText(_translate("MainWindow", "From:", None)) diff --git a/src/bitmessageqt/bitmessageui.ui b/src/bitmessageqt/bitmessageui.ui index 5d616be7..c2caebe0 100644 --- a/src/bitmessageqt/bitmessageui.ui +++ b/src/bitmessageqt/bitmessageui.ui @@ -6,8 +6,8 @@ 0 0 - 775 - 598 + 795 + 580 @@ -257,7 +257,7 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:9pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'Droid Sans'; font-size:9pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2';"><br /></p></body></html> @@ -1010,8 +1010,8 @@ p, li { white-space: pre-wrap; } 0 0 - 775 - 21 + 795 + 27 diff --git a/src/bitmessageqt/newchandialog.py b/src/bitmessageqt/newchandialog.py index 43235bfa..65da1f1f 100644 --- a/src/bitmessageqt/newchandialog.py +++ b/src/bitmessageqt/newchandialog.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'newchandialog.ui' # -# Created: Mon Jul 22 01:05:35 2013 -# by: PyQt4 UI code generator 4.10.2 +# Created: Wed Aug 7 16:51:29 2013 +# by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! @@ -26,7 +26,7 @@ except AttributeError: class Ui_newChanDialog(object): def setupUi(self, newChanDialog): newChanDialog.setObjectName(_fromUtf8("newChanDialog")) - newChanDialog.resize(530, 422) + newChanDialog.resize(553, 422) newChanDialog.setMinimumSize(QtCore.QSize(0, 0)) self.formLayout = QtGui.QFormLayout(newChanDialog) self.formLayout.setObjectName(_fromUtf8("formLayout")) @@ -87,13 +87,18 @@ class Ui_newChanDialog(object): QtCore.QObject.connect(self.radioButtonJoinChan, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBoxJoinChan.setShown) QtCore.QObject.connect(self.radioButtonCreateChan, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBoxCreateChan.setShown) QtCore.QMetaObject.connectSlotsByName(newChanDialog) + newChanDialog.setTabOrder(self.radioButtonJoinChan, self.radioButtonCreateChan) + newChanDialog.setTabOrder(self.radioButtonCreateChan, self.lineEditChanNameCreate) + newChanDialog.setTabOrder(self.lineEditChanNameCreate, self.lineEditChanNameJoin) + newChanDialog.setTabOrder(self.lineEditChanNameJoin, self.lineEditChanBitmessageAddress) + newChanDialog.setTabOrder(self.lineEditChanBitmessageAddress, self.buttonBox) def retranslateUi(self, newChanDialog): newChanDialog.setWindowTitle(_translate("newChanDialog", "Dialog", None)) self.radioButtonCreateChan.setText(_translate("newChanDialog", "Create a new chan", None)) self.radioButtonJoinChan.setText(_translate("newChanDialog", "Join a chan", None)) self.groupBoxCreateChan.setTitle(_translate("newChanDialog", "Create a chan", None)) - self.label_4.setText(_translate("newChanDialog", "Enter a name for your chan. If you choose a sufficiently complex chan name (like a strong and unique passphrase) and none of your friends share it publicly then the chan will be secure and private.", None)) + self.label_4.setText(_translate("newChanDialog", "

Enter a name for your chan. If you choose a sufficiently complex chan name (like a strong and unique passphrase) and none of your friends share it publicly then the chan will be secure and private. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.

", None)) self.label_5.setText(_translate("newChanDialog", "Chan name:", None)) self.groupBoxJoinChan.setTitle(_translate("newChanDialog", "Join a chan", None)) self.label.setText(_translate("newChanDialog", "

A chan exists when a group of people share the same decryption keys. The keys and bitmessage address used by a chan are generated from a human-friendly word or phrase (the chan name). To send a message to everyone in the chan, send a normal person-to-person message to the chan address.

Chans are experimental and completely unmoderatable.

", None)) diff --git a/src/bitmessageqt/newchandialog.ui b/src/bitmessageqt/newchandialog.ui index 3713c6a3..2d42b3db 100644 --- a/src/bitmessageqt/newchandialog.ui +++ b/src/bitmessageqt/newchandialog.ui @@ -6,7 +6,7 @@ 0 0 - 530 + 553 422 @@ -46,7 +46,7 @@ - Enter a name for your chan. If you choose a sufficiently complex chan name (like a strong and unique passphrase) and none of your friends share it publicly then the chan will be secure and private. + <html><head/><body><p>Enter a name for your chan. If you choose a sufficiently complex chan name (like a strong and unique passphrase) and none of your friends share it publicly then the chan will be secure and private. If you and someone else both create a chan with the same chan name then it is currently very likely that they will be the same chan.</p></body></html> true @@ -130,6 +130,14 @@
+ + radioButtonJoinChan + radioButtonCreateChan + lineEditChanNameCreate + lineEditChanNameJoin + lineEditChanBitmessageAddress + buttonBox + diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index d7672e9d..40a57f5b 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Fri Jul 12 12:37:53 2013 -# by: PyQt4 UI code generator 4.10.1 +# Created: Wed Aug 7 16:58:45 2013 +# by: PyQt4 UI code generator 4.10 # # WARNING! All changes made in this file will be lost! @@ -26,7 +26,7 @@ except AttributeError: class Ui_settingsDialog(object): def setupUi(self, settingsDialog): settingsDialog.setObjectName(_fromUtf8("settingsDialog")) - settingsDialog.resize(445, 343) + settingsDialog.resize(462, 343) self.gridLayout = QtGui.QGridLayout(settingsDialog) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.buttonBox = QtGui.QDialogButtonBox(settingsDialog) @@ -41,33 +41,36 @@ class Ui_settingsDialog(object): self.tabUserInterface.setObjectName(_fromUtf8("tabUserInterface")) self.gridLayout_5 = QtGui.QGridLayout(self.tabUserInterface) self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) - self.checkBoxStartOnLogon = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxStartOnLogon.setObjectName(_fromUtf8("checkBoxStartOnLogon")) - self.gridLayout_5.addWidget(self.checkBoxStartOnLogon, 0, 0, 1, 1) - self.checkBoxStartInTray = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxStartInTray.setObjectName(_fromUtf8("checkBoxStartInTray")) - self.gridLayout_5.addWidget(self.checkBoxStartInTray, 1, 0, 1, 1) - self.checkBoxMinimizeToTray = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxMinimizeToTray.setChecked(True) - self.checkBoxMinimizeToTray.setObjectName(_fromUtf8("checkBoxMinimizeToTray")) - self.gridLayout_5.addWidget(self.checkBoxMinimizeToTray, 2, 0, 1, 1) - self.checkBoxShowTrayNotifications = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxShowTrayNotifications.setObjectName(_fromUtf8("checkBoxShowTrayNotifications")) - self.gridLayout_5.addWidget(self.checkBoxShowTrayNotifications, 3, 0, 1, 1) - self.checkBoxPortableMode = QtGui.QCheckBox(self.tabUserInterface) - self.checkBoxPortableMode.setObjectName(_fromUtf8("checkBoxPortableMode")) - self.gridLayout_5.addWidget(self.checkBoxPortableMode, 4, 0, 1, 1) - self.label_7 = QtGui.QLabel(self.tabUserInterface) - self.label_7.setWordWrap(True) - self.label_7.setObjectName(_fromUtf8("label_7")) - self.gridLayout_5.addWidget(self.label_7, 5, 0, 1, 1) self.labelSettingsNote = QtGui.QLabel(self.tabUserInterface) self.labelSettingsNote.setText(_fromUtf8("")) self.labelSettingsNote.setWordWrap(True) self.labelSettingsNote.setObjectName(_fromUtf8("labelSettingsNote")) - self.gridLayout_5.addWidget(self.labelSettingsNote, 6, 0, 1, 1) + self.gridLayout_5.addWidget(self.labelSettingsNote, 7, 0, 1, 1) spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) - self.gridLayout_5.addItem(spacerItem, 7, 0, 1, 1) + self.gridLayout_5.addItem(spacerItem, 8, 0, 1, 1) + self.checkBoxStartInTray = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxStartInTray.setObjectName(_fromUtf8("checkBoxStartInTray")) + self.gridLayout_5.addWidget(self.checkBoxStartInTray, 1, 0, 1, 1) + self.checkBoxShowTrayNotifications = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxShowTrayNotifications.setObjectName(_fromUtf8("checkBoxShowTrayNotifications")) + self.gridLayout_5.addWidget(self.checkBoxShowTrayNotifications, 3, 0, 1, 1) + self.checkBoxMinimizeToTray = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxMinimizeToTray.setChecked(True) + self.checkBoxMinimizeToTray.setObjectName(_fromUtf8("checkBoxMinimizeToTray")) + self.gridLayout_5.addWidget(self.checkBoxMinimizeToTray, 2, 0, 1, 1) + self.label_7 = QtGui.QLabel(self.tabUserInterface) + self.label_7.setWordWrap(True) + self.label_7.setObjectName(_fromUtf8("label_7")) + self.gridLayout_5.addWidget(self.label_7, 5, 0, 1, 1) + self.checkBoxStartOnLogon = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxStartOnLogon.setObjectName(_fromUtf8("checkBoxStartOnLogon")) + self.gridLayout_5.addWidget(self.checkBoxStartOnLogon, 0, 0, 1, 1) + self.checkBoxPortableMode = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxPortableMode.setObjectName(_fromUtf8("checkBoxPortableMode")) + self.gridLayout_5.addWidget(self.checkBoxPortableMode, 4, 0, 1, 1) + self.checkBoxWillinglySendToMobile = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxWillinglySendToMobile.setObjectName(_fromUtf8("checkBoxWillinglySendToMobile")) + self.gridLayout_5.addWidget(self.checkBoxWillinglySendToMobile, 6, 0, 1, 1) self.tabWidgetSettings.addTab(self.tabUserInterface, _fromUtf8("")) self.tabNetworkSettings = QtGui.QWidget() self.tabNetworkSettings.setObjectName(_fromUtf8("tabNetworkSettings")) @@ -252,12 +255,13 @@ class Ui_settingsDialog(object): def retranslateUi(self, settingsDialog): settingsDialog.setWindowTitle(_translate("settingsDialog", "Settings", None)) - self.checkBoxStartOnLogon.setText(_translate("settingsDialog", "Start Bitmessage on user login", None)) self.checkBoxStartInTray.setText(_translate("settingsDialog", "Start Bitmessage in the tray (don\'t show main window)", None)) - self.checkBoxMinimizeToTray.setText(_translate("settingsDialog", "Minimize to tray", None)) self.checkBoxShowTrayNotifications.setText(_translate("settingsDialog", "Show notification when message received", None)) - self.checkBoxPortableMode.setText(_translate("settingsDialog", "Run in Portable Mode", None)) + self.checkBoxMinimizeToTray.setText(_translate("settingsDialog", "Minimize to tray", None)) self.label_7.setText(_translate("settingsDialog", "In Portable Mode, messages and config files are stored in the same directory as the program rather than the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive.", None)) + self.checkBoxStartOnLogon.setText(_translate("settingsDialog", "Start Bitmessage on user login", None)) + self.checkBoxPortableMode.setText(_translate("settingsDialog", "Run in Portable Mode", None)) + self.checkBoxWillinglySendToMobile.setText(_translate("settingsDialog", "Willingly include unencrypted destination address when sending to a mobile device", None)) self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabUserInterface), _translate("settingsDialog", "User Interface", None)) self.groupBox.setTitle(_translate("settingsDialog", "Listening port", None)) self.label.setText(_translate("settingsDialog", "Listen for connections on port:", None)) diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 9414e1a4..44c4f973 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -6,7 +6,7 @@ 0 0 - 445 + 462 343 @@ -37,13 +37,29 @@ User Interface - - + + - Start Bitmessage on user login + + + + true + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -51,6 +67,13 @@ + + + + Show notification when message received + + + @@ -61,20 +84,6 @@ - - - - Show notification when message received - - - - - - - Run in Portable Mode - - - @@ -85,28 +94,26 @@ - - + + - - - - true + Start Bitmessage on user login - - - - Qt::Vertical + + + + Run in Portable Mode - - - 20 - 40 - + + + + + + Willingly include unencrypted destination address when sending to a mobile device - + diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py index 6fed68a5..d92a37ec 100644 --- a/src/class_singleCleaner.py +++ b/src/class_singleCleaner.py @@ -33,9 +33,9 @@ class singleCleaner(threading.Thread): for hash, storedValue in shared.inventory.items(): objectType, streamNumber, payload, receivedTime = storedValue if int(time.time()) - 3600 > receivedTime: - t = (hash, objectType, streamNumber, payload, receivedTime) + t = (hash, objectType, streamNumber, payload, receivedTime,'') shared.sqlSubmitQueue.put( - '''INSERT INTO inventory VALUES (?,?,?,?,?)''') + '''INSERT INTO inventory VALUES (?,?,?,?,?,?)''') shared.sqlSubmitQueue.put(t) shared.sqlReturnQueue.get() del shared.inventory[hash] diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index 56f23859..b29b7f8d 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -9,6 +9,7 @@ import proofofwork import sys from class_addressGenerator import pointMult import tr +from debug import logger # This thread, of which there is only one, does the heavy lifting: # calculating POWs. @@ -517,6 +518,15 @@ class singleWorker(threading.Thread): pubkeyPayload[readPosition:readPosition + 10]) readPosition += streamNumberLength behaviorBitfield = pubkeyPayload[readPosition:readPosition + 4] + # Mobile users may ask us to include their address's RIPE hash on a message + # unencrypted. Before we actually do it the sending human must check a box + # in the settings menu to allow it. + if shared.isBitSetWithinBitfield(behaviorBitfield,30): # if receiver is a mobile device who expects that their address RIPE is included unencrypted on the front of the message.. + if not shared.safeConfigGetBoolean('bitmessagesettings','willinglysendtomobile'): # if we are Not willing to include the receiver's RIPE hash on the message.. + logger.info('The receiver is a mobile user but the sender (you) has not selected that you are willing to send to mobiles. Aborting send.') + shared.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,tr.translateText("MainWindow",'Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1').arg(unicode(strftime(shared.config.get('bitmessagesettings', 'timeformat'),localtime(int(time.time()))),'utf-8'))))) + # if the human changes their setting and then sends another message or restarts their client, this one will send at that time. + continue readPosition += 4 # to bypass the bitfield of behaviors # pubSigningKeyBase256 = # pubkeyPayload[readPosition:readPosition+64] #We don't use this @@ -663,6 +673,10 @@ class singleWorker(threading.Thread): with shared.printLock: print 'Not bothering to generate ackdata because we are sending to a chan.' fullAckPayload = '' + elif not shared.isBitSetWithinBitfield(behaviorBitfield,31): + with shared.printLock: + print 'Not bothering to generate ackdata because the receiver said that they won\'t relay it anyway.' + fullAckPayload = '' else: fullAckPayload = self.generateFullAckMessage( ackdata, toStreamNumber, embeddedTime) # The fullAckPayload is a normal msg protocol message with the proof of work already completed that the receiver of this message can easily send out. diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 5bdec02d..97215a24 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -46,7 +46,7 @@ class sqlThread(threading.Thread): self.cur.execute( '''CREATE TABLE pubkeys (hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE)''' ) self.cur.execute( - '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, UNIQUE(hash) ON CONFLICT REPLACE)''' ) + '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, first20bytesofencryptedmessage blob, UNIQUE(hash) ON CONFLICT REPLACE)''' ) self.cur.execute( '''CREATE TABLE knownnodes (timelastseen int, stream int, services blob, host blob, port blob, UNIQUE(host, stream, port) ON CONFLICT REPLACE)''' ) # This table isn't used in the program yet but I @@ -55,7 +55,7 @@ class sqlThread(threading.Thread): '''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') self.cur.execute( '''CREATE TABLE settings (key blob, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' ) - self.cur.execute( '''INSERT INTO settings VALUES('version','1')''') + self.cur.execute( '''INSERT INTO settings VALUES('version','2')''') self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( int(time.time()),)) self.conn.commit() @@ -188,7 +188,20 @@ class sqlThread(threading.Thread): if not shared.config.has_option('bitmessagesettings', 'sockslisten'): shared.config.set('bitmessagesettings', 'sockslisten', 'false') - + + # Add a new column to the inventory table to store the first 20 bytes of encrypted messages to support Android app + item = '''SELECT value FROM settings WHERE key='version';''' + parameters = '' + self.cur.execute(item, parameters) + if int(self.cur.fetchall()[0][0]) == 1: + print 'upgrading database' + item = '''ALTER TABLE inventory ADD first20bytesofencryptedmessage blob DEFAULT '' ''' + parameters = '' + self.cur.execute(item, parameters) + item = '''update settings set value=? WHERE key='version';''' + parameters = (2,) + self.cur.execute(item, parameters) + try: testpayload = '\x00\x00' t = ('1234', testpayload, '12345678', 'no') diff --git a/src/defaultKnownNodes.py b/src/defaultKnownNodes.py index 52e31fff..f481581c 100644 --- a/src/defaultKnownNodes.py +++ b/src/defaultKnownNodes.py @@ -37,12 +37,10 @@ def createDefaultKnownNodes(appdata): #print stream1 #print allKnownNodes - output = open(appdata + 'knownnodes.dat', 'wb') + with open(appdata + 'knownnodes.dat', 'wb') as output: + # Pickle dictionary using protocol 0. + pickle.dump(allKnownNodes, output) - # Pickle dictionary using protocol 0. - pickle.dump(allKnownNodes, output) - - output.close() return allKnownNodes def readDefaultKnownNodes(appdata): diff --git a/src/message_data_reader.py b/src/message_data_reader.py index c600935d..084ef553 100644 --- a/src/message_data_reader.py +++ b/src/message_data_reader.py @@ -5,19 +5,10 @@ import sqlite3 from time import strftime, localtime import sys +import shared +import string -APPNAME = "PyBitmessage" -from os import path, environ -if sys.platform == 'darwin': - if "HOME" in environ: - appdata = path.join(environ["HOME"], "Library/Application support/", APPNAME) + '/' - else: - print 'Could not find home folder, please report this message and your OS X version to the BitMessage Github.' - sys.exit() -elif 'win' in sys.platform: - appdata = path.join(environ['APPDATA'], APPNAME) + '\\' -else: - appdata = path.expanduser(path.join("~", "." + APPNAME + "/")) +appdata = shared.lookupAppdataFolder() conn = sqlite3.connect( appdata + 'messages.dat' ) conn.text_factory = str diff --git a/src/shared.py b/src/shared.py index 9b8c9325..da83f374 100644 --- a/src/shared.py +++ b/src/shared.py @@ -101,10 +101,8 @@ def assembleVersionMessage(remoteHost, remotePort, myStreamNumber): random.seed() payload += eightBytesOfRandomDataUsedToDetectConnectionsToSelf - userAgent = '/PyBitmessage:' + shared.softwareVersion + \ - '/' # Length of userAgent must be less than 253. - payload += pack('>B', len( - userAgent)) # user agent string length. If the user agent is more than 252 bytes long, this code isn't going to work. + userAgent = '/PyBitmessage:' + shared.softwareVersion + '/' + payload += encodeVarint(len(userAgent)) payload += userAgent payload += encodeVarint( 1) # The number of streams about which I care. PyBitmessage currently only supports 1 per connection. @@ -203,17 +201,17 @@ def decodeWalletImportFormat(WIFstring): fullString = arithmetic.changebase(WIFstring,58,256) privkey = fullString[:-4] if fullString[-4:] != hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: - logger.error('Major problem! When trying to decode one of your private keys, the checksum ' - 'failed. Here is the PRIVATE key: %s\n' % str(WIFstring)) + logger.critical('Major problem! When trying to decode one of your private keys, the checksum ' + 'failed. Here is the PRIVATE key: %s' % str(WIFstring)) return "" else: #checksum passed if privkey[0] == '\x80': return privkey[1:] else: - logger.error('Major problem! When trying to decode one of your private keys, the ' + logger.critical('Major problem! When trying to decode one of your private keys, the ' 'checksum passed but the key doesn\'t begin with hex 80. Here is the ' - 'PRIVATE key: %s\n' % str(WIFstring)) + 'PRIVATE key: %s' % str(WIFstring)) return "" @@ -243,8 +241,7 @@ def reloadMyAddressHashes(): myAddressesByHash[hash] = addressInKeysFile else: - logger.error('Error in reloadMyAddressHashes: Can\'t handle address ' - 'versions other than 2 or 3.\n') + logger.error('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2 or 3.\n') if not keyfileSecure: fixSensitiveFilePermissions(appdata + 'keys.dat', hasEnabledKeys) @@ -320,8 +317,8 @@ def flushInventory(): sqlLock.acquire() for hash, storedValue in inventory.items(): objectType, streamNumber, payload, receivedTime = storedValue - t = (hash,objectType,streamNumber,payload,receivedTime) - sqlSubmitQueue.put('''INSERT INTO inventory VALUES (?,?,?,?,?)''') + t = (hash,objectType,streamNumber,payload,receivedTime,'') + sqlSubmitQueue.put('''INSERT INTO inventory VALUES (?,?,?,?,?,?)''') sqlSubmitQueue.put(t) sqlReturnQueue.get() del inventory[hash] @@ -383,6 +380,12 @@ def fixSensitiveFilePermissions(filename, hasEnabledKeys): except Exception, e: logger.exception('Keyfile permissions could not be fixed.') raise + +def isBitSetWithinBitfield(fourByteString, n): + # Uses MSB 0 bit numbering across 4 bytes of data + n = 31 - n + x, = unpack('>L', fourByteString) + return x & 2**n != 0 Peer = collections.namedtuple('Peer', ['host', 'port'])