diff --git a/README.md b/README.md index 979872f9..aa0b2294 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,4 @@ references * [project website](https://bitmessage.org) * [protocol specification](https://bitmessage.org/wiki/Protocol_specification) * [whitepaper](https://bitmessage.org/bitmessage.pdf) +* [installation](https://bitmessage.org/wiki/Compiling_instructions) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py old mode 100644 new mode 100755 index cae8daeb..fd233bd5 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -23,6 +23,14 @@ import json import singleton import os +# OSX python version check +import sys +if sys.platform == 'darwin': + if float("{1}.{2}".format(*sys.version_info)) < 7.5: + print "You should use python 2.7.5 or greater." + print "Your version: {0}.{1}.{2}".format(*sys.version_info) + sys.exit(0) + # Classes from class_sqlThread import * from class_singleCleaner import * @@ -34,13 +42,6 @@ from class_addressGenerator import * # Helper Functions import helper_bootstrap -import sys -if sys.platform == 'darwin': - if float("{1}.{2}".format(*sys.version_info)) < 7.5: - print "You should use python 2.7.5 or greater." - print "Your version: {0}.{1}.{2}".format(*sys.version_info) - sys.exit(0) - def connectToStream(streamNumber): selfInitiatedConnections[streamNumber] = {} if sys.platform[0:3] == 'win': diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 396d0b6e..378989e0 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -14,6 +14,7 @@ except ImportError: from addresses import * import shared from bitmessageui import * +from namecoin import namecoinConnection, ensureNamecoinOptions from newaddressdialog import * from newsubscriptiondialog import * from regenerateaddresses import * @@ -141,6 +142,8 @@ class MyForm(QtGui.QMainWindow): "clicked()"), self.click_pushButtonSend) QtCore.QObject.connect(self.ui.pushButtonLoadFromAddressBook, QtCore.SIGNAL( "clicked()"), self.click_pushButtonLoadFromAddressBook) + QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonFetchNamecoinID) QtCore.QObject.connect(self.ui.radioButtonBlacklist, QtCore.SIGNAL( "clicked()"), self.click_radioButtonBlacklist) QtCore.QObject.connect(self.ui.radioButtonWhitelist, QtCore.SIGNAL( @@ -429,10 +432,25 @@ class MyForm(QtGui.QMainWindow): "removeInboxRowByMsgid(PyQt_PyObject)"), self.removeInboxRowByMsgid) self.UISignalThread.start() -# Below this point, it would be good if all of the necessary global data -# structures were initialized. + # Below this point, it would be good if all of the necessary global data + # structures were initialized. self.rerenderComboBoxSendFrom() + + # Check to see whether we can connect to namecoin. Hide the 'Fetch Namecoin ID' button if we can't. + try: + options = {} + options["type"] = shared.config.get('bitmessagesettings', 'namecoinrpctype') + options["host"] = shared.config.get('bitmessagesettings', 'namecoinrpchost') + options["port"] = shared.config.get('bitmessagesettings', 'namecoinrpcport') + options["user"] = shared.config.get('bitmessagesettings', 'namecoinrpcuser') + options["password"] = shared.config.get('bitmessagesettings', 'namecoinrpcpassword') + nc = namecoinConnection(options) + if nc.test()[0] == 'failed': + self.ui.pushButtonFetchNamecoinID.hide() + except: + print 'There was a problem testing for a Namecoin daemon. Hiding the Fetch Namecoin ID button' + self.ui.pushButtonFetchNamecoinID.hide() # Show or hide the application window after clicking an item within the @@ -1699,6 +1717,17 @@ class MyForm(QtGui.QMainWindow): self.statusBar().showMessage(_translate( "MainWindow", "Right click one or more entries in your address book and select \'Send message to this address\'.")) + def click_pushButtonFetchNamecoinID(self): + nc = namecoinConnection() + err, addr = nc.query(str(self.ui.lineEditTo.text())) + if err is not None: + self.statusBar().showMessage(_translate( + "MainWindow", "Error: " + err)) + else: + self.ui.lineEditTo.setText(addr) + self.statusBar().showMessage(_translate( + "MainWindow", "Fetched address from namecoin identity.")) + def redrawLabelFrom(self, index): self.ui.labelFrom.setText( self.ui.comboBoxSendFrom.itemData(index).toPyObject()) @@ -2028,6 +2057,18 @@ class MyForm(QtGui.QMainWindow): self.settingsDialogInstance.ui.lineEditSocksPassword.text())) shared.config.set('bitmessagesettings', 'sockslisten', str( self.settingsDialogInstance.ui.checkBoxSocksListen.isChecked())) + + shared.config.set('bitmessagesettings', 'namecoinrpctype', + self.settingsDialogInstance.getNamecoinType()) + shared.config.set('bitmessagesettings', 'namecoinrpchost', str( + self.settingsDialogInstance.ui.lineEditNamecoinHost.text())) + shared.config.set('bitmessagesettings', 'namecoinrpcport', str( + self.settingsDialogInstance.ui.lineEditNamecoinPort.text())) + shared.config.set('bitmessagesettings', 'namecoinrpcuser', str( + self.settingsDialogInstance.ui.lineEditNamecoinUser.text())) + shared.config.set('bitmessagesettings', 'namecoinrpcpassword', str( + self.settingsDialogInstance.ui.lineEditNamecoinPassword.text())) + if float(self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) >= 1: shared.config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(int(float( self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) * shared.networkDefaultProofOfWorkNonceTrialsPerByte))) @@ -2347,7 +2388,7 @@ class MyForm(QtGui.QMainWindow): # self.ui.comboBoxSendFrom.setEditText(str(self.ui.tableWidgetInbox.item(currentInboxRow,0).text)) self.ui.textEditMessage.setText('\n\n------------------------------------------------------\n' + self.ui.tableWidgetInbox.item( currentInboxRow, 2).data(Qt.UserRole).toPyObject()) - if self.ui.tableWidgetInbox.item(currentInboxRow, 2).text()[0:3] == 'Re:': + if self.ui.tableWidgetInbox.item(currentInboxRow, 2).text()[0:3] in ['Re:', 'RE:']: self.ui.lineEditSubject.setText( self.ui.tableWidgetInbox.item(currentInboxRow, 2).text()) else: @@ -3057,6 +3098,35 @@ class settingsDialog(QtGui.QDialog): self.ui.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float(shared.config.getint( 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes')) / shared.networkDefaultPayloadLengthExtraBytes))) + # Namecoin integration tab + nmctype = shared.config.get('bitmessagesettings', 'namecoinrpctype') + self.ui.lineEditNamecoinHost.setText(str( + shared.config.get('bitmessagesettings', 'namecoinrpchost'))) + self.ui.lineEditNamecoinPort.setText(str( + shared.config.get('bitmessagesettings', 'namecoinrpcport'))) + self.ui.lineEditNamecoinUser.setText(str( + shared.config.get('bitmessagesettings', 'namecoinrpcuser'))) + self.ui.lineEditNamecoinPassword.setText(str( + shared.config.get('bitmessagesettings', 'namecoinrpcpassword'))) + + if nmctype == "namecoind": + self.ui.radioButtonNamecoinNamecoind.setChecked(True) + elif nmctype == "nmcontrol": + self.ui.radioButtonNamecoinNmcontrol.setChecked(True) + self.ui.lineEditNamecoinUser.setEnabled(False) + self.ui.labelNamecoinUser.setEnabled(False) + self.ui.lineEditNamecoinPassword.setEnabled(False) + self.ui.labelNamecoinPassword.setEnabled(False) + else: + assert False + + QtCore.QObject.connect(self.ui.radioButtonNamecoinNamecoind, QtCore.SIGNAL( + "toggled(bool)"), self.namecoinTypeChanged) + QtCore.QObject.connect(self.ui.radioButtonNamecoinNmcontrol, QtCore.SIGNAL( + "toggled(bool)"), self.namecoinTypeChanged) + QtCore.QObject.connect(self.ui.pushButtonNamecoinTest, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonNamecoinTest) + #'System' tab removed for now. """try: maxCores = shared.config.getint('bitmessagesettings', 'maxcores') @@ -3096,6 +3166,49 @@ class settingsDialog(QtGui.QDialog): self.ui.lineEditSocksPassword.setEnabled(True) self.ui.lineEditTCPPort.setEnabled(False) + # Check status of namecoin integration radio buttons and translate + # it to a string as in the options. + def getNamecoinType(self): + if self.ui.radioButtonNamecoinNamecoind.isChecked(): + return "namecoind" + if self.ui.radioButtonNamecoinNmcontrol.isChecked(): + return "nmcontrol" + assert False + + # Namecoin connection type was changed. + def namecoinTypeChanged(self, checked): + nmctype = self.getNamecoinType() + assert nmctype == "namecoind" or nmctype == "nmcontrol" + + isNamecoind = (nmctype == "namecoind") + self.ui.lineEditNamecoinUser.setEnabled(isNamecoind) + self.ui.labelNamecoinUser.setEnabled(isNamecoind) + self.ui.lineEditNamecoinPassword.setEnabled(isNamecoind) + self.ui.labelNamecoinPassword.setEnabled(isNamecoind) + + if isNamecoind: + self.ui.lineEditNamecoinPort.setText(shared.namecoinDefaultRpcPort) + else: + self.ui.lineEditNamecoinPort.setText("9000") + + # Test the namecoin settings specified in the settings dialog. + def click_pushButtonNamecoinTest(self): + self.ui.labelNamecoinTestResult.setText(_translate( + "MainWindow", "Testing...")) + options = {} + options["type"] = self.getNamecoinType() + options["host"] = self.ui.lineEditNamecoinHost.text() + options["port"] = self.ui.lineEditNamecoinPort.text() + options["user"] = self.ui.lineEditNamecoinUser.text() + options["password"] = self.ui.lineEditNamecoinPassword.text() + nc = namecoinConnection(options) + response = nc.test() + responseStatus = response[0] + responseText = response[1] + self.ui.labelNamecoinTestResult.setText(responseText) + if responseStatus== 'success': + self.parent.ui.pushButtonFetchNamecoinID.show() + class SpecialAddressBehaviorDialog(QtGui.QDialog): diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index 54a3ee6f..c051c076 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file 'bitmessageui.ui' # -# Created: Fri Aug 9 14:17:50 2013 -# by: PyQt4 UI code generator 4.10 +# Created: Mon Aug 12 00:08:20 2013 +# by: PyQt4 UI code generator 4.10.2 # # WARNING! All changes made in this file will be lost! @@ -110,7 +110,13 @@ class Ui_MainWindow(object): font.setPointSize(7) self.pushButtonLoadFromAddressBook.setFont(font) self.pushButtonLoadFromAddressBook.setObjectName(_fromUtf8("pushButtonLoadFromAddressBook")) - self.gridLayout_2.addWidget(self.pushButtonLoadFromAddressBook, 3, 2, 1, 2) + self.gridLayout_2.addWidget(self.pushButtonLoadFromAddressBook, 3, 2, 1, 1) + self.pushButtonFetchNamecoinID = QtGui.QPushButton(self.send) + font = QtGui.QFont() + font.setPointSize(7) + self.pushButtonFetchNamecoinID.setFont(font) + self.pushButtonFetchNamecoinID.setObjectName(_fromUtf8("pushButtonFetchNamecoinID")) + self.gridLayout_2.addWidget(self.pushButtonFetchNamecoinID, 3, 3, 1, 1) self.label_4 = QtGui.QLabel(self.send) self.label_4.setObjectName(_fromUtf8("label_4")) self.gridLayout_2.addWidget(self.label_4, 5, 0, 1, 1) @@ -422,7 +428,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, 795, 27)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 795, 18)) self.menubar.setObjectName(_fromUtf8("menubar")) self.menuFile = QtGui.QMenu(self.menubar) self.menuFile.setObjectName(_fromUtf8("menuFile")) @@ -533,14 +539,15 @@ class Ui_MainWindow(object): item.setText(_translate("MainWindow", "Received", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.inbox), _translate("MainWindow", "Inbox", None)) self.pushButtonLoadFromAddressBook.setText(_translate("MainWindow", "Load from Address book", None)) + self.pushButtonFetchNamecoinID.setText(_translate("MainWindow", "Fetch Namecoin ID", None)) self.label_4.setText(_translate("MainWindow", "Message:", None)) self.label_3.setText(_translate("MainWindow", "Subject:", None)) self.radioButtonSpecific.setText(_translate("MainWindow", "Send to one or more specific people", None)) self.textEditMessage.setHtml(_translate("MainWindow", "\n" "\n" -"


", None)) +"\n" +"


", None)) self.label.setText(_translate("MainWindow", "To:", None)) self.label_2.setText(_translate("MainWindow", "From:", None)) self.radioButtonBroadcast.setText(_translate("MainWindow", "Broadcast to everyone who is subscribed to your address", None)) diff --git a/src/bitmessageqt/bitmessageui.ui b/src/bitmessageqt/bitmessageui.ui index c2caebe0..683703d8 100644 --- a/src/bitmessageqt/bitmessageui.ui +++ b/src/bitmessageqt/bitmessageui.ui @@ -195,7 +195,7 @@ Send - + @@ -207,6 +207,18 @@ + + + + + 7 + + + + Fetch Namecoin ID + + + @@ -257,8 +269,8 @@ <!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:'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> +</style></head><body style=" font-family:'MS Shell Dlg 2'; 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;"><br /></p></body></html> @@ -1011,7 +1023,7 @@ p, li { white-space: pre-wrap; } 0 0 795 - 27 + 18 diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 40a57f5b..87e8ba7b 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'settings.ui' # -# Created: Wed Aug 7 16:58:45 2013 +# Created: Wed Aug 14 18:31:34 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(462, 343) + settingsDialog.resize(567, 343) self.gridLayout = QtGui.QGridLayout(settingsDialog) self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.buttonBox = QtGui.QDialogButtonBox(settingsDialog) @@ -230,6 +230,75 @@ class Ui_settingsDialog(object): spacerItem7 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) self.gridLayout_7.addItem(spacerItem7, 3, 1, 1, 1) self.tabWidgetSettings.addTab(self.tab_2, _fromUtf8("")) + self.tabNamecoin = QtGui.QWidget() + self.tabNamecoin.setObjectName(_fromUtf8("tabNamecoin")) + self.gridLayout_8 = QtGui.QGridLayout(self.tabNamecoin) + self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) + spacerItem8 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem8, 2, 0, 1, 1) + self.label_16 = QtGui.QLabel(self.tabNamecoin) + self.label_16.setWordWrap(True) + self.label_16.setObjectName(_fromUtf8("label_16")) + self.gridLayout_8.addWidget(self.label_16, 0, 0, 1, 3) + self.label_17 = QtGui.QLabel(self.tabNamecoin) + self.label_17.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_17.setObjectName(_fromUtf8("label_17")) + self.gridLayout_8.addWidget(self.label_17, 2, 1, 1, 1) + self.lineEditNamecoinHost = QtGui.QLineEdit(self.tabNamecoin) + self.lineEditNamecoinHost.setObjectName(_fromUtf8("lineEditNamecoinHost")) + self.gridLayout_8.addWidget(self.lineEditNamecoinHost, 2, 2, 1, 1) + spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem9, 3, 0, 1, 1) + spacerItem10 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem10, 4, 0, 1, 1) + self.label_18 = QtGui.QLabel(self.tabNamecoin) + self.label_18.setEnabled(True) + self.label_18.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_18.setObjectName(_fromUtf8("label_18")) + self.gridLayout_8.addWidget(self.label_18, 3, 1, 1, 1) + self.lineEditNamecoinPort = QtGui.QLineEdit(self.tabNamecoin) + self.lineEditNamecoinPort.setObjectName(_fromUtf8("lineEditNamecoinPort")) + self.gridLayout_8.addWidget(self.lineEditNamecoinPort, 3, 2, 1, 1) + spacerItem11 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_8.addItem(spacerItem11, 8, 1, 1, 1) + self.labelNamecoinUser = QtGui.QLabel(self.tabNamecoin) + self.labelNamecoinUser.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.labelNamecoinUser.setObjectName(_fromUtf8("labelNamecoinUser")) + self.gridLayout_8.addWidget(self.labelNamecoinUser, 4, 1, 1, 1) + self.lineEditNamecoinUser = QtGui.QLineEdit(self.tabNamecoin) + self.lineEditNamecoinUser.setObjectName(_fromUtf8("lineEditNamecoinUser")) + self.gridLayout_8.addWidget(self.lineEditNamecoinUser, 4, 2, 1, 1) + spacerItem12 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem12, 5, 0, 1, 1) + self.labelNamecoinPassword = QtGui.QLabel(self.tabNamecoin) + self.labelNamecoinPassword.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.labelNamecoinPassword.setObjectName(_fromUtf8("labelNamecoinPassword")) + self.gridLayout_8.addWidget(self.labelNamecoinPassword, 5, 1, 1, 1) + self.lineEditNamecoinPassword = QtGui.QLineEdit(self.tabNamecoin) + self.lineEditNamecoinPassword.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText) + self.lineEditNamecoinPassword.setEchoMode(QtGui.QLineEdit.Password) + self.lineEditNamecoinPassword.setObjectName(_fromUtf8("lineEditNamecoinPassword")) + self.gridLayout_8.addWidget(self.lineEditNamecoinPassword, 5, 2, 1, 1) + self.labelNamecoinTestResult = QtGui.QLabel(self.tabNamecoin) + self.labelNamecoinTestResult.setText(_fromUtf8("")) + self.labelNamecoinTestResult.setObjectName(_fromUtf8("labelNamecoinTestResult")) + self.gridLayout_8.addWidget(self.labelNamecoinTestResult, 7, 0, 1, 2) + self.pushButtonNamecoinTest = QtGui.QPushButton(self.tabNamecoin) + self.pushButtonNamecoinTest.setObjectName(_fromUtf8("pushButtonNamecoinTest")) + self.gridLayout_8.addWidget(self.pushButtonNamecoinTest, 7, 2, 1, 1) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.label_21 = QtGui.QLabel(self.tabNamecoin) + self.label_21.setObjectName(_fromUtf8("label_21")) + self.horizontalLayout.addWidget(self.label_21) + self.radioButtonNamecoinNamecoind = QtGui.QRadioButton(self.tabNamecoin) + self.radioButtonNamecoinNamecoind.setObjectName(_fromUtf8("radioButtonNamecoinNamecoind")) + self.horizontalLayout.addWidget(self.radioButtonNamecoinNamecoind) + self.radioButtonNamecoinNmcontrol = QtGui.QRadioButton(self.tabNamecoin) + self.radioButtonNamecoinNmcontrol.setObjectName(_fromUtf8("radioButtonNamecoinNmcontrol")) + self.horizontalLayout.addWidget(self.radioButtonNamecoinNmcontrol) + self.gridLayout_8.addLayout(self.horizontalLayout, 1, 0, 1, 3) + self.tabWidgetSettings.addTab(self.tabNamecoin, _fromUtf8("")) self.gridLayout.addWidget(self.tabWidgetSettings, 0, 0, 1, 1) self.retranslateUi(settingsDialog) @@ -287,4 +356,14 @@ class Ui_settingsDialog(object): self.label_13.setText(_translate("settingsDialog", "Maximum acceptable total difficulty:", None)) self.label_14.setText(_translate("settingsDialog", "Maximum acceptable small message difficulty:", None)) self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tab_2), _translate("settingsDialog", "Max acceptable difficulty", None)) + self.label_16.setText(_translate("settingsDialog", "

Bitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to test.

(Getting your own Bitmessage address into Namecoin is still rather difficult).

Bitmessage can use either namecoind directly or a running nmcontrol instance.

", None)) + self.label_17.setText(_translate("settingsDialog", "Host:", None)) + self.label_18.setText(_translate("settingsDialog", "Port:", None)) + self.labelNamecoinUser.setText(_translate("settingsDialog", "Username:", None)) + self.labelNamecoinPassword.setText(_translate("settingsDialog", "Password:", None)) + self.pushButtonNamecoinTest.setText(_translate("settingsDialog", "Test", None)) + self.label_21.setText(_translate("settingsDialog", "Connect to:", None)) + self.radioButtonNamecoinNamecoind.setText(_translate("settingsDialog", "Namecoind", None)) + self.radioButtonNamecoinNmcontrol.setText(_translate("settingsDialog", "NMControl", None)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabNamecoin), _translate("settingsDialog", "Namecoin integration", None)) diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 44c4f973..0ca22088 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -6,7 +6,7 @@ 0 0 - 462 + 567 343 @@ -505,6 +505,189 @@
+ + + Namecoin integration + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Bitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to <span style=" font-style:italic;">test. </span></p><p>(Getting your own Bitmessage address into Namecoin is still rather difficult).</p><p>Bitmessage can use either namecoind directly or a running nmcontrol instance.</p></body></html> + + + true + + + + + + + Host: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + Port: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Username: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Password: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText + + + QLineEdit::Password + + + + + + + + + + + + + + Test + + + + + + + + + Connect to: + + + + + + + Namecoind + + + + + + + NMControl + + + + + + + diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 625b2b2f..6ac0f168 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -6,6 +6,7 @@ import shutil # used for moving the messages.dat file import sys import os from debug import logger +from namecoin import ensureNamecoinOptions # This thread exists because SQLITE3 is so un-threadsafe that we must # submit queries to it and it puts results back in a different queue. They @@ -190,6 +191,8 @@ class sqlThread(threading.Thread): if not shared.config.has_option('bitmessagesettings', 'sockslisten'): shared.config.set('bitmessagesettings', 'sockslisten', 'false') + ensureNamecoinOptions() + # 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 = '' diff --git a/src/helper_startup.py b/src/helper_startup.py index d60ad681..256dbcaa 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -3,6 +3,8 @@ import ConfigParser import sys import os +from namecoin import ensureNamecoinOptions + storeConfigFilesInSameDirectoryAsProgramByDefault = False # The user may de-select Portable Mode in the settings if they want the config files to stay in the application data folder. def loadConfig(): @@ -67,6 +69,7 @@ def loadConfig(): shared.config.set( 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', '0') shared.config.set('bitmessagesettings', 'dontconnect', 'true') + ensureNamecoinOptions() if storeConfigFilesInSameDirectoryAsProgramByDefault: # Just use the same directory as the program and forget about diff --git a/src/namecoin.py b/src/namecoin.py new file mode 100644 index 00000000..037eae80 --- /dev/null +++ b/src/namecoin.py @@ -0,0 +1,283 @@ +# Copyright (C) 2013 by Daniel Kraft +# This file is part of the Bitmessage project. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import base64 +import json +import socket +import sys + +import shared +import tr # translate + +configSection = "bitmessagesettings" + +# Error thrown when the RPC call returns an error. +class RPCError (Exception): + error = None + + def __init__ (self, data): + self.error = data + +# This class handles the Namecoin identity integration. +class namecoinConnection (object): + user = None + password = None + host = None + port = None + nmctype = None + bufsize = 4096 + queryid = 1 + + # Initialise. If options are given, take the connection settings from + # them instead of loading from the configs. This can be used to test + # currently entered connection settings in the config dialog without + # actually changing the values (yet). + def __init__ (self, options = None): + if options is None: + self.nmctype = shared.config.get (configSection, "namecoinrpctype") + self.host = shared.config.get (configSection, "namecoinrpchost") + self.port = shared.config.get (configSection, "namecoinrpcport") + self.user = shared.config.get (configSection, "namecoinrpcuser") + self.password = shared.config.get (configSection, + "namecoinrpcpassword") + else: + self.nmctype = options["type"] + self.host = options["host"] + self.port = options["port"] + self.user = options["user"] + self.password = options["password"] + + assert self.nmctype == "namecoind" or self.nmctype == "nmcontrol" + + # Query for the bitmessage address corresponding to the given identity + # string. If it doesn't contain a slash, id/ is prepended. We return + # the result as (Error, Address) pair, where the Error is an error + # message to display or None in case of success. + def query (self, string): + slashPos = string.find ("/") + if slashPos < 0: + string = "id/" + string + + try: + if self.nmctype == "namecoind": + res = self.callRPC ("name_show", [string]) + res = res["value"] + elif self.nmctype == "nmcontrol": + res = self.callRPC ("data", ["getValue", string]) + res = res["reply"] + if res == False: + raise RPCError ({"code": -4}) + else: + assert False + except RPCError as exc: + if exc.error["code"] == -4: + return (tr.translateText("MainWindow",'The name %1 was not found.').arg(unicode(string)), None) + else: + return (tr.translateText("MainWindow",'The namecoin query failed (%1)').arg(unicode(exc.error["message"])), None) + except Exception as exc: + print "Namecoin query exception: %s" % str (exc) + return (tr.translateText("MainWindow",'The namecoin query failed.'), None) + + try: + val = json.loads (res) + except: + return (tr.translateText("MainWindow",'The name %1 has no valid JSON data.').arg(unicode(string)), None) + + if "bitmessage" in val: + return (None, val["bitmessage"]) + return (tr.translateText("MainWindow",'The name %1 has no associated Bitmessage address.').arg(unicode(string)), None) + + # Test the connection settings. This routine tries to query a "getinfo" + # command, and builds either an error message or a success message with + # some info from it. + def test (self): + try: + if self.nmctype == "namecoind": + res = self.callRPC ("getinfo", []) + vers = res["version"] + + v3 = vers % 100 + vers = vers / 100 + v2 = vers % 100 + vers = vers / 100 + v1 = vers + if v3 == 0: + versStr = "0.%d.%d" % (v1, v2) + else: + versStr = "0.%d.%d.%d" % (v1, v2, v3) + return ('success', tr.translateText("MainWindow",'Success! Namecoind version %1 running.').arg(unicode(versStr)) ) + + elif self.nmctype == "nmcontrol": + res = self.callRPC ("data", ["status"]) + prefix = "Plugin data running" + if ("reply" in res) and res["reply"][:len(prefix)] == prefix: + return ('success', tr.translateText("MainWindow",'Success! NMControll is up and running.')) + + print "Unexpected nmcontrol reply: %s" % res + return ('failed', tr.translateText("MainWindow",'Couldn\'t understand NMControl.')) + + else: + assert False + + except Exception as exc: + print "Exception testing the namecoin connection:\n%s" % str (exc) + return ('failed', "The connection to namecoin failed.") + + # Helper routine that actually performs an JSON RPC call. + def callRPC (self, method, params): + data = {"method": method, "params": params, "id": self.queryid} + if self.nmctype == "namecoind": + resp = self.queryHTTP (json.dumps (data)) + elif self.nmctype == "nmcontrol": + resp = self.queryServer (json.dumps (data)) + else: + assert False + val = json.loads (resp) + + if val["id"] != self.queryid: + raise Exception ("ID mismatch in JSON RPC answer.") + self.queryid = self.queryid + 1 + + if val["error"] is not None: + raise RPCError (val["error"]) + + return val["result"] + + # Query the server via HTTP. + def queryHTTP (self, data): + header = "POST / HTTP/1.1\n" + header += "User-Agent: bitmessage\n" + header += "Host: %s\n" % self.host + header += "Content-Type: application/json\n" + header += "Content-Length: %d\n" % len (data) + header += "Accept: application/json\n" + authstr = "%s:%s" % (self.user, self.password) + header += "Authorization: Basic %s\n" % base64.b64encode (authstr) + + resp = self.queryServer ("%s\n%s" % (header, data)) + lines = resp.split ("\r\n") + result = None + body = False + for line in lines: + if line == "" and not body: + body = True + elif body: + if result is not None: + raise Exception ("Expected a single line in HTTP response.") + result = line + + return result + + # Helper routine sending data to the RPC server and returning the result. + def queryServer (self, data): + try: + s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.settimeout(3) + s.connect ((self.host, int (self.port))) + s.sendall (data) + result = "" + + while True: + tmp = s.recv (self.bufsize) + if not tmp: + break + result += tmp + + s.close () + + return result + + except socket.error as exc: + raise Exception ("Socket error in RPC connection: %s" % str (exc)) + +# Look up the namecoin data folder. +# FIXME: Check whether this works on other platforms as well! +def lookupNamecoinFolder (): + app = "namecoin" + from os import path, environ + if sys.platform == "darwin": + if "HOME" in environ: + dataFolder = path.join (os.environ["HOME"], + "Library/Application Support/", app) + '/' + else: + print ("Could not find home folder, please report this message" + + " and your OS X version to the BitMessage Github.") + sys.exit() + + elif "win32" in sys.platform or "win64" in sys.platform: + dataFolder = path.join(environ["APPDATA"], app) + "\\" + else: + dataFolder = path.join(environ["HOME"], ".%s" % app) + "/" + + return dataFolder + +# Ensure all namecoin options are set, by setting those to default values +# that aren't there. +def ensureNamecoinOptions (): + if not shared.config.has_option (configSection, "namecoinrpctype"): + shared.config.set (configSection, "namecoinrpctype", "namecoind") + if not shared.config.has_option (configSection, "namecoinrpchost"): + shared.config.set (configSection, "namecoinrpchost", "localhost") + + hasUser = shared.config.has_option (configSection, "namecoinrpcuser") + hasPass = shared.config.has_option (configSection, "namecoinrpcpassword") + hasPort = shared.config.has_option (configSection, "namecoinrpcport") + + # Try to read user/password from .namecoin configuration file. + defaultUser = "" + defaultPass = "" + try: + nmcFolder = lookupNamecoinFolder () + nmcConfig = nmcFolder + "namecoin.conf" + nmc = open (nmcConfig, "r") + + while True: + line = nmc.readline () + if line == "": + break + parts = line.split ("=") + if len (parts) == 2: + key = parts[0] + val = parts[1].rstrip () + + if key == "rpcuser" and not hasUser: + defaultUser = val + if key == "rpcpassword" and not hasPass: + defaultPass = val + if key == "rpcport": + shared.namecoinDefaultRpcPort = val + + nmc.close () + + except Exception as exc: + print "Failure reading namecoin config file: %s" % str (exc) + + # If still nothing found, set empty at least. + if (not hasUser): + shared.config.set (configSection, "namecoinrpcuser", defaultUser) + if (not hasPass): + shared.config.set (configSection, "namecoinrpcpassword", defaultPass) + + # Set default port now, possibly to found value. + if (not hasPort): + shared.config.set (configSection, "namecoinrpcport", + shared.namecoinDefaultRpcPort) diff --git a/src/shared.py b/src/shared.py index da83f374..d2b82d11 100644 --- a/src/shared.py +++ b/src/shared.py @@ -69,6 +69,11 @@ ackdataForWhichImWatching = {} networkDefaultProofOfWorkNonceTrialsPerByte = 320 #The amount of work that should be performed (and demanded) per byte of the payload. Double this number to double the work. networkDefaultPayloadLengthExtraBytes = 14000 #To make sending short messages a little more difficult, this value is added to the payload length for use in calculating the proof of work target. +# Remember here the RPC port read from namecoin.conf so we can restore to +# it as default whenever the user changes the "method" selection for +# namecoin integration to "namecoind". +namecoinDefaultRpcPort = "8336" + def isInSqlInventory(hash): t = (hash,) shared.sqlLock.acquire() @@ -355,7 +360,7 @@ def checkSensitiveFilePermissions(filename): return True except: # Swallow exception here, but we might run into trouble later! - logger.error('Could not determine filesystem type.', filename) + logger.error('Could not determine filesystem type. %s', filename) present_permissions = os.stat(filename)[0] disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO return present_permissions & disallowed_permissions == 0