Merge branch 'master' into master

This commit is contained in:
Roland Häder 2018-05-15 01:11:21 +02:00 committed by GitHub
commit 58147fb211
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
143 changed files with 13377 additions and 6144 deletions

View File

@ -1,4 +1,4 @@
#PyBitmessage Installation Instructions
# PyBitmessage Installation Instructions
For an up-to-date version of these instructions, please visit the
[Bitmessage Wiki](https://bitmessage.org/wiki/Compiling_instructions).
@ -6,7 +6,7 @@ For an up-to-date version of these instructions, please visit the
PyBitmessage can be run either straight from source or from an installed
package.
##Dependencies
## Dependencies
Before running PyBitmessage, make sure you have all the necessary dependencies
installed on your system.
@ -16,7 +16,7 @@ Here's a list of dependencies needed for PyBitmessage
- openssl
- (Fedora & Redhat only) openssl-compat-bitcoin-libs
##Running PyBitmessage
## Running PyBitmessage
PyBitmessage can be run two ways: straight from source or via a package which
is installed on your system. Since PyBitmessage is Beta, it is best to run
PyBitmessage from source, so that you may update as needed.
@ -24,7 +24,7 @@ PyBitmessage from source, so that you may update as needed.
Under Linux/Uni* just do "make run" and PyMessage will be started from source
code without changing to the right path by yourself.
####Updating
#### Updating
To update PyBitmessage from source (Linux/OS X), you can do these easy steps:
```
cd PyBitmessage/src/
@ -34,7 +34,7 @@ python bitmessagemain.py
```
Voilà! Bitmessage is updated!
####Linux
#### Linux
To run PyBitmessage from the command-line, you must download the source, then
run `src/bitmessagemain.py`.
```
@ -44,7 +44,7 @@ cd PyBitmessage/ && python src/bitmessagemain.py
That's it! *Honestly*!
####Windows
#### Windows
On Windows you can download an executable for Bitmessage
[here](https://bitmessage.org/download/windows/Bitmessage.exe).
@ -52,7 +52,7 @@ However, if you would like to run PyBitmessage via Python in Windows, you can
go [here](https://bitmessage.org/wiki/Compiling_instructions#Windows) for
information on how to do so.
####OS X
#### OS X
First off, install Homebrew.
```
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
@ -69,13 +69,12 @@ git clone git://github.com/Bitmessage/PyBitmessage.git
cd PyBitmessage && python src/bitmessagemain.py
```
##Creating a package for installation
## Creating a package for installation
If you really want, you can make a package for PyBitmessage, which you may
install yourself or distribute to friends. This isn't recommended, since
PyBitmessage is in Beta, and subject to frequent change.
####Linux
#### Linux
First off, since PyBitmessage uses something nifty called
[packagemonkey](https://github.com/fuzzgun/packagemonkey), go ahead and get
that installed. You may have to build it from source.
@ -93,11 +92,12 @@ rpm.sh - create a RPM package
slack.sh - create a package for Slackware
```
####OS X
#### OS X
Please refer to
[this page](https://bitmessage.org/forum/index.php/topic,2761.0.html) on the
forums for instructions on how to create a package on OS X.
Please note that some versions of OS X don't work.
###Windows
#TODO: Create Windows package creation instructions
#### Windows
## TODO: Create Windows package creation instructions

14
PULL_REQUEST_TEMPLATE.md Normal file
View File

@ -0,0 +1,14 @@
## Code contributions to the Bitmessage project
- try to explain what the code is about
- try to follow [PEP0008](https://www.python.org/dev/peps/pep-0008/)
- make the pull request against the ["v0.6" branch](https://github.com/Bitmessage/PyBitmessage/tree/v0.6)
- it should be possible to do a fast-forward merge of the pull requests
- PGP-sign the commits included in the pull request
- You can get paid for merged commits if you register at [Tip4Commit](https://tip4commit.com/github/Bitmessage/PyBitmessage)
If for some reason you don't want to use github, you can submit the patch using Bitmessage to the "bitmessage" chan, or to one of the developers.
## Translations
For helping with translations, please use [Transifex](https://www.transifex.com/bitmessage-project/pybitmessage/). There is no need to submit pull requests for translations.
For translating technical terms it is recommended to consult the [Microsoft Language Portal](https://www.microsoft.com/Language/en-US/Default.aspx).

View File

@ -5,7 +5,7 @@ Bitmessage is a P2P communications protocol used to send encrypted messages to
another person or to many subscribers. It is decentralized and trustless,
meaning that you need-not inherently trust any entities like root certificate
authorities. It uses strong authentication, which means that the sender of a
message cannot be spoofed, and it aims to hide "non-content" data, like the
message cannot be spoofed, and it aims to hide metadata, like the
sender and receiver of messages, from passive eavesdroppers like those running
warrantless wiretapping programs.

215
checkdeps.py Normal file
View File

@ -0,0 +1,215 @@
"""Check dependendies and give recommendations about how to satisfy them"""
from distutils.errors import CompileError
try:
from setuptools.dist import Distribution
from setuptools.extension import Extension
from setuptools.command.build_ext import build_ext
HAVE_SETUPTOOLS = True
except ImportError:
HAVE_SETUPTOOLS = False
from importlib import import_module
import os
import sys
PACKAGE_MANAGER = {
"OpenBSD": "pkg_add",
"FreeBSD": "pkg install",
"Debian": "apt-get install",
"Ubuntu": "apt-get install",
"Ubuntu 12": "apt-get install",
"openSUSE": "zypper install",
"Fedora": "dnf install",
"Guix": "guix package -i",
"Gentoo": "emerge"
}
PACKAGES = {
"PyQt4": {
"OpenBSD": "py-qt4",
"FreeBSD": "py27-qt4",
"Debian": "python-qt4",
"Ubuntu": "python-qt4",
"Ubuntu 12": "python-qt4",
"openSUSE": "python-qt",
"Fedora": "PyQt4",
"Guix": "python2-pyqt@4.11.4",
"Gentoo": "dev-python/PyQt4",
'optional': True,
'description': "You only need PyQt if you want to use the GUI. " \
"When only running as a daemon, this can be skipped.\n" \
"However, you would have to install it manually " \
"because setuptools does not support PyQt."
},
"msgpack": {
"OpenBSD": "py-msgpack",
"FreeBSD": "py27-msgpack-python",
"Debian": "python-msgpack",
"Ubuntu": "python-msgpack",
"Ubuntu 12": "msgpack-python",
"openSUSE": "python-msgpack-python",
"Fedora": "python2-msgpack",
"Guix": "python2-msgpack",
"Gentoo": "dev-python/msgpack",
"optional": True,
"description": "python-msgpack is recommended for improved performance of message encoding/decoding"
},
"pyopencl": {
"FreeBSD": "py27-pyopencl",
"Debian": "python-pyopencl",
"Ubuntu": "python-pyopencl",
"Ubuntu 12": "python-pyopencl",
"Fedora": "python2-pyopencl",
"openSUSE": "",
"OpenBSD": "",
"Guix": "",
"Gentoo": "dev-python/pyopencl",
"optional": True,
'description': "If you install pyopencl, you will be able to use " \
"GPU acceleration for proof of work. \n" \
"You also need a compatible GPU and drivers."
},
"setuptools": {
"OpenBSD": "py-setuptools",
"FreeBSD": "py27-setuptools",
"Debian": "python-setuptools",
"Ubuntu": "python-setuptools",
"Ubuntu 12": "python-setuptools",
"Fedora": "python2-setuptools",
"openSUSE": "python-setuptools",
"Guix": "python2-setuptools",
"Gentoo": "",
"optional": False,
}
}
COMPILING = {
"Debian": "build-essential libssl-dev",
"Ubuntu": "build-essential libssl-dev",
"Fedora": "gcc-c++ redhat-rpm-config python-devel openssl-devel",
"openSUSE": "gcc-c++ libopenssl-devel python-devel",
"optional": False,
}
def detectOSRelease():
with open("/etc/os-release", 'r') as osRelease:
version = None
for line in osRelease:
if line.startswith("NAME="):
line = line.lower()
if "fedora" in line:
detectOS.result = "Fedora"
elif "opensuse" in line:
detectOS.result = "openSUSE"
elif "ubuntu" in line:
detectOS.result = "Ubuntu"
elif "debian" in line:
detectOS.result = "Debian"
elif "gentoo" in line or "calculate" in line:
detectOS.result = "Gentoo"
else:
detectOS.result = None
if line.startswith("VERSION_ID="):
try:
version = float(line.split("=")[1].replace("\"", ""))
except ValueError:
pass
if detectOS.result == "Ubuntu" and version < 14:
detectOS.result = "Ubuntu 12"
def detectOS():
if detectOS.result is not None:
return detectOS.result
if sys.platform.startswith('openbsd'):
detectOS.result = "OpenBSD"
elif sys.platform.startswith('freebsd'):
detectOS.result = "FreeBSD"
elif sys.platform.startswith('win'):
detectOS.result = "Windows"
elif os.path.isfile("/etc/os-release"):
detectOSRelease()
elif os.path.isfile("/etc/config.scm"):
detectOS.result = "Guix"
return detectOS.result
def detectPrereqs(missing=True):
available = []
for module in PACKAGES:
try:
import_module(module)
if not missing:
available.append(module)
except ImportError:
if missing:
available.append(module)
return available
def prereqToPackages():
if not detectPrereqs():
return
print "%s %s" % (
PACKAGE_MANAGER[detectOS()], " ".join(
PACKAGES[x][detectOS()] for x in detectPrereqs()))
def compilerToPackages():
if not detectOS() in COMPILING:
return
print "%s %s" % (
PACKAGE_MANAGER[detectOS.result], COMPILING[detectOS.result])
def testCompiler():
if not HAVE_SETUPTOOLS:
# silent, we can't test without setuptools
return True
bitmsghash = Extension(
'bitmsghash',
sources=['src/bitmsghash/bitmsghash.cpp'],
libraries=['pthread', 'crypto'],
)
dist = Distribution()
dist.ext_modules = [bitmsghash]
cmd = build_ext(dist)
cmd.initialize_options()
cmd.finalize_options()
cmd.force = True
try:
cmd.run()
except CompileError:
return False
else:
fullPath = os.path.join(cmd.build_lib, cmd.get_ext_filename("bitmsghash"))
return os.path.isfile(fullPath)
detectOS.result = None
prereqs = detectPrereqs()
compiler = testCompiler()
if (not compiler or prereqs) and detectOS() in PACKAGE_MANAGER:
print "It looks like you're using %s. " \
"It is highly recommended to use the package manager\n" \
"to install the missing dependencies." % (detectOS.result)
if not compiler:
print "Building the bitmsghash module failed.\n" \
"You may be missing a C++ compiler and/or the OpenSSL headers."
if prereqs:
mandatory = list(x for x in prereqs if "optional" not in PACKAGES[x] or not PACKAGES[x]["optional"])
optional = list(x for x in prereqs if "optional" in PACKAGES[x] and PACKAGES[x]["optional"])
if mandatory:
print "Missing mandatory dependencies: %s" % (" ".join(mandatory))
if optional:
print "Missing optional dependencies: %s" % (" ".join(optional))
for package in optional:
print PACKAGES[package].get('description')
if (not compiler or prereqs) and detectOS() in PACKAGE_MANAGER:
print "You can install the missing dependencies by running, as root:"
if not compiler:
compilerToPackages()
prereqToPackages()
else:
print "All the dependencies satisfied, you can install PyBitmessage"

View File

@ -9,11 +9,11 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
width="793.70081"
height="1122.5197"
id="svg2"
version="1.1"
inkscape:version="0.48.4 r9939"
inkscape:version="0.92.1 r"
sodipodi:docname="can-icon.svg">
<defs
id="defs4" />
@ -45,7 +45,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
@ -55,129 +55,95 @@
id="layer1">
<g
id="g3096"
transform="translate(9.8994953,147.07822)">
<g
transform="matrix(1.3545269,0.80578955,-0.74303549,1.326065,262.78034,-205.62293)"
id="g3062">
<g
id="g3034"
transform="translate(-163.64471,-765.69563)">
<path
transform="matrix(0.94897167,0,0,0.90277282,75.32333,234.23474)"
d="m 482.85713,726.64789 c 0,31.55913 -62.04054,57.14285 -138.57142,57.14285 -76.53089,0 -138.57143,-25.58372 -138.57143,-57.14285 0,-31.55913 62.04054,-57.14286 138.57143,-57.14286 76.53088,0 138.57142,25.58373 138.57142,57.14286 z"
sodipodi:ry="57.142857"
sodipodi:rx="138.57143"
sodipodi:cy="726.64789"
sodipodi:cx="344.28571"
id="path2992-7"
style="fill:#000000"
sodipodi:type="arc" />
<rect
y="884.67688"
x="262.63968"
height="61.619308"
width="286.88333"
id="rect3032"
style="fill:#ffffff" />
</g>
<g
id="g3034-7"
transform="translate(-163.64473,-764.39834)"
style="fill:#ffffff">
<path
transform="matrix(0.94897167,0,0,0.90277282,75.32333,234.23474)"
d="m 482.85713,726.64789 c 0,31.55913 -62.04054,57.14285 -138.57142,57.14285 -76.53089,0 -138.57143,-25.58372 -138.57143,-57.14285 0,-31.55913 62.04054,-57.14286 138.57143,-57.14286 76.53088,0 138.57142,25.58373 138.57142,57.14286 z"
sodipodi:ry="57.142857"
sodipodi:rx="138.57143"
sodipodi:cy="726.64789"
sodipodi:cx="344.28571"
id="path2992-7-8"
style="fill:#ffffff"
sodipodi:type="arc" />
<rect
y="884.67688"
x="262.63968"
height="61.619308"
width="286.88333"
id="rect3032-3"
style="fill:#ffffff" />
</g>
</g>
<path
transform="matrix(1.3545269,0.80578955,-0.74303549,1.326065,313.39741,-629.9389)"
d="m 482.85713,726.64789 c 0,31.55913 -62.04054,57.14285 -138.57142,57.14285 -76.53089,0 -138.57143,-25.58372 -138.57143,-57.14285 0,-31.55913 62.04054,-57.14286 138.57143,-57.14286 76.53088,0 138.57142,25.58373 138.57142,57.14286 z"
sodipodi:ry="57.142857"
sodipodi:rx="138.57143"
sodipodi:cy="726.64789"
sodipodi:cx="344.28571"
id="path2992"
style="fill:#000000"
sodipodi:type="arc" />
transform="translate(10.559462,156.88343)">
<path
style="fill:#e6e6e6;fill-rule:evenodd;stroke:#241c1c;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="M 55.39264,532.81707 342.36431,41.508115 C 438.76759,-31.483548 745.9833,178.54104 718.72589,265.9098 L 453.97326,771.38082 C 513.19721,665.35571 140.41673,439.89351 55.39264,532.81707 Z"
id="path2391"
inkscape:connector-curvature="0"
id="path3105"
d="M 320.96654,38.913858 51.9306,499.516"
style="fill:none;stroke:#000000;stroke-width:1.54755318px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
inkscape:connector-curvature="0"
id="path3109"
d="M 673.80552,249.29044 425.59993,723.16952"
style="fill:none;stroke:#000000;stroke-width:1.50693178px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
sodipodi:nodetypes="ccccc"
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
inkscape:connector-curvature="0"
id="path3111"
d="M 370.82523,26.309365 105.49275,476.8148"
style="fill:#808080;stroke:#000000;stroke-width:1.54387176px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.11949684" />
d="M 395.54691,28.063323 112.5256,508.60245"
style="fill:#808080;stroke:#000000;stroke-width:1.64679658px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.11949684"
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
inkscape:connector-curvature="0"
id="path2997"
d="M 181.18883,489.0675 437.71536,40.464131"
style="fill:none;stroke:#000000;stroke-width:1.55417168px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.06918239" />
<path
inkscape:connector-curvature="0"
id="path3775"
d="M 118.52909,457.95387 C 156.02362,394.72369 355.64326,55.091483 362.40403,43.025817 l 8.32966,-14.8656 13.15478,1.565404 c 12.59812,1.49917 50.83911,10.510248 51.00938,12.01982 0.045,0.399053 -46.10108,81.351889 -102.54685,179.895169 -56.44577,98.5433 -113.677,198.76894 -127.18051,222.72367 l -24.55187,43.55403 -13.06611,-3.54249 c -16.49443,-4.472 -37.21456,-8.12101 -49.62577,-8.73955 l -9.59943,-0.4784 10.20173,-17.20402 0,0 z"
style="fill:#ececec;stroke:#000000;stroke-width:1.56326485;stroke-opacity:0.06918239" />
d="M 193.26809,521.672 466.89638,43.16174"
style="fill:none;stroke:#000000;stroke-width:1.65778315px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.06918239"
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
inkscape:connector-curvature="0"
id="path3777"
d="M 265.93611,524.57433 515.39415,72.865001"
style="fill:#b3b3b3;stroke:#000000;stroke-width:1.54755318px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.07547171" />
<path
inkscape:connector-curvature="0"
id="path3783"
d="M 218.42026,426.60319 C 253.61387,365.63298 417.60665,79.155333 431.09037,55.091497 l 7.00902,-12.508718 7.89951,2.823652 c 14.56271,5.205381 37.58149,15.185601 51.6594,22.397893 l 13.95467,7.149145 -25.45749,47.431901 c -14.00162,26.08755 -69.58873,127.10612 -123.52693,224.48572 l -98.06942,177.05381 -11.84131,-5.86061 c -17.92612,-8.87215 -44.42926,-20.43356 -56.45302,-24.62637 -5.90572,-2.05941 -11.32796,-4.09551 -12.04941,-4.52469 -0.83836,-0.49873 11.50512,-22.98451 34.20487,-62.31004 z"
style="fill:#e6e6e6;stroke:#000000;stroke-width:1.54755318;stroke-opacity:0.07547171" />
<path
inkscape:connector-curvature="0"
id="path3785"
d="M 79.671264,455.01075 C 90.532206,436.67436 149.69871,335.53924 211.15239,230.26602 331.88877,23.438608 323.76858,36.262419 336.84837,31.760491 c 5.93265,-2.041958 30.03993,-4.510628 30.32186,-3.105078 0.0821,0.409248 -58.80038,100.934397 -130.84993,223.389207 l -130.99918,222.64509 -5.04257,0.53642 c -12.217214,1.29964 -28.319524,5.66399 -34.08727,9.23897 l -6.267188,3.88453 19.747172,-33.33888 z"
style="fill:#e6e6e6;stroke:#000000;stroke-width:1.54755318;stroke-opacity:0.11949684" />
d="M 283.66518,559.54595 549.75376,77.722668"
style="fill:#b3b3b3;stroke:#000000;stroke-width:1.65072334px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.07547171"
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
inkscape:connector-curvature="0"
id="path3787"
d="M 414.69412,653.42954 657.84449,196.92693"
style="fill:none;stroke:#000000;stroke-width:1.54755318px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.21383649" />
d="M 442.34039,696.99151 701.70079,210.05539"
style="fill:none;stroke:#000000;stroke-width:1.65072334px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.21383649"
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
style="fill:#e6e6e6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 393.66782,26.589965 c -19.64834,0.192 -36.82244,4.5488 -50.17969,14.0606 L 55.837742,532.19937 c 12.78728,-13.4059 31.76749,-19.932 54.652348,-20.9707 L 395.86118,26.595865 c -0.73275,-0.0042 -1.46728,-0.013 -2.19336,-0.0059 z"
id="path2391-6"
inkscape:connector-curvature="0"
id="path3789"
d="m 432.89205,699.31284 c 0.17105,-7.00112 -5.92592,-25.26221 -12.06513,-36.13638 l -5.18621,-9.18613 6.39957,-11.51258 c 7.57517,-13.6274 209.52877,-392.42011 225.13618,-422.27505 l 10.74842,-20.5603 3.78885,6.2232 c 5.34342,8.77662 10.631,24.86087 11.21936,34.12823 0.47079,7.41538 -0.0329,8.8489 -8.73578,24.86068 -5.07938,9.34538 -58.93099,111.94581 -119.67008,228.00102 -60.73907,116.0552 -110.73218,211.10533 -111.09578,211.22251 -0.3636,0.11718 -0.60636,-2.02717 -0.53946,-4.76523 l 0,0 z M 383.58623,615.47494 629.5772,157.12597"
style="fill:#b3b3b3;stroke:#000000;stroke-width:1.54755318;stroke-opacity:0.21383649" />
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
style="fill:#ececec;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 395.86314,26.595875 110.49009,511.22867 c 23.70053,-1.076 51.57709,3.7251 81.08008,12.8555 L 467.65416,40.365375 c -25.63117,-8.6186 -50.14818,-13.6453 -71.79102,-13.7695 z"
id="path2391-9"
inkscape:connector-curvature="0"
id="path3791"
d="m 408.26635,642.79453 c -2.77517,-3.91094 -9.09904,-11.83931 -14.05304,-17.6186 l -9.00726,-10.5078 11.06562,-19.93851 c 6.08602,-10.96623 60.695,-112.54525 121.35316,-225.73125 60.65817,-113.186 110.57207,-205.90346 110.91978,-206.03881 0.96223,-0.37455 11.55983,11.72618 19.91814,22.74327 l 7.46029,9.83339 -4.53134,9.80365 C 645.37692,218.35301 419.87091,640.72729 416.18164,645.8899 l -2.86948,4.01545 -5.04578,-7.1108 z"
style="fill:#cccccc;stroke:#000000;stroke-width:1.54755318;stroke-opacity:0.21383649" />
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
style="fill:#e6e6e6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 467.65416,40.363375 191.57017,524.08417 c 29.57345,9.1522 60.77051,22.6569 91.02735,38.9335 L 549.75181,77.724775 c -27.23349,-15.3782 -55.27046,-28.3405 -82.09765,-37.3614 z"
id="path2391-2"
inkscape:connector-curvature="0"
id="path3092"
d="m 371.74406,601.83061 c -21.9825,-21.71509 -57.77462,-48.54944 -91.21678,-68.38776 -7.00036,-4.15271 -12.90367,-7.66798 -13.11848,-7.81172 -0.2148,-0.14376 51.06833,-93.4237 113.96253,-207.28879 C 444.26554,204.47725 499.99579,103.54769 505.21636,94.054416 l 9.49193,-17.260489 12.31004,7.244658 c 16.16532,9.513561 42.09046,26.931205 55.80488,37.492175 14.43229,11.11376 43.65233,37.36326 43.15949,38.77184 -1.19016,3.40165 -242.33727,452.21019 -242.92494,452.11733 -0.3889,-0.0615 -5.48007,-4.82664 -11.3137,-10.58932 l 0,0 z"
style="fill:#b3b3b3" />
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
style="fill:#e6e6e6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 549.75376,77.722668 282.59752,563.01767 c 47.65328,25.635 92.95914,58.1483 125.85154,91.4453 l 262.54485,-485.375 c -31.6082,-32.5347 -75.25565,-65.3989 -121.24015,-91.365302 z"
id="path2391-0"
inkscape:connector-curvature="0"
id="path3094"
d="m 370.32984,599.64651 c -13.89342,-13.08279 -30.14402,-26.59088 -31.98957,-26.59088 -0.69915,0 -2.08148,-1.29748 -3.07184,-2.88329 -0.99036,-1.58582 -2.41623,-2.50287 -3.16861,-2.03787 -0.75238,0.46499 -1.36796,0.23149 -1.36796,-0.51889 0,-1.85189 -23.8082,-18.60406 -44.54773,-31.34517 -9.3338,-5.73412 -17.16014,-10.54638 -17.39186,-10.69391 -0.41663,-0.26524 97.56855,-178.64134 195.50085,-355.89748 48.34786,-87.508983 50.71018,-91.472283 53.61341,-89.948371 7.3399,3.852715 34.04527,20.993951 48.43449,31.088381 16.63992,11.67337 56.24235,44.92528 57.5435,48.31602 0.74942,1.95294 -57.86712,112.36525 -180.22205,339.47312 -32.74563,60.78045 -59.93672,110.92569 -60.42466,111.43384 -0.48793,0.50817 -6.29652,-4.1698 -12.90797,-10.3955 z"
style="fill:#e6e6e6" />
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 670.99391,169.08797 -262.5469,485.375 c 13.9989,14.1711 25.7445,28.479 34.4707,42.457 l 260.3145,-488.3418 c -8.2409,-12.7677 -19.2291,-26.0995 -32.2383,-39.4902 z"
id="path2391-36"
inkscape:connector-curvature="0"
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<path
style="fill:#b3b3b3;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 703.23221,208.57817 442.91971,696.9219 c 14.5278,23.2712 20.6735,45.6196 14.8555,64.8692 l 260.8496,-496.57623 c 4.4659,-15.7747 -1.7012,-35.4253 -15.3926,-56.6367 z"
id="path2391-62"
inkscape:connector-curvature="0"
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
<ellipse
transform="matrix(0.85942572,0.51126062,-0.48882326,0.87238284,0,0)"
id="path2992"
style="fill:#000000;stroke-width:1.6510005"
cx="541.95758"
cy="429.53775"
rx="232.96017"
ry="92.650627"
inkscape:export-xdpi="4.57552"
inkscape:export-ydpi="4.57552" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -14,7 +14,7 @@ def signal_handler(signal, frame):
print "Got signal %i in %s/%s" % (signal, current_process().name, current_thread().name)
if current_process().name != "MainProcess":
raise StopIteration("Interrupted")
if current_thread().name != "MainThread":
if current_thread().name != "PyBitmessage":
return
shutdown = 1

View File

@ -0,0 +1,60 @@
#!/usr/bin/env python2.7
import collectd
import json
import xmlrpclib
pybmurl = ""
api = ""
def init_callback():
global api
api = xmlrpclib.ServerProxy(pybmurl)
collectd.info('pybitmessagestatus.py init done')
def config_callback(ObjConfiguration):
global pybmurl
apiUsername = ""
apiPassword = ""
apiInterface = "127.0.0.1"
apiPort = 8445
for node in ObjConfiguration.children:
key = node.key.lower()
if key.lower() == "apiusername" and node.values:
apiUsername = node.values[0]
elif key.lower() == "apipassword" and node.values:
apiPassword = node.values[0]
elif key.lower() == "apiinterface" and node.values:
apiInterface = node.values[0]
elif key.lower() == "apiport" and node.values:
apiPort = node.values[0]
pybmurl = "http://" + apiUsername + ":" + apiPassword + "@" + apiInterface+ ":" + str(int(apiPort)) + "/"
collectd.info('pybitmessagestatus.py config done')
def read_callback():
try:
clientStatus = json.loads(api.clientStatus())
except:
collectd.info("Exception loading or parsing JSON")
return
for i in ["networkConnections", "numberOfPubkeysProcessed", "numberOfMessagesProcessed", "numberOfBroadcastsProcessed"]:
metric = collectd.Values()
metric.plugin = "pybitmessagestatus"
if i[0:6] == "number":
metric.type = 'counter'
else:
metric.type = 'gauge'
metric.type_instance = i.lower()
try:
metric.values = [clientStatus[i]]
except:
collectd.info("Value for %s missing" % (i))
metric.dispatch()
if __name__ == "__main__":
main()
else:
collectd.register_init(init_callback)
collectd.register_config(config_callback)
collectd.register_read(read_callback)

View File

@ -0,0 +1,18 @@
[Unit]
Description=Bitmessage Daemon
After=network.target auditd.service
[Service]
ExecStart=/usr/bin/python2 /usr/src/PyBitmessage/src/bitmessagemain.py
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
Type=forking
PIDFile=/var/lib/bitmessage/.config/PyBitmessage/singleton.lock
User=bitmessage
Group=nogroup
WorkingDirectory=/var/lib/bitmessage
Environment="HOME=/var/lib/bitmessage"
[Install]
WantedBy=multi-user.target

323
setup.py
View File

@ -2,170 +2,32 @@
import os
import sys
try:
from setuptools import setup, Extension
haveSetuptools = True
except ImportError:
haveSetuptools = False
from importlib import import_module
import shutil
from setuptools import setup, Extension
from setuptools.command.install import install
from src.version import softwareVersion
packageManager = {
"OpenBSD": "pkg_add",
"FreeBSD": "pkg install",
"Debian": "apt-get install",
"Ubuntu": "apt-get install",
"openSUSE": "zypper install",
"Fedora": "dnf install",
"Guix": "guix package -i",
"Gentoo": "emerge"
}
packageName = {
"PyQt4": {
"OpenBSD": "py-qt4",
"FreeBSD": "py27-qt4",
"Debian": "python-qt4",
"Ubuntu": "python-qt4",
"openSUSE": "python-qt",
"Fedora": "PyQt4",
"Guix": "python2-pyqt@4.11.4",
"Gentoo": "dev-python/PyQt4",
'optional': True,
'description': "You only need PyQt if you want to use the GUI. " \
"When only running as a daemon, this can be skipped.\n" \
"However, you would have to install it manually " \
"because setuptools does not support PyQt."
},
"msgpack": {
"OpenBSD": "py-msgpack",
"FreeBSD": "py27-msgpack-python",
"Debian": "python-msgpack",
"Ubuntu": "python-msgpack",
"openSUSE": "python-msgpack-python",
"Fedora": "python2-msgpack",
"Guix": "python2-msgpack",
"Gentoo": "dev-python/msgpack"
},
"pyopencl": {
"FreeBSD": "py27-pyopencl",
"Debian": "python-pyopencl",
"Ubuntu": "python-pyopencl",
"Fedora": "python2-pyopencl",
"openSUSE": "",
"OpenBSD": "",
"Guix": "",
"Gentoo": "dev-python/pyopencl",
"optional": True,
'description': "If you install pyopencl, you will be able to use " \
"GPU acceleration for proof of work. \n" \
"You also need a compatible GPU and drivers."
},
"setuptools": {
"OpenBSD": "py-setuptools",
"FreeBSD": "py27-setuptools",
"Debian": "python-setuptools",
"Ubuntu": "python-setuptools",
"Fedora": "python2-setuptools",
"openSUSE": "python-setuptools",
"Guix": "python2-setuptools",
"Gentoo": "",
}
}
compiling = {
"Debian": "build-essential libssl-dev",
"Ubuntu": "build-essential libssl-dev",
"Fedora": "gcc-c++ redhat-rpm-config python-devel openssl-devel",
"openSUSE": "gcc-c++ libopenssl-devel python-devel",
}
def detectOS():
if detectOS.result is not None:
return detectOS.result
if sys.platform.startswith('openbsd'):
detectOS.result = "OpenBSD"
elif sys.platform.startswith('freebsd'):
detectOS.result = "FreeBSD"
elif sys.platform.startswith('win'):
detectOS.result = "Windows"
elif os.path.isfile("/etc/os-release"):
with open("/etc/os-release", 'rt') as osRelease:
for line in osRelease:
if line.startswith("NAME="):
line = line.lower()
if "fedora" in line:
detectOS.result = "Fedora"
elif "opensuse" in line:
detectOS.result = "openSUSE"
elif "ubuntu" in line:
detectOS.result = "Ubuntu"
elif "debian" in line:
detectOS.result = "Debian"
elif "gentoo" in line or "calculate" in line:
detectOS.result = "Gentoo"
else:
detectOS.result = None
elif os.path.isfile("/etc/config.scm"):
detectOS.result = "Guix"
return detectOS.result
def detectPrereqs(missing=False):
available = []
for module in packageName.keys():
class InstallCmd(install):
def run(self):
# prepare icons directories
try:
import_module(module)
if not missing:
available.append(module)
except ImportError:
if missing:
available.append(module)
return available
os.makedirs('desktop/icons/scalable')
except os.error:
pass
shutil.copyfile(
'desktop/can-icon.svg', 'desktop/icons/scalable/pybitmessage.svg')
try:
os.makedirs('desktop/icons/24x24')
except os.error:
pass
shutil.copyfile(
'desktop/icon24.png', 'desktop/icons/24x24/pybitmessage.png')
return install.run(self)
def prereqToPackages():
print "You can install the requirements by running, as root:"
print "%s %s" % (
packageManager[detectOS()], " ".join(
packageName[x][detectOS()] for x in detectPrereqs(True)))
for package in detectPrereqs(True):
if packageName[package]['optional']:
print packageName[package]['description']
def compilerToPackages():
if not detectOS() in compiling:
return
print "You can install the requirements by running, as root:"
print "%s %s" % (
packageManager[detectOS()], compiling[detectOS()])
if __name__ == "__main__":
detectOS.result = None
detectPrereqs.result = None
if detectPrereqs(True) != [] and detectOS() in packageManager:
if detectOS() is not None:
print "It looks like you're using %s. " \
"It is highly recommended to use the package manager " \
"instead of setuptools." % (detectOS())
prereqToPackages()
for module in detectPrereqs(True):
if not packageName[module]['optional']:
sys.exit()
if not haveSetuptools:
print "It looks like you're missing setuptools."
sys.exit()
if detectPrereqs(True) != []:
print "Press Return to continue"
try:
nothing = raw_input()
except NameError:
pass
here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, 'README.md')) as f:
README = f.read()
@ -176,55 +38,102 @@ if __name__ == "__main__":
libraries=['pthread', 'crypto'],
)
try:
dist = setup(
name='pybitmessage',
version=softwareVersion,
description="Reference client for Bitmessage: "
"a P2P communications protocol",
long_description=README,
license='MIT',
# TODO: add author info
#author='',
#author_email='',
url='https://bitmessage.org',
# TODO: add keywords
#keywords='',
install_requires=['msgpack-python'],
classifiers=[
"License :: OSI Approved :: MIT License"
"Operating System :: OS Independent",
"Programming Language :: Python :: 2.7 :: Only",
"Topic :: Internet",
"Topic :: Security :: Cryptography",
"Topic :: Software Development :: Libraries :: Python Modules",
],
package_dir={'pybitmessage': 'src'},
packages=[
'pybitmessage',
'pybitmessage.bitmessageqt',
'pybitmessage.bitmessagecurses',
'pybitmessage.messagetypes',
'pybitmessage.network',
'pybitmessage.pyelliptic',
'pybitmessage.socks',
],
package_data={'': [
'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem',
'translations/*.ts', 'translations/*.qm',
'images/*.png', 'images/*.ico', 'images/*.icns'
]},
ext_modules=[bitmsghash],
zip_safe=False,
#entry_points={
# 'console_scripts': [
# 'pybitmessage = pybitmessage.bitmessagemain:main'
# ]
#},
scripts=['src/pybitmessage']
)
except SystemExit:
print "It looks like building the package failed.\n" \
"You may be missing a C++ compiler and the OpenSSL headers."
compilerToPackages()
installRequires = []
packages = [
'pybitmessage',
'pybitmessage.bitmessageqt',
'pybitmessage.bitmessagecurses',
'pybitmessage.messagetypes',
'pybitmessage.network',
'pybitmessage.pyelliptic',
'pybitmessage.socks',
'pybitmessage.storage',
'pybitmessage.plugins'
]
# this will silently accept alternative providers of msgpack
# if they are already installed
try:
import msgpack
installRequires.append("msgpack-python")
except ImportError:
try:
import umsgpack
installRequires.append("umsgpack")
except ImportError:
packages += ['pybitmessage.fallback', 'pybitmessage.fallback.umsgpack']
dist = setup(
name='pybitmessage',
version=softwareVersion,
description="Reference client for Bitmessage: "
"a P2P communications protocol",
long_description=README,
license='MIT',
# TODO: add author info
#author='',
#author_email='',
url='https://bitmessage.org',
# TODO: add keywords
#keywords='',
install_requires=installRequires,
extras_require={
'gir': ['pygobject'],
'qrcode': ['qrcode'],
'pyopencl': ['pyopencl'],
'notify2': ['notify2'],
'sound;platform_system=="Windows"': ['winsound']
},
classifiers=[
"License :: OSI Approved :: MIT License"
"Operating System :: OS Independent",
"Programming Language :: Python :: 2.7 :: Only",
"Topic :: Internet",
"Topic :: Security :: Cryptography",
"Topic :: Software Development :: Libraries :: Python Modules",
],
package_dir={'pybitmessage': 'src'},
packages=packages,
package_data={'': [
'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem',
'translations/*.ts', 'translations/*.qm',
'images/*.png', 'images/*.ico', 'images/*.icns'
]},
data_files=[
('share/applications/',
['desktop/pybitmessage.desktop']),
('share/icons/hicolor/scalable/apps/',
['desktop/icons/scalable/pybitmessage.svg']),
('share/icons/hicolor/24x24/apps/',
['desktop/icons/24x24/pybitmessage.png'])
],
ext_modules=[bitmsghash],
zip_safe=False,
entry_points={
'bitmessage.gui.menu': [
'popMenuYourIdentities.qrcode = '
'pybitmessage.plugins.qrcodeui [qrcode]'
],
'bitmessage.notification.message': [
'notify2 = pybitmessage.plugins.notification_notify2'
'[gir, notify2]'
],
'bitmessage.notification.sound': [
'theme.canberra = pybitmessage.plugins.sound_canberra',
'file.gstreamer = pybitmessage.plugins.sound_gstreamer'
'[gir]',
'file.fallback = pybitmessage.plugins.sound_playfile'
'[sound]'
],
'bitmessage.indicator': [
'libmessaging ='
'pybitmessage.plugins.indicator_libmessaging [gir]'
],
# 'console_scripts': [
# 'pybitmessage = pybitmessage.bitmessagemain:main'
# ]
},
scripts=['src/pybitmessage'],
cmdclass={'install': InstallCmd}
)

View File

@ -13,8 +13,9 @@ if __name__ == "__main__":
sys.exit(0)
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
import base64
import json
from binascii import hexlify
from binascii import hexlify, unhexlify
import shared
import time
@ -25,14 +26,16 @@ import helper_inbox
import helper_sent
import hashlib
import protocol
import state
from pyelliptic.openssl import OpenSSL
import queues
import shutdown
from struct import pack
import network.stats
# Classes
from helper_sql import sqlQuery,sqlExecute,SqlBulkExecute,sqlStoredProcedure
from helper_ackPayload import genAckPayload
from debug import logger
from inventory import Inventory
from version import softwareVersion
@ -53,6 +56,8 @@ class APIError(Exception):
class StoppableXMLRPCServer(SimpleXMLRPCServer):
allow_reuse_address = True
def serve_forever(self):
while state.shutdown == 0:
self.handle_request()
@ -139,7 +144,10 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
def _decode(self, text, decode_type):
try:
return text.decode(decode_type)
if decode_type == 'hex':
return unhexlify(text)
elif decode_type == 'base64':
return base64.b64decode(text)
except Exception as e:
raise APIError(22, "Decode error - " + str(e) + ". Had trouble while decoding string: " + repr(text))
@ -169,22 +177,20 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
def HandleListAddresses(self, method):
data = '{"addresses":['
configSections = BMConfigParser().sections()
for addressInKeysFile in configSections:
if addressInKeysFile != 'bitmessagesettings':
status, addressVersionNumber, streamNumber, hash01 = decodeAddress(
addressInKeysFile)
if len(data) > 20:
data += ','
if BMConfigParser().has_option(addressInKeysFile, 'chan'):
chan = BMConfigParser().getboolean(addressInKeysFile, 'chan')
else:
chan = False
label = BMConfigParser().get(addressInKeysFile, 'label')
if method == 'listAddresses2':
label = label.encode('base64')
data += json.dumps({'label': label, 'address': addressInKeysFile, 'stream':
streamNumber, 'enabled': BMConfigParser().getboolean(addressInKeysFile, 'enabled'), 'chan': chan}, indent=4, separators=(',', ': '))
for addressInKeysFile in BMConfigParser().addresses():
status, addressVersionNumber, streamNumber, hash01 = decodeAddress(
addressInKeysFile)
if len(data) > 20:
data += ','
if BMConfigParser().has_option(addressInKeysFile, 'chan'):
chan = BMConfigParser().getboolean(addressInKeysFile, 'chan')
else:
chan = False
label = BMConfigParser().get(addressInKeysFile, 'label')
if method == 'listAddresses2':
label = base64.b64encode(label)
data += json.dumps({'label': label, 'address': addressInKeysFile, 'stream':
streamNumber, 'enabled': BMConfigParser().getboolean(addressInKeysFile, 'enabled'), 'chan': chan}, indent=4, separators=(',', ': '))
data += ']}'
return data
@ -203,7 +209,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
label = shared.fixPotentiallyInvalidUTF8Data(label)
if len(data) > 20:
data += ','
data += json.dumps({'label':label.encode('base64'), 'address': address}, indent=4, separators=(',', ': '))
data += json.dumps({'label':base64.b64encode(label), 'address': address}, indent=4, separators=(',', ': '))
data += ']}'
return data
@ -485,8 +491,10 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25:
data += ','
data += json.dumps({'msgid': hexlify(msgid), 'toAddress': toAddress, 'fromAddress': fromAddress, 'subject': subject.encode(
'base64'), 'message': message.encode('base64'), 'encodingType': encodingtype, 'receivedTime': received, 'read': read}, indent=4, separators=(',', ': '))
data += json.dumps({'msgid': hexlify(msgid), 'toAddress': toAddress,
'fromAddress': fromAddress, 'subject': base64.b64encode(subject),
'message': base64.b64encode(message), 'encodingType': encodingtype,
'receivedTime': received, 'read': read}, indent=4, separators=(',', ': '))
data += ']}'
return data
@ -523,7 +531,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
msgid, toAddress, fromAddress, subject, received, message, encodingtype, read = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':subject.encode('base64'), 'message':message.encode('base64'), 'encodingType':encodingtype, 'receivedTime':received, 'read': read}, indent=4, separators=(',', ': '))
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':base64.b64encode(subject), 'message':base64.b64encode(message), 'encodingType':encodingtype, 'receivedTime':received, 'read': read}, indent=4, separators=(',', ': '))
data += ']}'
return data
@ -536,7 +544,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25:
data += ','
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':subject.encode('base64'), 'message':message.encode('base64'), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':base64.b64encode(subject), 'message':base64.b64encode(message), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += ']}'
return data
@ -563,7 +571,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25:
data += ','
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':subject.encode('base64'), 'message':message.encode('base64'), 'encodingType':encodingtype, 'receivedTime':received}, indent=4, separators=(',', ': '))
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':base64.b64encode(subject), 'message':base64.b64encode(message), 'encodingType':encodingtype, 'receivedTime':received}, indent=4, separators=(',', ': '))
data += ']}'
return data
@ -577,7 +585,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
msgid, toAddress, fromAddress, subject, lastactiontime, message, encodingtype, status, ackdata = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':subject.encode('base64'), 'message':message.encode('base64'), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':base64.b64encode(subject), 'message':base64.b64encode(message), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += ']}'
return data
@ -594,7 +602,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
message = shared.fixPotentiallyInvalidUTF8Data(message)
if len(data) > 25:
data += ','
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':subject.encode('base64'), 'message':message.encode('base64'), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':base64.b64encode(subject), 'message':base64.b64encode(message), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += ']}'
return data
@ -609,7 +617,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
msgid, toAddress, fromAddress, subject, lastactiontime, message, encodingtype, status, ackdata = row
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
message = shared.fixPotentiallyInvalidUTF8Data(message)
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':subject.encode('base64'), 'message':message.encode('base64'), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += json.dumps({'msgid':hexlify(msgid), 'toAddress':toAddress, 'fromAddress':fromAddress, 'subject':base64.b64encode(subject), 'message':base64.b64encode(message), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': '))
data += ']}'
return data
@ -650,8 +658,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
TTL = 4*24*60*60
elif len(params) == 6:
toAddress, fromAddress, subject, message, encodingType, TTL = params
if encodingType != 2:
raise APIError(6, 'The encoding type must be 2 because that is the only one this program currently supports.')
if encodingType not in [2, 3]:
raise APIError(6, 'The encoding type must be 2 or 3.')
subject = self._decode(subject, "base64")
message = self._decode(message, "base64")
if len(subject + message) > (2 ** 18 - 500):
@ -672,7 +680,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
if not fromAddressEnabled:
raise APIError(14, 'Your fromAddress is disabled. Cannot send.')
ackdata = OpenSSL.rand(32)
stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
t = ('',
toAddress,
@ -716,8 +725,8 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
TTL = 4*24*60*60
elif len(params) == 5:
fromAddress, subject, message, encodingType, TTL = params
if encodingType != 2:
raise APIError(6, 'The encoding type must be 2 because that is the only one this program currently supports.')
if encodingType not in [2, 3]:
raise APIError(6, 'The encoding type must be 2 or 3.')
subject = self._decode(subject, "base64")
message = self._decode(message, "base64")
if len(subject + message) > (2 ** 18 - 500):
@ -733,7 +742,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
fromAddress, 'enabled')
except:
raise APIError(13, 'could not find your fromAddress in the keys.dat file.')
ackdata = OpenSSL.rand(32)
ackdata = genAckPayload(streamNumber, 0)
toAddress = '[Broadcast subscribers]'
ripe = ''
@ -818,15 +827,12 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
def ListSubscriptions(self, params):
queryreturn = sqlQuery('''SELECT label, address, enabled FROM subscriptions''')
data = '{"subscriptions":['
data = {'subscriptions': []}
for row in queryreturn:
label, address, enabled = row
label = shared.fixPotentiallyInvalidUTF8Data(label)
if len(data) > 20:
data += ','
data += json.dumps({'label':label.encode('base64'), 'address': address, 'enabled': enabled == 1}, indent=4, separators=(',',': '))
data += ']}'
return data
data['subscriptions'].append({'label':base64.b64encode(label), 'address': address, 'enabled': enabled == 1})
return json.dumps(data, indent=4, separators=(',',': '))
def HandleDisseminatePreEncryptedMsg(self, params):
# The device issuing this command to PyBitmessage supplies a msg object that has
@ -860,8 +866,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
objectType, toStreamNumber, encryptedPayload, int(time.time()) + TTL,'')
with shared.printLock:
print 'Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', hexlify(inventoryHash)
protocol.broadcastToSendDataQueues((
toStreamNumber, 'advertiseobject', inventoryHash))
queues.invQueue.put((toStreamNumber, inventoryHash))
def HandleTrashSentMessageByAckDAta(self, params):
# This API method should only be used when msgid is not available
@ -907,8 +912,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL,'')
with shared.printLock:
print 'broadcasting inv within API command disseminatePubkey with hash:', hexlify(inventoryHash)
protocol.broadcastToSendDataQueues((
pubkeyStreamNumber, 'advertiseobject', inventoryHash))
queues.invQueue.put((pubkeyStreamNumber, inventoryHash))
def HandleGetMessageDataByDestinationHash(self, params):
# Method will eventually be used by a particular Android app to
@ -946,13 +950,13 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
return data
def HandleClientStatus(self, params):
if len(shared.connectedHostsList) == 0:
if len(network.stats.connectedHostsList()) == 0:
networkStatus = 'notConnected'
elif len(shared.connectedHostsList) > 0 and not shared.clientHasReceivedIncomingConnections:
elif len(network.stats.connectedHostsList()) > 0 and not shared.clientHasReceivedIncomingConnections:
networkStatus = 'connectedButHaveNotReceivedIncomingConnections'
else:
networkStatus = 'connectedAndReceivingIncomingConnections'
return json.dumps({'networkConnections':len(shared.connectedHostsList),'numberOfMessagesProcessed':shared.numberOfMessagesProcessed, 'numberOfBroadcastsProcessed':shared.numberOfBroadcastsProcessed, 'numberOfPubkeysProcessed':shared.numberOfPubkeysProcessed, 'networkStatus':networkStatus, 'softwareName':'PyBitmessage','softwareVersion':softwareVersion}, indent=4, separators=(',', ': '))
return json.dumps({'networkConnections':len(network.stats.connectedHostsList()),'numberOfMessagesProcessed':shared.numberOfMessagesProcessed, 'numberOfBroadcastsProcessed':shared.numberOfBroadcastsProcessed, 'numberOfPubkeysProcessed':shared.numberOfPubkeysProcessed, 'networkStatus':networkStatus, 'softwareName':'PyBitmessage','softwareVersion':softwareVersion}, indent=4, separators=(',', ': '))
def HandleDecodeAddress(self, params):
# Return a meaningful decoding of an address.
@ -961,7 +965,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
address, = params
status, addressVersion, streamNumber, ripe = decodeAddress(address)
return json.dumps({'status':status, 'addressVersion':addressVersion,
'streamNumber':streamNumber, 'ripe':ripe.encode('base64')}, indent=4,
'streamNumber':streamNumber, 'ripe':base64.b64encode(ripe)}, indent=4,
separators=(',', ': '))
def HandleHelloWorld(self, params):
@ -977,8 +981,14 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
queues.UISignalQueue.put(('updateStatusBar', message))
def HandleDeleteAndVacuum(self, params):
sqlStoredProcedure('deleteandvacuume')
return 'done'
if not params:
sqlStoredProcedure('deleteandvacuume')
return 'done'
def HandleShutdown(self, params):
if not params:
shutdown.doCleanShutdown()
return 'done'
handlers = {}
handlers['helloWorld'] = HandleHelloWorld
@ -1030,10 +1040,11 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
handlers['clientStatus'] = HandleClientStatus
handlers['decodeAddress'] = HandleDecodeAddress
handlers['deleteAndVacuum'] = HandleDeleteAndVacuum
handlers['shutdown'] = HandleShutdown
def _handle_request(self, method, params):
if (self.handlers.has_key(method)):
return self.handlers[method](self ,params)
return self.handlers[method](self, params)
else:
raise APIError(20, 'Invalid method: %s' % method)
@ -1055,3 +1066,5 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
except Exception as e:
logger.exception(e)
return "API Error 0021: Unexpected API Failure - %s" % str(e)

View File

@ -1,17 +1,19 @@
#!/usr/bin/python2.7
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
# Created by Adam Melton (.dok) referenceing https://bitmessage.org/wiki/API_Reference for API documentation
# Distributed under the MIT/X11 software license. See http://www.opensource.org/licenses/mit-license.php.
# This is an example of a daemon client for PyBitmessage 0.4.2, by .dok (Version 0.3.0)
# This is an example of a daemon client for PyBitmessage 0.6.2, by .dok (Version 0.3.1) , modified
import xmlrpclib
import datetime
import hashlib
import getopt
#import hashlib
#import getopt
import imghdr
import ntpath
import json
import socket
import time
import sys
import os
@ -36,7 +38,7 @@ def userInput(message): #Checks input for exit or quit. Also formats for input,
elif (uInput.lower() == 'quit'): #Quits the program
print '\n Bye\n'
sys.exit()
os.exit()
os._exit() # _
else:
return uInput
@ -54,7 +56,7 @@ def lookupAppdataFolder(): #gets the appropriate folders for the .dat files depe
dataFolder = path.join(os.environ["HOME"], "Library/Application support/", APPNAME) + '/'
else:
print ' Could not find home folder, please report this message and your OS X version to the Daemon Github.'
os.exit()
os._exit()
elif 'win32' in sys.platform or 'win64' in sys.platform:
dataFolder = path.join(environ['APPDATA'], APPNAME) + '\\'
@ -116,7 +118,7 @@ def apiInit(apiEnabled):
apiUsr = userInput("API Username")
apiPwd = userInput("API Password")
apiInterface = userInput("API Interface. (127.0.0.1)")
#apiInterface = userInput("API Interface. (127.0.0.1)")
apiPort = userInput("API Port")
apiEnabled = userInput("API Enabled? (True) or (False)").lower()
daemon = userInput("Daemon mode Enabled? (True) or (False)").lower()
@ -206,7 +208,7 @@ def apiData():
apiInit("") #Initalize the keys.dat file with API information
#keys.dat file was found or appropriately configured, allow information retrieval
apiEnabled = apiInit(BMConfigParser().safeGetBoolean('bitmessagesettings','apienabled')) #if false it will prompt the user, if true it will return true
#apiEnabled = apiInit(BMConfigParser().safeGetBoolean('bitmessagesettings','apienabled')) #if false it will prompt the user, if true it will return true
BMConfigParser().read(keysPath)#read again since changes have been made
apiPort = int(BMConfigParser().get('bitmessagesettings', 'apiport'))
@ -423,7 +425,7 @@ def unsubscribe():
break
uInput = userInput("Are you sure, (Y)es or (N)o?").lower()
userInput("Are you sure, (Y)es or (N)o?").lower() # #uInput =
api.deleteSubscription(address)
print ('\n You are now unsubscribed from: ' + address + '\n')
@ -521,9 +523,9 @@ def listAdd(): #Lists all of the addresses and their info
print ' | # | Label | Address |S#|Enabled|'
print ' |---|-------------------|-------------------------------------|--|-------|'
for addNum in range (0, numAddresses): #processes all of the addresses and lists them out
label = str(jsonAddresses['addresses'][addNum]['label'])
label = (jsonAddresses['addresses'][addNum]['label' ]).encode('utf') # may still misdiplay in some consoles
address = str(jsonAddresses['addresses'][addNum]['address'])
stream = str(jsonAddresses['addresses'][addNum]['stream'])
stream = str(jsonAddresses['addresses'][addNum]['stream'])
enabled = str(jsonAddresses['addresses'][addNum]['enabled'])
if (len(label) > 19):
@ -592,7 +594,7 @@ def genMilAddr(): #Generate address
lbl = "random" + str(i)
addressLabel = lbl.encode('base64')
try:
generatedAddress = api.createRandomAddress(addressLabel)
api.createRandomAddress(addressLabel) # generatedAddress =
except:
print '\n Connection Error\n'
usrPrompt = 0
@ -911,7 +913,7 @@ def inbox(unreadOnly = False): #Lists the messages by: Message Number, To Addres
if not message['read']: messagesUnread += 1
if (messagesPrinted%20 == 0 and messagesPrinted != 0):
uInput = userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower()
userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower() # uInput =
print '\n -----------------------------------'
print ' There are %d unread messages of %d messages in the inbox.' % (messagesUnread, numMessages)
@ -939,7 +941,7 @@ def outbox():
print ' Last Action Time:', datetime.datetime.fromtimestamp(float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S')
if (msgNum%20 == 0 and msgNum != 0):
uInput = userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower()
userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower() # uInput =
print '\n -----------------------------------'
print ' There are ',numMessages,' messages in the outbox.'
@ -1283,6 +1285,13 @@ def clientStatus():
print "\nnumberOfMessagesProcessed: " + str(clientStatus['numberOfMessagesProcessed']) + "\n"
print "\nnumberOfBroadcastsProcessed: " + str(clientStatus['numberOfBroadcastsProcessed']) + "\n"
def shutdown():
try:
api.shutdown()
except socket.error:
pass
print "\nShutdown command relayed\n"
def UI(usrInput): #Main user menu
global usrPrompt
@ -1311,12 +1320,12 @@ def UI(usrInput): #Main user menu
print ' |------------------------|----------------------------------------------|'
print ' | subscribe | Subscribes to an address |'
print ' | unsubscribe | Unsubscribes from an address |'
#print ' | listSubscriptions | Lists all of the subscriptions. |'
#print' | listSubscriptions | Lists all of the subscriptions. |'
print ' |------------------------|----------------------------------------------|'
print ' | create | Creates a channel |'
print ' | create | Creates a channel |'
print ' | join | Joins a channel |'
print ' | leave | Leaves a channel |'
print ' |------------------------|----------------------------------------------|'
print ' |------------------------|----------------------------------------------|'
print ' | inbox | Lists the message information for the inbox |'
print ' | outbox | Lists the message information for the outbox |'
print ' | send | Send a new message or broadcast |'
@ -1360,7 +1369,7 @@ def UI(usrInput): #Main user menu
elif usrInput == "quit": #Quits the application
print '\n Bye\n'
sys.exit()
os.exit()
os._exit()
elif usrInput == "listaddresses": #Lists all of the identities in the addressbook
listAdd()
@ -1437,17 +1446,17 @@ def UI(usrInput): #Main user menu
elif usrInput == "create":
createChan()
userPrompt = 1
usrPrompt = 1
main()
elif usrInput == "join":
joinChan()
userPrompt = 1
usrPrompt = 1
main()
elif usrInput == "leave":
leaveChan()
userPrompt = 1
usrPrompt = 1
main()
elif usrInput == "inbox":
@ -1660,7 +1669,7 @@ def UI(usrInput): #Main user menu
usrPrompt = 1
else:
print '\n Invalid Entry.\n'
userPrompt = 1
usrPrompt = 1
main()
elif usrInput == "exit":
@ -1705,6 +1714,11 @@ def UI(usrInput): #Main user menu
usrPrompt = 1
main()
elif usrInput == "shutdown":
shutdown()
usrPrompt = 1
main()
elif usrInput == "million+":
genMilAddr()
usrPrompt = 1
@ -1727,7 +1741,7 @@ def main():
if (usrPrompt == 0):
print '\n ------------------------------'
print ' | Bitmessage Daemon by .dok |'
print ' | Version 0.2.6 for BM 0.3.5 |'
print ' | Version 0.3.1 for BM 0.6.2 |'
print ' ------------------------------'
api = xmlrpclib.ServerProxy(apiData()) #Connect to BitMessage using these api credentials
@ -1756,4 +1770,4 @@ def main():
UI("quit")
if __name__ == "__main__":
main()
main()

View File

@ -20,6 +20,7 @@ import curses
import dialog
from dialog import Dialog
from helper_sql import *
from helper_ackPayload import genAckPayload
from addresses import *
import ConfigParser
@ -778,7 +779,8 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F
if len(shared.connectedHostsList) == 0:
set_background_title(d, "Not connected warning")
scrollbox(d, unicode("Because you are not currently connected to the network, "))
ackdata = OpenSSL.rand(32)
stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
sqlExecute(
"INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
"",
@ -802,7 +804,8 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F
set_background_title(d, "Empty sender error")
scrollbox(d, unicode("You must specify an address to send the message from."))
else:
ackdata = OpenSSL.rand(32)
# dummy ackdata, no need for stealth
ackdata = genAckPayload(streamNumber, 0)
recv = BROADCAST_STR
ripe = ""
sqlExecute(
@ -927,7 +930,7 @@ def loadSent():
statstr = "Message sent at "+t+"."
elif status == "doingmsgpow":
statstr = "The proof of work required to send the message has been queued."
elif status == "askreceived":
elif status == "ackreceived":
t = l10n.formatTimestamp(lastactiontime, False)
statstr = "Acknowledgment of the message received at "+t+"."
elif status == "broadcastqueued":
@ -1024,20 +1027,19 @@ def run(stdscr):
curses.init_pair(9, curses.COLOR_YELLOW, curses.COLOR_BLACK) # orangish
# Init list of address in 'Your Identities' tab
configSections = BMConfigParser().sections()
configSections = BMConfigParser().addressses()
for addressInKeysFile in configSections:
if addressInKeysFile != "bitmessagesettings":
isEnabled = BMConfigParser().getboolean(addressInKeysFile, "enabled")
addresses.append([BMConfigParser().get(addressInKeysFile, "label"), isEnabled, addressInKeysFile])
# Set address color
if not isEnabled:
addresses[len(addresses)-1].append(8) # gray
elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'chan'):
addresses[len(addresses)-1].append(9) # orange
elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'mailinglist'):
addresses[len(addresses)-1].append(5) # magenta
else:
addresses[len(addresses)-1].append(0) # black
isEnabled = BMConfigParser().getboolean(addressInKeysFile, "enabled")
addresses.append([BMConfigParser().get(addressInKeysFile, "label"), isEnabled, addressInKeysFile])
# Set address color
if not isEnabled:
addresses[len(addresses)-1].append(8) # gray
elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'chan'):
addresses[len(addresses)-1].append(9) # orange
elif BMConfigParser().safeGetBoolean(addressInKeysFile, 'mailinglist'):
addresses[len(addresses)-1].append(5) # magenta
else:
addresses[len(addresses)-1].append(0) # black
addresses.reverse()
stdscr.clear()

View File

@ -22,11 +22,14 @@ depends.check_dependencies()
import signal # Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully.
# The next 3 are used for the API
from singleinstance import singleinstance
import errno
import socket
import ctypes
from struct import pack
from subprocess import call
import time
from time import sleep
from random import randint
import getopt
from api import MySimpleXMLRPCRequestHandler, StoppableXMLRPCServer
from helper_startup import isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections
@ -42,18 +45,27 @@ import threading
from class_sqlThread import sqlThread
from class_singleCleaner import singleCleaner
from class_objectProcessor import objectProcessor
from class_outgoingSynSender import outgoingSynSender
from class_singleListener import singleListener
from class_singleWorker import singleWorker
from class_addressGenerator import addressGenerator
from class_smtpDeliver import smtpDeliver
from class_smtpServer import smtpServer
from bmconfigparser import BMConfigParser
from inventory import Inventory
from network.connectionpool import BMConnectionPool
from network.dandelion import Dandelion
from network.networkthread import BMNetworkThread
from network.receivequeuethread import ReceiveQueueThread
from network.announcethread import AnnounceThread
from network.invthread import InvThread
from network.addrthread import AddrThread
from network.downloadthread import DownloadThread
# Helper Functions
import helper_bootstrap
import helper_generic
from helper_threading import *
import helper_threading
def connectToStream(streamNumber):
@ -62,13 +74,13 @@ def connectToStream(streamNumber):
if isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections():
# Some XP and Vista systems can only have 10 outgoing connections at a time.
maximumNumberOfHalfOpenConnections = 9
state.maximumNumberOfHalfOpenConnections = 9
else:
maximumNumberOfHalfOpenConnections = 64
state.maximumNumberOfHalfOpenConnections = 64
try:
# don't overload Tor
if BMConfigParser().get('bitmessagesettings', 'socksproxytype') != 'none':
maximumNumberOfHalfOpenConnections = 4
state.maximumNumberOfHalfOpenConnections = 4
except:
pass
@ -80,13 +92,13 @@ def connectToStream(streamNumber):
if streamNumber*2+1 not in knownnodes.knownNodes:
knownnodes.knownNodes[streamNumber*2+1] = {}
for i in range(maximumNumberOfHalfOpenConnections):
a = outgoingSynSender()
a.setup(streamNumber, selfInitiatedConnections)
a.start()
BMConnectionPool().connectToStream(streamNumber)
def _fixWinsock():
if not ('win32' in sys.platform) and not ('win64' in sys.platform):
def _fixSocket():
if sys.platform.startswith('linux'):
socket.SO_BINDTODEVICE = 25
if not sys.platform.startswith('win'):
return
# Python 2 on Windows doesn't define a wrapper for
@ -137,7 +149,7 @@ def _fixWinsock():
socket.IPV6_V6ONLY = 27
# This thread, of which there is only one, runs the API.
class singleAPI(threading.Thread, StoppableThread):
class singleAPI(threading.Thread, helper_threading.StoppableThread):
def __init__(self):
threading.Thread.__init__(self, name="singleAPI")
self.initStop()
@ -154,8 +166,25 @@ class singleAPI(threading.Thread, StoppableThread):
pass
def run(self):
se = StoppableXMLRPCServer((BMConfigParser().get('bitmessagesettings', 'apiinterface'), BMConfigParser().getint(
'bitmessagesettings', 'apiport')), MySimpleXMLRPCRequestHandler, True, True)
port = BMConfigParser().getint('bitmessagesettings', 'apiport')
try:
from errno import WSAEADDRINUSE
except (ImportError, AttributeError):
errno.WSAEADDRINUSE = errno.EADDRINUSE
for attempt in range(50):
try:
if attempt > 0:
port = randint(32767, 65535)
se = StoppableXMLRPCServer((BMConfigParser().get('bitmessagesettings', 'apiinterface'), port),
MySimpleXMLRPCRequestHandler, True, True)
except socket.error as e:
if e.errno in (errno.EADDRINUSE, errno.WSAEADDRINUSE):
continue
else:
if attempt > 0:
BMConfigParser().set("bitmessagesettings", "apiport", str(port))
BMConfigParser().save()
break
se.register_introspection_functions()
se.serve_forever()
@ -169,14 +198,27 @@ if shared.useVeryEasyProofOfWorkForTesting:
defaults.networkDefaultPayloadLengthExtraBytes / 100)
class Main:
def start(self, daemon=False):
_fixWinsock()
def start(self):
_fixSocket()
shared.daemon = daemon
daemon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon')
# get curses flag
if '-c' in sys.argv:
state.curses = True
try:
opts, args = getopt.getopt(sys.argv[1:], "hcd",
["help", "curses", "daemon"])
except getopt.GetoptError:
self.usage()
sys.exit(2)
for opt, arg in opts:
if opt in ("-h", "--help"):
self.usage()
sys.exit()
elif opt in ("-d", "--daemon"):
daemon = True
elif opt in ("-c", "--curses"):
state.curses = True
# is the application already running? If yes then exit.
shared.thisapp = singleinstance("", daemon)
@ -188,6 +230,13 @@ class Main:
self.setSignalHandler()
helper_threading.set_thread_name("PyBitmessage")
state.dandelion = BMConfigParser().safeGetInt('network', 'dandelion')
# dandelion requires outbound connections, without them, stem objects will get stuck forever
if state.dandelion and not BMConfigParser().safeGetBoolean('bitmessagesettings', 'sendoutgoingconnections'):
state.dandelion = 0
helper_bootstrap.knownNodes()
# Start the address generation thread
addressGeneratorThread = addressGenerator()
@ -204,6 +253,9 @@ class Main:
sqlLookup.daemon = False # DON'T close the main program even if there are threads left. The closeEvent should command this thread to exit gracefully.
sqlLookup.start()
Inventory() # init
Dandelion() # init, needs to be early because other thread may access it early
# SMTP delivery thread
if daemon and BMConfigParser().safeGet("bitmessagesettings", "smtpdeliver", '') != '':
smtpDeliveryThread = smtpDeliver()
@ -242,13 +294,29 @@ class Main:
singleAPIThread.daemon = True # close the main program even if there are threads left
singleAPIThread.start()
BMConnectionPool()
asyncoreThread = BMNetworkThread()
asyncoreThread.daemon = True
asyncoreThread.start()
for i in range(BMConfigParser().getint("threads", "receive")):
receiveQueueThread = ReceiveQueueThread(i)
receiveQueueThread.daemon = True
receiveQueueThread.start()
announceThread = AnnounceThread()
announceThread.daemon = True
announceThread.start()
state.invThread = InvThread()
state.invThread.daemon = True
state.invThread.start()
state.addrThread = AddrThread()
state.addrThread.daemon = True
state.addrThread.start()
state.downloadThread = DownloadThread()
state.downloadThread.daemon = True
state.downloadThread.start()
connectToStream(1)
singleListenerThread = singleListener()
singleListenerThread.setup(selfInitiatedConnections)
singleListenerThread.daemon = True # close the main program even if there are threads left
singleListenerThread.start()
if BMConfigParser().safeGetBoolean('bitmessagesettings','upnp'):
import upnp
upnpThread = upnp.uPnPThread()
@ -272,33 +340,77 @@ class Main:
else:
BMConfigParser().remove_option('bitmessagesettings', 'dontconnect')
while True:
time.sleep(20)
if daemon:
while state.shutdown == 0:
sleep(1)
def daemonize(self):
if os.fork():
exit(0)
shared.thisapp.lock() # relock
grandfatherPid = os.getpid()
parentPid = None
try:
if os.fork():
# unlock
shared.thisapp.cleanup()
# wait until grandchild ready
while True:
sleep(1)
os._exit(0)
except AttributeError:
# fork not implemented
pass
else:
parentPid = os.getpid()
shared.thisapp.lock() # relock
os.umask(0)
os.setsid()
if os.fork():
exit(0)
shared.thisapp.lock() # relock
try:
os.setsid()
except AttributeError:
# setsid not implemented
pass
try:
if os.fork():
# unlock
shared.thisapp.cleanup()
# wait until child ready
while True:
sleep(1)
os._exit(0)
except AttributeError:
# fork not implemented
pass
else:
shared.thisapp.lock() # relock
shared.thisapp.lockPid = None # indicate we're the final child
sys.stdout.flush()
sys.stderr.flush()
si = file('/dev/null', 'r')
so = file('/dev/null', 'a+')
se = file('/dev/null', 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
if not sys.platform.startswith('win'):
si = file(os.devnull, 'r')
so = file(os.devnull, 'a+')
se = file(os.devnull, 'a+', 0)
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
if parentPid:
# signal ready
os.kill(parentPid, signal.SIGTERM)
os.kill(grandfatherPid, signal.SIGTERM)
def setSignalHandler(self):
signal.signal(signal.SIGINT, helper_generic.signal_handler)
signal.signal(signal.SIGTERM, helper_generic.signal_handler)
# signal.signal(signal.SIGINT, signal.SIG_DFL)
def usage(self):
print 'Usage: ' + sys.argv[0] + ' [OPTIONS]'
print '''
Options:
-h, --help show this help message and exit
-c, --curses use curses (text mode) interface
-d, --daemon run in daemon (background) mode
All parameters are optional.
'''
def stop(self):
with shared.printLock:
print('Stopping Bitmessage Deamon.')
@ -316,8 +428,7 @@ class Main:
def main():
mainprogram = Main()
mainprogram.start(
BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon'))
mainprogram.start()
if __name__ == "__main__":
main()

View File

@ -1,83 +1,56 @@
from debug import logger
withMessagingMenu = False
try:
import gi
gi.require_version('MessagingMenu', '1.0')
from gi.repository import MessagingMenu
gi.require_version('Notify', '0.7')
from gi.repository import Notify
withMessagingMenu = True
except (ImportError, ValueError):
MessagingMenu = None
import sys
try:
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtNetwork import QLocalSocket, QLocalServer
except Exception as err:
logmsg = 'PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download it from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\' (without quotes).'
logger.critical(logmsg, exc_info=True)
import sys
sys.exit()
try:
_encoding = QtGui.QApplication.UnicodeUTF8
except AttributeError:
logger.exception('QtGui.QApplication.UnicodeUTF8 error', exc_info=True)
from addresses import *
from tr import _translate
from addresses import decodeAddress, addBMIfNotPresent
import shared
from bitmessageui import *
from bitmessageui import Ui_MainWindow
from bmconfigparser import BMConfigParser
import defaults
from namecoin import namecoinConnection, ensureNamecoinOptions
from newaddressdialog import *
from newaddresswizard import *
from namecoin import namecoinConnection
from messageview import MessageView
from migrationwizard import *
from foldertree import *
from newsubscriptiondialog import *
from regenerateaddresses import *
from newchandialog import *
from safehtmlparser import *
from specialaddressbehavior import *
from emailgateway import *
from settings import *
from migrationwizard import Ui_MigrationWizard
from foldertree import (
AccountMixin, Ui_FolderWidget, Ui_AddressWidget, Ui_SubscriptionWidget,
MessageList_AddressWidget, MessageList_SubjectWidget,
Ui_AddressBookWidgetItemLabel, Ui_AddressBookWidgetItemAddress)
from settings import Ui_settingsDialog
import settingsmixin
import support
from about import *
from help import *
from iconglossary import *
from connect import *
import locale
import sys
from time import strftime, localtime, gmtime
import time
import os
import hashlib
from pyelliptic.openssl import OpenSSL
import platform
import textwrap
import debug
import random
import subprocess
from sqlite3 import register_adapter
import string
import datetime
from helper_sql import *
from datetime import datetime, timedelta
from helper_ackPayload import genAckPayload
from helper_sql import sqlQuery, sqlExecute, sqlExecuteChunked, sqlStoredProcedure
import helper_search
import l10n
import openclpow
import types
from utils import *
from collections import OrderedDict
from account import *
from class_objectHashHolder import objectHashHolder
from class_singleWorker import singleWorker
from dialogs import AddAddressDialog
from utils import str_broadcast_subscribers, avatarize
from account import (
getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount,
GatewayAccount, MailchuckAccount, AccountColor)
import dialogs
from helper_generic import powQueueSize
from inventory import PendingDownload, PendingUpload, PendingUploadDeadlineException
from inventory import (
PendingDownloadQueue, PendingUpload,
PendingUploadDeadlineException)
from uisignaler import UISignaler
import knownnodes
import paths
from proofofwork import getPowType
@ -85,14 +58,15 @@ import queues
import shutdown
import state
from statusbar import BMStatusBar
import throttle
from version import softwareVersion
from network.asyncore_pollchoose import set_rates
import sound
try:
from plugins.plugin import get_plugin, get_plugins
except ImportError:
get_plugins = False
def _translate(context, text, disambiguation = None, encoding = None, number = None):
if number is None:
return QtGui.QApplication.translate(context, text)
else:
return QtGui.QApplication.translate(context, text, None, QtCore.QCoreApplication.CodecForTr, number)
def change_translation(newlocale):
global qmytranslator, qsystranslator
@ -136,30 +110,23 @@ def change_translation(newlocale):
except:
logger.error("Failed to set locale to %s", lang, exc_info=True)
class MyForm(settingsmixin.SMainWindow):
# sound type constants
SOUND_NONE = 0
SOUND_KNOWN = 1
SOUND_UNKNOWN = 2
SOUND_CONNECTED = 3
SOUND_DISCONNECTED = 4
SOUND_CONNECTION_GREEN = 5
# the last time that a message arrival sound was played
lastSoundTime = datetime.datetime.now() - datetime.timedelta(days=1)
lastSoundTime = datetime.now() - timedelta(days=1)
# the maximum frequency of message sounds in seconds
maxSoundFrequencySec = 60
str_chan = '[chan]'
REPLY_TYPE_SENDER = 0
REPLY_TYPE_CHAN = 1
def init_file_menu(self):
QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL(
"triggered()"), self.quit)
QtCore.QObject.connect(self.ui.actionNetworkSwitch, QtCore.SIGNAL(
"triggered()"), self.network_switch)
QtCore.QObject.connect(self.ui.actionManageKeys, QtCore.SIGNAL(
"triggered()"), self.click_actionManageKeys)
QtCore.QObject.connect(self.ui.actionDeleteAllTrashedMessages,
@ -181,6 +148,8 @@ class MyForm(settingsmixin.SMainWindow):
"clicked()"), self.click_pushButtonAddSubscription)
QtCore.QObject.connect(self.ui.pushButtonTTL, QtCore.SIGNAL(
"clicked()"), self.click_pushButtonTTL)
QtCore.QObject.connect(self.ui.pushButtonClear, QtCore.SIGNAL(
"clicked()"), self.click_pushButtonClear)
QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL(
"clicked()"), self.click_pushButtonSend)
QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL(
@ -343,6 +312,10 @@ class MyForm(settingsmixin.SMainWindow):
_translate(
"MainWindow", "Set avatar..."),
self.on_action_AddressBookSetAvatar)
self.actionAddressBookSetSound = \
self.ui.addressBookContextMenuToolbar.addAction(
_translate("MainWindow", "Set notification sound..."),
self.on_action_AddressBookSetSound)
self.actionAddressBookNew = self.ui.addressBookContextMenuToolbar.addAction(
_translate(
"MainWindow", "Add New Address"), self.on_action_AddressBookNew)
@ -409,7 +382,8 @@ class MyForm(settingsmixin.SMainWindow):
# sort ascending when creating
if treeWidget.topLevelItemCount() == 0:
treeWidget.header().setSortIndicator(0, Qt.AscendingOrder)
treeWidget.header().setSortIndicator(
0, QtCore.Qt.AscendingOrder)
# init dictionary
db = getSortedSubscriptions(True)
@ -494,7 +468,8 @@ class MyForm(settingsmixin.SMainWindow):
# sort ascending when creating
if treeWidget.topLevelItemCount() == 0:
treeWidget.header().setSortIndicator(0, Qt.AscendingOrder)
treeWidget.header().setSortIndicator(
0, QtCore.Qt.AscendingOrder)
# init dictionary
db = {}
enabled = {}
@ -623,7 +598,7 @@ class MyForm(settingsmixin.SMainWindow):
if 'win32' in sys.platform or 'win64' in sys.platform:
# Auto-startup for Windows
RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
self.settings = QSettings(RUN_PATH, QSettings.NativeFormat)
self.settings = QtCore.QSettings(RUN_PATH, QtCore.QSettings.NativeFormat)
self.settings.remove(
"PyBitmessage") # In case the user moves the program and the registry entry is no longer valid, this will delete the old registry entry.
if BMConfigParser().getboolean('bitmessagesettings', 'startonlogon'):
@ -659,14 +634,12 @@ class MyForm(settingsmixin.SMainWindow):
self.rerenderTabTreeMessages()
# Set welcome message
self.ui.textEditInboxMessage.setText(
"""
self.ui.textEditInboxMessage.setText(_translate("MainWindow", """
Welcome to easy and secure Bitmessage
* send messages to other people
* send broadcast messages like twitter or
* discuss in chan(nel)s with other people
"""
)
"""))
# Initialize the address book
self.rerenderAddressBook()
@ -716,6 +689,10 @@ class MyForm(settingsmixin.SMainWindow):
"itemSelectionChanged ()"), self.treeWidgetItemClicked)
QtCore.QObject.connect(self.ui.treeWidgetChans, QtCore.SIGNAL(
"itemChanged (QTreeWidgetItem *, int)"), self.treeWidgetItemChanged)
QtCore.QObject.connect(
self.ui.tabWidget, QtCore.SIGNAL("currentChanged(int)"),
self.tabWidgetCurrentChanged
)
# Put the colored icon on the status bar
# self.pushButtonStatusIcon.setIcon(QIcon(":/newPrefix/images/yellowicon.png"))
@ -724,7 +701,8 @@ class MyForm(settingsmixin.SMainWindow):
self.pushButtonStatusIcon = QtGui.QPushButton(self)
self.pushButtonStatusIcon.setText('')
self.pushButtonStatusIcon.setIcon(QIcon(':/newPrefix/images/redicon.png'))
self.pushButtonStatusIcon.setIcon(
QtGui.QIcon(':/newPrefix/images/redicon.png'))
self.pushButtonStatusIcon.setFlat(True)
self.statusbar.insertPermanentWidget(0, self.pushButtonStatusIcon)
QtCore.QObject.connect(self.pushButtonStatusIcon, QtCore.SIGNAL(
@ -864,14 +842,6 @@ class MyForm(settingsmixin.SMainWindow):
self.raise_()
self.activateWindow()
# pointer to the application
# app = None
# The most recent message
newMessageItem = None
# The most recent broadcast
newBroadcastItem = None
# show the application window
def appIndicatorShow(self):
if self.actionShow is None:
@ -888,6 +858,12 @@ class MyForm(settingsmixin.SMainWindow):
self.actionShow.setChecked(False)
self.appIndicatorShowOrHideWindow()
def appIndicatorSwitchQuietMode(self):
BMConfigParser().set(
'bitmessagesettings', 'showtraynotifications',
str(not self.actionQuiet.isChecked())
)
# application indicator show or hide
"""# application indicator show or hide
def appIndicatorShowBitmessage(self):
@ -901,48 +877,83 @@ class MyForm(settingsmixin.SMainWindow):
self.appIndicatorShowOrHideWindow()"""
# Show the program window and select inbox tab
def appIndicatorInbox(self, mm_app, source_id):
def appIndicatorInbox(self, item=None):
self.appIndicatorShow()
# select inbox
self.ui.tabWidget.setCurrentIndex(0)
selectedItem = None
if source_id == 'Subscriptions':
# select unread broadcast
if self.newBroadcastItem is not None:
selectedItem = self.newBroadcastItem
self.newBroadcastItem = None
else:
# select unread message
if self.newMessageItem is not None:
selectedItem = self.newMessageItem
self.newMessageItem = None
# make it the current item
if selectedItem is not None:
try:
self.ui.tableWidgetInbox.setCurrentItem(selectedItem)
except Exception:
self.ui.tableWidgetInbox.setCurrentCell(0, 0)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.inbox)
)
self.ui.treeWidgetYourIdentities.setCurrentItem(
self.ui.treeWidgetYourIdentities.topLevelItem(0).child(0)
)
if item:
self.ui.tableWidgetInbox.setCurrentItem(item)
self.tableWidgetInboxItemClicked()
else:
# just select the first item
self.ui.tableWidgetInbox.setCurrentCell(0, 0)
self.tableWidgetInboxItemClicked()
# Show the program window and select send tab
def appIndicatorSend(self):
self.appIndicatorShow()
self.ui.tabWidget.setCurrentIndex(1)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.send)
)
# Show the program window and select subscriptions tab
def appIndicatorSubscribe(self):
self.appIndicatorShow()
self.ui.tabWidget.setCurrentIndex(2)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.subscriptions)
)
# Show the program window and select channels tab
def appIndicatorChannel(self):
self.appIndicatorShow()
self.ui.tabWidget.setCurrentIndex(3)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.chans)
)
def updateUnreadStatus(self, widget, row, msgid, unread=True):
"""
Switch unread for item of msgid and related items in
other STableWidgets "All Accounts" and "Chans"
"""
related = [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans]
try:
related.remove(widget)
related = related.pop()
except ValueError:
rrow = None
related = []
else:
# maybe use instead:
# rrow = related.row(msgid), msgid should be QTableWidgetItem
# related = related.findItems(msgid, QtCore.Qt.MatchExactly),
# returns an empty list
for rrow in xrange(related.rowCount()):
if msgid == str(related.item(rrow, 3).data(
QtCore.Qt.UserRole).toPyObject()):
break
else:
rrow = None
status = widget.item(row, 0).unread
if status == unread:
font = QtGui.QFont()
font.setBold(not status)
widget.item(row, 3).setFont(font)
for col in (0, 1, 2):
widget.item(row, col).setUnread(not status)
try:
related.item(rrow, 3).setFont(font)
except (TypeError, AttributeError):
pass
else:
for col in (0, 1, 2):
related.item(rrow, col).setUnread(not status)
def propagateUnreadCount(self, address = None, folder = "inbox", widget = None, type = 1):
widgets = [self.ui.treeWidgetYourIdentities, self.ui.treeWidgetSubscriptions, self.ui.treeWidgetChans]
queryReturn = sqlQuery("SELECT toaddress, folder, COUNT(msgid) AS cnt FROM inbox WHERE read = 0 GROUP BY toaddress, folder")
@ -1061,7 +1072,7 @@ class MyForm(settingsmixin.SMainWindow):
l10n.formatTimestamp(lastactiontime))
newItem = myTableWidgetItem(statusText)
newItem.setToolTip(statusText)
newItem.setData(Qt.UserRole, QByteArray(ackdata))
newItem.setData(QtCore.Qt.UserRole, QtCore.QByteArray(ackdata))
newItem.setData(33, int(lastactiontime))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
@ -1070,7 +1081,7 @@ class MyForm(settingsmixin.SMainWindow):
return acct
def addMessageListItemInbox(self, tableWidget, msgfolder, msgid, toAddress, fromAddress, subject, received, read):
font = QFont()
font = QtGui.QFont()
font.setBold(True)
if toAddress == str_broadcast_subscribers:
acct = accountClass(fromAddress)
@ -1092,7 +1103,7 @@ class MyForm(settingsmixin.SMainWindow):
# time received
time_item = myTableWidgetItem(l10n.formatTimestamp(received))
time_item.setToolTip(l10n.formatTimestamp(received))
time_item.setData(Qt.UserRole, QByteArray(msgid))
time_item.setData(QtCore.Qt.UserRole, QtCore.QByteArray(msgid))
time_item.setData(33, int(received))
time_item.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
@ -1129,7 +1140,8 @@ class MyForm(settingsmixin.SMainWindow):
toAddress, fromAddress, subject, status, ackdata, lastactiontime = row
self.addMessageListItemSent(tableWidget, toAddress, fromAddress, subject, status, ackdata, lastactiontime)
tableWidget.horizontalHeader().setSortIndicator(3, Qt.DescendingOrder)
tableWidget.horizontalHeader().setSortIndicator(
3, QtCore.Qt.DescendingOrder)
tableWidget.setSortingEnabled(True)
tableWidget.horizontalHeaderItem(3).setText(_translate("MainWindow", "Sent", None))
tableWidget.setUpdatesEnabled(True)
@ -1161,7 +1173,8 @@ class MyForm(settingsmixin.SMainWindow):
msgfolder, msgid, toAddress, fromAddress, subject, received, read = row
self.addMessageListItemInbox(tableWidget, msgfolder, msgid, toAddress, fromAddress, subject, received, read)
tableWidget.horizontalHeader().setSortIndicator(3, Qt.DescendingOrder)
tableWidget.horizontalHeader().setSortIndicator(
3, QtCore.Qt.DescendingOrder)
tableWidget.setSortingEnabled(True)
tableWidget.selectRow(0)
tableWidget.horizontalHeaderItem(3).setText(_translate("MainWindow", "Received", None))
@ -1174,7 +1187,7 @@ class MyForm(settingsmixin.SMainWindow):
QtCore.QObject.connect(self.tray, QtCore.SIGNAL(
traySignal), self.__icon_activated)
m = QMenu()
m = QtGui.QMenu()
self.actionStatus = QtGui.QAction(_translate(
"MainWindow", "Not Connected"), m, checkable=False)
@ -1194,6 +1207,14 @@ class MyForm(settingsmixin.SMainWindow):
if not sys.platform[0:3] == 'win':
m.addAction(self.actionShow)
# quiet mode
self.actionQuiet = QtGui.QAction(_translate(
"MainWindow", "Quiet Mode"), m, checkable=True)
self.actionQuiet.setChecked(not BMConfigParser().getboolean(
'bitmessagesettings', 'showtraynotifications'))
self.actionQuiet.triggered.connect(self.appIndicatorSwitchQuietMode)
m.addAction(self.actionQuiet)
# Send
actionSend = QtGui.QAction(_translate(
"MainWindow", "Send"), m, checkable=False)
@ -1224,262 +1245,141 @@ class MyForm(settingsmixin.SMainWindow):
self.tray.setContextMenu(m)
self.tray.show()
# Ubuntu Messaging menu object
mmapp = None
# is the operating system Ubuntu?
def isUbuntu(self):
for entry in platform.uname():
if "Ubuntu" in entry:
return True
return False
# When an unread inbox row is selected on then clear the messaging menu
def ubuntuMessagingMenuClear(self, inventoryHash):
# if this isn't ubuntu then don't do anything
if not self.isUbuntu():
return
# has messageing menu been installed
if not withMessagingMenu:
return
# if there are no items on the messaging menu then
# the subsequent query can be avoided
if not (self.mmapp.has_source("Subscriptions") or self.mmapp.has_source("Messages")):
return
queryreturn = sqlQuery(
'''SELECT toaddress, read FROM inbox WHERE msgid=?''', inventoryHash)
for row in queryreturn:
toAddress, read = row
if not read:
if toAddress == str_broadcast_subscribers:
if self.mmapp.has_source("Subscriptions"):
self.mmapp.remove_source("Subscriptions")
else:
if self.mmapp.has_source("Messages"):
self.mmapp.remove_source("Messages")
# returns the number of unread messages and subscriptions
def getUnread(self):
unreadMessages = 0
unreadSubscriptions = 0
counters = [0, 0]
queryreturn = sqlQuery(
'''SELECT msgid, toaddress, read FROM inbox where folder='inbox' ''')
for row in queryreturn:
msgid, toAddress, read = row
try:
if toAddress == str_broadcast_subscribers:
toLabel = str_broadcast_subscribers
else:
toLabel = BMConfigParser().get(toAddress, 'label')
except:
toLabel = ''
if toLabel == '':
toLabel = toAddress
queryreturn = sqlQuery('''
SELECT msgid, toaddress, read FROM inbox where folder='inbox'
''')
for msgid, toAddress, read in queryreturn:
if not read:
if toLabel == str_broadcast_subscribers:
# increment the unread subscriptions
unreadSubscriptions = unreadSubscriptions + 1
else:
# increment the unread messages
unreadMessages = unreadMessages + 1
return unreadMessages, unreadSubscriptions
# increment the unread subscriptions if True (1)
# else messages (0)
counters[toAddress == str_broadcast_subscribers] += 1
# show the number of unread messages and subscriptions on the messaging
# menu
def ubuntuMessagingMenuUnread(self, drawAttention):
unreadMessages, unreadSubscriptions = self.getUnread()
# unread messages
if unreadMessages > 0:
self.mmapp.append_source(
"Messages", None, "Messages (" + str(unreadMessages) + ")")
if drawAttention:
self.mmapp.draw_attention("Messages")
# unread subscriptions
if unreadSubscriptions > 0:
self.mmapp.append_source("Subscriptions", None, "Subscriptions (" + str(
unreadSubscriptions) + ")")
if drawAttention:
self.mmapp.draw_attention("Subscriptions")
# initialise the Ubuntu messaging menu
def ubuntuMessagingMenuInit(self):
global withMessagingMenu
# if this isn't ubuntu then don't do anything
if not self.isUbuntu():
return
# has messageing menu been installed
if not withMessagingMenu:
logger.warning('WARNING: MessagingMenu is not available. Is libmessaging-menu-dev installed?')
return
# create the menu server
if withMessagingMenu:
try:
self.mmapp = MessagingMenu.App(
desktop_id='pybitmessage.desktop')
self.mmapp.register()
self.mmapp.connect('activate-source', self.appIndicatorInbox)
self.ubuntuMessagingMenuUnread(True)
except Exception:
withMessagingMenu = False
logger.warning('WARNING: messaging menu disabled')
# update the Ubuntu messaging menu
def ubuntuMessagingMenuUpdate(self, drawAttention, newItem, toLabel):
# if this isn't ubuntu then don't do anything
if not self.isUbuntu():
return
# has messageing menu been installed
if not withMessagingMenu:
logger.warning('WARNING: messaging menu disabled or libmessaging-menu-dev not installed')
return
# remember this item to that the messaging menu can find it
if toLabel == str_broadcast_subscribers:
self.newBroadcastItem = newItem
else:
self.newMessageItem = newItem
# Remove previous messages and subscriptions entries, then recreate them
# There might be a better way to do it than this
if self.mmapp.has_source("Messages"):
self.mmapp.remove_source("Messages")
if self.mmapp.has_source("Subscriptions"):
self.mmapp.remove_source("Subscriptions")
# update the menu entries
self.ubuntuMessagingMenuUnread(drawAttention)
# returns true if the given sound category is a connection sound
# rather than a received message sound
def isConnectionSound(self, category):
if (category is self.SOUND_CONNECTED or
category is self.SOUND_DISCONNECTED or
category is self.SOUND_CONNECTION_GREEN):
return True
return False
return counters
# play a sound
def playSound(self, category, label):
# filename of the sound to be played
soundFilename = None
# whether to play a sound or not
play = True
def _choose_ext(basename):
for ext in sound.extensions:
if os.path.isfile(os.extsep.join([basename, ext])):
return os.extsep + ext
# if the address had a known label in the address book
if label is not None:
if label:
# Does a sound file exist for this particular contact?
if (os.path.isfile(state.appdata + 'sounds/' + label + '.wav') or
os.path.isfile(state.appdata + 'sounds/' + label + '.mp3')):
soundFilename = state.appdata + 'sounds/' + label
# Avoid making sounds more frequently than the threshold.
# This suppresses playing sounds repeatedly when there
# are many new messages
if (soundFilename is None and
not self.isConnectionSound(category)):
# elapsed time since the last sound was played
dt = datetime.datetime.now() - self.lastSoundTime
# suppress sounds which are more frequent than the threshold
if dt.total_seconds() < self.maxSoundFrequencySec:
play = False
soundFilename = state.appdata + 'sounds/' + label
ext = _choose_ext(soundFilename)
if not ext:
category = sound.SOUND_KNOWN
soundFilename = None
if soundFilename is None:
# Avoid making sounds more frequently than the threshold.
# This suppresses playing sounds repeatedly when there
# are many new messages
if not sound.is_connection_sound(category):
# elapsed time since the last sound was played
dt = datetime.now() - self.lastSoundTime
# suppress sounds which are more frequent than the threshold
if dt.total_seconds() < self.maxSoundFrequencySec:
return
# the sound is for an address which exists in the address book
if category is self.SOUND_KNOWN:
if category is sound.SOUND_KNOWN:
soundFilename = state.appdata + 'sounds/known'
# the sound is for an unknown address
elif category is self.SOUND_UNKNOWN:
elif category is sound.SOUND_UNKNOWN:
soundFilename = state.appdata + 'sounds/unknown'
# initial connection sound
elif category is self.SOUND_CONNECTED:
elif category is sound.SOUND_CONNECTED:
soundFilename = state.appdata + 'sounds/connected'
# disconnected sound
elif category is self.SOUND_DISCONNECTED:
elif category is sound.SOUND_DISCONNECTED:
soundFilename = state.appdata + 'sounds/disconnected'
# sound when the connection status becomes green
elif category is self.SOUND_CONNECTION_GREEN:
soundFilename = state.appdata + 'sounds/green'
elif category is sound.SOUND_CONNECTION_GREEN:
soundFilename = state.appdata + 'sounds/green'
if soundFilename is not None and play is True:
if not self.isConnectionSound(category):
# record the last time that a received message sound was played
self.lastSoundTime = datetime.datetime.now()
if soundFilename is None:
logger.warning("Probably wrong category number in playSound()")
return
# if not wav then try mp3 format
if not os.path.isfile(soundFilename + '.wav'):
soundFilename = soundFilename + '.mp3'
else:
soundFilename = soundFilename + '.wav'
if not sound.is_connection_sound(category):
# record the last time that a received message sound was played
self.lastSoundTime = datetime.now()
if os.path.isfile(soundFilename):
if 'linux' in sys.platform:
# Note: QSound was a nice idea but it didn't work
if '.mp3' in soundFilename:
gst_available=False
try:
subprocess.call(["gst123", soundFilename],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
gst_available=True
except:
logger.warning("WARNING: gst123 must be installed in order to play mp3 sounds")
if not gst_available:
try:
subprocess.call(["mpg123", soundFilename],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
gst_available=True
except:
logger.warning("WARNING: mpg123 must be installed in order to play mp3 sounds")
else:
try:
subprocess.call(["aplay", soundFilename],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
except:
logger.warning("WARNING: aplay must be installed in order to play WAV sounds")
elif sys.platform[0:3] == 'win':
# use winsound on Windows
import winsound
winsound.PlaySound(soundFilename, winsound.SND_FILENAME)
try: # try already known format
soundFilename += ext
except (TypeError, NameError):
ext = _choose_ext(soundFilename)
if not ext:
try: # if no user sound file found try to play from theme
return self._theme_player(category, label)
except TypeError:
return
soundFilename += ext
self._player(soundFilename)
# Adapters and converters for QT <-> sqlite
def sqlInit(self):
register_adapter(QtCore.QByteArray, str)
# Try init the distro specific appindicator,
# for example the Ubuntu MessagingMenu
def indicatorInit(self):
def _noop_update(*args, **kwargs):
pass
try:
self.indicatorUpdate = get_plugin('indicator')(self)
except (NameError, TypeError):
logger.warning("No indicator plugin found")
self.indicatorUpdate = _noop_update
# initialise the message notifier
def notifierInit(self):
if withMessagingMenu:
Notify.init('pybitmessage')
# shows a notification
def notifierShow(self, title, subtitle, fromCategory, label):
self.playSound(fromCategory, label)
if withMessagingMenu:
n = Notify.Notification.new(
title, subtitle, 'notification-message-email')
try:
n.show()
except:
# n.show() has been known to throw this exception:
# gi._glib.GError: GDBus.Error:org.freedesktop.Notifications.
# MaxNotificationsExceeded: Exceeded maximum number of
# notifications
pass
return
else:
def _simple_notify(
title, subtitle, category, label=None, icon=None):
self.tray.showMessage(title, subtitle, 1, 2000)
self._notifier = _simple_notify
# does nothing if isAvailable returns false
self._player = QtGui.QSound.play
if not get_plugins:
return
_plugin = get_plugin('notification.message')
if _plugin:
self._notifier = _plugin
else:
logger.warning("No notification.message plugin found")
self._theme_player = get_plugin('notification.sound', 'theme')
if not QtGui.QSound.isAvailable():
_plugin = get_plugin(
'notification.sound', 'file', fallback='file.fallback')
if _plugin:
self._player = _plugin
else:
logger.warning("No notification.sound plugin found")
def notifierShow(
self, title, subtitle, category, label=None, icon=None):
self.playSound(category, label)
self._notifier(
unicode(title), unicode(subtitle), category, label, icon)
# tree
def treeWidgetKeyPressEvent(self, event):
return self.handleKeyPress(event, self.getCurrentTreeWidget())
@ -1523,8 +1423,12 @@ class MyForm(settingsmixin.SMainWindow):
currentAddress = self.getCurrentAccount()
if currentAddress:
self.setSendFromComboBox(currentAddress)
self.ui.tabWidgetSend.setCurrentIndex(0)
self.ui.tabWidget.setCurrentIndex(1)
self.ui.tabWidgetSend.setCurrentIndex(
self.ui.tabWidgetSend.indexOf(self.ui.sendDirect)
)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.send)
)
self.ui.lineEditTo.setFocus()
event.ignore()
elif event.key() == QtCore.Qt.Key_F:
@ -1550,11 +1454,11 @@ class MyForm(settingsmixin.SMainWindow):
# the same directory as this program. It is important that you
# back up this file.', QMessageBox.Ok)
reply = QtGui.QMessageBox.information(self, 'keys.dat?', _translate(
"MainWindow", "You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file."), QMessageBox.Ok)
"MainWindow", "You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file."), QtGui.QMessageBox.Ok)
else:
QtGui.QMessageBox.information(self, 'keys.dat?', _translate(
"MainWindow", "You may manage your keys by editing the keys.dat file stored in\n %1 \nIt is important that you back up this file.").arg(state.appdata), QMessageBox.Ok)
"MainWindow", "You may manage your keys by editing the keys.dat file stored in\n %1 \nIt is important that you back up this file.").arg(state.appdata), QtGui.QMessageBox.Ok)
elif sys.platform == 'win32' or sys.platform == 'win64':
if state.appdata == '':
reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Open keys.dat?"), _translate(
@ -1580,44 +1484,66 @@ class MyForm(settingsmixin.SMainWindow):
elif self.getCurrentFolder(self.ui.treeWidgetChans) == "trash":
self.loadMessagelist(self.ui.tableWidgetInboxChans, self.getCurrentAccount(self.ui.treeWidgetChans), "trash")
# menu botton 'regenerate deterministic addresses'
# menu button 'regenerate deterministic addresses'
def click_actionRegenerateDeterministicAddresses(self):
self.regenerateAddressesDialogInstance = regenerateAddressesDialog(
self)
if self.regenerateAddressesDialogInstance.exec_():
if self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text() == "":
QMessageBox.about(self, _translate("MainWindow", "bad passphrase"), _translate(
"MainWindow", "You must type your passphrase. If you don\'t have one then this is not the form for you."))
dialog = dialogs.RegenerateAddressesDialog(self)
if dialog.exec_():
if dialog.lineEditPassphrase.text() == "":
QtGui.QMessageBox.about(
self, _translate("MainWindow", "bad passphrase"),
_translate(
"MainWindow",
"You must type your passphrase. If you don\'t"
" have one then this is not the form for you."
))
return
streamNumberForAddress = int(
self.regenerateAddressesDialogInstance.ui.lineEditStreamNumber.text())
streamNumberForAddress = int(dialog.lineEditStreamNumber.text())
try:
addressVersionNumber = int(
self.regenerateAddressesDialogInstance.ui.lineEditAddressVersionNumber.text())
dialog.lineEditAddressVersionNumber.text())
except:
QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate(
"MainWindow", "Your address version number must be a number: either 3 or 4."))
QtGui.QMessageBox.about(
self,
_translate("MainWindow", "Bad address version number"),
_translate(
"MainWindow",
"Your address version number must be a number:"
" either 3 or 4."
))
return
if addressVersionNumber < 3 or addressVersionNumber > 4:
QMessageBox.about(self, _translate("MainWindow", "Bad address version number"), _translate(
"MainWindow", "Your address version number must be either 3 or 4."))
QtGui.QMessageBox.about(
self,
_translate("MainWindow", "Bad address version number"),
_translate(
"MainWindow",
"Your address version number must be either 3 or 4."
))
return
queues.addressGeneratorQueue.put(('createDeterministicAddresses', addressVersionNumber, streamNumberForAddress, "regenerated deterministic address", self.regenerateAddressesDialogInstance.ui.spinBoxNumberOfAddressesToMake.value(
), self.regenerateAddressesDialogInstance.ui.lineEditPassphrase.text().toUtf8(), self.regenerateAddressesDialogInstance.ui.checkBoxEighteenByteRipe.isChecked()))
self.ui.tabWidget.setCurrentIndex(3)
queues.addressGeneratorQueue.put((
'createDeterministicAddresses',
addressVersionNumber, streamNumberForAddress,
"regenerated deterministic address",
dialog.spinBoxNumberOfAddressesToMake.value(),
dialog.lineEditPassphrase.text().toUtf8(),
dialog.checkBoxEighteenByteRipe.isChecked()
))
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.chans)
)
# opens 'join chan' dialog
def click_actionJoinChan(self):
NewChanDialog(self)
dialogs.NewChanDialog(self)
def showConnectDialog(self):
self.connectDialogInstance = connectDialog(self)
if self.connectDialogInstance.exec_():
if self.connectDialogInstance.ui.radioButtonConnectNow.isChecked():
BMConfigParser().remove_option('bitmessagesettings', 'dontconnect')
dialog = dialogs.ConnectDialog(self)
if dialog.exec_():
if dialog.radioButtonConnectNow.isChecked():
BMConfigParser().remove_option(
'bitmessagesettings', 'dontconnect')
BMConfigParser().save()
else:
elif dialog.radioButtonConfigureNetwork.isChecked():
self.click_actionSettings()
def showMigrationWizard(self, level):
@ -1626,7 +1552,7 @@ class MyForm(settingsmixin.SMainWindow):
pass
else:
pass
def changeEvent(self, event):
if event.type() == QtCore.QEvent.LanguageChange:
self.ui.retranslateUi(self)
@ -1640,14 +1566,13 @@ class MyForm(settingsmixin.SMainWindow):
if event.type() == QtCore.QEvent.WindowStateChange:
if self.windowState() & QtCore.Qt.WindowMinimized:
if BMConfigParser().getboolean('bitmessagesettings', 'minimizetotray') and not 'darwin' in sys.platform:
QTimer.singleShot(0, self.appIndicatorHide)
QtCore.QTimer.singleShot(0, self.appIndicatorHide)
elif event.oldState() & QtCore.Qt.WindowMinimized:
# The window state has just been changed to
# Normal/Maximised/FullScreen
pass
# QtGui.QWidget.changeEvent(self, event)
def __icon_activated(self, reason):
if reason == QtGui.QSystemTrayIcon.Trigger:
self.actionShow.setChecked(not self.actionShow.isChecked())
@ -1658,19 +1583,26 @@ class MyForm(settingsmixin.SMainWindow):
def setStatusIcon(self, color):
# print 'setting status icon color'
_notifications_enabled = not BMConfigParser().getboolean(
'bitmessagesettings', 'hidetrayconnectionnotifications')
if color == 'red':
self.pushButtonStatusIcon.setIcon(
QIcon(":/newPrefix/images/redicon.png"))
QtGui.QIcon(":/newPrefix/images/redicon.png"))
shared.statusIconColor = 'red'
# if the connection is lost then show a notification
if self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'):
self.notifierShow('Bitmessage', unicode(_translate(
"MainWindow", "Connection lost").toUtf8(),'utf-8'),
self.SOUND_DISCONNECTED, None)
if self.connected and _notifications_enabled:
self.notifierShow(
'Bitmessage',
_translate("MainWindow", "Connection lost"),
sound.SOUND_DISCONNECTED)
if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp') and \
BMConfigParser().get('bitmessagesettings', 'socksproxytype') == "none":
self.statusBar().showMessage(_translate(
"MainWindow", "Problems connecting? Try enabling UPnP in the Network Settings"), 10000)
self.updateStatusBar(
_translate(
"MainWindow",
"Problems connecting? Try enabling UPnP in the Network"
" Settings"
))
self.connected = False
if self.actionStatus is not None:
@ -1678,16 +1610,17 @@ class MyForm(settingsmixin.SMainWindow):
"MainWindow", "Not Connected"))
self.setTrayIconFile("can-icon-24px-red.png")
if color == 'yellow':
if self.statusBar().currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.':
self.statusBar().clearMessage()
self.pushButtonStatusIcon.setIcon(QIcon(
":/newPrefix/images/yellowicon.png"))
if self.statusbar.currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.':
self.statusbar.clearMessage()
self.pushButtonStatusIcon.setIcon(
QtGui.QIcon(":/newPrefix/images/yellowicon.png"))
shared.statusIconColor = 'yellow'
# if a new connection has been established then show a notification
if not self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'):
self.notifierShow('Bitmessage', unicode(_translate(
"MainWindow", "Connected").toUtf8(),'utf-8'),
self.SOUND_CONNECTED, None)
if not self.connected and _notifications_enabled:
self.notifierShow(
'Bitmessage',
_translate("MainWindow", "Connected"),
sound.SOUND_CONNECTED)
self.connected = True
if self.actionStatus is not None:
@ -1695,15 +1628,16 @@ class MyForm(settingsmixin.SMainWindow):
"MainWindow", "Connected"))
self.setTrayIconFile("can-icon-24px-yellow.png")
if color == 'green':
if self.statusBar().currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.':
self.statusBar().clearMessage()
if self.statusbar.currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.':
self.statusbar.clearMessage()
self.pushButtonStatusIcon.setIcon(
QIcon(":/newPrefix/images/greenicon.png"))
QtGui.QIcon(":/newPrefix/images/greenicon.png"))
shared.statusIconColor = 'green'
if not self.connected and not BMConfigParser().getboolean('bitmessagesettings', 'hidetrayconnectionnotifications'):
self.notifierShow('Bitmessage', unicode(_translate(
"MainWindow", "Connected").toUtf8(),'utf-8'),
self.SOUND_CONNECTION_GREEN, None)
if not self.connected and _notifications_enabled:
self.notifierShow(
'Bitmessage',
_translate("MainWindow", "Connected"),
sound.SOUND_CONNECTION_GREEN)
self.connected = True
if self.actionStatus is not None:
@ -1713,7 +1647,7 @@ class MyForm(settingsmixin.SMainWindow):
def initTrayIcon(self, iconFileName, app):
self.currentTrayIconFileName = iconFileName
self.tray = QSystemTrayIcon(
self.tray = QtGui.QSystemTrayIcon(
self.calcTrayIcon(iconFileName, self.findInboxUnreadCount()), app)
def setTrayIconFile(self, iconFileName):
@ -1742,9 +1676,10 @@ class MyForm(settingsmixin.SMainWindow):
fontMetrics = QtGui.QFontMetrics(font)
rect = fontMetrics.boundingRect(txt)
# draw text
painter = QPainter()
painter = QtGui.QPainter()
painter.begin(pixmap)
painter.setPen(QtGui.QPen(QtGui.QColor(255, 0, 0), Qt.SolidPattern))
painter.setPen(
QtGui.QPen(QtGui.QColor(255, 0, 0), QtCore.Qt.SolidPattern))
painter.setFont(font)
painter.drawText(24-rect.right()-marginX, -rect.top()+marginY, txt)
painter.end()
@ -1753,13 +1688,14 @@ class MyForm(settingsmixin.SMainWindow):
def drawTrayIcon(self, iconFileName, inboxUnreadCount):
self.tray.setIcon(self.calcTrayIcon(iconFileName, inboxUnreadCount))
def changedInboxUnread(self, row = None):
self.drawTrayIcon(self.currentTrayIconFileName, self.findInboxUnreadCount())
def changedInboxUnread(self, row=None):
self.drawTrayIcon(
self.currentTrayIconFileName, self.findInboxUnreadCount())
self.rerenderTabTreeMessages()
self.rerenderTabTreeSubscriptions()
self.rerenderTabTreeChans()
def findInboxUnreadCount(self, count = None):
def findInboxUnreadCount(self, count=None):
if count is None:
queryreturn = sqlQuery('''SELECT count(*) from inbox WHERE folder='inbox' and read=0''')
cnt = 0
@ -1779,8 +1715,7 @@ class MyForm(settingsmixin.SMainWindow):
continue
for i in range(sent.rowCount()):
rowAddress = sent.item(
i, 0).data(Qt.UserRole)
rowAddress = sent.item(i, 0).data(QtCore.Qt.UserRole)
if toAddress == rowAddress:
sent.item(i, 3).setToolTip(textToDisplay)
try:
@ -1800,9 +1735,9 @@ class MyForm(settingsmixin.SMainWindow):
continue
for i in range(sent.rowCount()):
toAddress = sent.item(
i, 0).data(Qt.UserRole)
i, 0).data(QtCore.Qt.UserRole)
tableAckdata = sent.item(
i, 3).data(Qt.UserRole).toPyObject()
i, 3).data(QtCore.Qt.UserRole).toPyObject()
status, addressVersionNumber, streamNumber, ripe = decodeAddress(
toAddress)
if ackdata == tableAckdata:
@ -1823,21 +1758,26 @@ class MyForm(settingsmixin.SMainWindow):
self.ui.tableWidgetInboxSubscriptions,
self.ui.tableWidgetInboxChans]):
for i in range(inbox.rowCount()):
if msgid == str(inbox.item(i, 3).data(Qt.UserRole).toPyObject()):
self.statusBar().showMessage(_translate(
"MainWindow", "Message trashed"), 10000)
if msgid == str(inbox.item(i, 3).data(QtCore.Qt.UserRole).toPyObject()):
self.updateStatusBar(
_translate("MainWindow", "Message trashed"))
treeWidget = self.widgetConvert(inbox)
self.propagateUnreadCount(inbox.item(i, 1 if inbox.item(i, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), self.getCurrentFolder(treeWidget), treeWidget, 0)
self.propagateUnreadCount(inbox.item(i, 1 if inbox.item(i, 1).type == AccountMixin.SUBSCRIPTION else 0).data(QtCore.Qt.UserRole), self.getCurrentFolder(treeWidget), treeWidget, 0)
inbox.removeRow(i)
break
def newVersionAvailable(self, version):
self.notifiedNewVersion = ".".join(str(n) for n in version)
self.statusBar().showMessage(_translate("MainWindow", "New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest").arg(self.notifiedNewVersion), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"New version of PyBitmessage is available: %1. Download it"
" from https://github.com/Bitmessage/PyBitmessage/releases/latest"
).arg(self.notifiedNewVersion)
)
def displayAlert(self, title, text, exitAfterUserClicksOk):
self.statusBar().showMessage(text)
QtGui.QMessageBox.critical(self, title, text, QMessageBox.Ok)
self.updateStatusBar(text)
QtGui.QMessageBox.critical(self, title, text, QtGui.QMessageBox.Ok)
if exitAfterUserClicksOk:
os._exit(0)
@ -1865,7 +1805,7 @@ class MyForm(settingsmixin.SMainWindow):
oldRows[item.address] = [item.label, item.type, i]
if self.ui.tableWidgetAddressBook.rowCount() == 0:
self.ui.tableWidgetAddressBook.horizontalHeader().setSortIndicator(0, Qt.AscendingOrder)
self.ui.tableWidgetAddressBook.horizontalHeader().setSortIndicator(0, QtCore.Qt.AscendingOrder)
if self.ui.tableWidgetAddressBook.isSortingEnabled():
self.ui.tableWidgetAddressBook.setSortingEnabled(False)
@ -1899,32 +1839,39 @@ class MyForm(settingsmixin.SMainWindow):
completerList.append(unicode(newRows[address][0], encoding="UTF-8") + " <" + address + ">")
# sort
self.ui.tableWidgetAddressBook.sortByColumn(0, Qt.AscendingOrder)
self.ui.tableWidgetAddressBook.sortByColumn(
0, QtCore.Qt.AscendingOrder)
self.ui.tableWidgetAddressBook.setSortingEnabled(True)
self.ui.lineEditTo.completer().model().setStringList(completerList)
def rerenderSubscriptions(self):
self.rerenderTabTreeSubscriptions()
def click_pushButtonTTL(self):
QtGui.QMessageBox.information(self, 'Time To Live', _translate(
"MainWindow", """The TTL, or Time-To-Live is the length of time that the network will hold the message.
The recipient must get it during this time. If your Bitmessage client does not hear an acknowledgement, it
will resend the message automatically. The longer the Time-To-Live, the
more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate."""), QMessageBox.Ok)
more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate."""), QtGui.QMessageBox.Ok)
def click_pushButtonClear(self):
self.ui.lineEditSubject.setText("")
self.ui.lineEditTo.setText("")
self.ui.textEditMessage.setText("")
self.ui.comboBoxSendFrom.setCurrentIndex(0)
def click_pushButtonSend(self):
encoding = 3 if QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier else 2
self.statusBar().clearMessage()
self.statusbar.clearMessage()
if self.ui.tabWidgetSend.currentIndex() == 0:
if self.ui.tabWidgetSend.currentIndex() == \
self.ui.tabWidgetSend.indexOf(self.ui.sendDirect):
# message to specific people
sendMessageToPeople = True
fromAddress = str(self.ui.comboBoxSendFrom.itemData(
self.ui.comboBoxSendFrom.currentIndex(),
Qt.UserRole).toString())
self.ui.comboBoxSendFrom.currentIndex(),
QtCore.Qt.UserRole).toString())
toAddresses = str(self.ui.lineEditTo.text().toUtf8())
subject = str(self.ui.lineEditSubject.text().toUtf8())
message = str(
@ -1933,23 +1880,29 @@ class MyForm(settingsmixin.SMainWindow):
# broadcast message
sendMessageToPeople = False
fromAddress = str(self.ui.comboBoxSendFromBroadcast.itemData(
self.ui.comboBoxSendFromBroadcast.currentIndex(),
Qt.UserRole).toString())
self.ui.comboBoxSendFromBroadcast.currentIndex(),
QtCore.Qt.UserRole).toString())
subject = str(self.ui.lineEditSubjectBroadcast.text().toUtf8())
message = str(
self.ui.textEditMessageBroadcast.document().toPlainText().toUtf8())
"""
The whole network message must fit in 2^18 bytes. Let's assume 500
bytes of overhead. If someone wants to get that too an exact
number you are welcome to but I think that it would be a better
use of time to support message continuation so that users can
send messages of any length.
The whole network message must fit in 2^18 bytes.
Let's assume 500 bytes of overhead. If someone wants to get that
too an exact number you are welcome to but I think that it would
be a better use of time to support message continuation so that
users can send messages of any length.
"""
if len(message) > (2 ** 18 - 500):
QMessageBox.about(self, _translate("MainWindow", "Message too long"), _translate(
"MainWindow", "The message that you are trying to send is too long by %1 bytes. (The maximum is 261644 bytes). Please cut it down before sending.").arg(len(message) - (2 ** 18 - 500)))
if len(message) > (2 ** 18 - 500):
QtGui.QMessageBox.about(
self, _translate("MainWindow", "Message too long"),
_translate(
"MainWindow",
"The message that you are trying to send is too long"
" by %1 bytes. (The maximum is 261644 bytes). Please"
" cut it down before sending."
).arg(len(message) - (2 ** 18 - 500)))
return
acct = accountClass(fromAddress)
if sendMessageToPeople: # To send a message to specific people (rather than broadcast)
@ -1963,12 +1916,16 @@ class MyForm(settingsmixin.SMainWindow):
if "<" in toAddress and ">" in toAddress:
toAddress = toAddress.split('<')[1].split('>')[0]
# email address
elif toAddress.find("@") >= 0:
if toAddress.find("@") >= 0:
if isinstance(acct, GatewayAccount):
acct.createMessage(toAddress, fromAddress, subject, message)
subject = acct.subject
toAddress = acct.toAddress
else:
if QtGui.QMessageBox.question(self, "Sending an email?", _translate("MainWindow",
"You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?"),
QtGui.QMessageBox.Yes|QtGui.QMessageBox.No) != QtGui.QMessageBox.Yes:
continue
email = acct.getLabel()
if email[-14:] != "@mailchuck.com": #attempt register
# 12 character random email address
@ -1978,8 +1935,14 @@ class MyForm(settingsmixin.SMainWindow):
BMConfigParser().set(fromAddress, 'label', email)
BMConfigParser().set(fromAddress, 'gateway', 'mailchuck')
BMConfigParser().save()
self.statusBar().showMessage(_translate(
"MainWindow", "Error: Your account wasn't registered at an email gateway. Sending registration now as %1, please wait for the registration to be processed before retrying sending.").arg(email), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: Your account wasn't registered at"
" an email gateway. Sending registration"
" now as %1, please wait for the registration"
" to be processed before retrying sending."
).arg(email)
)
return
status, addressVersionNumber, streamNumber, ripe = decodeAddress(
toAddress)
@ -1991,48 +1954,91 @@ class MyForm(settingsmixin.SMainWindow):
logger.error('Error: Could not decode recipient address ' + toAddress + ':' + status)
if status == 'missingbm':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: Bitmessage addresses start with BM- Please check the recipient address %1").arg(toAddress), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: Bitmessage addresses start with"
" BM- Please check the recipient address %1"
).arg(toAddress))
elif status == 'checksumfailed':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: The recipient address %1 is not typed or copied correctly. Please check it.").arg(toAddress), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: The recipient address %1 is not"
" typed or copied correctly. Please check it."
).arg(toAddress))
elif status == 'invalidcharacters':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: The recipient address %1 contains invalid characters. Please check it.").arg(toAddress), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: The recipient address %1 contains"
" invalid characters. Please check it."
).arg(toAddress))
elif status == 'versiontoohigh':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: The version of the recipient address %1 is too high. Either you need to upgrade your Bitmessage software or your acquaintance is being clever.").arg(toAddress), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: The version of the recipient address"
" %1 is too high. Either you need to upgrade"
" your Bitmessage software or your"
" acquaintance is being clever."
).arg(toAddress))
elif status == 'ripetooshort':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance.").arg(toAddress), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: Some data encoded in the recipient"
" address %1 is too short. There might be"
" something wrong with the software of"
" your acquaintance."
).arg(toAddress))
elif status == 'ripetoolong':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance.").arg(toAddress), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: Some data encoded in the recipient"
" address %1 is too long. There might be"
" something wrong with the software of"
" your acquaintance."
).arg(toAddress))
elif status == 'varintmalformed':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance.").arg(toAddress), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: Some data encoded in the recipient"
" address %1 is malformed. There might be"
" something wrong with the software of"
" your acquaintance."
).arg(toAddress))
else:
self.statusBar().showMessage(_translate(
"MainWindow", "Error: Something is wrong with the recipient address %1.").arg(toAddress), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: Something is wrong with the"
" recipient address %1."
).arg(toAddress))
elif fromAddress == '':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: You must specify a From address. If you don\'t have one, go to the \'Your Identities\' tab."), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: You must specify a From address. If you"
" don\'t have one, go to the"
" \'Your Identities\' tab.")
)
else:
toAddress = addBMIfNotPresent(toAddress)
if addressVersionNumber > 4 or addressVersionNumber <= 1:
QMessageBox.about(self, _translate("MainWindow", "Address version number"), _translate(
QtGui.QMessageBox.about(self, _translate("MainWindow", "Address version number"), _translate(
"MainWindow", "Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.").arg(toAddress).arg(str(addressVersionNumber)))
continue
if streamNumber > 1 or streamNumber == 0:
QMessageBox.about(self, _translate("MainWindow", "Stream number"), _translate(
QtGui.QMessageBox.about(self, _translate("MainWindow", "Stream number"), _translate(
"MainWindow", "Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version.").arg(toAddress).arg(str(streamNumber)))
continue
self.statusBar().clearMessage()
self.statusbar.clearMessage()
if shared.statusIconColor == 'red':
self.statusBar().showMessage(_translate(
"MainWindow", "Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect."))
ackdata = OpenSSL.rand(32)
self.updateStatusBar(_translate(
"MainWindow",
"Warning: You are currently not connected."
" Bitmessage will do the work necessary to"
" send the message but it won\'t send until"
" you connect.")
)
stealthLevel = BMConfigParser().safeGetInt(
'bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
t = ()
sqlExecute(
'''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
@ -2071,22 +2077,26 @@ class MyForm(settingsmixin.SMainWindow):
if self.replyFromTab is not None:
self.ui.tabWidget.setCurrentIndex(self.replyFromTab)
self.replyFromTab = None
self.statusBar().showMessage(_translate(
"MainWindow", "Message queued."), 10000)
#self.ui.tableWidgetInbox.setCurrentCell(0, 0)
self.updateStatusBar(_translate(
"MainWindow", "Message queued."))
# self.ui.tableWidgetInbox.setCurrentCell(0, 0)
else:
self.statusBar().showMessage(_translate(
"MainWindow", "Your \'To\' field is empty."), 10000)
self.updateStatusBar(_translate(
"MainWindow", "Your \'To\' field is empty."))
else: # User selected 'Broadcast'
if fromAddress == '':
self.statusBar().showMessage(_translate(
"MainWindow", "Error: You must specify a From address. If you don\'t have one, go to the \'Your Identities\' tab."), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: You must specify a From address. If you don\'t"
" have one, go to the \'Your Identities\' tab."
))
else:
self.statusBar().clearMessage()
self.statusbar.clearMessage()
# We don't actually need the ackdata for acknowledgement since
# this is a broadcast message, but we can use it to update the
# user interface when the POW is done generating.
ackdata = OpenSSL.rand(32)
streamNumber = decodeAddress(fromAddress)[2]
ackdata = genAckPayload(streamNumber, 0)
toAddress = str_broadcast_subscribers
ripe = ''
t = ('', # msgid. We don't know what this will be until the POW is done.
@ -2118,40 +2128,47 @@ class MyForm(settingsmixin.SMainWindow):
self.ui.comboBoxSendFromBroadcast.setCurrentIndex(0)
self.ui.lineEditSubjectBroadcast.setText('')
self.ui.textEditMessageBroadcast.reset()
self.ui.tabWidget.setCurrentIndex(1)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.send)
)
self.ui.tableWidgetInboxSubscriptions.setCurrentCell(0, 0)
self.statusBar().showMessage(_translate(
"MainWindow", "Broadcast queued."), 10000)
self.updateStatusBar(_translate(
"MainWindow", "Broadcast queued."))
def click_pushButtonLoadFromAddressBook(self):
self.ui.tabWidget.setCurrentIndex(5)
for i in range(4):
time.sleep(0.1)
self.statusBar().clearMessage()
self.statusbar.clearMessage()
time.sleep(0.1)
self.statusBar().showMessage(_translate(
"MainWindow", "Right click one or more entries in your address book and select \'Send message to this address\'."), 10000)
self.updateStatusBar(_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()
identities = str(self.ui.lineEditTo.text().toUtf8()).split(";")
err, addr = nc.query(identities[-1].strip())
if err is not None:
self.statusBar().showMessage(_translate(
"MainWindow", "Error: " + err), 10000)
self.updateStatusBar(
_translate("MainWindow", "Error: %1").arg(err))
else:
identities[-1] = addr
self.ui.lineEditTo.setText("; ".join(identities))
self.statusBar().showMessage(_translate(
"MainWindow", "Fetched address from namecoin identity."), 10000)
self.updateStatusBar(_translate(
"MainWindow", "Fetched address from namecoin identity."))
def setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(self, address):
# If this is a chan then don't let people broadcast because no one
# should subscribe to chan addresses.
if BMConfigParser().safeGetBoolean(str(address), 'mailinglist'):
self.ui.tabWidgetSend.setCurrentIndex(1)
else:
self.ui.tabWidgetSend.setCurrentIndex(0)
self.ui.tabWidgetSend.setCurrentIndex(
self.ui.tabWidgetSend.indexOf(
self.ui.sendBroadcast
if BMConfigParser().safeGetBoolean(str(address), 'mailinglist')
else self.ui.sendDirect
))
def rerenderComboBoxSendFrom(self):
self.ui.comboBoxSendFrom.clear()
@ -2166,8 +2183,11 @@ class MyForm(settingsmixin.SMainWindow):
self.ui.comboBoxSendFrom.addItem(avatarize(addressInKeysFile), label, addressInKeysFile)
# self.ui.comboBoxSendFrom.model().sort(1, Qt.AscendingOrder)
for i in range(self.ui.comboBoxSendFrom.count()):
address = str(self.ui.comboBoxSendFrom.itemData(i, Qt.UserRole).toString())
self.ui.comboBoxSendFrom.setItemData(i, AccountColor(address).accountColor(), Qt.ForegroundRole)
address = str(self.ui.comboBoxSendFrom.itemData(
i, QtCore.Qt.UserRole).toString())
self.ui.comboBoxSendFrom.setItemData(
i, AccountColor(address).accountColor(),
QtCore.Qt.ForegroundRole)
self.ui.comboBoxSendFrom.insertItem(0, '', '')
if(self.ui.comboBoxSendFrom.count() == 2):
self.ui.comboBoxSendFrom.setCurrentIndex(1)
@ -2186,8 +2206,11 @@ class MyForm(settingsmixin.SMainWindow):
label = addressInKeysFile
self.ui.comboBoxSendFromBroadcast.addItem(avatarize(addressInKeysFile), label, addressInKeysFile)
for i in range(self.ui.comboBoxSendFromBroadcast.count()):
address = str(self.ui.comboBoxSendFromBroadcast.itemData(i, Qt.UserRole).toString())
self.ui.comboBoxSendFromBroadcast.setItemData(i, AccountColor(address).accountColor(), Qt.ForegroundRole)
address = str(self.ui.comboBoxSendFromBroadcast.itemData(
i, QtCore.Qt.UserRole).toString())
self.ui.comboBoxSendFromBroadcast.setItemData(
i, AccountColor(address).accountColor(),
QtCore.Qt.ForegroundRole)
self.ui.comboBoxSendFromBroadcast.insertItem(0, '', '')
if(self.ui.comboBoxSendFromBroadcast.count() == 2):
self.ui.comboBoxSendFromBroadcast.setCurrentIndex(1)
@ -2244,108 +2267,121 @@ class MyForm(settingsmixin.SMainWindow):
else:
acct = ret
self.propagateUnreadCount(acct.address)
if BMConfigParser().getboolean('bitmessagesettings', 'showtraynotifications'):
self.notifierShow(unicode(_translate("MainWindow",'New Message').toUtf8(),'utf-8'), unicode(_translate("MainWindow",'From ').toUtf8(),'utf-8') + unicode(acct.fromLabel, 'utf-8'), self.SOUND_UNKNOWN, None)
if BMConfigParser().getboolean(
'bitmessagesettings', 'showtraynotifications'):
self.notifierShow(
_translate("MainWindow", "New Message"),
_translate("MainWindow", "From %1").arg(
unicode(acct.fromLabel, 'utf-8')),
sound.SOUND_UNKNOWN
)
if self.getCurrentAccount() is not None and ((self.getCurrentFolder(treeWidget) != "inbox" and self.getCurrentFolder(treeWidget) is not None) or self.getCurrentAccount(treeWidget) != acct.address):
# Ubuntu should notify of new message irespective of whether it's in current message list or not
self.ubuntuMessagingMenuUpdate(True, None, acct.toLabel)
if hasattr(acct, "feedback") and acct.feedback != GatewayAccount.ALL_OK:
# Ubuntu should notify of new message irespective of
# whether it's in current message list or not
self.indicatorUpdate(True, to_label=acct.toLabel)
# cannot find item to pass here ):
if hasattr(acct, "feedback") \
and acct.feedback != GatewayAccount.ALL_OK:
if acct.feedback == GatewayAccount.REGISTRATION_DENIED:
self.dialog = EmailGatewayRegistrationDialog(self, _translate("EmailGatewayRegistrationDialog", "Registration failed:"),
_translate("EmailGatewayRegistrationDialog", "The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:")
)
if self.dialog.exec_():
email = str(self.dialog.ui.lineEditEmail.text().toUtf8())
# register resets address variables
acct.register(email)
BMConfigParser().set(acct.fromAddress, 'label', email)
BMConfigParser().set(acct.fromAddress, 'gateway', 'mailchuck')
BMConfigParser().save()
self.statusBar().showMessage(_translate(
"MainWindow", "Sending email gateway registration request"), 10000)
dialogs.EmailGatewayDialog(
self, BMConfigParser(), acct).exec_()
def click_pushButtonAddAddressBook(self):
self.AddAddressDialogInstance = AddAddressDialog(self)
if self.AddAddressDialogInstance.exec_():
if self.AddAddressDialogInstance.ui.labelAddressCheck.text() == _translate("MainWindow", "Address is valid."):
# First we must check to see if the address is already in the
# address book. The user cannot add it again or else it will
# cause problems when updating and deleting the entry.
address = addBMIfNotPresent(str(
self.AddAddressDialogInstance.ui.lineEditAddress.text()))
label = self.AddAddressDialogInstance.ui.newAddressLabel.text().toUtf8()
self.addEntryToAddressBook(address,label)
else:
self.statusBar().showMessage(_translate(
"MainWindow", "The address you entered was invalid. Ignoring it."), 10000)
def click_pushButtonAddAddressBook(self, dialog=None):
if not dialog:
dialog = dialogs.AddAddressDialog(self)
dialog.exec_()
try:
address, label = dialog.data
except AttributeError:
return
def addEntryToAddressBook(self,address,label):
queryreturn = sqlQuery('''select * from addressbook where address=?''', address)
if queryreturn == []:
sqlExecute('''INSERT INTO addressbook VALUES (?,?)''', str(label), address)
self.rerenderMessagelistFromLabels()
self.rerenderMessagelistToLabels()
self.rerenderAddressBook()
else:
self.statusBar().showMessage(_translate(
"MainWindow", "Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want."), 10000)
# First we must check to see if the address is already in the
# address book. The user cannot add it again or else it will
# cause problems when updating and deleting the entry.
if shared.isAddressInMyAddressBook(address):
self.updateStatusBar(_translate(
"MainWindow",
"Error: You cannot add the same address to your"
" address book twice. Try renaming the existing one"
" if you want."
))
return
self.addEntryToAddressBook(address, label)
def addEntryToAddressBook(self, address, label):
if shared.isAddressInMyAddressBook(address):
return
sqlExecute('''INSERT INTO addressbook VALUES (?,?)''', label, address)
self.rerenderMessagelistFromLabels()
self.rerenderMessagelistToLabels()
self.rerenderAddressBook()
def addSubscription(self, address, label):
address = addBMIfNotPresent(address)
#This should be handled outside of this function, for error displaying and such, but it must also be checked here.
# This should be handled outside of this function, for error displaying
# and such, but it must also be checked here.
if shared.isAddressInMySubscriptionsList(address):
return
#Add to database (perhaps this should be separated from the MyForm class)
sqlExecute('''INSERT INTO subscriptions VALUES (?,?,?)''',str(label),address,True)
# Add to database (perhaps this should be separated from the MyForm class)
sqlExecute(
'''INSERT INTO subscriptions VALUES (?,?,?)''',
label, address, True
)
self.rerenderMessagelistFromLabels()
shared.reloadBroadcastSendersForWhichImWatching()
self.rerenderAddressBook()
self.rerenderTabTreeSubscriptions()
def click_pushButtonAddSubscription(self):
self.NewSubscriptionDialogInstance = NewSubscriptionDialog(self)
if self.NewSubscriptionDialogInstance.exec_():
if self.NewSubscriptionDialogInstance.ui.labelAddressCheck.text() != _translate("MainWindow", "Address is valid."):
self.statusBar().showMessage(_translate("MainWindow", "The address you entered was invalid. Ignoring it."), 10000)
return
address = addBMIfNotPresent(str(self.NewSubscriptionDialogInstance.ui.lineEditSubscriptionAddress.text()))
# We must check to see if the address is already in the subscriptions list. The user cannot add it again or else it will cause problems when updating and deleting the entry.
if shared.isAddressInMySubscriptionsList(address):
self.statusBar().showMessage(_translate("MainWindow", "Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want."), 10000)
return
label = self.NewSubscriptionDialogInstance.ui.newsubscriptionlabel.text().toUtf8()
self.addSubscription(address, label)
# Now, if the user wants to display old broadcasts, let's get them out of the inventory and put them
# in the objectProcessorQueue to be processed
if self.NewSubscriptionDialogInstance.ui.checkBoxDisplayMessagesAlreadyInInventory.isChecked():
status, addressVersion, streamNumber, ripe = decodeAddress(address)
shared.inventory.flush()
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint(
addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()
tag = doubleHashOfAddressData[32:]
for value in shared.inventory.by_type_and_tag(3, tag):
queues.objectProcessorQueue.put((value.type, value.payload))
dialog = dialogs.NewSubscriptionDialog(self)
dialog.exec_()
try:
address, label = dialog.data
except AttributeError:
return
# We must check to see if the address is already in the
# subscriptions list. The user cannot add it again or else it
# will cause problems when updating and deleting the entry.
if shared.isAddressInMySubscriptionsList(address):
self.updateStatusBar(_translate(
"MainWindow",
"Error: You cannot add the same address to your"
" subscriptions twice. Perhaps rename the existing one"
" if you want."
))
return
self.addSubscription(address, label)
# Now, if the user wants to display old broadcasts, let's get
# them out of the inventory and put them
# to the objectProcessorQueue to be processed
if dialog.checkBoxDisplayMessagesAlreadyInInventory.isChecked():
for value in dialog.recent:
queues.objectProcessorQueue.put((
value.type, value.payload
))
def click_pushButtonStatusIcon(self):
logger.debug('click_pushButtonStatusIcon')
self.iconGlossaryInstance = iconGlossaryDialog(self)
if self.iconGlossaryInstance.exec_():
pass
dialogs.IconGlossaryDialog(self, config=BMConfigParser()).exec_()
def click_actionHelp(self):
self.helpDialogInstance = helpDialog(self)
self.helpDialogInstance.exec_()
dialogs.HelpDialog(self).exec_()
def click_actionSupport(self):
support.createSupportMessage(self)
def click_actionAbout(self):
self.aboutDialogInstance = aboutDialog(self)
self.aboutDialogInstance.exec_()
dialogs.AboutDialog(self).exec_()
def click_actionSettings(self):
self.settingsDialogInstance = settingsDialog(self)
if self._firstrun:
self.settingsDialogInstance.ui.tabWidgetSettings.setCurrentIndex(1)
if self.settingsDialogInstance.exec_():
if self._firstrun:
BMConfigParser().remove_option(
'bitmessagesettings', 'dontconnect')
BMConfigParser().set('bitmessagesettings', 'startonlogon', str(
self.settingsDialogInstance.ui.checkBoxStartOnLogon.isChecked()))
BMConfigParser().set('bitmessagesettings', 'minimizetotray', str(
@ -2371,7 +2407,7 @@ class MyForm(settingsmixin.SMainWindow):
if int(BMConfigParser().get('bitmessagesettings', 'port')) != int(self.settingsDialogInstance.ui.lineEditTCPPort.text()):
if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect'):
QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate(
QtGui.QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate(
"MainWindow", "You must restart Bitmessage for the port number change to take effect."))
BMConfigParser().set('bitmessagesettings', 'port', str(
self.settingsDialogInstance.ui.lineEditTCPPort.text()))
@ -2385,10 +2421,10 @@ class MyForm(settingsmixin.SMainWindow):
#print 'self.settingsDialogInstance.ui.comboBoxProxyType.currentText())[0:5]', self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5]
if BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'none' and self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] == 'SOCKS':
if shared.statusIconColor != 'red':
QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate(
QtGui.QMessageBox.about(self, _translate("MainWindow", "Restart"), _translate(
"MainWindow", "Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any)."))
if BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] != 'SOCKS':
self.statusBar().clearMessage()
self.statusbar.clearMessage()
state.resetNetworkProtocolAvailability() # just in case we changed something in the network connectivity
if self.settingsDialogInstance.ui.comboBoxProxyType.currentText()[0:5] == 'SOCKS':
BMConfigParser().set('bitmessagesettings', 'socksproxytype', str(
@ -2413,16 +2449,16 @@ class MyForm(settingsmixin.SMainWindow):
int(float(self.settingsDialogInstance.ui.lineEditMaxDownloadRate.text()))))
BMConfigParser().set('bitmessagesettings', 'maxuploadrate', str(
int(float(self.settingsDialogInstance.ui.lineEditMaxUploadRate.text()))))
except:
QMessageBox.about(self, _translate("MainWindow", "Number needed"), _translate(
except ValueError:
QtGui.QMessageBox.about(self, _translate("MainWindow", "Number needed"), _translate(
"MainWindow", "Your maximum download and upload rate must be numbers. Ignoring what you typed."))
else:
set_rates(BMConfigParser().safeGetInt("bitmessagesettings", "maxdownloadrate"),
BMConfigParser().safeGetInt("bitmessagesettings", "maxuploadrate"))
BMConfigParser().set('bitmessagesettings', 'maxoutboundconnections', str(
int(float(self.settingsDialogInstance.ui.lineEditMaxOutboundConnections.text()))))
throttle.SendThrottle().resetLimit()
throttle.ReceiveThrottle().resetLimit()
BMConfigParser().set('bitmessagesettings', 'namecoinrpctype',
self.settingsDialogInstance.getNamecoinType())
BMConfigParser().set('bitmessagesettings', 'namecoinrpchost', str(
@ -2494,7 +2530,7 @@ class MyForm(settingsmixin.SMainWindow):
if (float(self.settingsDialogInstance.ui.lineEditDays.text()) >=0 and float(self.settingsDialogInstance.ui.lineEditMonths.text()) >=0):
shared.maximumLengthOfTimeToBotherResendingMessages = (float(str(self.settingsDialogInstance.ui.lineEditDays.text())) * 24 * 60 * 60) + (float(str(self.settingsDialogInstance.ui.lineEditMonths.text())) * (60 * 60 * 24 *365)/12)
if shared.maximumLengthOfTimeToBotherResendingMessages < 432000: # If the time period is less than 5 hours, we give zero values to all fields. No message will be sent again.
QMessageBox.about(self, _translate("MainWindow", "Will not resend ever"), _translate(
QtGui.QMessageBox.about(self, _translate("MainWindow", "Will not resend ever"), _translate(
"MainWindow", "Note that the time limit you entered is less than the amount of time Bitmessage waits for the first resend attempt therefore your messages will never be resent."))
BMConfigParser().set('bitmessagesettings', 'stopresendingafterxdays', '0')
BMConfigParser().set('bitmessagesettings', 'stopresendingafterxmonths', '0')
@ -2510,7 +2546,7 @@ class MyForm(settingsmixin.SMainWindow):
if 'win32' in sys.platform or 'win64' in sys.platform:
# Auto-startup for Windows
RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
self.settings = QSettings(RUN_PATH, QSettings.NativeFormat)
self.settings = QtCore.QSettings(RUN_PATH, QtCore.QSettings.NativeFormat)
if BMConfigParser().getboolean('bitmessagesettings', 'startonlogon'):
self.settings.setValue("PyBitmessage", sys.argv[0])
else:
@ -2559,160 +2595,87 @@ class MyForm(settingsmixin.SMainWindow):
pass
def on_action_SpecialAddressBehaviorDialog(self):
self.dialog = SpecialAddressBehaviorDialog(self)
# For Modal dialogs
if self.dialog.exec_():
addressAtCurrentRow = self.getCurrentAccount()
if BMConfigParser().safeGetBoolean(addressAtCurrentRow, 'chan'):
return
if self.dialog.ui.radioButtonBehaveNormalAddress.isChecked():
BMConfigParser().set(str(
addressAtCurrentRow), 'mailinglist', 'false')
# Set the color to either black or grey
if BMConfigParser().getboolean(addressAtCurrentRow, 'enabled'):
self.setCurrentItemColor(QApplication.palette()
.text().color())
else:
self.setCurrentItemColor(QtGui.QColor(128, 128, 128))
else:
BMConfigParser().set(str(
addressAtCurrentRow), 'mailinglist', 'true')
BMConfigParser().set(str(addressAtCurrentRow), 'mailinglistname', str(
self.dialog.ui.lineEditMailingListName.text().toUtf8()))
self.setCurrentItemColor(QtGui.QColor(137, 04, 177)) #magenta
self.rerenderComboBoxSendFrom()
self.rerenderComboBoxSendFromBroadcast()
BMConfigParser().save()
self.rerenderMessagelistToLabels()
dialogs.SpecialAddressBehaviorDialog(self, BMConfigParser())
def on_action_EmailGatewayDialog(self):
self.dialog = EmailGatewayDialog(self)
dialog = dialogs.EmailGatewayDialog(self, config=BMConfigParser())
# For Modal dialogs
if self.dialog.exec_():
addressAtCurrentRow = self.getCurrentAccount()
acct = accountClass(addressAtCurrentRow)
# no chans / mailinglists
if acct.type != AccountMixin.NORMAL:
return
if self.dialog.ui.radioButtonUnregister.isChecked() and isinstance(acct, GatewayAccount):
acct.unregister()
BMConfigParser().remove_option(addressAtCurrentRow, 'gateway')
BMConfigParser().save()
self.statusBar().showMessage(_translate(
"MainWindow", "Sending email gateway unregistration request"), 10000)
elif self.dialog.ui.radioButtonStatus.isChecked() and isinstance(acct, GatewayAccount):
acct.status()
self.statusBar().showMessage(_translate(
"MainWindow", "Sending email gateway status request"), 10000)
elif self.dialog.ui.radioButtonSettings.isChecked() and isinstance(acct, GatewayAccount):
acct.settings()
listOfAddressesInComboBoxSendFrom = [str(self.ui.comboBoxSendFrom.itemData(i).toPyObject()) for i in range(self.ui.comboBoxSendFrom.count())]
if acct.fromAddress in listOfAddressesInComboBoxSendFrom:
currentIndex = listOfAddressesInComboBoxSendFrom.index(acct.fromAddress)
self.ui.comboBoxSendFrom.setCurrentIndex(currentIndex)
else:
self.ui.comboBoxSendFrom.setCurrentIndex(0)
self.ui.lineEditTo.setText(acct.toAddress)
self.ui.lineEditSubject.setText(acct.subject)
self.ui.textEditMessage.setText(acct.message)
self.ui.tabWidgetSend.setCurrentIndex(0)
self.ui.tabWidget.setCurrentIndex(1)
self.ui.textEditMessage.setFocus()
elif self.dialog.ui.radioButtonRegister.isChecked():
email = str(self.dialog.ui.lineEditEmail.text().toUtf8())
acct = MailchuckAccount(addressAtCurrentRow)
acct.register(email)
BMConfigParser().set(addressAtCurrentRow, 'label', email)
BMConfigParser().set(addressAtCurrentRow, 'gateway', 'mailchuck')
BMConfigParser().save()
self.statusBar().showMessage(_translate(
"MainWindow", "Sending email gateway registration request"), 10000)
else:
pass
#print "well nothing"
# shared.writeKeysFile()
# self.rerenderInboxToLabels()
dialog.exec_()
try:
acct = dialog.data
except AttributeError:
return
# Only settings remain here
acct.settings()
for i in range(self.ui.comboBoxSendFrom.count()):
if str(self.ui.comboBoxSendFrom.itemData(i).toPyObject()) \
== acct.fromAddress:
self.ui.comboBoxSendFrom.setCurrentIndex(i)
break
else:
self.ui.comboBoxSendFrom.setCurrentIndex(0)
self.ui.lineEditTo.setText(acct.toAddress)
self.ui.lineEditSubject.setText(acct.subject)
self.ui.textEditMessage.setText(acct.message)
self.ui.tabWidgetSend.setCurrentIndex(
self.ui.tabWidgetSend.indexOf(self.ui.sendDirect)
)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.send)
)
self.ui.textEditMessage.setFocus()
def on_action_MarkAllRead(self):
def partialUpdate(folder, msgids):
if len(msgids) == 0:
return 0
if folder == 'sent':
return sqlExecute(
"UPDATE sent SET read = 1 WHERE ackdata IN(%s) AND read=0" %(",".join("?"*len(msgids))), *msgids)
else:
return sqlExecute(
"UPDATE inbox SET read = 1 WHERE msgid IN(%s) AND read=0" %(",".join("?"*len(msgids))), *msgids)
if QtGui.QMessageBox.question(self, "Marking all messages as read?", _translate("MainWindow", "Are you sure you would like to mark all messages read?"), QMessageBox.Yes|QMessageBox.No) != QMessageBox.Yes:
if QtGui.QMessageBox.question(
self, "Marking all messages as read?",
_translate(
"MainWindow",
"Are you sure you would like to mark all messages read?"
), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
) != QtGui.QMessageBox.Yes:
return
addressAtCurrentRow = self.getCurrentAccount()
# addressAtCurrentRow = self.getCurrentAccount()
tableWidget = self.getCurrentMessagelist()
if tableWidget.rowCount() == 0:
idCount = tableWidget.rowCount()
if idCount == 0:
return
msgids = []
font = QFont()
font = QtGui.QFont()
font.setBold(False)
markread = 0
for i in range(0, tableWidget.rowCount()):
msgids = []
for i in range(0, idCount):
msgids.append(str(tableWidget.item(
i, 3).data(Qt.UserRole).toPyObject()))
i, 3).data(QtCore.Qt.UserRole).toPyObject()))
tableWidget.item(i, 0).setUnread(False)
tableWidget.item(i, 1).setUnread(False)
tableWidget.item(i, 2).setUnread(False)
tableWidget.item(i, 3).setFont(font)
# sqlite default limit, unfortunately getting/setting isn't exposed to python
if i % 999 == 999:
markread += partialUpdate(self.getCurrentFolder(), msgids)
msgids = []
if len(msgids) > 0:
markread += partialUpdate(self.getCurrentFolder(), msgids)
markread = sqlExecuteChunked(
"UPDATE %s SET read = 1 WHERE %s IN({0}) AND read=0" % (
('sent', 'ackdata') if self.getCurrentFolder() == 'sent'
else ('inbox', 'msgid')
), idCount, *msgids
)
if markread > 0:
self.propagateUnreadCount(addressAtCurrentRow, self.getCurrentFolder(), None, 0)
self.propagateUnreadCount()
# addressAtCurrentRow, self.getCurrentFolder(), None, 0)
def click_NewAddressDialog(self):
addresses = []
for addressInKeysFile in getSortedAccounts():
addresses.append(addressInKeysFile)
# self.dialog = Ui_NewAddressWizard(addresses)
# self.dialog.exec_()
# print "Name: " + self.dialog.field("name").toString()
# print "Email: " + self.dialog.field("email").toString()
# return
self.dialog = NewAddressDialog(self)
# For Modal dialogs
if self.dialog.exec_():
# self.dialog.ui.buttonBox.enabled = False
if self.dialog.ui.radioButtonRandomAddress.isChecked():
if self.dialog.ui.radioButtonMostAvailable.isChecked():
streamNumberForAddress = 1
else:
# User selected 'Use the same stream as an existing
# address.'
streamNumberForAddress = decodeAddress(
self.dialog.ui.comboBoxExisting.currentText())[2]
queues.addressGeneratorQueue.put(('createRandomAddress', 4, streamNumberForAddress, str(
self.dialog.ui.newaddresslabel.text().toUtf8()), 1, "", self.dialog.ui.checkBoxEighteenByteRipe.isChecked()))
else:
if self.dialog.ui.lineEditPassphrase.text() != self.dialog.ui.lineEditPassphraseAgain.text():
QMessageBox.about(self, _translate("MainWindow", "Passphrase mismatch"), _translate(
"MainWindow", "The passphrase you entered twice doesn\'t match. Try again."))
elif self.dialog.ui.lineEditPassphrase.text() == "":
QMessageBox.about(self, _translate(
"MainWindow", "Choose a passphrase"), _translate("MainWindow", "You really do need a passphrase."))
else:
streamNumberForAddress = 1 # this will eventually have to be replaced by logic to determine the most available stream number.
queues.addressGeneratorQueue.put(('createDeterministicAddresses', 4, streamNumberForAddress, "unused deterministic address", self.dialog.ui.spinBoxNumberOfAddressesToMake.value(
), self.dialog.ui.lineEditPassphrase.text().toUtf8(), self.dialog.ui.checkBoxEighteenByteRipe.isChecked()))
else:
logger.debug('new address dialog box rejected')
dialogs.NewAddressDialog(self)
def network_switch(self):
dontconnect_option = not BMConfigParser().safeGetBoolean(
'bitmessagesettings', 'dontconnect')
BMConfigParser().set(
'bitmessagesettings', 'dontconnect', str(dontconnect_option))
BMConfigParser().save()
self.ui.updateNetworkSwitchMenuLabel(dontconnect_option)
# Quit selected from menu or application indicator
def quit(self):
@ -2744,21 +2707,22 @@ class MyForm(settingsmixin.SMainWindow):
QtGui.QMessageBox.Yes|QtGui.QMessageBox.No|QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel)
if reply == QtGui.QMessageBox.No:
waitForPow = False
elif reply == QtGui.QMessage.Cancel:
elif reply == QtGui.QMessageBox.Cancel:
return
if PendingDownload().len() > 0:
if PendingDownloadQueue.totalSize() > 0:
reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Synchronisation pending"),
_translate("MainWindow", "Bitmessage hasn't synchronised with the network, %n object(s) to be downloaded. If you quit now, it may cause delivery delays. Wait until the synchronisation finishes?", None, QtCore.QCoreApplication.CodecForTr, PendingDownload().len()),
_translate("MainWindow", "Bitmessage hasn't synchronised with the network, %n object(s) to be downloaded. If you quit now, it may cause delivery delays. Wait until the synchronisation finishes?", None, QtCore.QCoreApplication.CodecForTr, PendingDownloadQueue.totalSize()),
QtGui.QMessageBox.Yes|QtGui.QMessageBox.No|QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel)
if reply == QtGui.QMessageBox.Yes:
waitForSync = True
elif reply == QtGui.QMessageBox.Cancel:
return
else:
PendingDownload().stop()
PendingDownloadQueue.stop()
if shared.statusIconColor == 'red':
if shared.statusIconColor == 'red' and not BMConfigParser().safeGetBoolean(
'bitmessagesettings', 'dontconnect'):
reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Not connected"),
_translate("MainWindow", "Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes?"),
QtGui.QMessageBox.Yes|QtGui.QMessageBox.No|QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel)
@ -2770,23 +2734,27 @@ class MyForm(settingsmixin.SMainWindow):
self.quitAccepted = True
self.statusBar().showMessage(_translate(
"MainWindow", "Shutting down PyBitmessage... %1%").arg(str(0)))
self.updateStatusBar(_translate(
"MainWindow", "Shutting down PyBitmessage... %1%").arg(0))
if waitForConnection:
self.statusBar().showMessage(_translate(
self.updateStatusBar(_translate(
"MainWindow", "Waiting for network connection..."))
while shared.statusIconColor == 'red':
time.sleep(0.5)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
# this probably will not work correctly, because there is a delay between the status icon turning red and inventory exchange, but it's better than nothing.
if waitForSync:
self.statusBar().showMessage(_translate(
self.updateStatusBar(_translate(
"MainWindow", "Waiting for finishing synchronisation..."))
while PendingDownload().len() > 0:
while PendingDownloadQueue.totalSize() > 0:
time.sleep(0.5)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
if waitForPow:
# check if PoW queue empty
@ -2798,54 +2766,83 @@ class MyForm(settingsmixin.SMainWindow):
if curWorkerQueue > maxWorkerQueue:
maxWorkerQueue = curWorkerQueue
if curWorkerQueue > 0:
self.statusBar().showMessage(_translate("MainWindow", "Waiting for PoW to finish... %1%").arg(str(50 * (maxWorkerQueue - curWorkerQueue) / maxWorkerQueue)))
self.updateStatusBar(_translate(
"MainWindow", "Waiting for PoW to finish... %1%"
).arg(50 * (maxWorkerQueue - curWorkerQueue)
/ maxWorkerQueue)
)
time.sleep(0.5)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
self.statusBar().showMessage(_translate("MainWindow", "Shutting down Pybitmessage... %1%").arg(str(50)))
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
self.updateStatusBar(_translate(
"MainWindow", "Shutting down Pybitmessage... %1%").arg(50))
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
if maxWorkerQueue > 0:
time.sleep(0.5) # a bit of time so that the hashHolder is populated
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
# a bit of time so that the hashHolder is populated
time.sleep(0.5)
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
# check if upload (of objects created locally) pending
self.statusBar().showMessage(_translate("MainWindow", "Waiting for objects to be sent... %1%").arg(str(50)))
self.updateStatusBar(_translate(
"MainWindow", "Waiting for objects to be sent... %1%").arg(50))
try:
while PendingUpload().progress() < 1:
self.statusBar().showMessage(_translate("MainWindow", "Waiting for objects to be sent... %1%").arg(str(int(50 + 20 * PendingUpload().progress()))))
self.updateStatusBar(_translate(
"MainWindow",
"Waiting for objects to be sent... %1%"
).arg(int(50 + 20 * PendingUpload().progress()))
)
time.sleep(0.5)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
except PendingUploadDeadlineException:
pass
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
# save state and geometry self and all widgets
self.statusBar().showMessage(_translate("MainWindow", "Saving settings... %1%").arg(str(70)))
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
self.updateStatusBar(_translate(
"MainWindow", "Saving settings... %1%").arg(70))
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
self.saveSettings()
for attr, obj in self.ui.__dict__.iteritems():
if hasattr(obj, "__class__") and isinstance(obj, settingsmixin.SettingsMixin):
if hasattr(obj, "__class__") \
and isinstance(obj, settingsmixin.SettingsMixin):
saveMethod = getattr(obj, "saveSettings", None)
if callable (saveMethod):
if callable(saveMethod):
obj.saveSettings()
self.statusBar().showMessage(_translate("MainWindow", "Shutting down core... %1%").arg(str(80)))
QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 1000)
self.updateStatusBar(_translate(
"MainWindow", "Shutting down core... %1%").arg(80))
QtCore.QCoreApplication.processEvents(
QtCore.QEventLoop.AllEvents, 1000
)
shutdown.doCleanShutdown()
self.statusBar().showMessage(_translate("MainWindow", "Stopping notifications... %1%").arg(str(90)))
self.updateStatusBar(_translate(
"MainWindow", "Stopping notifications... %1%").arg(90))
self.tray.hide()
# unregister the messaging system
if self.mmapp is not None:
self.mmapp.unregister()
self.statusBar().showMessage(_translate("MainWindow", "Shutdown imminent... %1%").arg(str(100)))
self.updateStatusBar(_translate(
"MainWindow", "Shutdown imminent... %1%").arg(100))
shared.thisapp.cleanup()
logger.info("Shutdown complete")
super(MyForm, myapp).close()
#return
# return
os._exit(0)
# window close event
@ -2900,32 +2897,32 @@ class MyForm(settingsmixin.SMainWindow):
tableWidget = self.getCurrentMessagelist()
if not tableWidget:
return
font = QFont()
font.setBold(True)
inventoryHashesToMarkUnread = []
modified = 0
msgids = set()
# modified = 0
for row in tableWidget.selectedIndexes():
currentRow = row.row()
inventoryHashToMarkUnread = str(tableWidget.item(
currentRow, 3).data(Qt.UserRole).toPyObject())
if inventoryHashToMarkUnread in inventoryHashesToMarkUnread:
# it returns columns as separate items, so we skip dupes
continue
if not tableWidget.item(currentRow, 0).unread:
modified += 1
inventoryHashesToMarkUnread.append(inventoryHashToMarkUnread)
tableWidget.item(currentRow, 0).setUnread(True)
tableWidget.item(currentRow, 1).setUnread(True)
tableWidget.item(currentRow, 2).setUnread(True)
tableWidget.item(currentRow, 3).setFont(font)
#sqlite requires the exact number of ?s to prevent injection
rowcount = sqlExecute('''UPDATE inbox SET read=0 WHERE msgid IN (%s) AND read=1''' % (
"?," * len(inventoryHashesToMarkUnread))[:-1], *inventoryHashesToMarkUnread)
if rowcount == 1:
# performance optimisation
self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), self.getCurrentFolder())
else:
self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), self.getCurrentFolder(), self.getCurrentTreeWidget(), 0)
msgid = str(tableWidget.item(
currentRow, 3).data(QtCore.Qt.UserRole).toPyObject())
msgids.add(msgid)
# if not tableWidget.item(currentRow, 0).unread:
# modified += 1
self.updateUnreadStatus(tableWidget, currentRow, msgid, False)
# for 1081
idCount = len(msgids)
# rowcount =
sqlExecuteChunked(
'''UPDATE inbox SET read=0 WHERE msgid IN ({0}) AND read=1''',
idCount, *msgids
)
self.propagateUnreadCount()
# if rowcount == 1:
# # performance optimisation
# self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(QtCore.Qt.UserRole), self.getCurrentFolder())
# else:
# self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(QtCore.Qt.UserRole), self.getCurrentFolder(), self.getCurrentTreeWidget(), 0)
# tableWidget.selectRow(currentRow + 1)
# This doesn't de-select the last message if you try to mark it unread, but that doesn't interfere. Might not be necessary.
# We could also select upwards, but then our problem would be with the topmost message.
@ -2989,7 +2986,7 @@ class MyForm(settingsmixin.SMainWindow):
fromAddressAtCurrentInboxRow = tableWidget.item(
currentInboxRow, 1).address
msgid = str(tableWidget.item(
currentInboxRow, 3).data(Qt.UserRole).toPyObject())
currentInboxRow, 3).data(QtCore.Qt.UserRole).toPyObject())
queryreturn = sqlQuery(
'''select message from inbox where msgid=?''', msgid)
if queryreturn != []:
@ -3002,23 +2999,28 @@ class MyForm(settingsmixin.SMainWindow):
'message': self.ui.textEditMessage
}
if toAddressAtCurrentInboxRow == str_broadcast_subscribers:
self.ui.tabWidgetSend.setCurrentIndex(0)
self.ui.tabWidgetSend.setCurrentIndex(
self.ui.tabWidgetSend.indexOf(self.ui.sendDirect)
)
# toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow
elif not BMConfigParser().has_section(toAddressAtCurrentInboxRow):
QtGui.QMessageBox.information(self, _translate("MainWindow", "Address is gone"), _translate(
"MainWindow", "Bitmessage cannot find your address %1. Perhaps you removed it?").arg(toAddressAtCurrentInboxRow), QMessageBox.Ok)
"MainWindow", "Bitmessage cannot find your address %1. Perhaps you removed it?").arg(toAddressAtCurrentInboxRow), QtGui.QMessageBox.Ok)
elif not BMConfigParser().getboolean(toAddressAtCurrentInboxRow, 'enabled'):
QtGui.QMessageBox.information(self, _translate("MainWindow", "Address disabled"), _translate(
"MainWindow", "Error: The address from which you are trying to send is disabled. You\'ll have to enable it on the \'Your Identities\' tab before using it."), QMessageBox.Ok)
"MainWindow", "Error: The address from which you are trying to send is disabled. You\'ll have to enable it on the \'Your Identities\' tab before using it."), QtGui.QMessageBox.Ok)
else:
self.setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(toAddressAtCurrentInboxRow)
if self.ui.tabWidgetSend.currentIndex() == 1:
broadcast_tab_index = self.ui.tabWidgetSend.indexOf(
self.ui.sendBroadcast
)
if self.ui.tabWidgetSend.currentIndex() == broadcast_tab_index:
widget = {
'subject': self.ui.lineEditSubjectBroadcast,
'from': self.ui.comboBoxSendFromBroadcast,
'message': self.ui.textEditMessageBroadcast
}
self.ui.tabWidgetSend.setCurrentIndex(1)
self.ui.tabWidgetSend.setCurrentIndex(broadcast_tab_index)
toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow
if fromAddressAtCurrentInboxRow == tableWidget.item(currentInboxRow, 1).label or (
isinstance(acct, GatewayAccount) and fromAddressAtCurrentInboxRow == acct.relayAddress):
@ -3042,7 +3044,9 @@ class MyForm(settingsmixin.SMainWindow):
widget['subject'].setText(tableWidget.item(currentInboxRow, 2).label)
else:
widget['subject'].setText('Re: ' + tableWidget.item(currentInboxRow, 2).label)
self.ui.tabWidget.setCurrentIndex(1)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.send)
)
widget['message'].setFocus()
def on_action_InboxAddSenderToAddressBook(self):
@ -3052,20 +3056,12 @@ class MyForm(settingsmixin.SMainWindow):
currentInboxRow = tableWidget.currentRow()
# tableWidget.item(currentRow,1).data(Qt.UserRole).toPyObject()
addressAtCurrentInboxRow = tableWidget.item(
currentInboxRow, 1).data(Qt.UserRole)
# Let's make sure that it isn't already in the address book
queryreturn = sqlQuery('''select * from addressbook where address=?''',
addressAtCurrentInboxRow)
if queryreturn == []:
sqlExecute('''INSERT INTO addressbook VALUES (?,?)''',
'--New entry. Change label in Address Book.--',
addressAtCurrentInboxRow)
self.rerenderAddressBook()
self.statusBar().showMessage(_translate(
"MainWindow", "Entry added to the Address Book. Edit the label to your liking."), 10000)
else:
self.statusBar().showMessage(_translate(
"MainWindow", "Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want."), 10000)
currentInboxRow, 1).data(QtCore.Qt.UserRole)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.send)
)
self.click_pushButtonAddAddressBook(
dialogs.AddAddressDialog(self, addressAtCurrentInboxRow))
def on_action_InboxAddSenderToBlackList(self):
tableWidget = self.getCurrentMessagelist()
@ -3074,9 +3070,9 @@ class MyForm(settingsmixin.SMainWindow):
currentInboxRow = tableWidget.currentRow()
# tableWidget.item(currentRow,1).data(Qt.UserRole).toPyObject()
addressAtCurrentInboxRow = tableWidget.item(
currentInboxRow, 1).data(Qt.UserRole)
currentInboxRow, 1).data(QtCore.Qt.UserRole)
recipientAddress = tableWidget.item(
currentInboxRow, 0).data(Qt.UserRole)
currentInboxRow, 0).data(QtCore.Qt.UserRole)
# Let's make sure that it isn't already in the address book
queryreturn = sqlQuery('''select * from blacklist where address=?''',
addressAtCurrentInboxRow)
@ -3086,11 +3082,15 @@ class MyForm(settingsmixin.SMainWindow):
label,
addressAtCurrentInboxRow, True)
self.ui.blackwhitelist.rerenderBlackWhiteList()
self.statusBar().showMessage(_translate(
"MainWindow", "Entry added to the blacklist. Edit the label to your liking."), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Entry added to the blacklist. Edit the label to your liking.")
)
else:
self.statusBar().showMessage(_translate(
"MainWindow", "Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want."), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: You cannot add the same address to your blacklist"
" twice. Try renaming the existing one if you want."))
def deleteRowFromMessagelist(self, row = None, inventoryHash = None, ackData = None, messageLists = None):
if messageLists is None:
@ -3099,15 +3099,16 @@ class MyForm(settingsmixin.SMainWindow):
messageLists = (messageLists)
for messageList in messageLists:
if row is not None:
inventoryHash = str(messageList.item(row, 3).data(Qt.UserRole).toPyObject())
inventoryHash = str(messageList.item(row, 3).data(
QtCore.Qt.UserRole).toPyObject())
messageList.removeRow(row)
elif inventoryHash is not None:
for i in range(messageList.rowCount() - 1, -1, -1):
if messageList.item(i, 3).data(Qt.UserRole).toPyObject() == inventoryHash:
if messageList.item(i, 3).data(QtCore.Qt.UserRole).toPyObject() == inventoryHash:
messageList.removeRow(i)
elif ackData is not None:
for i in range(messageList.rowCount() - 1, -1, -1):
if messageList.item(i, 3).data(Qt.UserRole).toPyObject() == ackData:
if messageList.item(i, 3).data(QtCore.Qt.UserRole).toPyObject() == ackData:
messageList.removeRow(i)
# Send item on the Inbox tab to trash
@ -3115,54 +3116,64 @@ class MyForm(settingsmixin.SMainWindow):
tableWidget = self.getCurrentMessagelist()
if not tableWidget:
return
unread = False
currentRow = 0
folder = self.getCurrentFolder()
shifted = QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier
while tableWidget.selectedIndexes():
currentRow = tableWidget.selectedIndexes()[0].row()
inventoryHashToTrash = str(tableWidget.item(
currentRow, 3).data(Qt.UserRole).toPyObject())
if folder == "trash" or shifted:
sqlExecute('''DELETE FROM inbox WHERE msgid=?''', inventoryHashToTrash)
else:
sqlExecute('''UPDATE inbox SET folder='trash' WHERE msgid=?''', inventoryHashToTrash)
if tableWidget.item(currentRow, 0).unread:
self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), folder, self.getCurrentTreeWidget(), -1)
if folder != "trash" and not shifted:
self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), "trash", self.getCurrentTreeWidget(), 1)
tableWidget.setUpdatesEnabled(False);
inventoryHashesToTrash = []
# ranges in reversed order
for r in sorted(tableWidget.selectedRanges(), key=lambda r: r.topRow())[::-1]:
for i in range(r.bottomRow()-r.topRow()+1):
inventoryHashToTrash = str(tableWidget.item(
r.topRow()+i, 3).data(QtCore.Qt.UserRole).toPyObject())
if inventoryHashToTrash in inventoryHashesToTrash:
continue
inventoryHashesToTrash.append(inventoryHashToTrash)
currentRow = r.topRow()
self.getCurrentMessageTextedit().setText("")
tableWidget.removeRow(currentRow)
self.statusBar().showMessage(_translate(
"MainWindow", "Moved items to trash."), 10000)
if currentRow == 0:
tableWidget.selectRow(currentRow)
tableWidget.model().removeRows(r.topRow(), r.bottomRow()-r.topRow()+1)
idCount = len(inventoryHashesToTrash)
if folder == "trash" or shifted:
sqlExecuteChunked('''DELETE FROM inbox WHERE msgid IN ({0})''',
idCount, *inventoryHashesToTrash)
else:
tableWidget.selectRow(currentRow - 1)
sqlExecuteChunked('''UPDATE inbox SET folder='trash' WHERE msgid IN ({0})''',
idCount, *inventoryHashesToTrash)
tableWidget.selectRow(0 if currentRow == 0 else currentRow - 1)
tableWidget.setUpdatesEnabled(True)
self.propagateUnreadCount(self.getCurrentAccount, folder)
self.updateStatusBar(_translate(
"MainWindow", "Moved items to trash."))
def on_action_TrashUndelete(self):
tableWidget = self.getCurrentMessagelist()
if not tableWidget:
return
unread = False
currentRow = 0
while tableWidget.selectedIndexes():
currentRow = tableWidget.selectedIndexes()[0].row()
inventoryHashToTrash = str(tableWidget.item(
currentRow, 3).data(Qt.UserRole).toPyObject())
sqlExecute('''UPDATE inbox SET folder='inbox' WHERE msgid=?''', inventoryHashToTrash)
if tableWidget.item(currentRow, 0).unread:
self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), "inbox", self.getCurrentTreeWidget(), 1)
self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), "trash", self.getCurrentTreeWidget(), -1)
tableWidget.setUpdatesEnabled(False)
inventoryHashesToTrash = []
# ranges in reversed order
for r in sorted(tableWidget.selectedRanges(), key=lambda r: r.topRow())[::-1]:
for i in range(r.bottomRow()-r.topRow()+1):
inventoryHashToTrash = str(tableWidget.item(
r.topRow()+i, 3).data(QtCore.Qt.UserRole).toPyObject())
if inventoryHashToTrash in inventoryHashesToTrash:
continue
inventoryHashesToTrash.append(inventoryHashToTrash)
currentRow = r.topRow()
self.getCurrentMessageTextedit().setText("")
tableWidget.removeRow(currentRow)
self.statusBar().showMessage(_translate(
"MainWindow", "Undeleted item."), 10000)
tableWidget.model().removeRows(r.topRow(), r.bottomRow()-r.topRow()+1)
if currentRow == 0:
tableWidget.selectRow(currentRow)
else:
tableWidget.selectRow(currentRow - 1)
idCount = len(inventoryHashesToTrash)
sqlExecuteChunked('''UPDATE inbox SET folder='inbox' WHERE msgid IN({0})''',
idCount, *inventoryHashesToTrash)
tableWidget.selectRow(0 if currentRow == 0 else currentRow - 1)
tableWidget.setUpdatesEnabled(True)
self.propagateUnreadCount(self.getCurrentAccount)
self.updateStatusBar(_translate("MainWindow", "Undeleted item."))
def on_action_InboxSaveMessageAs(self):
tableWidget = self.getCurrentMessagelist()
@ -3170,13 +3181,14 @@ class MyForm(settingsmixin.SMainWindow):
return
currentInboxRow = tableWidget.currentRow()
try:
subjectAtCurrentInboxRow = str(tableWidget.item(currentInboxRow,2).data(Qt.UserRole))
subjectAtCurrentInboxRow = str(tableWidget.item(
currentInboxRow, 2).data(QtCore.Qt.UserRole))
except:
subjectAtCurrentInboxRow = ''
# Retrieve the message data out of the SQL database
msgid = str(tableWidget.item(
currentInboxRow, 3).data(Qt.UserRole).toPyObject())
currentInboxRow, 3).data(QtCore.Qt.UserRole).toPyObject())
queryreturn = sqlQuery(
'''select message from inbox where msgid=?''', msgid)
if queryreturn != []:
@ -3184,16 +3196,16 @@ class MyForm(settingsmixin.SMainWindow):
message, = row
defaultFilename = "".join(x for x in subjectAtCurrentInboxRow if x.isalnum()) + '.txt'
filename = QFileDialog.getSaveFileName(self, _translate("MainWindow","Save As..."), defaultFilename, "Text files (*.txt);;All files (*.*)")
filename = QtGui.QFileDialog.getSaveFileName(self, _translate("MainWindow","Save As..."), defaultFilename, "Text files (*.txt);;All files (*.*)")
if filename == '':
return
try:
f = open(filename, 'w')
f.write(message)
f.close()
except Exception, e:
except Exception:
logger.exception('Message not saved', exc_info=True)
self.statusBar().showMessage(_translate("MainWindow", "Write error."), 10000)
self.updateStatusBar(_translate("MainWindow", "Write error."))
# Send item on the Sent tab to trash
def on_action_SentTrash(self):
@ -3207,26 +3219,25 @@ class MyForm(settingsmixin.SMainWindow):
while tableWidget.selectedIndexes() != []:
currentRow = tableWidget.selectedIndexes()[0].row()
ackdataToTrash = str(tableWidget.item(
currentRow, 3).data(Qt.UserRole).toPyObject())
currentRow, 3).data(QtCore.Qt.UserRole).toPyObject())
if folder == "trash" or shifted:
sqlExecute('''DELETE FROM sent WHERE ackdata=?''', ackdataToTrash)
else:
sqlExecute('''UPDATE sent SET folder='trash' WHERE ackdata=?''', ackdataToTrash)
if tableWidget.item(currentRow, 0).unread:
self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), folder, self.getCurrentTreeWidget(), -1)
self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(QtCore.Qt.UserRole), folder, self.getCurrentTreeWidget(), -1)
self.getCurrentMessageTextedit().setPlainText("")
tableWidget.removeRow(currentRow)
self.statusBar().showMessage(_translate(
"MainWindow", "Moved items to trash."), 10000)
if currentRow == 0:
self.ui.tableWidgetInbox.selectRow(currentRow)
else:
self.ui.tableWidgetInbox.selectRow(currentRow - 1)
self.updateStatusBar(_translate(
"MainWindow", "Moved items to trash."))
self.ui.tableWidgetInbox.selectRow(
currentRow if currentRow == 0 else currentRow - 1)
def on_action_ForceSend(self):
currentRow = self.ui.tableWidgetInbox.currentRow()
addressAtCurrentRow = self.ui.tableWidgetInbox.item(
currentRow, 0).data(Qt.UserRole)
currentRow, 0).data(QtCore.Qt.UserRole)
toRipe = decodeAddress(addressAtCurrentRow)[3]
sqlExecute(
'''UPDATE sent SET status='forcepow' WHERE toripe=? AND status='toodifficult' and folder='sent' ''',
@ -3241,7 +3252,7 @@ class MyForm(settingsmixin.SMainWindow):
def on_action_SentClipboard(self):
currentRow = self.ui.tableWidgetInbox.currentRow()
addressAtCurrentRow = self.ui.tableWidgetInbox.item(
currentRow, 0).data(Qt.UserRole)
currentRow, 0).data(QtCore.Qt.UserRole)
clipboard = QtGui.QApplication.clipboard()
clipboard.setText(str(addressAtCurrentRow))
@ -3296,11 +3307,13 @@ class MyForm(settingsmixin.SMainWindow):
self.ui.lineEditTo.setText(unicode(
self.ui.lineEditTo.text().toUtf8(), encoding="UTF-8") + '; ' + stringToAdd)
if listOfSelectedRows == {}:
self.statusBar().showMessage(_translate(
"MainWindow", "No addresses selected."), 10000)
self.updateStatusBar(_translate(
"MainWindow", "No addresses selected."))
else:
self.statusBar().clearMessage()
self.ui.tabWidget.setCurrentIndex(1)
self.statusbar.clearMessage()
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.send)
)
def on_action_AddressBookSubscribe(self):
listOfSelectedRows = {}
@ -3310,11 +3323,17 @@ class MyForm(settingsmixin.SMainWindow):
addressAtCurrentRow = str(self.ui.tableWidgetAddressBook.item(currentRow,1).text())
# Then subscribe to it... provided it's not already in the address book
if shared.isAddressInMySubscriptionsList(addressAtCurrentRow):
self.statusBar().showMessage(QtGui.QApplication.translate("MainWindow", "Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want."), 10000)
self.updateStatusBar(_translate(
"MainWindow",
"Error: You cannot add the same address to your"
" subscriptions twice. Perhaps rename the existing"
" one if you want."))
continue
labelAtCurrentRow = self.ui.tableWidgetAddressBook.item(currentRow,0).text().toUtf8()
self.addSubscription(addressAtCurrentRow, labelAtCurrentRow)
self.ui.tabWidget.setCurrentIndex(2)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.subscriptions)
)
def on_context_menuAddressBook(self, point):
self.popMenuAddressBook = QtGui.QMenu(self)
@ -3322,6 +3341,7 @@ class MyForm(settingsmixin.SMainWindow):
self.popMenuAddressBook.addAction(self.actionAddressBookClipboard)
self.popMenuAddressBook.addAction(self.actionAddressBookSubscribe)
self.popMenuAddressBook.addAction(self.actionAddressBookSetAvatar)
self.popMenuAddressBook.addAction(self.actionAddressBookSetSound)
self.popMenuAddressBook.addSeparator()
self.popMenuAddressBook.addAction(self.actionAddressBookNew)
normal = True
@ -3340,9 +3360,21 @@ class MyForm(settingsmixin.SMainWindow):
# Group of functions for the Subscriptions dialog box
def on_action_SubscriptionsNew(self):
self.click_pushButtonAddSubscription()
def on_action_SubscriptionsDelete(self):
if QtGui.QMessageBox.question(self, "Delete subscription?", _translate("MainWindow", "If you delete the subscription, messages that you already received will become inaccessible. Maybe you can consider disabling the subscription instead. Disabled subscriptions will not receive new messages, but you can still view messages you already received.\n\nAre you sure you want to delete the subscription?"), QMessageBox.Yes|QMessageBox.No) != QMessageBox.Yes:
if QtGui.QMessageBox.question(
self, "Delete subscription?",
_translate(
"MainWindow",
"If you delete the subscription, messages that you"
" already received will become inaccessible. Maybe"
" you can consider disabling the subscription instead."
" Disabled subscriptions will not receive new"
" messages, but you can still view messages you"
" already received.\n\nAre you sure you want to"
" delete the subscription?"
), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
) != QtGui.QMessageBox.Yes:
return
address = self.getCurrentAccount()
sqlExecute('''DELETE FROM subscriptions WHERE address=?''',
@ -3466,12 +3498,13 @@ class MyForm(settingsmixin.SMainWindow):
currentRow = messagelist.currentRow()
if currentRow >= 0:
msgid = str(messagelist.item(
currentRow, 3).data(Qt.UserRole).toPyObject()) # data is saved at the 4. column of the table...
currentRow, 3).data(QtCore.Qt.UserRole).toPyObject())
# data is saved at the 4. column of the table...
return msgid
return False
def getCurrentMessageTextedit(self):
currentIndex = self.ui.tabWidget.currentIndex();
currentIndex = self.ui.tabWidget.currentIndex()
messagelistList = [
self.ui.textEditInboxMessage,
False,
@ -3494,9 +3527,9 @@ class MyForm(settingsmixin.SMainWindow):
except:
return self.ui.textEditInboxMessage
def getCurrentSearchLine(self, currentIndex = None, retObj = False):
def getCurrentSearchLine(self, currentIndex=None, retObj=False):
if currentIndex is None:
currentIndex = self.ui.tabWidget.currentIndex();
currentIndex = self.ui.tabWidget.currentIndex()
messagelistList = [
self.ui.inboxSearchLineEdit,
False,
@ -3511,9 +3544,9 @@ class MyForm(settingsmixin.SMainWindow):
else:
return None
def getCurrentSearchOption(self, currentIndex = None):
def getCurrentSearchOption(self, currentIndex=None):
if currentIndex is None:
currentIndex = self.ui.tabWidget.currentIndex();
currentIndex = self.ui.tabWidget.currentIndex()
messagelistList = [
self.ui.inboxSearchOption,
False,
@ -3526,7 +3559,7 @@ class MyForm(settingsmixin.SMainWindow):
return None
# Group of functions for the Your Identities dialog box
def getCurrentItem(self, treeWidget = None):
def getCurrentItem(self, treeWidget=None):
if treeWidget is None:
treeWidget = self.getCurrentTreeWidget()
if treeWidget:
@ -3535,7 +3568,7 @@ class MyForm(settingsmixin.SMainWindow):
return currentItem
return False
def getCurrentAccount(self, treeWidget = None):
def getCurrentAccount(self, treeWidget=None):
currentItem = self.getCurrentItem(treeWidget)
if currentItem:
account = currentItem.address
@ -3544,7 +3577,7 @@ class MyForm(settingsmixin.SMainWindow):
# TODO need debug msg?
return False
def getCurrentFolder(self, treeWidget = None):
def getCurrentFolder(self, treeWidget=None):
if treeWidget is None:
treeWidget = self.getCurrentTreeWidget()
#treeWidget = self.ui.treeWidgetYourIdentities
@ -3570,9 +3603,21 @@ class MyForm(settingsmixin.SMainWindow):
def on_action_YourIdentitiesDelete(self):
account = self.getCurrentItem()
if account.type == AccountMixin.NORMAL:
return # maybe in the future
return # maybe in the future
elif account.type == AccountMixin.CHAN:
if QtGui.QMessageBox.question(self, "Delete channel?", _translate("MainWindow", "If you delete the channel, messages that you already received will become inaccessible. Maybe you can consider disabling the channel instead. Disabled channels will not receive new messages, but you can still view messages you already received.\n\nAre you sure you want to delete the channel?"), QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes:
if QtGui.QMessageBox.question(
self, "Delete channel?",
_translate(
"MainWindow",
"If you delete the channel, messages that you"
" already received will become inaccessible."
" Maybe you can consider disabling the channel"
" instead. Disabled channels will not receive new"
" messages, but you can still view messages you"
" already received.\n\nAre you sure you want to"
" delete the channel?"
), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
) == QtGui.QMessageBox.Yes:
BMConfigParser().remove_section(str(account.address))
else:
return
@ -3615,7 +3660,7 @@ class MyForm(settingsmixin.SMainWindow):
address = self.getCurrentAccount()
clipboard = QtGui.QApplication.clipboard()
clipboard.setText(str(address))
def on_action_ClipboardMessagelist(self):
tableWidget = self.getCurrentMessagelist()
currentColumn = tableWidget.currentColumn()
@ -3626,18 +3671,18 @@ class MyForm(settingsmixin.SMainWindow):
else:
currentColumn = 1
if self.getCurrentFolder() == "sent":
myAddress = tableWidget.item(currentRow, 1).data(Qt.UserRole)
otherAddress = tableWidget.item(currentRow, 0).data(Qt.UserRole)
myAddress = tableWidget.item(currentRow, 1).data(QtCore.Qt.UserRole)
otherAddress = tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole)
else:
myAddress = tableWidget.item(currentRow, 0).data(Qt.UserRole)
otherAddress = tableWidget.item(currentRow, 1).data(Qt.UserRole)
myAddress = tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole)
otherAddress = tableWidget.item(currentRow, 1).data(QtCore.Qt.UserRole)
account = accountClass(myAddress)
if isinstance(account, GatewayAccount) and otherAddress == account.relayAddress and (
(currentColumn in [0, 2] and self.getCurrentFolder() == "sent") or
(currentColumn in [1, 2] and self.getCurrentFolder() != "sent")):
text = str(tableWidget.item(currentRow, currentColumn).label)
else:
text = tableWidget.item(currentRow, currentColumn).data(Qt.UserRole)
text = tableWidget.item(currentRow, currentColumn).data(QtCore.Qt.UserRole)
text = unicode(str(text), 'utf-8', 'ignore')
clipboard = QtGui.QApplication.clipboard()
clipboard.setText(text)
@ -3680,7 +3725,10 @@ class MyForm(settingsmixin.SMainWindow):
current_files += [upper]
filters[0:0] = ['Image files (' + ' '.join(all_images_filter) + ')']
filters[1:1] = ['All files (*.*)']
sourcefile = QFileDialog.getOpenFileName(self, _translate("MainWindow","Set avatar..."), filter = ';;'.join(filters))
sourcefile = QtGui.QFileDialog.getOpenFileName(
self, _translate("MainWindow", "Set avatar..."),
filter = ';;'.join(filters)
)
# determine the correct filename (note that avatars don't use the suffix)
destination = state.appdata + 'avatars/' + hash + '.' + sourcefile.split('.')[-1]
exists = QtCore.QFile.exists(destination)
@ -3725,7 +3773,51 @@ class MyForm(settingsmixin.SMainWindow):
return False
return True
def on_action_AddressBookSetSound(self):
widget = self.ui.tableWidgetAddressBook
self.setAddressSound(widget.item(widget.currentRow(), 0).text())
def setAddressSound(self, addr):
filters = [unicode(_translate(
"MainWindow", "Sound files (%s)" %
' '.join(['*%s%s' % (os.extsep, ext) for ext in sound.extensions])
))]
sourcefile = unicode(QtGui.QFileDialog.getOpenFileName(
self, _translate("MainWindow", "Set notification sound..."),
filter=';;'.join(filters)
))
if not sourcefile:
return
destdir = os.path.join(state.appdata, 'sounds')
destfile = unicode(addr) + os.path.splitext(sourcefile)[-1]
destination = os.path.join(destdir, destfile)
if sourcefile == destination:
return
pattern = destfile.lower()
for item in os.listdir(destdir):
if item.lower() == pattern:
overwrite = QtGui.QMessageBox.question(
self, _translate("MainWindow", "Message"),
_translate(
"MainWindow",
"You have already set a notification sound"
" for this address book entry."
" Do you really want to overwrite it?"),
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No
) == QtGui.QMessageBox.Yes
if overwrite:
QtCore.QFile.remove(os.path.join(destdir, item))
break
if not QtCore.QFile.copy(sourcefile, destination):
logger.error(
'couldn\'t copy %s to %s', sourcefile, destination)
def on_context_menuYourIdentities(self, point):
currentItem = self.getCurrentItem()
self.popMenuYourIdentities = QtGui.QMenu(self)
@ -3743,6 +3835,12 @@ class MyForm(settingsmixin.SMainWindow):
self.popMenuYourIdentities.addAction(self.actionEmailGateway)
self.popMenuYourIdentities.addSeparator()
self.popMenuYourIdentities.addAction(self.actionMarkAllRead)
if get_plugins:
for plugin in get_plugins(
'gui.menu', 'popMenuYourIdentities'):
plugin(self)
self.popMenuYourIdentities.exec_(
self.ui.treeWidgetYourIdentities.mapToGlobal(point))
@ -3780,7 +3878,7 @@ class MyForm(settingsmixin.SMainWindow):
self.popMenuInbox.addAction(self.actionMarkUnread)
self.popMenuInbox.addSeparator()
address = tableWidget.item(
tableWidget.currentRow(), 0).data(Qt.UserRole)
tableWidget.currentRow(), 0).data(QtCore.Qt.UserRole)
account = accountClass(address)
if account.type == AccountMixin.CHAN:
self.popMenuInbox.addAction(self.actionReplyChan)
@ -3812,7 +3910,7 @@ class MyForm(settingsmixin.SMainWindow):
currentRow = self.ui.tableWidgetInbox.currentRow()
if currentRow >= 0:
ackData = str(self.ui.tableWidgetInbox.item(
currentRow, 3).data(Qt.UserRole).toPyObject())
currentRow, 3).data(QtCore.Qt.UserRole).toPyObject())
queryreturn = sqlQuery('''SELECT status FROM sent where ackdata=?''', ackData)
for row in queryreturn:
status, = row
@ -3849,7 +3947,7 @@ class MyForm(settingsmixin.SMainWindow):
searchOption = self.getCurrentSearchOption()
messageTextedit = self.getCurrentMessageTextedit()
if messageTextedit:
messageTextedit.setPlainText(QString(""))
messageTextedit.setPlainText(QtCore.QString(""))
messagelist = self.getCurrentMessagelist()
if messagelist:
account = self.getCurrentAccount()
@ -3904,54 +4002,38 @@ class MyForm(settingsmixin.SMainWindow):
messageTextedit = self.getCurrentMessageTextedit()
if not messageTextedit:
return
queryreturn = []
message = ""
if folder == 'sent':
ackdata = self.getCurrentMessageId()
if ackdata and messageTextedit:
queryreturn = sqlQuery(
'''select message, 1 from sent where ackdata=?''', ackdata)
msgid = self.getCurrentMessageId()
if msgid:
queryreturn = sqlQuery(
'''SELECT message FROM %s WHERE %s=?''' % (
('sent', 'ackdata') if folder == 'sent'
else ('inbox', 'msgid')
), msgid
)
try:
message = queryreturn[-1][0]
except NameError:
message = ""
except IndexError:
message = _translate(
"MainWindow",
"Error occurred: could not load message from disk."
)
else:
msgid = self.getCurrentMessageId()
if msgid and messageTextedit:
queryreturn = sqlQuery(
'''select message, read from inbox where msgid=?''', msgid)
if queryreturn != []:
refresh = False
propagate = False
tableWidget = self.getCurrentMessagelist()
currentRow = tableWidget.currentRow()
for row in queryreturn:
message, read = row
if tableWidget.item(currentRow, 0).unread == True:
refresh = True
if folder != 'sent':
markread = sqlExecute(
'''UPDATE inbox SET read = 1 WHERE msgid = ? AND read=0''', msgid)
if markread > 0:
propagate = True
if refresh:
if not tableWidget:
return
font = QFont()
font.setBold(False)
# inventoryHashesToMarkRead = []
# inventoryHashToMarkRead = str(tableWidget.item(
# currentRow, 3).data(Qt.UserRole).toPyObject())
# inventoryHashesToMarkRead.append(inventoryHashToMarkRead)
tableWidget.item(currentRow, 0).setUnread(False)
tableWidget.item(currentRow, 1).setUnread(False)
tableWidget.item(currentRow, 2).setUnread(False)
tableWidget.item(currentRow, 3).setFont(font)
if propagate:
self.propagateUnreadCount(tableWidget.item(currentRow, 1 if tableWidget.item(currentRow, 1).type == AccountMixin.SUBSCRIPTION else 0).data(Qt.UserRole), folder, self.getCurrentTreeWidget(), -1)
# refresh
if tableWidget.item(currentRow, 0).unread is True:
self.updateUnreadStatus(tableWidget, currentRow, msgid)
# propagate
if folder != 'sent' and sqlExecute(
'''UPDATE inbox SET read=1 WHERE msgid=? AND read=0''',
msgid
) > 0:
self.propagateUnreadCount()
else:
data = self.getCurrentMessageId()
if data != False:
message = "Error occurred: could not load message from disk."
messageTextedit.setCurrentFont(QtGui.QFont())
messageTextedit.setTextColor(QtGui.QColor())
messageTextedit.setContent(message)
@ -3963,10 +4045,16 @@ class MyForm(settingsmixin.SMainWindow):
self.rerenderMessagelistToLabels()
completerList = self.ui.lineEditTo.completer().model().stringList()
for i in range(len(completerList)):
if str(completerList[i]).endswith(" <" + item.address + ">"):
if unicode(completerList[i]).endswith(" <" + item.address + ">"):
completerList[i] = item.label + " <" + item.address + ">"
self.ui.lineEditTo.completer().model().setStringList(completerList)
def tabWidgetCurrentChanged(self, n):
if n == self.ui.tabWidget.indexOf(self.ui.networkstatus):
self.ui.networkstatus.startUpdate()
else:
self.ui.networkstatus.stopUpdate()
def writeNewAddressToTable(self, label, address, streamNumber):
self.rerenderTabTreeMessages()
self.rerenderTabTreeSubscriptions()
@ -3986,9 +4074,9 @@ class MyForm(settingsmixin.SMainWindow):
logger.info('Status bar: ' + message)
if option == 1:
self.statusBar().addImportant(message)
self.statusbar.addImportant(message)
else:
self.statusBar().showMessage(message, 10000)
self.statusbar.showMessage(message, 10000)
def initSettings(self):
QtCore.QCoreApplication.setOrganizationName("PyBitmessage")
@ -3996,50 +4084,12 @@ class MyForm(settingsmixin.SMainWindow):
QtCore.QCoreApplication.setApplicationName("pybitmessageqt")
self.loadSettings()
for attr, obj in self.ui.__dict__.iteritems():
if hasattr(obj, "__class__") and isinstance(obj, settingsmixin.SettingsMixin):
if hasattr(obj, "__class__") and \
isinstance(obj, settingsmixin.SettingsMixin):
loadMethod = getattr(obj, "loadSettings", None)
if callable (loadMethod):
if callable(loadMethod):
obj.loadSettings()
class helpDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_helpDialog()
self.ui.setupUi(self)
self.parent = parent
self.ui.labelHelpURI.setOpenExternalLinks(True)
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
class connectDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_connectDialog()
self.ui.setupUi(self)
self.parent = parent
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
class aboutDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_aboutDialog()
self.ui.setupUi(self)
self.parent = parent
self.ui.label.setText("PyBitmessage " + softwareVersion)
self.ui.labelVersion.setText(paths.lastCommit())
class regenerateAddressesDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_regenerateAddressesDialog()
self.ui.setupUi(self)
self.parent = parent
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
class settingsDialog(QtGui.QDialog):
@ -4072,8 +4122,9 @@ class settingsDialog(QtGui.QDialog):
else:
try:
import tempfile
file = tempfile.NamedTemporaryFile(dir=paths.lookupExeFolder(), delete=True)
file.close # should autodelete
tempfile.NamedTemporaryFile(
dir=paths.lookupExeFolder(), delete=True
).close() # should autodelete
except:
self.ui.checkBoxPortableMode.setDisabled(True)
@ -4273,175 +4324,20 @@ class settingsDialog(QtGui.QDialog):
self.parent.ui.pushButtonFetchNamecoinID.show()
class SpecialAddressBehaviorDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_SpecialAddressBehaviorDialog()
self.ui.setupUi(self)
self.parent = parent
addressAtCurrentRow = parent.getCurrentAccount()
if not BMConfigParser().safeGetBoolean(addressAtCurrentRow, 'chan'):
if BMConfigParser().safeGetBoolean(addressAtCurrentRow, 'mailinglist'):
self.ui.radioButtonBehaviorMailingList.click()
else:
self.ui.radioButtonBehaveNormalAddress.click()
try:
mailingListName = BMConfigParser().get(
addressAtCurrentRow, 'mailinglistname')
except:
mailingListName = ''
self.ui.lineEditMailingListName.setText(
unicode(mailingListName, 'utf-8'))
else: # if addressAtCurrentRow is a chan address
self.ui.radioButtonBehaviorMailingList.setDisabled(True)
self.ui.lineEditMailingListName.setText(_translate(
"MainWindow", "This is a chan address. You cannot use it as a pseudo-mailing list."))
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
class EmailGatewayDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_EmailGatewayDialog()
self.ui.setupUi(self)
self.parent = parent
addressAtCurrentRow = parent.getCurrentAccount()
acct = accountClass(addressAtCurrentRow)
if isinstance(acct, GatewayAccount):
self.ui.radioButtonUnregister.setEnabled(True)
self.ui.radioButtonStatus.setEnabled(True)
self.ui.radioButtonStatus.setChecked(True)
self.ui.radioButtonSettings.setEnabled(True)
else:
self.ui.radioButtonStatus.setEnabled(False)
self.ui.radioButtonSettings.setEnabled(False)
self.ui.radioButtonUnregister.setEnabled(False)
label = BMConfigParser().get(addressAtCurrentRow, 'label')
if label.find("@mailchuck.com") > -1:
self.ui.lineEditEmail.setText(label)
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
class EmailGatewayRegistrationDialog(QtGui.QDialog):
def __init__(self, parent, title, label):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_EmailGatewayRegistrationDialog()
self.ui.setupUi(self)
self.parent = parent
self.setWindowTitle(title)
self.ui.label.setText(label)
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
class NewSubscriptionDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_NewSubscriptionDialog()
self.ui.setupUi(self)
self.parent = parent
QtCore.QObject.connect(self.ui.lineEditSubscriptionAddress, QtCore.SIGNAL(
"textChanged(QString)"), self.addressChanged)
self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText(
_translate("MainWindow", "Enter an address above."))
def addressChanged(self, QString):
self.ui.checkBoxDisplayMessagesAlreadyInInventory.setEnabled(False)
self.ui.checkBoxDisplayMessagesAlreadyInInventory.setChecked(False)
status, addressVersion, streamNumber, ripe = decodeAddress(str(QString))
if status == 'missingbm':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "The address should start with ''BM-''"))
elif status == 'checksumfailed':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "The address is not typed or copied correctly (the checksum failed)."))
elif status == 'versiontoohigh':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "The version number of this address is higher than this software can support. Please upgrade Bitmessage."))
elif status == 'invalidcharacters':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "The address contains invalid characters."))
elif status == 'ripetooshort':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is too short."))
elif status == 'ripetoolong':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is too long."))
elif status == 'varintmalformed':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is malformed."))
elif status == 'success':
self.ui.labelAddressCheck.setText(
_translate("MainWindow", "Address is valid."))
if addressVersion <= 3:
self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText(
_translate("MainWindow", "Address is an old type. We cannot display its past broadcasts."))
else:
shared.inventory.flush()
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint(
addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()
tag = doubleHashOfAddressData[32:]
count = len(shared.inventory.by_type_and_tag(3, tag))
if count == 0:
self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText(
_translate("MainWindow", "There are no recent broadcasts from this address to display."))
else:
self.ui.checkBoxDisplayMessagesAlreadyInInventory.setEnabled(True)
self.ui.checkBoxDisplayMessagesAlreadyInInventory.setText(
_translate("MainWindow", "Display the %1 recent broadcast(s) from this address.").arg(count))
class NewAddressDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_NewAddressDialog()
self.ui.setupUi(self)
self.parent = parent
row = 1
# Let's fill out the 'existing address' combo box with addresses from
# the 'Your Identities' tab.
for addressInKeysFile in getSortedAccounts():
self.ui.radioButtonExisting.click()
self.ui.comboBoxExisting.addItem(
addressInKeysFile)
row += 1
self.ui.groupBoxDeterministic.setHidden(True)
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
class iconGlossaryDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_iconGlossaryDialog()
self.ui.setupUi(self)
self.parent = parent
self.ui.labelPortNumber.setText(_translate(
"MainWindow", "You are using TCP port %1. (This can be changed in the settings).").arg(str(BMConfigParser().getint('bitmessagesettings', 'port'))))
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
# In order for the time columns on the Inbox and Sent tabs to be sorted
# correctly (rather than alphabetically), we need to overload the <
# operator and use this class instead of QTableWidgetItem.
class myTableWidgetItem(QTableWidgetItem):
class myTableWidgetItem(QtGui.QTableWidgetItem):
def __lt__(self, other):
return int(self.data(33).toPyObject()) < int(other.data(33).toPyObject())
from uisignaler import UISignaler
app = None
myapp = None
class MySingleApplication(QApplication):
class MySingleApplication(QtGui.QApplication):
"""
Listener to allow our Qt form to get focus when another instance of the
application is open.
@ -4491,12 +4387,14 @@ class MySingleApplication(QApplication):
if myapp:
myapp.appIndicatorShow()
def init():
global app
if not app:
app = MySingleApplication(sys.argv)
return app
def run():
global myapp
app = init()
@ -4504,12 +4402,16 @@ def run():
app.setStyleSheet("QStatusBar::item { border: 0px solid black }")
myapp = MyForm()
myapp.sqlInit()
myapp.appIndicatorInit(app)
myapp.ubuntuMessagingMenuInit()
myapp.indicatorInit()
myapp.notifierInit()
if BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect'):
myapp.showConnectDialog() # ask the user if we may connect
myapp._firstrun = BMConfigParser().safeGetBoolean(
'bitmessagesettings', 'dontconnect')
if myapp._firstrun:
myapp.showConnectDialog() # ask the user if we may connect
myapp.ui.updateNetworkSwitchMenuLabel()
# try:
# if BMConfigParser().get('bitmessagesettings', 'mailchuck') < 1:
# myapp.showMigrationWizard(BMConfigParser().get('bitmessagesettings', 'mailchuck'))

View File

@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'about.ui'
#
# Created: Tue Jan 21 22:29:38 2014
# by: PyQt4 UI code generator 4.10.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_aboutDialog(object):
def setupUi(self, aboutDialog):
aboutDialog.setObjectName(_fromUtf8("aboutDialog"))
aboutDialog.resize(360, 315)
self.buttonBox = QtGui.QDialogButtonBox(aboutDialog)
self.buttonBox.setGeometry(QtCore.QRect(20, 280, 311, 32))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.label = QtGui.QLabel(aboutDialog)
self.label.setGeometry(QtCore.QRect(10, 106, 341, 20))
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.label.setFont(font)
self.label.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter)
self.label.setObjectName(_fromUtf8("label"))
self.labelVersion = QtGui.QLabel(aboutDialog)
self.labelVersion.setGeometry(QtCore.QRect(10, 116, 341, 41))
self.labelVersion.setObjectName(_fromUtf8("labelVersion"))
self.labelVersion.setAlignment(QtCore.Qt.AlignCenter|QtCore.Qt.AlignVCenter)
self.label_2 = QtGui.QLabel(aboutDialog)
self.label_2.setGeometry(QtCore.QRect(10, 150, 341, 41))
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.label_3 = QtGui.QLabel(aboutDialog)
self.label_3.setGeometry(QtCore.QRect(20, 200, 331, 71))
self.label_3.setWordWrap(True)
self.label_3.setOpenExternalLinks(True)
self.label_3.setObjectName(_fromUtf8("label_3"))
self.label_5 = QtGui.QLabel(aboutDialog)
self.label_5.setGeometry(QtCore.QRect(10, 190, 341, 20))
self.label_5.setAlignment(QtCore.Qt.AlignCenter)
self.label_5.setObjectName(_fromUtf8("label_5"))
self.retranslateUi(aboutDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), aboutDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), aboutDialog.reject)
QtCore.QMetaObject.connectSlotsByName(aboutDialog)
def retranslateUi(self, aboutDialog):
aboutDialog.setWindowTitle(_translate("aboutDialog", "About", None))
self.label.setText(_translate("aboutDialog", "PyBitmessage", None))
self.labelVersion.setText(_translate("aboutDialog", "version ?", None))
self.label_2.setText(_translate("aboutDialog", "<html><head/><body><p>Copyright &copy; 2012-2016 Jonathan Warren<br/>Copyright &copy; 2013-2016 The Bitmessage Developers</p></body></html>", None))
self.label_3.setText(_translate("aboutDialog", "<html><head/><body><p>Distributed under the MIT/X11 software license; see <a href=\"http://www.opensource.org/licenses/mit-license.php\"><span style=\" text-decoration: underline; color:#0000ff;\">http://www.opensource.org/licenses/mit-license.php</span></a></p></body></html>", None))
self.label_5.setText(_translate("aboutDialog", "This is Beta software.", None))

View File

@ -6,117 +6,91 @@
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>315</height>
<width>430</width>
<height>340</height>
</rect>
</property>
<property name="windowTitle">
<string>About</string>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>20</x>
<y>280</y>
<width>311</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>70</x>
<y>126</y>
<width>111</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>PyBitmessage</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QLabel" name="labelVersion">
<property name="geometry">
<rect>
<x>190</x>
<y>126</y>
<width>161</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>version ?</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>10</x>
<y>150</y>
<width>341</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copyright © 2012-2014 Jonathan Warren&lt;br/&gt;Copyright © 2013-2014 The Bitmessage Developers&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>20</x>
<y>200</y>
<width>331</width>
<height>71</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Distributed under the MIT/X11 software license; see &lt;a href=&quot;http://www.opensource.org/licenses/mit-license.php&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://www.opensource.org/licenses/mit-license.php&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="label_5">
<property name="geometry">
<rect>
<x>10</x>
<y>190</y>
<width>341</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>This is Beta software.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="label_4">
<property name="text">
<string notr="true"/>
</property>
<property name="pixmap">
<pixmap resource="bitmessage_icons.qrc">:/newPrefix/images/can-icon-24px.png</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="labelVersion">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/Bitmessage/PyBitmessage/tree/:branch:&quot;&gt;&lt;span style=&quot;text-decoration:none; color:#0000ff;&quot;&gt;PyBitmessage :version:&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item alignment="Qt::AlignLeft">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Copyright © 2012-2016 Jonathan Warren&lt;br/&gt;Copyright © 2013-2017 The Bitmessage Developers&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeft</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>This is Beta software.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Distributed under the MIT/X11 software license; see &lt;a href=&quot;http://www.opensource.org/licenses/mit-license.php&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://www.opensource.org/licenses/mit-license.php&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<resources>
<include location="bitmessage_icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>

View File

@ -5,6 +5,7 @@ import re
import sys
import inspect
from helper_sql import *
from helper_ackPayload import genAckPayload
from addresses import decodeAddress
from bmconfigparser import BMConfigParser
from foldertree import AccountMixin
@ -13,7 +14,7 @@ from utils import str_broadcast_subscribers
import time
def getSortedAccounts():
configSections = filter(lambda x: x != 'bitmessagesettings', BMConfigParser().sections())
configSections = BMConfigParser().addresses()
configSections.sort(cmp =
lambda x,y: cmp(unicode(BMConfigParser().get(x, 'label'), 'utf-8').lower(), unicode(BMConfigParser().get(y, 'label'), 'utf-8').lower())
)
@ -166,7 +167,8 @@ class GatewayAccount(BMAccount):
def send(self):
status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.toAddress)
ackdata = OpenSSL.rand(32)
stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
t = ()
sqlExecute(
'''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',

View File

@ -1,65 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'addaddressdialog.ui'
#
# Created: Sat Nov 30 20:35:38 2013
# by: PyQt4 UI code generator 4.10.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_AddAddressDialog(object):
def setupUi(self, AddAddressDialog):
AddAddressDialog.setObjectName(_fromUtf8("AddAddressDialog"))
AddAddressDialog.resize(368, 162)
self.formLayout = QtGui.QFormLayout(AddAddressDialog)
self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow)
self.formLayout.setObjectName(_fromUtf8("formLayout"))
self.label_2 = QtGui.QLabel(AddAddressDialog)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.formLayout.setWidget(0, QtGui.QFormLayout.SpanningRole, self.label_2)
self.newAddressLabel = QtGui.QLineEdit(AddAddressDialog)
self.newAddressLabel.setObjectName(_fromUtf8("newAddressLabel"))
self.formLayout.setWidget(2, QtGui.QFormLayout.SpanningRole, self.newAddressLabel)
self.label = QtGui.QLabel(AddAddressDialog)
self.label.setObjectName(_fromUtf8("label"))
self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.label)
self.lineEditAddress = QtGui.QLineEdit(AddAddressDialog)
self.lineEditAddress.setObjectName(_fromUtf8("lineEditAddress"))
self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.lineEditAddress)
self.labelAddressCheck = QtGui.QLabel(AddAddressDialog)
self.labelAddressCheck.setText(_fromUtf8(""))
self.labelAddressCheck.setWordWrap(True)
self.labelAddressCheck.setObjectName(_fromUtf8("labelAddressCheck"))
self.formLayout.setWidget(6, QtGui.QFormLayout.SpanningRole, self.labelAddressCheck)
self.buttonBox = QtGui.QDialogButtonBox(AddAddressDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.formLayout.setWidget(7, QtGui.QFormLayout.FieldRole, self.buttonBox)
self.retranslateUi(AddAddressDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), AddAddressDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), AddAddressDialog.reject)
QtCore.QMetaObject.connectSlotsByName(AddAddressDialog)
def retranslateUi(self, AddAddressDialog):
AddAddressDialog.setWindowTitle(_translate("AddAddressDialog", "Add new entry", None))
self.label_2.setText(_translate("AddAddressDialog", "Label", None))
self.label.setText(_translate("AddAddressDialog", "Address", None))

View File

@ -7,9 +7,15 @@
<x>0</x>
<y>0</y>
<width>368</width>
<height>162</height>
<height>232</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>368</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>Add new entry</string>
</property>
@ -25,7 +31,7 @@
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLineEdit" name="newAddressLabel"/>
<widget class="QLineEdit" name="lineEditLabel"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
@ -47,7 +53,7 @@
</property>
</widget>
</item>
<item row="7" column="1">
<item row="8" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -57,6 +63,19 @@
</property>
</widget>
</item>
<item row="7" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>

View File

@ -0,0 +1,354 @@
from PyQt4 import QtCore, QtGui
from addresses import decodeAddress, encodeVarint, addBMIfNotPresent
from account import (
GatewayAccount, MailchuckAccount, AccountMixin, accountClass,
getSortedAccounts
)
from tr import _translate
from retranslateui import RetranslateMixin
import widgets
import queues
import hashlib
from inventory import Inventory
class AddressCheckMixin(object):
def __init__(self):
self.valid = False
QtCore.QObject.connect(self.lineEditAddress, QtCore.SIGNAL(
"textChanged(QString)"), self.addressChanged)
def _onSuccess(self, addressVersion, streamNumber, ripe):
pass
def addressChanged(self, QString):
status, addressVersion, streamNumber, ripe = decodeAddress(
str(QString))
self.valid = status == 'success'
if self.valid:
self.labelAddressCheck.setText(
_translate("MainWindow", "Address is valid."))
self._onSuccess(addressVersion, streamNumber, ripe)
elif status == 'missingbm':
self.labelAddressCheck.setText(_translate(
"MainWindow", # dialog name should be here
"The address should start with ''BM-''"
))
elif status == 'checksumfailed':
self.labelAddressCheck.setText(_translate(
"MainWindow",
"The address is not typed or copied correctly"
" (the checksum failed)."
))
elif status == 'versiontoohigh':
self.labelAddressCheck.setText(_translate(
"MainWindow",
"The version number of this address is higher than this"
" software can support. Please upgrade Bitmessage."
))
elif status == 'invalidcharacters':
self.labelAddressCheck.setText(_translate(
"MainWindow",
"The address contains invalid characters."
))
elif status == 'ripetooshort':
self.labelAddressCheck.setText(_translate(
"MainWindow",
"Some data encoded in the address is too short."
))
elif status == 'ripetoolong':
self.labelAddressCheck.setText(_translate(
"MainWindow",
"Some data encoded in the address is too long."
))
elif status == 'varintmalformed':
self.labelAddressCheck.setText(_translate(
"MainWindow",
"Some data encoded in the address is malformed."
))
class AddressDataDialog(QtGui.QDialog, AddressCheckMixin):
def __init__(self, parent):
super(AddressDataDialog, self).__init__(parent)
self.parent = parent
def accept(self):
if self.valid:
self.data = (
addBMIfNotPresent(str(self.lineEditAddress.text())),
str(self.lineEditLabel.text().toUtf8())
)
else:
queues.UISignalQueue.put(('updateStatusBar', _translate(
"MainWindow",
"The address you entered was invalid. Ignoring it."
)))
super(AddressDataDialog, self).accept()
class AddAddressDialog(AddressDataDialog, RetranslateMixin):
def __init__(self, parent=None, address=None):
super(AddAddressDialog, self).__init__(parent)
widgets.load('addaddressdialog.ui', self)
AddressCheckMixin.__init__(self)
if address:
self.lineEditAddress.setText(address)
class NewAddressDialog(QtGui.QDialog, RetranslateMixin):
def __init__(self, parent=None):
super(NewAddressDialog, self).__init__(parent)
widgets.load('newaddressdialog.ui', self)
# Let's fill out the 'existing address' combo box with addresses
# from the 'Your Identities' tab.
for address in getSortedAccounts():
self.radioButtonExisting.click()
self.comboBoxExisting.addItem(address)
self.groupBoxDeterministic.setHidden(True)
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
self.show()
def accept(self):
self.hide()
# self.buttonBox.enabled = False
if self.radioButtonRandomAddress.isChecked():
if self.radioButtonMostAvailable.isChecked():
streamNumberForAddress = 1
else:
# User selected 'Use the same stream as an existing
# address.'
streamNumberForAddress = decodeAddress(
self.comboBoxExisting.currentText())[2]
queues.addressGeneratorQueue.put((
'createRandomAddress', 4, streamNumberForAddress,
str(self.newaddresslabel.text().toUtf8()), 1, "",
self.checkBoxEighteenByteRipe.isChecked()
))
else:
if self.lineEditPassphrase.text() != \
self.lineEditPassphraseAgain.text():
QtGui.QMessageBox.about(
self, _translate("MainWindow", "Passphrase mismatch"),
_translate(
"MainWindow",
"The passphrase you entered twice doesn\'t"
" match. Try again.")
)
elif self.lineEditPassphrase.text() == "":
QtGui.QMessageBox.about(
self, _translate("MainWindow", "Choose a passphrase"),
_translate(
"MainWindow", "You really do need a passphrase.")
)
else:
# this will eventually have to be replaced by logic
# to determine the most available stream number.
streamNumberForAddress = 1
queues.addressGeneratorQueue.put((
'createDeterministicAddresses', 4, streamNumberForAddress,
"unused deterministic address",
self.spinBoxNumberOfAddressesToMake.value(),
self.lineEditPassphrase.text().toUtf8(),
self.checkBoxEighteenByteRipe.isChecked()
))
class NewSubscriptionDialog(AddressDataDialog, RetranslateMixin):
def __init__(self, parent=None):
super(NewSubscriptionDialog, self).__init__(parent)
widgets.load('newsubscriptiondialog.ui', self)
AddressCheckMixin.__init__(self)
def _onSuccess(self, addressVersion, streamNumber, ripe):
if addressVersion <= 3:
self.checkBoxDisplayMessagesAlreadyInInventory.setText(_translate(
"MainWindow",
"Address is an old type. We cannot display its past"
" broadcasts."
))
else:
Inventory().flush()
doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(
encodeVarint(addressVersion) +
encodeVarint(streamNumber) + ripe
).digest()).digest()
tag = doubleHashOfAddressData[32:]
self.recent = Inventory().by_type_and_tag(3, tag)
count = len(self.recent)
if count == 0:
self.checkBoxDisplayMessagesAlreadyInInventory.setText(
_translate(
"MainWindow",
"There are no recent broadcasts from this address"
" to display."
))
else:
self.checkBoxDisplayMessagesAlreadyInInventory.setEnabled(True)
self.checkBoxDisplayMessagesAlreadyInInventory.setText(
_translate(
"MainWindow",
"Display the %n recent broadcast(s) from this address.",
None,
QtCore.QCoreApplication.CodecForTr,
count
))
class RegenerateAddressesDialog(QtGui.QDialog, RetranslateMixin):
def __init__(self, parent=None):
super(RegenerateAddressesDialog, self).__init__(parent)
widgets.load('regenerateaddresses.ui', self)
self.groupBox.setTitle('')
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
class SpecialAddressBehaviorDialog(QtGui.QDialog, RetranslateMixin):
def __init__(self, parent=None, config=None):
super(SpecialAddressBehaviorDialog, self).__init__(parent)
widgets.load('specialaddressbehavior.ui', self)
self.address = parent.getCurrentAccount()
self.parent = parent
self.config = config
try:
self.address_is_chan = config.safeGetBoolean(
self.address, 'chan'
)
except AttributeError:
pass
else:
if self.address_is_chan: # address is a chan address
self.radioButtonBehaviorMailingList.setDisabled(True)
self.lineEditMailingListName.setText(_translate(
"SpecialAddressBehaviorDialog",
"This is a chan address. You cannot use it as a"
" pseudo-mailing list."
))
else:
if config.safeGetBoolean(self.address, 'mailinglist'):
self.radioButtonBehaviorMailingList.click()
else:
self.radioButtonBehaveNormalAddress.click()
try:
mailingListName = config.get(
self.address, 'mailinglistname')
except:
mailingListName = ''
self.lineEditMailingListName.setText(
unicode(mailingListName, 'utf-8')
)
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
self.show()
def accept(self):
self.hide()
if self.address_is_chan:
return
if self.radioButtonBehaveNormalAddress.isChecked():
self.config.set(str(self.address), 'mailinglist', 'false')
# Set the color to either black or grey
if self.config.getboolean(self.address, 'enabled'):
self.parent.setCurrentItemColor(
QtGui.QApplication.palette().text().color()
)
else:
self.parent.setCurrentItemColor(QtGui.QColor(128, 128, 128))
else:
self.config.set(str(self.address), 'mailinglist', 'true')
self.config.set(str(self.address), 'mailinglistname', str(
self.lineEditMailingListName.text().toUtf8()))
self.parent.setCurrentItemColor(
QtGui.QColor(137, 04, 177)) # magenta
self.parent.rerenderComboBoxSendFrom()
self.parent.rerenderComboBoxSendFromBroadcast()
self.config.save()
self.parent.rerenderMessagelistToLabels()
class EmailGatewayDialog(QtGui.QDialog, RetranslateMixin):
def __init__(self, parent, config=None, account=None):
super(EmailGatewayDialog, self).__init__(parent)
widgets.load('emailgateway.ui', self)
self.parent = parent
self.config = config
if account:
self.acct = account
self.setWindowTitle(_translate(
"EmailGatewayDialog", "Registration failed:"))
self.label.setText(_translate(
"EmailGatewayDialog",
"The requested email address is not available,"
" please try a new one."
))
self.radioButtonRegister.hide()
self.radioButtonStatus.hide()
self.radioButtonSettings.hide()
self.radioButtonUnregister.hide()
else:
address = parent.getCurrentAccount()
self.acct = accountClass(address)
try:
label = config.get(address, 'label')
except AttributeError:
pass
else:
if "@" in label:
self.lineEditEmail.setText(label)
if isinstance(self.acct, GatewayAccount):
self.radioButtonUnregister.setEnabled(True)
self.radioButtonStatus.setEnabled(True)
self.radioButtonStatus.setChecked(True)
self.radioButtonSettings.setEnabled(True)
self.lineEditEmail.setEnabled(False)
else:
self.acct = MailchuckAccount(address)
self.lineEditEmail.setFocus()
QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
def accept(self):
self.hide()
# no chans / mailinglists
if self.acct.type != AccountMixin.NORMAL:
return
if not isinstance(self.acct, GatewayAccount):
return
if self.radioButtonRegister.isChecked() \
or self.radioButtonRegister.isHidden():
email = str(self.lineEditEmail.text().toUtf8())
self.acct.register(email)
self.config.set(self.acct.fromAddress, 'label', email)
self.config.set(self.acct.fromAddress, 'gateway', 'mailchuck')
self.config.save()
queues.UISignalQueue.put(('updateStatusBar', _translate(
"EmailGatewayDialog",
"Sending email gateway registration request"
)))
elif self.radioButtonUnregister.isChecked():
self.acct.unregister()
self.config.remove_option(self.acct.fromAddress, 'gateway')
self.config.save()
queues.UISignalQueue.put(('updateStatusBar', _translate(
"EmailGatewayDialog",
"Sending email gateway unregistration request"
)))
elif self.radioButtonStatus.isChecked():
self.acct.status()
queues.UISignalQueue.put(('updateStatusBar', _translate(
"EmailGatewayDialog",
"Sending email gateway status request"
)))
elif self.radioButtonSettings.isChecked():
self.data = self.acct
super(EmailGatewayDialog, self).accept()

View File

@ -15,6 +15,8 @@ class AddressPassPhraseValidatorMixin():
self.buttonBox = buttonBox
self.addressMandatory = addressMandatory
self.isValid = False
# save default text
self.okButtonLabel = self.buttonBox.button(QtGui.QDialogButtonBox.Ok).text()
def setError(self, string):
if string is not None and self.feedBackObject is not None:
@ -26,6 +28,10 @@ class AddressPassPhraseValidatorMixin():
self.isValid = False
if self.buttonBox:
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(False)
if string is not None and self.feedBackObject is not None:
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText(_translate("AddressValidator", "Invalid"))
else:
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText(_translate("AddressValidator", "Validating..."))
def setOK(self, string):
if string is not None and self.feedBackObject is not None:
@ -37,6 +43,7 @@ class AddressPassPhraseValidatorMixin():
self.isValid = True
if self.buttonBox:
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setEnabled(True)
self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText(self.okButtonLabel)
def checkQueue(self):
gotOne = False

View File

@ -337,6 +337,9 @@ class Ui_MainWindow(object):
self.labelHumanFriendlyTTLDescription.setMinimumSize(QtCore.QSize(45, 0))
self.labelHumanFriendlyTTLDescription.setObjectName(_fromUtf8("labelHumanFriendlyTTLDescription"))
self.horizontalLayout_5.addWidget(self.labelHumanFriendlyTTLDescription, 1, QtCore.Qt.AlignLeft)
self.pushButtonClear = QtGui.QPushButton(self.send)
self.pushButtonClear.setObjectName(_fromUtf8("pushButtonClear"))
self.horizontalLayout_5.addWidget(self.pushButtonClear, 0, QtCore.Qt.AlignRight)
self.pushButtonSend = QtGui.QPushButton(self.send)
self.pushButtonSend.setObjectName(_fromUtf8("pushButtonSend"))
self.horizontalLayout_5.addWidget(self.pushButtonSend, 0, QtCore.Qt.AlignRight)
@ -586,6 +589,8 @@ class Ui_MainWindow(object):
icon = QtGui.QIcon.fromTheme(_fromUtf8("dialog-password"))
self.actionManageKeys.setIcon(icon)
self.actionManageKeys.setObjectName(_fromUtf8("actionManageKeys"))
self.actionNetworkSwitch = QtGui.QAction(MainWindow)
self.actionNetworkSwitch.setObjectName(_fromUtf8("actionNetworkSwitch"))
self.actionExit = QtGui.QAction(MainWindow)
icon = QtGui.QIcon.fromTheme(_fromUtf8("application-exit"))
self.actionExit.setIcon(icon)
@ -621,6 +626,7 @@ class Ui_MainWindow(object):
self.menuFile.addAction(self.actionManageKeys)
self.menuFile.addAction(self.actionDeleteAllTrashedMessages)
self.menuFile.addAction(self.actionRegenerateDeterministicAddresses)
self.menuFile.addAction(self.actionNetworkSwitch)
self.menuFile.addAction(self.actionExit)
self.menuSettings.addAction(self.actionSettings)
self.menuHelp.addAction(self.actionHelp)
@ -631,8 +637,12 @@ class Ui_MainWindow(object):
self.menubar.addAction(self.menuHelp.menuAction())
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(0)
self.tabWidgetSend.setCurrentIndex(0)
self.tabWidget.setCurrentIndex(
self.tabWidget.indexOf(self.inbox)
)
self.tabWidgetSend.setCurrentIndex(
self.tabWidgetSend.indexOf(self.sendDirect)
)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
MainWindow.setTabOrder(self.tableWidgetInbox, self.textEditInboxMessage)
MainWindow.setTabOrder(self.textEditInboxMessage, self.comboBoxSendFrom)
@ -641,6 +651,16 @@ class Ui_MainWindow(object):
MainWindow.setTabOrder(self.lineEditSubject, self.textEditMessage)
MainWindow.setTabOrder(self.textEditMessage, self.pushButtonAddSubscription)
def updateNetworkSwitchMenuLabel(self, dontconnect=None):
if dontconnect is None:
dontconnect = BMConfigParser().safeGetBoolean(
'bitmessagesettings', 'dontconnect')
self.actionNetworkSwitch.setText(
_translate("MainWindow", "Go online", None)
if dontconnect else
_translate("MainWindow", "Go offline", None)
)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "Bitmessage", None))
self.treeWidgetYourIdentities.headerItem().setText(0, _translate("MainWindow", "Identities", None))
@ -684,6 +704,7 @@ class Ui_MainWindow(object):
except:
pass
self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours))
self.pushButtonClear.setText(_translate("MainWindow", "Clear", None))
self.pushButtonSend.setText(_translate("MainWindow", "Send", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.send), _translate("MainWindow", "Send", None))
self.treeWidgetSubscriptions.headerItem().setText(0, _translate("MainWindow", "Subscriptions", None))

View File

@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'connect.ui'
#
# Created: Wed Jul 24 12:42:01 2013
# by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_connectDialog(object):
def setupUi(self, connectDialog):
connectDialog.setObjectName(_fromUtf8("connectDialog"))
connectDialog.resize(400, 124)
self.gridLayout = QtGui.QGridLayout(connectDialog)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.label = QtGui.QLabel(connectDialog)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout.addWidget(self.label, 0, 0, 1, 2)
self.radioButtonConnectNow = QtGui.QRadioButton(connectDialog)
self.radioButtonConnectNow.setChecked(True)
self.radioButtonConnectNow.setObjectName(_fromUtf8("radioButtonConnectNow"))
self.gridLayout.addWidget(self.radioButtonConnectNow, 1, 0, 1, 2)
self.radioButtonConfigureNetwork = QtGui.QRadioButton(connectDialog)
self.radioButtonConfigureNetwork.setObjectName(_fromUtf8("radioButtonConfigureNetwork"))
self.gridLayout.addWidget(self.radioButtonConfigureNetwork, 2, 0, 1, 2)
spacerItem = QtGui.QSpacerItem(185, 24, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem, 3, 0, 1, 1)
self.buttonBox = QtGui.QDialogButtonBox(connectDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.gridLayout.addWidget(self.buttonBox, 3, 1, 1, 1)
self.retranslateUi(connectDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), connectDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), connectDialog.reject)
QtCore.QMetaObject.connectSlotsByName(connectDialog)
def retranslateUi(self, connectDialog):
connectDialog.setWindowTitle(_translate("connectDialog", "Bitmessage", None))
self.label.setText(_translate("connectDialog", "Bitmessage won\'t connect to anyone until you let it. ", None))
self.radioButtonConnectNow.setText(_translate("connectDialog", "Connect now", None))
self.radioButtonConfigureNetwork.setText(_translate("connectDialog", "Let me configure special network settings first", None))

View File

@ -38,7 +38,14 @@
</property>
</widget>
</item>
<item row="3" column="0">
<item row="3" column="0" colspan="2">
<widget class="QRadioButton" name="radioButtonWorkOffline">
<property name="text">
<string>Work offline</string>
</property>
</widget>
</item>
<item row="4" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -51,7 +58,7 @@
</property>
</spacer>
</item>
<item row="3" column="1">
<item row="4" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>

View File

@ -1,42 +1,76 @@
from PyQt4 import QtCore, QtGui
from addaddressdialog import Ui_AddAddressDialog
from addresses import decodeAddress
from PyQt4 import QtGui
from tr import _translate
from retranslateui import RetranslateMixin
import widgets
from newchandialog import NewChanDialog
from address_dialogs import (
AddAddressDialog, NewAddressDialog, NewSubscriptionDialog,
RegenerateAddressesDialog, SpecialAddressBehaviorDialog, EmailGatewayDialog
)
import paths
from version import softwareVersion
class AddAddressDialog(QtGui.QDialog):
__all__ = [
"NewChanDialog", "AddAddressDialog", "NewAddressDialog",
"NewSubscriptionDialog", "RegenerateAddressesDialog",
"SpecialAddressBehaviorDialog", "EmailGatewayDialog"
]
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_AddAddressDialog()
self.ui.setupUi(self)
self.parent = parent
QtCore.QObject.connect(self.ui.lineEditAddress, QtCore.SIGNAL(
"textChanged(QString)"), self.addressChanged)
def addressChanged(self, QString):
status, a, b, c = decodeAddress(str(QString))
if status == 'missingbm':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "The address should start with ''BM-''"))
elif status == 'checksumfailed':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "The address is not typed or copied correctly (the checksum failed)."))
elif status == 'versiontoohigh':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "The version number of this address is higher than this software can support. Please upgrade Bitmessage."))
elif status == 'invalidcharacters':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "The address contains invalid characters."))
elif status == 'ripetooshort':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is too short."))
elif status == 'ripetoolong':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is too long."))
elif status == 'varintmalformed':
self.ui.labelAddressCheck.setText(_translate(
"MainWindow", "Some data encoded in the address is malformed."))
elif status == 'success':
self.ui.labelAddressCheck.setText(
_translate("MainWindow", "Address is valid."))
class AboutDialog(QtGui.QDialog, RetranslateMixin):
def __init__(self, parent=None):
super(AboutDialog, self).__init__(parent)
widgets.load('about.ui', self)
last_commit = paths.lastCommit()
version = softwareVersion
commit = last_commit.get('commit')
if commit:
version += '-' + commit[:7]
self.labelVersion.setText(
self.labelVersion.text().replace(
':version:', version
).replace(':branch:', commit or 'v%s' % version)
)
self.labelVersion.setOpenExternalLinks(True)
try:
self.label_2.setText(
self.label_2.text().replace(
'2017', str(last_commit.get('time').year)
))
except AttributeError:
pass
self.setFixedSize(QtGui.QWidget.sizeHint(self))
class IconGlossaryDialog(QtGui.QDialog, RetranslateMixin):
def __init__(self, parent=None, config=None):
super(IconGlossaryDialog, self).__init__(parent)
widgets.load('iconglossary.ui', self)
# FIXME: check the window title visibility here
self.groupBox.setTitle('')
self.labelPortNumber.setText(_translate(
"iconGlossaryDialog",
"You are using TCP port %1. (This can be changed in the settings)."
).arg(config.getint('bitmessagesettings', 'port')))
self.setFixedSize(QtGui.QWidget.sizeHint(self))
class HelpDialog(QtGui.QDialog, RetranslateMixin):
def __init__(self, parent=None):
super(HelpDialog, self).__init__(parent)
widgets.load('help.ui', self)
self.setFixedSize(QtGui.QWidget.sizeHint(self))
class ConnectDialog(QtGui.QDialog, RetranslateMixin):
def __init__(self, parent=None):
super(ConnectDialog, self).__init__(parent)
widgets.load('connect.ui', self)
self.setFixedSize(QtGui.QWidget.sizeHint(self))

View File

@ -1,103 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'emailgateway.ui'
#
# Created: Fri Apr 26 17:43:31 2013
# by: PyQt4 UI code generator 4.9.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_EmailGatewayDialog(object):
def setupUi(self, EmailGatewayDialog):
EmailGatewayDialog.setObjectName(_fromUtf8("EmailGatewayDialog"))
EmailGatewayDialog.resize(386, 172)
self.gridLayout = QtGui.QGridLayout(EmailGatewayDialog)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.radioButtonRegister = QtGui.QRadioButton(EmailGatewayDialog)
self.radioButtonRegister.setChecked(True)
self.radioButtonRegister.setObjectName(_fromUtf8("radioButtonRegister"))
self.gridLayout.addWidget(self.radioButtonRegister, 1, 0, 1, 1)
self.radioButtonStatus = QtGui.QRadioButton(EmailGatewayDialog)
self.radioButtonStatus.setObjectName(_fromUtf8("radioButtonStatus"))
self.gridLayout.addWidget(self.radioButtonStatus, 4, 0, 1, 1)
self.radioButtonSettings = QtGui.QRadioButton(EmailGatewayDialog)
self.radioButtonSettings.setObjectName(_fromUtf8("radioButtonSettings"))
self.gridLayout.addWidget(self.radioButtonSettings, 5, 0, 1, 1)
self.radioButtonUnregister = QtGui.QRadioButton(EmailGatewayDialog)
self.radioButtonUnregister.setObjectName(_fromUtf8("radioButtonUnregister"))
self.gridLayout.addWidget(self.radioButtonUnregister, 6, 0, 1, 1)
self.label = QtGui.QLabel(EmailGatewayDialog)
self.label.setWordWrap(True)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.label_2 = QtGui.QLabel(EmailGatewayDialog)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
self.lineEditEmail = QtGui.QLineEdit(EmailGatewayDialog)
self.lineEditEmail.setEnabled(True)
self.lineEditEmail.setObjectName(_fromUtf8("lineEditEmail"))
self.gridLayout.addWidget(self.lineEditEmail, 3, 0, 1, 1)
self.buttonBox = QtGui.QDialogButtonBox(EmailGatewayDialog)
self.buttonBox.setMinimumSize(QtCore.QSize(368, 0))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.gridLayout.addWidget(self.buttonBox, 7, 0, 1, 1)
self.retranslateUi(EmailGatewayDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), EmailGatewayDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), EmailGatewayDialog.reject)
QtCore.QObject.connect(self.radioButtonRegister, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditEmail.setEnabled)
QtCore.QObject.connect(self.radioButtonStatus, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditEmail.setDisabled)
QtCore.QObject.connect(self.radioButtonSettings, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditEmail.setDisabled)
QtCore.QObject.connect(self.radioButtonUnregister, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditEmail.setDisabled)
QtCore.QMetaObject.connectSlotsByName(EmailGatewayDialog)
EmailGatewayDialog.setTabOrder(self.radioButtonRegister, self.lineEditEmail)
EmailGatewayDialog.setTabOrder(self.lineEditEmail, self.radioButtonUnregister)
EmailGatewayDialog.setTabOrder(self.radioButtonUnregister, self.buttonBox)
def retranslateUi(self, EmailGatewayDialog):
EmailGatewayDialog.setWindowTitle(QtGui.QApplication.translate("EmailGatewayDialog", "Email gateway", None, QtGui.QApplication.UnicodeUTF8))
self.radioButtonRegister.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Register on email gateway", None, QtGui.QApplication.UnicodeUTF8))
self.radioButtonStatus.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Account status at email gateway", None, QtGui.QApplication.UnicodeUTF8))
self.radioButtonSettings.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Change account settings at email gateway", None, QtGui.QApplication.UnicodeUTF8))
self.radioButtonUnregister.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Unregister from email gateway", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("EmailGatewayDialog", "Desired email address (including @mailchuck.com):", None, QtGui.QApplication.UnicodeUTF8))
class Ui_EmailGatewayRegistrationDialog(object):
def setupUi(self, EmailGatewayRegistrationDialog):
EmailGatewayRegistrationDialog.setObjectName(_fromUtf8("EmailGatewayRegistrationDialog"))
EmailGatewayRegistrationDialog.resize(386, 172)
self.gridLayout = QtGui.QGridLayout(EmailGatewayRegistrationDialog)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.label = QtGui.QLabel(EmailGatewayRegistrationDialog)
self.label.setWordWrap(True)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.lineEditEmail = QtGui.QLineEdit(EmailGatewayRegistrationDialog)
self.lineEditEmail.setObjectName(_fromUtf8("lineEditEmail"))
self.gridLayout.addWidget(self.lineEditEmail, 1, 0, 1, 1)
self.buttonBox = QtGui.QDialogButtonBox(EmailGatewayRegistrationDialog)
self.buttonBox.setMinimumSize(QtCore.QSize(368, 0))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.gridLayout.addWidget(self.buttonBox, 7, 0, 1, 1)
self.retranslateUi(EmailGatewayRegistrationDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), EmailGatewayRegistrationDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), EmailGatewayRegistrationDialog.reject)
QtCore.QMetaObject.connectSlotsByName(EmailGatewayRegistrationDialog)
def retranslateUi(self, EmailGatewayRegistrationDialog):
EmailGatewayRegistrationDialog.setWindowTitle(QtGui.QApplication.translate("EmailGatewayRegistrationDialog", "Email gateway registration", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("EmailGatewayRegistrationDialog", "Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.\nPlease type the desired email address (including @mailchuck.com) below:", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -7,20 +7,20 @@
<x>0</x>
<y>0</y>
<width>386</width>
<height>172</height>
<height>240</height>
</rect>
</property>
<property name="windowTitle">
<string>Email gateway</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Desired email address (including @mailchuck.com)</string>
<string>Desired email address (including @mailchuck.com):</string>
</property>
</widget>
</item>
@ -50,28 +50,60 @@
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLineEdit" name="lineEditEmailAddress">
<item row="4" column="0">
<widget class="QLineEdit" name="lineEditEmail">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>@mailchuck.com</string>
</property>
<property name="cursorPosition">
<number>0</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Email gateway alows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.</string>
<string>Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QRadioButton" name="radioButtonStatus">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Account status at email gateway</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QRadioButton" name="radioButtonSettings">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Change account settings at email gateway</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QRadioButton" name="radioButtonUnregister">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Unregister from email gateway</string>
</property>
@ -84,7 +116,10 @@
</widget>
<tabstops>
<tabstop>radioButtonRegister</tabstop>
<tabstop>lineEditEmailAddress</tabstop>
<tabstop>lineEditEmail</tabstop>
<tabstop>radioButtonStatus</tabstop>
<tabstop>radioButtonSettings</tabstop>
<tabstop>radioButtonUnregister</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
@ -124,7 +159,7 @@
<connection>
<sender>radioButtonRegister</sender>
<signal>clicked(bool)</signal>
<receiver>lineEditEmailAddress</receiver>
<receiver>lineEditEmail</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
@ -140,7 +175,7 @@
<connection>
<sender>radioButtonUnregister</sender>
<signal>clicked(bool)</signal>
<receiver>lineEditEmailAddress</receiver>
<receiver>lineEditEmail</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
@ -153,5 +188,37 @@
</hint>
</hints>
</connection>
<connection>
<sender>radioButtonStatus</sender>
<signal>clicked(bool)</signal>
<receiver>lineEditEmail</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
<connection>
<sender>radioButtonSettings</sender>
<signal>clicked(bool)</signal>
<receiver>lineEditEmail</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,12 +1,20 @@
from PyQt4 import QtCore, QtGui
from string import find, rfind, rstrip, lstrip
from tr import _translate
from bmconfigparser import BMConfigParser
from helper_sql import *
from utils import *
from settingsmixin import SettingsMixin
class AccountMixin (object):
# for pylupdate
_translate("MainWindow", "inbox")
_translate("MainWindow", "new")
_translate("MainWindow", "sent")
_translate("MainWindow", "trash")
class AccountMixin(object):
ALL = 0
NORMAL = 1
CHAN = 2
@ -97,7 +105,8 @@ class AccountMixin (object):
retval, = row
retval = unicode(retval, 'utf-8')
elif self.address is None or self.type == AccountMixin.ALL:
return unicode(str(QtGui.QApplication.translate("MainWindow", "All accounts")), 'utf-8')
return unicode(
str(_translate("MainWindow", "All accounts")), 'utf-8')
if retval is None:
return unicode(self.address, 'utf-8')
else:
@ -115,17 +124,16 @@ class Ui_FolderWidget(QtGui.QTreeWidgetItem, AccountMixin):
def setFolderName(self, fname):
self.folderName = str(fname)
def data(self, column, role):
if column == 0:
if role == QtCore.Qt.DisplayRole:
return QtGui.QApplication.translate("MainWindow", self.folderName) + (" (" + str(self.unreadCount) + ")" if self.unreadCount > 0 else "")
elif role == QtCore.Qt.EditRole:
return QtGui.QApplication.translate("MainWindow", self.folderName)
elif role == QtCore.Qt.ToolTipRole:
return QtGui.QApplication.translate("MainWindow", self.folderName)
elif role == QtCore.Qt.DecorationRole:
pass
return _translate("MainWindow", self.folderName) + (
" (" + str(self.unreadCount) + ")"
if self.unreadCount > 0 else ""
)
elif role in (QtCore.Qt.EditRole, QtCore.Qt.ToolTipRole):
return _translate("MainWindow", self.folderName)
elif role == QtCore.Qt.FontRole:
font = QtGui.QFont()
font.setBold(self.unreadCount > 0)
@ -166,16 +174,19 @@ class Ui_AddressWidget(QtGui.QTreeWidgetItem, AccountMixin, SettingsMixin):
self.setEnabled(enabled)
self.setUnreadCount(unreadCount)
self.setType()
def _getLabel(self):
if self.address is None:
return unicode(QtGui.QApplication.translate("MainWindow", "All accounts").toUtf8(), 'utf-8', 'ignore')
return unicode(_translate(
"MainWindow", "All accounts").toUtf8(), 'utf-8', 'ignore')
else:
try:
return unicode(BMConfigParser().get(self.address, 'label'), 'utf-8', 'ignore')
return unicode(
BMConfigParser().get(self.address, 'label'),
'utf-8', 'ignore')
except:
return unicode(self.address, 'utf-8')
def _getAddressBracket(self, unreadCount = False):
ret = ""
if unreadCount:

View File

@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'help.ui'
#
# Created: Wed Jan 14 22:42:39 2015
# by: PyQt4 UI code generator 4.9.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_helpDialog(object):
def setupUi(self, helpDialog):
helpDialog.setObjectName(_fromUtf8("helpDialog"))
helpDialog.resize(335, 96)
self.formLayout = QtGui.QFormLayout(helpDialog)
self.formLayout.setObjectName(_fromUtf8("formLayout"))
self.labelHelpURI = QtGui.QLabel(helpDialog)
self.labelHelpURI.setOpenExternalLinks(True)
self.labelHelpURI.setObjectName(_fromUtf8("labelHelpURI"))
self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.labelHelpURI)
self.label = QtGui.QLabel(helpDialog)
self.label.setWordWrap(True)
self.label.setObjectName(_fromUtf8("label"))
self.formLayout.setWidget(0, QtGui.QFormLayout.SpanningRole, self.label)
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.formLayout.setItem(2, QtGui.QFormLayout.LabelRole, spacerItem)
self.buttonBox = QtGui.QDialogButtonBox(helpDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.buttonBox)
self.retranslateUi(helpDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), helpDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), helpDialog.reject)
QtCore.QMetaObject.connectSlotsByName(helpDialog)
def retranslateUi(self, helpDialog):
helpDialog.setWindowTitle(QtGui.QApplication.translate("helpDialog", "Help", None, QtGui.QApplication.UnicodeUTF8))
self.labelHelpURI.setText(QtGui.QApplication.translate("helpDialog", "<a href=\"https://bitmessage.org/wiki/PyBitmessage_Help\">https://bitmessage.org/wiki/PyBitmessage_Help</a>", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("helpDialog", "As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki:", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -1,98 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'iconglossary.ui'
#
# Created: Thu Jun 13 20:15:48 2013
# by: PyQt4 UI code generator 4.10.1
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_iconGlossaryDialog(object):
def setupUi(self, iconGlossaryDialog):
iconGlossaryDialog.setObjectName(_fromUtf8("iconGlossaryDialog"))
iconGlossaryDialog.resize(424, 282)
self.gridLayout = QtGui.QGridLayout(iconGlossaryDialog)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.groupBox = QtGui.QGroupBox(iconGlossaryDialog)
self.groupBox.setObjectName(_fromUtf8("groupBox"))
self.gridLayout_2 = QtGui.QGridLayout(self.groupBox)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.label = QtGui.QLabel(self.groupBox)
self.label.setText(_fromUtf8(""))
self.label.setPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/redicon.png")))
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
self.label_2 = QtGui.QLabel(self.groupBox)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.gridLayout_2.addWidget(self.label_2, 0, 1, 1, 1)
self.label_3 = QtGui.QLabel(self.groupBox)
self.label_3.setText(_fromUtf8(""))
self.label_3.setPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/yellowicon.png")))
self.label_3.setObjectName(_fromUtf8("label_3"))
self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1)
self.label_4 = QtGui.QLabel(self.groupBox)
self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.label_4.setWordWrap(True)
self.label_4.setObjectName(_fromUtf8("label_4"))
self.gridLayout_2.addWidget(self.label_4, 1, 1, 2, 1)
spacerItem = QtGui.QSpacerItem(20, 73, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.gridLayout_2.addItem(spacerItem, 2, 0, 2, 1)
self.labelPortNumber = QtGui.QLabel(self.groupBox)
self.labelPortNumber.setObjectName(_fromUtf8("labelPortNumber"))
self.gridLayout_2.addWidget(self.labelPortNumber, 3, 1, 1, 1)
self.label_5 = QtGui.QLabel(self.groupBox)
self.label_5.setText(_fromUtf8(""))
self.label_5.setPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/greenicon.png")))
self.label_5.setObjectName(_fromUtf8("label_5"))
self.gridLayout_2.addWidget(self.label_5, 4, 0, 1, 1)
self.label_6 = QtGui.QLabel(self.groupBox)
self.label_6.setWordWrap(True)
self.label_6.setObjectName(_fromUtf8("label_6"))
self.gridLayout_2.addWidget(self.label_6, 4, 1, 1, 1)
self.gridLayout.addWidget(self.groupBox, 0, 0, 1, 1)
self.buttonBox = QtGui.QDialogButtonBox(iconGlossaryDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.gridLayout.addWidget(self.buttonBox, 1, 0, 1, 1)
self.retranslateUi(iconGlossaryDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), iconGlossaryDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), iconGlossaryDialog.reject)
QtCore.QMetaObject.connectSlotsByName(iconGlossaryDialog)
def retranslateUi(self, iconGlossaryDialog):
iconGlossaryDialog.setWindowTitle(_translate("iconGlossaryDialog", "Icon Glossary", None))
self.groupBox.setTitle(_translate("iconGlossaryDialog", "Icon Glossary", None))
self.label_2.setText(_translate("iconGlossaryDialog", "You have no connections with other peers. ", None))
self.label_4.setText(_translate("iconGlossaryDialog", "You have made at least one connection to a peer using an outgoing connection but you have not yet received any incoming connections. Your firewall or home router probably isn\'t configured to forward incoming TCP connections to your computer. Bitmessage will work just fine but it would help the Bitmessage network if you allowed for incoming connections and will help you be a better-connected node.", None))
self.labelPortNumber.setText(_translate("iconGlossaryDialog", "You are using TCP port ?. (This can be changed in the settings).", None))
self.label_6.setText(_translate("iconGlossaryDialog", "You do have connections with other peers and your firewall is correctly configured.", None))
import bitmessage_icons_rc
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
iconGlossaryDialog = QtGui.QDialog()
ui = Ui_iconGlossaryDialog()
ui.setupUi(iconGlossaryDialog)
iconGlossaryDialog.show()
sys.exit(app.exec_())

View File

@ -76,7 +76,7 @@
<item row="3" column="1">
<widget class="QLabel" name="labelPortNumber">
<property name="text">
<string>You are using TCP port ?. (This can be changed in the settings).</string>
<string notr="true">You are using TCP port ?. (This can be changed in the settings).</string>
</property>
</widget>
</item>

View File

@ -53,19 +53,24 @@ class MessageView(QtGui.QTextBrowser):
def confirmURL(self, link):
if link.scheme() == "mailto":
QtGui.QApplication.activeWindow().ui.lineEditTo.setText(link.path())
window = QtGui.QApplication.activeWindow()
window.ui.lineEditTo.setText(link.path())
if link.hasQueryItem("subject"):
QtGui.QApplication.activeWindow().ui.lineEditSubject.setText(link.queryItemValue("subject"))
window.ui.lineEditSubject.setText(
link.queryItemValue("subject"))
if link.hasQueryItem("body"):
QtGui.QApplication.activeWindow().ui.textEditMessage.setText(link.queryItemValue("body"))
QtGui.QApplication.activeWindow().setSendFromComboBox()
QtGui.QApplication.activeWindow().ui.tabWidgetSend.setCurrentIndex(0)
QtGui.QApplication.activeWindow().ui.tabWidget.setCurrentIndex(1)
QtGui.QApplication.activeWindow().ui.textEditMessage.setFocus()
window.ui.textEditMessage.setText(
link.queryItemValue("body"))
window.setSendFromComboBox()
window.ui.tabWidgetSend.setCurrentIndex(0)
window.ui.tabWidget.setCurrentIndex(
window.ui.tabWidget.indexOf(window.ui.send)
)
window.ui.textEditMessage.setFocus()
return
reply = QtGui.QMessageBox.warning(self,
QtGui.QApplication.translate("MessageView", "Follow external link"),
QtGui.QApplication.translate("MessageView", "The link \"%1\" will open in a browser. It may be a security risk, it could de-anonymise you or download malicious data. Are you sure?").arg(str(link.toString())),
QtGui.QApplication.translate("MessageView", "The link \"%1\" will open in a browser. It may be a security risk, it could de-anonymise you or download malicious data. Are you sure?").arg(unicode(link.toString())),
QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
if reply == QtGui.QMessageBox.Yes:
QtGui.QDesktopServices.openUrl(link)
@ -98,7 +103,7 @@ class MessageView(QtGui.QTextBrowser):
if self.mode == MessageView.MODE_HTML:
pos = self.out.find(">", self.outpos)
if pos > self.outpos:
self.outpos = pos
self.outpos = pos + 1
cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.MoveAnchor)
cursor.insertHtml(QtCore.QString(self.out[startpos:self.outpos]))
self.verticalScrollBar().setValue(position)

View File

@ -1,13 +1,17 @@
from PyQt4 import QtCore, QtGui
import time
import shared
from tr import _translate
from inventory import Inventory, PendingDownload, PendingUpload
from inventory import Inventory, PendingDownloadQueue, PendingUpload
import knownnodes
import l10n
import network.stats
from retranslateui import RetranslateMixin
from uisignaler import UISignaler
import widgets
import throttle
from network.connectionpool import BMConnectionPool
class NetworkStatus(QtGui.QWidget, RetranslateMixin):
@ -15,6 +19,13 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin):
super(NetworkStatus, self).__init__(parent)
widgets.load('networkstatus.ui', self)
header = self.tableWidgetConnectionCount.horizontalHeader()
header.setResizeMode(QtGui.QHeaderView.ResizeToContents)
# Somehow this value was 5 when I tested
if header.sortIndicatorSection() > 4:
header.setSortIndicator(0, QtCore.Qt.AscendingOrder)
self.startup = time.localtime()
self.labelStartupTime.setText(_translate("networkstatus", "Since startup on %1").arg(
l10n.formatTimestamp(self.startup)))
@ -27,11 +38,20 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin):
QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
"updateNumberOfBroadcastsProcessed()"), self.updateNumberOfBroadcastsProcessed)
QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL(
"updateNetworkStatusTab()"), self.updateNetworkStatusTab)
"updateNetworkStatusTab(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.updateNetworkStatusTab)
self.timer = QtCore.QTimer()
self.timer.start(2000) # milliseconds
QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.runEveryTwoSeconds)
QtCore.QObject.connect(
self.timer, QtCore.SIGNAL("timeout()"), self.runEveryTwoSeconds)
def startUpdate(self):
Inventory().numberOfInventoryLookupsPerformed = 0
self.runEveryTwoSeconds()
self.timer.start(2000) # milliseconds
def stopUpdate(self):
self.timer.stop()
def formatBytes(self, num):
for x in [_translate("networkstatus", "byte(s)", None, QtCore.QCoreApplication.CodecForTr, num), "kB", "MB", "GB"]:
@ -45,7 +65,7 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin):
return "%4.0f kB" % num
def updateNumberOfObjectsToBeSynced(self):
self.labelSyncStatus.setText(_translate("networkstatus", "Object(s) to be synced: %n", None, QtCore.QCoreApplication.CodecForTr, PendingDownload().len() + PendingUpload().len()))
self.labelSyncStatus.setText(_translate("networkstatus", "Object(s) to be synced: %n", None, QtCore.QCoreApplication.CodecForTr, network.stats.pendingDownload() + network.stats.pendingUpload()))
def updateNumberOfMessagesProcessed(self):
self.updateNumberOfObjectsToBeSynced()
@ -68,55 +88,74 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin):
sent and received by 2.
"""
self.labelBytesRecvCount.setText(_translate(
"networkstatus", "Down: %1/s Total: %2").arg(self.formatByteRate(throttle.ReceiveThrottle().getSpeed()), self.formatBytes(throttle.ReceiveThrottle().total)))
"networkstatus", "Down: %1/s Total: %2").arg(self.formatByteRate(network.stats.downloadSpeed()), self.formatBytes(network.stats.receivedBytes())))
self.labelBytesSentCount.setText(_translate(
"networkstatus", "Up: %1/s Total: %2").arg(self.formatByteRate(throttle.SendThrottle().getSpeed()), self.formatBytes(throttle.SendThrottle().total)))
"networkstatus", "Up: %1/s Total: %2").arg(self.formatByteRate(network.stats.uploadSpeed()), self.formatBytes(network.stats.sentBytes())))
def updateNetworkStatusTab(self):
totalNumberOfConnectionsFromAllStreams = 0 # One would think we could use len(sendDataQueues) for this but the number doesn't always match: just because we have a sendDataThread running doesn't mean that the connection has been fully established (with the exchange of version messages).
streamNumberTotals = {}
for host, streamNumber in shared.connectedHostsList.items():
if not streamNumber in streamNumberTotals:
streamNumberTotals[streamNumber] = 1
else:
streamNumberTotals[streamNumber] += 1
def updateNetworkStatusTab(self, outbound, add, destination):
if outbound:
try:
c = BMConnectionPool().outboundConnections[destination]
except KeyError:
if add:
return
else:
try:
c = BMConnectionPool().inboundConnections[destination]
except KeyError:
try:
c = BMConnectionPool().inboundConnections[destination.host]
except KeyError:
if add:
return
while self.tableWidgetConnectionCount.rowCount() > 0:
self.tableWidgetConnectionCount.removeRow(0)
for streamNumber, connectionCount in streamNumberTotals.items():
self.tableWidgetConnectionCount.setUpdatesEnabled(False)
self.tableWidgetConnectionCount.setSortingEnabled(False)
if add:
self.tableWidgetConnectionCount.insertRow(0)
if streamNumber == 0:
newItem = QtGui.QTableWidgetItem("?")
self.tableWidgetConnectionCount.setItem(0, 0,
QtGui.QTableWidgetItem("%s:%i" % (destination.host, destination.port))
)
self.tableWidgetConnectionCount.setItem(0, 2,
QtGui.QTableWidgetItem("%s" % (c.userAgent))
)
self.tableWidgetConnectionCount.setItem(0, 3,
QtGui.QTableWidgetItem("%s" % (c.tlsVersion))
)
self.tableWidgetConnectionCount.setItem(0, 4,
QtGui.QTableWidgetItem("%s" % (",".join(map(str,c.streams))))
)
try:
# FIXME hard coded stream no
rating = "%.1f" % (knownnodes.knownNodes[1][destination]['rating'])
except KeyError:
rating = "-"
self.tableWidgetConnectionCount.setItem(0, 1,
QtGui.QTableWidgetItem("%s" % (rating))
)
if outbound:
brush = QtGui.QBrush(QtGui.QColor("yellow"), QtCore.Qt.SolidPattern)
else:
newItem = QtGui.QTableWidgetItem(str(streamNumber))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.tableWidgetConnectionCount.setItem(0, 0, newItem)
newItem = QtGui.QTableWidgetItem(str(connectionCount))
newItem.setFlags(
QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.tableWidgetConnectionCount.setItem(0, 1, newItem)
"""for currentRow in range(self.tableWidgetConnectionCount.rowCount()):
rowStreamNumber = int(self.tableWidgetConnectionCount.item(currentRow,0).text())
if streamNumber == rowStreamNumber:
foundTheRowThatNeedsUpdating = True
self.tableWidgetConnectionCount.item(currentRow,1).setText(str(connectionCount))
#totalNumberOfConnectionsFromAllStreams += connectionCount
if foundTheRowThatNeedsUpdating == False:
#Add a line to the table for this stream number and update its count with the current connection count.
self.tableWidgetConnectionCount.insertRow(0)
newItem = QtGui.QTableWidgetItem(str(streamNumber))
newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled )
self.tableWidgetConnectionCount.setItem(0,0,newItem)
newItem = QtGui.QTableWidgetItem(str(connectionCount))
newItem.setFlags( QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled )
self.tableWidgetConnectionCount.setItem(0,1,newItem)
totalNumberOfConnectionsFromAllStreams += connectionCount"""
brush = QtGui.QBrush(QtGui.QColor("green"), QtCore.Qt.SolidPattern)
for j in (range(1)):
self.tableWidgetConnectionCount.item(0, j).setBackground(brush)
self.tableWidgetConnectionCount.item(0, 0).setData(QtCore.Qt.UserRole, destination)
self.tableWidgetConnectionCount.item(0, 1).setData(QtCore.Qt.UserRole, outbound)
else:
for i in range(self.tableWidgetConnectionCount.rowCount()):
if self.tableWidgetConnectionCount.item(i, 0).data(QtCore.Qt.UserRole).toPyObject() != destination:
continue
if self.tableWidgetConnectionCount.item(i, 1).data(QtCore.Qt.UserRole).toPyObject() == outbound:
self.tableWidgetConnectionCount.removeRow(i)
break
self.tableWidgetConnectionCount.setUpdatesEnabled(True)
self.tableWidgetConnectionCount.setSortingEnabled(True)
self.labelTotalConnections.setText(_translate(
"networkstatus", "Total Connections: %1").arg(str(len(shared.connectedHostsList))))
if len(shared.connectedHostsList) > 0 and shared.statusIconColor == 'red': # FYI: The 'singlelistener' thread sets the icon color to green when it receives an incoming connection, meaning that the user's firewall is configured correctly.
"networkstatus", "Total Connections: %1").arg(str(self.tableWidgetConnectionCount.rowCount())))
# FYI: The 'singlelistener' thread sets the icon color to green when it receives an incoming connection, meaning that the user's firewall is configured correctly.
if self.tableWidgetConnectionCount.rowCount() and shared.statusIconColor == 'red':
self.window().setStatusIcon('yellow')
elif len(shared.connectedHostsList) == 0:
elif self.tableWidgetConnectionCount.rowCount() == 0 and shared.statusIconColor != "red":
self.window().setStatusIcon('red')
# timer driven
@ -128,6 +167,6 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin):
self.updateNumberOfObjectsToBeSynced()
def retranslateUi(self):
super(QtGui.QWidget, self).retranslateUi()
super(NetworkStatus, self).retranslateUi()
self.labelStartupTime.setText(_translate("networkstatus", "Since startup on %1").arg(
l10n.formatTimestamp(self.startup)))

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>602</width>
<height>252</height>
<height>254</height>
</rect>
</property>
<property name="sizePolicy">
@ -18,12 +18,12 @@
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
<property name="spacing">
<number>20</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
<enum>QLayout::SetNoConstraint</enum>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
@ -31,7 +31,7 @@
<number>20</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QLabel" name="labelTotalConnections">
@ -85,17 +85,26 @@
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
@ -108,12 +117,42 @@
</attribute>
<column>
<property name="text">
<string>Stream #</string>
<string>Peer</string>
</property>
<property name="toolTip">
<string>IP address or hostname</string>
</property>
</column>
<column>
<property name="text">
<string>Connections</string>
<string>Rating</string>
</property>
<property name="toolTip">
<string>PyBitmessage tracks the success rate of connection attempts to individual nodes. The rating ranges from -1 to 1 and affects the likelihood of selecting the node in the future</string>
</property>
</column>
<column>
<property name="text">
<string>User agent</string>
</property>
<property name="toolTip">
<string>Peer's self-reported software</string>
</property>
</column>
<column>
<property name="text">
<string>TLS</string>
</property>
<property name="toolTip">
<string>Connection encryption</string>
</property>
</column>
<column>
<property name="text">
<string>Stream #</string>
</property>
<property name="toolTip">
<string>List of streams negotiated between you and the peer</string>
</property>
</column>
</widget>
@ -125,6 +164,9 @@
<property name="spacing">
<number>4</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item>
<widget class="QLabel" name="labelStartupTime">
<property name="sizePolicy">

View File

@ -1,193 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'newaddressdialog.ui'
#
# Created: Sun Sep 15 23:53:31 2013
# by: PyQt4 UI code generator 4.10.2
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_NewAddressDialog(object):
def setupUi(self, NewAddressDialog):
NewAddressDialog.setObjectName(_fromUtf8("NewAddressDialog"))
NewAddressDialog.resize(723, 704)
self.formLayout = QtGui.QFormLayout(NewAddressDialog)
self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow)
self.formLayout.setObjectName(_fromUtf8("formLayout"))
self.label = QtGui.QLabel(NewAddressDialog)
self.label.setAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft)
self.label.setWordWrap(True)
self.label.setObjectName(_fromUtf8("label"))
self.formLayout.setWidget(0, QtGui.QFormLayout.SpanningRole, self.label)
self.label_5 = QtGui.QLabel(NewAddressDialog)
self.label_5.setWordWrap(True)
self.label_5.setObjectName(_fromUtf8("label_5"))
self.formLayout.setWidget(2, QtGui.QFormLayout.SpanningRole, self.label_5)
self.line = QtGui.QFrame(NewAddressDialog)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.line.sizePolicy().hasHeightForWidth())
self.line.setSizePolicy(sizePolicy)
self.line.setMinimumSize(QtCore.QSize(100, 2))
self.line.setFrameShape(QtGui.QFrame.HLine)
self.line.setFrameShadow(QtGui.QFrame.Sunken)
self.line.setObjectName(_fromUtf8("line"))
self.formLayout.setWidget(4, QtGui.QFormLayout.SpanningRole, self.line)
self.radioButtonRandomAddress = QtGui.QRadioButton(NewAddressDialog)
self.radioButtonRandomAddress.setChecked(True)
self.radioButtonRandomAddress.setObjectName(_fromUtf8("radioButtonRandomAddress"))
self.buttonGroup = QtGui.QButtonGroup(NewAddressDialog)
self.buttonGroup.setObjectName(_fromUtf8("buttonGroup"))
self.buttonGroup.addButton(self.radioButtonRandomAddress)
self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.radioButtonRandomAddress)
self.radioButtonDeterministicAddress = QtGui.QRadioButton(NewAddressDialog)
self.radioButtonDeterministicAddress.setObjectName(_fromUtf8("radioButtonDeterministicAddress"))
self.buttonGroup.addButton(self.radioButtonDeterministicAddress)
self.formLayout.setWidget(6, QtGui.QFormLayout.LabelRole, self.radioButtonDeterministicAddress)
self.checkBoxEighteenByteRipe = QtGui.QCheckBox(NewAddressDialog)
self.checkBoxEighteenByteRipe.setObjectName(_fromUtf8("checkBoxEighteenByteRipe"))
self.formLayout.setWidget(9, QtGui.QFormLayout.SpanningRole, self.checkBoxEighteenByteRipe)
self.groupBoxDeterministic = QtGui.QGroupBox(NewAddressDialog)
self.groupBoxDeterministic.setObjectName(_fromUtf8("groupBoxDeterministic"))
self.gridLayout = QtGui.QGridLayout(self.groupBoxDeterministic)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.label_9 = QtGui.QLabel(self.groupBoxDeterministic)
self.label_9.setObjectName(_fromUtf8("label_9"))
self.gridLayout.addWidget(self.label_9, 6, 0, 1, 1)
self.label_8 = QtGui.QLabel(self.groupBoxDeterministic)
self.label_8.setObjectName(_fromUtf8("label_8"))
self.gridLayout.addWidget(self.label_8, 5, 0, 1, 3)
self.spinBoxNumberOfAddressesToMake = QtGui.QSpinBox(self.groupBoxDeterministic)
self.spinBoxNumberOfAddressesToMake.setMinimum(1)
self.spinBoxNumberOfAddressesToMake.setProperty("value", 8)
self.spinBoxNumberOfAddressesToMake.setObjectName(_fromUtf8("spinBoxNumberOfAddressesToMake"))
self.gridLayout.addWidget(self.spinBoxNumberOfAddressesToMake, 4, 3, 1, 1)
self.label_6 = QtGui.QLabel(self.groupBoxDeterministic)
self.label_6.setObjectName(_fromUtf8("label_6"))
self.gridLayout.addWidget(self.label_6, 0, 0, 1, 1)
self.label_11 = QtGui.QLabel(self.groupBoxDeterministic)
self.label_11.setObjectName(_fromUtf8("label_11"))
self.gridLayout.addWidget(self.label_11, 4, 0, 1, 3)
spacerItem = QtGui.QSpacerItem(73, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem, 6, 1, 1, 1)
self.label_10 = QtGui.QLabel(self.groupBoxDeterministic)
self.label_10.setObjectName(_fromUtf8("label_10"))
self.gridLayout.addWidget(self.label_10, 6, 2, 1, 1)
spacerItem1 = QtGui.QSpacerItem(42, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem1, 6, 3, 1, 1)
self.label_7 = QtGui.QLabel(self.groupBoxDeterministic)
self.label_7.setObjectName(_fromUtf8("label_7"))
self.gridLayout.addWidget(self.label_7, 2, 0, 1, 1)
self.lineEditPassphraseAgain = QtGui.QLineEdit(self.groupBoxDeterministic)
self.lineEditPassphraseAgain.setEchoMode(QtGui.QLineEdit.Password)
self.lineEditPassphraseAgain.setObjectName(_fromUtf8("lineEditPassphraseAgain"))
self.gridLayout.addWidget(self.lineEditPassphraseAgain, 3, 0, 1, 4)
self.lineEditPassphrase = QtGui.QLineEdit(self.groupBoxDeterministic)
self.lineEditPassphrase.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText)
self.lineEditPassphrase.setEchoMode(QtGui.QLineEdit.Password)
self.lineEditPassphrase.setObjectName(_fromUtf8("lineEditPassphrase"))
self.gridLayout.addWidget(self.lineEditPassphrase, 1, 0, 1, 4)
self.formLayout.setWidget(8, QtGui.QFormLayout.LabelRole, self.groupBoxDeterministic)
self.groupBox = QtGui.QGroupBox(NewAddressDialog)
self.groupBox.setObjectName(_fromUtf8("groupBox"))
self.gridLayout_2 = QtGui.QGridLayout(self.groupBox)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.label_2 = QtGui.QLabel(self.groupBox)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 2)
self.newaddresslabel = QtGui.QLineEdit(self.groupBox)
self.newaddresslabel.setObjectName(_fromUtf8("newaddresslabel"))
self.gridLayout_2.addWidget(self.newaddresslabel, 1, 0, 1, 2)
self.radioButtonMostAvailable = QtGui.QRadioButton(self.groupBox)
self.radioButtonMostAvailable.setChecked(True)
self.radioButtonMostAvailable.setObjectName(_fromUtf8("radioButtonMostAvailable"))
self.gridLayout_2.addWidget(self.radioButtonMostAvailable, 2, 0, 1, 2)
self.label_3 = QtGui.QLabel(self.groupBox)
self.label_3.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.label_3.setObjectName(_fromUtf8("label_3"))
self.gridLayout_2.addWidget(self.label_3, 3, 1, 1, 1)
self.radioButtonExisting = QtGui.QRadioButton(self.groupBox)
self.radioButtonExisting.setChecked(False)
self.radioButtonExisting.setObjectName(_fromUtf8("radioButtonExisting"))
self.gridLayout_2.addWidget(self.radioButtonExisting, 4, 0, 1, 2)
self.label_4 = QtGui.QLabel(self.groupBox)
self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.label_4.setObjectName(_fromUtf8("label_4"))
self.gridLayout_2.addWidget(self.label_4, 5, 1, 1, 1)
spacerItem2 = QtGui.QSpacerItem(13, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout_2.addItem(spacerItem2, 6, 0, 1, 1)
self.comboBoxExisting = QtGui.QComboBox(self.groupBox)
self.comboBoxExisting.setEnabled(False)
self.comboBoxExisting.setEditable(True)
self.comboBoxExisting.setObjectName(_fromUtf8("comboBoxExisting"))
self.gridLayout_2.addWidget(self.comboBoxExisting, 6, 1, 1, 1)
self.formLayout.setWidget(7, QtGui.QFormLayout.LabelRole, self.groupBox)
self.buttonBox = QtGui.QDialogButtonBox(NewAddressDialog)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.MinimumExpanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth())
self.buttonBox.setSizePolicy(sizePolicy)
self.buttonBox.setMinimumSize(QtCore.QSize(160, 0))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.formLayout.setWidget(10, QtGui.QFormLayout.SpanningRole, self.buttonBox)
self.retranslateUi(NewAddressDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), NewAddressDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), NewAddressDialog.reject)
QtCore.QObject.connect(self.radioButtonExisting, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.comboBoxExisting.setEnabled)
QtCore.QObject.connect(self.radioButtonDeterministicAddress, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBoxDeterministic.setShown)
QtCore.QObject.connect(self.radioButtonRandomAddress, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBox.setShown)
QtCore.QMetaObject.connectSlotsByName(NewAddressDialog)
NewAddressDialog.setTabOrder(self.radioButtonRandomAddress, self.radioButtonDeterministicAddress)
NewAddressDialog.setTabOrder(self.radioButtonDeterministicAddress, self.newaddresslabel)
NewAddressDialog.setTabOrder(self.newaddresslabel, self.radioButtonMostAvailable)
NewAddressDialog.setTabOrder(self.radioButtonMostAvailable, self.radioButtonExisting)
NewAddressDialog.setTabOrder(self.radioButtonExisting, self.comboBoxExisting)
NewAddressDialog.setTabOrder(self.comboBoxExisting, self.lineEditPassphrase)
NewAddressDialog.setTabOrder(self.lineEditPassphrase, self.lineEditPassphraseAgain)
NewAddressDialog.setTabOrder(self.lineEditPassphraseAgain, self.spinBoxNumberOfAddressesToMake)
NewAddressDialog.setTabOrder(self.spinBoxNumberOfAddressesToMake, self.checkBoxEighteenByteRipe)
NewAddressDialog.setTabOrder(self.checkBoxEighteenByteRipe, self.buttonBox)
def retranslateUi(self, NewAddressDialog):
NewAddressDialog.setWindowTitle(_translate("NewAddressDialog", "Create new Address", None))
self.label.setText(_translate("NewAddressDialog", "Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged. You may generate addresses by using either random numbers or by using a passphrase. If you use a passphrase, the address is called a \"deterministic\" address.\n"
"The \'Random Number\' option is selected by default but deterministic addresses have several pros and cons:", None))
self.label_5.setText(_translate("NewAddressDialog", "<html><head/><body><p><span style=\" font-weight:600;\">Pros:<br/></span>You can recreate your addresses on any computer from memory. <br/>You need-not worry about backing up your keys.dat file as long as you can remember your passphrase. <br/><span style=\" font-weight:600;\">Cons:<br/></span>You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost. <br/>You must remember the address version number and the stream number along with your passphrase. <br/>If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you.</p></body></html>", None))
self.radioButtonRandomAddress.setText(_translate("NewAddressDialog", "Use a random number generator to make an address", None))
self.radioButtonDeterministicAddress.setText(_translate("NewAddressDialog", "Use a passphrase to make addresses", None))
self.checkBoxEighteenByteRipe.setText(_translate("NewAddressDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None))
self.groupBoxDeterministic.setTitle(_translate("NewAddressDialog", "Make deterministic addresses", None))
self.label_9.setText(_translate("NewAddressDialog", "Address version number: 4", None))
self.label_8.setText(_translate("NewAddressDialog", "In addition to your passphrase, you must remember these numbers:", None))
self.label_6.setText(_translate("NewAddressDialog", "Passphrase", None))
self.label_11.setText(_translate("NewAddressDialog", "Number of addresses to make based on your passphrase:", None))
self.label_10.setText(_translate("NewAddressDialog", "Stream number: 1", None))
self.label_7.setText(_translate("NewAddressDialog", "Retype passphrase", None))
self.groupBox.setTitle(_translate("NewAddressDialog", "Randomly generate address", None))
self.label_2.setText(_translate("NewAddressDialog", "Label (not shown to anyone except you)", None))
self.radioButtonMostAvailable.setText(_translate("NewAddressDialog", "Use the most available stream", None))
self.label_3.setText(_translate("NewAddressDialog", " (best if this is the first of many addresses you will create)", None))
self.radioButtonExisting.setText(_translate("NewAddressDialog", "Use the same stream as an existing address", None))
self.label_4.setText(_translate("NewAddressDialog", "(saves you some bandwidth and processing power)", None))

View File

@ -15,8 +15,6 @@ class NewChanDialog(QtGui.QDialog, RetranslateMixin):
self.parent = parent
self.chanAddress.setValidator(AddressValidator(self.chanAddress, self.chanPassPhrase, self.validatorFeedback, self.buttonBox, False))
self.chanPassPhrase.setValidator(PassPhraseValidator(self.chanPassPhrase, self.chanAddress, self.validatorFeedback, self.buttonBox, False))
QtCore.QObject.connect(self.chanAddress, QtCore.SIGNAL('textEdited()'), self.chanAddress.validator(), QtCore.SLOT('checkData(self)'))
QtCore.QObject.connect(self.chanPassPhrase, QtCore.SIGNAL('textEdited()'), self.chanPassPhrase.validator(), QtCore.SLOT('checkData(self)'))
self.timer = QtCore.QTimer()
QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.delayedUpdateStatus)
@ -37,8 +35,10 @@ class NewChanDialog(QtGui.QDialog, RetranslateMixin):
addressGeneratorQueue.put(('joinChan', addBMIfNotPresent(self.chanAddress.text().toUtf8()), str_chan + ' ' + str(self.chanPassPhrase.text().toUtf8()), self.chanPassPhrase.text().toUtf8(), True))
addressGeneratorReturnValue = apiAddressGeneratorReturnQueue.get(True)
if len(addressGeneratorReturnValue) > 0 and addressGeneratorReturnValue[0] != 'chan name does not match address':
UISignalQueue.put(('updateStatusBar', _translate("newchandialog", "Successfully created / joined chan %1").arg(str(self.chanPassPhrase.text().toUtf8()))))
self.parent.ui.tabWidget.setCurrentIndex(3)
UISignalQueue.put(('updateStatusBar', _translate("newchandialog", "Successfully created / joined chan %1").arg(unicode(self.chanPassPhrase.text()))))
self.parent.ui.tabWidget.setCurrentIndex(
self.parent.ui.tabWidget.indexOf(self.parent.ui.chans)
)
self.done(QtGui.QDialog.Accepted)
else:
UISignalQueue.put(('updateStatusBar', _translate("newchandialog", "Chan creation / joining failed")))

View File

@ -53,7 +53,7 @@
<item row="0" column="0">
<widget class="QLabel" name="chanPassPhraseLabel">
<property name="text">
<string>Chan passhphrase/name:</string>
<string>Chan passphrase/name:</string>
</property>
</widget>
</item>

View File

@ -1,69 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'newsubscriptiondialog.ui'
#
# Created: Sat Nov 30 21:53:38 2013
# by: PyQt4 UI code generator 4.10.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_NewSubscriptionDialog(object):
def setupUi(self, NewSubscriptionDialog):
NewSubscriptionDialog.setObjectName(_fromUtf8("NewSubscriptionDialog"))
NewSubscriptionDialog.resize(368, 173)
self.formLayout = QtGui.QFormLayout(NewSubscriptionDialog)
self.formLayout.setObjectName(_fromUtf8("formLayout"))
self.label_2 = QtGui.QLabel(NewSubscriptionDialog)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.label_2)
self.newsubscriptionlabel = QtGui.QLineEdit(NewSubscriptionDialog)
self.newsubscriptionlabel.setObjectName(_fromUtf8("newsubscriptionlabel"))
self.formLayout.setWidget(1, QtGui.QFormLayout.SpanningRole, self.newsubscriptionlabel)
self.label = QtGui.QLabel(NewSubscriptionDialog)
self.label.setObjectName(_fromUtf8("label"))
self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.label)
self.lineEditSubscriptionAddress = QtGui.QLineEdit(NewSubscriptionDialog)
self.lineEditSubscriptionAddress.setObjectName(_fromUtf8("lineEditSubscriptionAddress"))
self.formLayout.setWidget(3, QtGui.QFormLayout.SpanningRole, self.lineEditSubscriptionAddress)
self.labelAddressCheck = QtGui.QLabel(NewSubscriptionDialog)
self.labelAddressCheck.setText(_fromUtf8(""))
self.labelAddressCheck.setWordWrap(True)
self.labelAddressCheck.setObjectName(_fromUtf8("labelAddressCheck"))
self.formLayout.setWidget(4, QtGui.QFormLayout.SpanningRole, self.labelAddressCheck)
self.checkBoxDisplayMessagesAlreadyInInventory = QtGui.QCheckBox(NewSubscriptionDialog)
self.checkBoxDisplayMessagesAlreadyInInventory.setEnabled(False)
self.checkBoxDisplayMessagesAlreadyInInventory.setObjectName(_fromUtf8("checkBoxDisplayMessagesAlreadyInInventory"))
self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.checkBoxDisplayMessagesAlreadyInInventory)
self.buttonBox = QtGui.QDialogButtonBox(NewSubscriptionDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.formLayout.setWidget(6, QtGui.QFormLayout.FieldRole, self.buttonBox)
self.retranslateUi(NewSubscriptionDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), NewSubscriptionDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), NewSubscriptionDialog.reject)
QtCore.QMetaObject.connectSlotsByName(NewSubscriptionDialog)
def retranslateUi(self, NewSubscriptionDialog):
NewSubscriptionDialog.setWindowTitle(_translate("NewSubscriptionDialog", "Add new entry", None))
self.label_2.setText(_translate("NewSubscriptionDialog", "Label", None))
self.label.setText(_translate("NewSubscriptionDialog", "Address", None))
self.checkBoxDisplayMessagesAlreadyInInventory.setText(_translate("NewSubscriptionDialog", "Enter an address above.", None))

View File

@ -7,9 +7,15 @@
<x>0</x>
<y>0</y>
<width>368</width>
<height>173</height>
<height>254</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>368</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>Add new entry</string>
</property>
@ -22,7 +28,7 @@
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLineEdit" name="newsubscriptionlabel"/>
<widget class="QLineEdit" name="lineEditLabel"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
@ -32,7 +38,7 @@
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLineEdit" name="lineEditSubscriptionAddress"/>
<widget class="QLineEdit" name="lineEditAddress"/>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="labelAddressCheck">
@ -44,17 +50,17 @@
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="checkBoxDisplayMessagesAlreadyInInventory">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>CheckBox</string>
<string>Enter an address above.</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="7" column="1">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -64,6 +70,19 @@
</property>
</widget>
</item>
<item row="5" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>

View File

@ -1,124 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'regenerateaddresses.ui'
#
# Created: Sun Sep 15 23:50:23 2013
# by: PyQt4 UI code generator 4.10.2
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_regenerateAddressesDialog(object):
def setupUi(self, regenerateAddressesDialog):
regenerateAddressesDialog.setObjectName(_fromUtf8("regenerateAddressesDialog"))
regenerateAddressesDialog.resize(532, 332)
self.gridLayout_2 = QtGui.QGridLayout(regenerateAddressesDialog)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
self.buttonBox = QtGui.QDialogButtonBox(regenerateAddressesDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.gridLayout_2.addWidget(self.buttonBox, 1, 0, 1, 1)
self.groupBox = QtGui.QGroupBox(regenerateAddressesDialog)
self.groupBox.setObjectName(_fromUtf8("groupBox"))
self.gridLayout = QtGui.QGridLayout(self.groupBox)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.label_6 = QtGui.QLabel(self.groupBox)
self.label_6.setObjectName(_fromUtf8("label_6"))
self.gridLayout.addWidget(self.label_6, 1, 0, 1, 1)
self.lineEditPassphrase = QtGui.QLineEdit(self.groupBox)
self.lineEditPassphrase.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText)
self.lineEditPassphrase.setEchoMode(QtGui.QLineEdit.Password)
self.lineEditPassphrase.setObjectName(_fromUtf8("lineEditPassphrase"))
self.gridLayout.addWidget(self.lineEditPassphrase, 2, 0, 1, 5)
self.label_11 = QtGui.QLabel(self.groupBox)
self.label_11.setObjectName(_fromUtf8("label_11"))
self.gridLayout.addWidget(self.label_11, 3, 0, 1, 3)
self.spinBoxNumberOfAddressesToMake = QtGui.QSpinBox(self.groupBox)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.spinBoxNumberOfAddressesToMake.sizePolicy().hasHeightForWidth())
self.spinBoxNumberOfAddressesToMake.setSizePolicy(sizePolicy)
self.spinBoxNumberOfAddressesToMake.setMinimum(1)
self.spinBoxNumberOfAddressesToMake.setProperty("value", 8)
self.spinBoxNumberOfAddressesToMake.setObjectName(_fromUtf8("spinBoxNumberOfAddressesToMake"))
self.gridLayout.addWidget(self.spinBoxNumberOfAddressesToMake, 3, 3, 1, 1)
spacerItem = QtGui.QSpacerItem(132, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem, 3, 4, 1, 1)
self.label_2 = QtGui.QLabel(self.groupBox)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.gridLayout.addWidget(self.label_2, 4, 0, 1, 1)
self.lineEditAddressVersionNumber = QtGui.QLineEdit(self.groupBox)
self.lineEditAddressVersionNumber.setEnabled(True)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lineEditAddressVersionNumber.sizePolicy().hasHeightForWidth())
self.lineEditAddressVersionNumber.setSizePolicy(sizePolicy)
self.lineEditAddressVersionNumber.setMaximumSize(QtCore.QSize(31, 16777215))
self.lineEditAddressVersionNumber.setText(_fromUtf8(""))
self.lineEditAddressVersionNumber.setObjectName(_fromUtf8("lineEditAddressVersionNumber"))
self.gridLayout.addWidget(self.lineEditAddressVersionNumber, 4, 1, 1, 1)
spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem1, 4, 2, 1, 1)
self.label_3 = QtGui.QLabel(self.groupBox)
self.label_3.setObjectName(_fromUtf8("label_3"))
self.gridLayout.addWidget(self.label_3, 5, 0, 1, 1)
self.lineEditStreamNumber = QtGui.QLineEdit(self.groupBox)
self.lineEditStreamNumber.setEnabled(False)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.lineEditStreamNumber.sizePolicy().hasHeightForWidth())
self.lineEditStreamNumber.setSizePolicy(sizePolicy)
self.lineEditStreamNumber.setMaximumSize(QtCore.QSize(31, 16777215))
self.lineEditStreamNumber.setObjectName(_fromUtf8("lineEditStreamNumber"))
self.gridLayout.addWidget(self.lineEditStreamNumber, 5, 1, 1, 1)
spacerItem2 = QtGui.QSpacerItem(325, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem2, 5, 2, 1, 3)
self.checkBoxEighteenByteRipe = QtGui.QCheckBox(self.groupBox)
self.checkBoxEighteenByteRipe.setObjectName(_fromUtf8("checkBoxEighteenByteRipe"))
self.gridLayout.addWidget(self.checkBoxEighteenByteRipe, 6, 0, 1, 5)
self.label_4 = QtGui.QLabel(self.groupBox)
self.label_4.setWordWrap(True)
self.label_4.setObjectName(_fromUtf8("label_4"))
self.gridLayout.addWidget(self.label_4, 7, 0, 1, 5)
self.label = QtGui.QLabel(self.groupBox)
self.label.setWordWrap(True)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout.addWidget(self.label, 0, 0, 1, 5)
self.gridLayout_2.addWidget(self.groupBox, 0, 0, 1, 1)
self.retranslateUi(regenerateAddressesDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), regenerateAddressesDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), regenerateAddressesDialog.reject)
QtCore.QMetaObject.connectSlotsByName(regenerateAddressesDialog)
def retranslateUi(self, regenerateAddressesDialog):
regenerateAddressesDialog.setWindowTitle(_translate("regenerateAddressesDialog", "Regenerate Existing Addresses", None))
self.groupBox.setTitle(_translate("regenerateAddressesDialog", "Regenerate existing addresses", None))
self.label_6.setText(_translate("regenerateAddressesDialog", "Passphrase", None))
self.label_11.setText(_translate("regenerateAddressesDialog", "Number of addresses to make based on your passphrase:", None))
self.label_2.setText(_translate("regenerateAddressesDialog", "Address version number:", None))
self.label_3.setText(_translate("regenerateAddressesDialog", "Stream number:", None))
self.lineEditStreamNumber.setText(_translate("regenerateAddressesDialog", "1", None))
self.checkBoxEighteenByteRipe.setText(_translate("regenerateAddressesDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None))
self.label_4.setText(_translate("regenerateAddressesDialog", "You must check (or not check) this box just like you did (or didn\'t) when you made your addresses the first time.", None))
self.label.setText(_translate("regenerateAddressesDialog", "If you have previously made deterministic addresses but lost them due to an accident (like hard drive failure), you can regenerate them here. If you used the random number generator to make your addresses then this form will be of no use to you.", None))

View File

@ -22,7 +22,8 @@ class SafeHTMLParser(HTMLParser):
replaces_pre = [["&", "&amp;"], ["\"", "&quot;"], ["<", "&lt;"], [">", "&gt;"]]
replaces_post = [["\n", "<br/>"], ["\t", "&nbsp;&nbsp;&nbsp;&nbsp;"], [" ", "&nbsp; "], [" ", "&nbsp; "], ["<br/> ", "<br/>&nbsp;"]]
src_schemes = [ "data" ]
uriregex1 = re.compile(r'(?i)\b((?:(https?|ftp|bitcoin):(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?]))')
#uriregex1 = re.compile(r'(?i)\b((?:(https?|ftp|bitcoin):(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?]))')
uriregex1 = re.compile(r'((https?|ftp|bitcoin):(?:/{1,3}|[a-z0-9%])(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
uriregex2 = re.compile(r'<a href="([^"]+)&amp;')
emailregex = re.compile(r'\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b')
@ -53,20 +54,20 @@ class SafeHTMLParser(HTMLParser):
self.allow_external_src = False
def add_if_acceptable(self, tag, attrs = None):
if not tag in SafeHTMLParser.acceptable_elements:
if tag not in SafeHTMLParser.acceptable_elements:
return
self.sanitised += "<"
if inspect.stack()[1][3] == "handle_endtag":
self.sanitised += "/"
self.sanitised += tag
if not attrs is None:
if attrs is not None:
for attr, val in attrs:
if tag == "img" and attr == "src" and not self.allow_picture:
val = ""
elif attr == "src" and not self.allow_external_src:
url = urlparse(val)
if url.scheme not in SafeHTMLParser.src_schemes:
val == ""
val = ""
self.sanitised += " " + quote_plus(attr)
if not (val is None):
self.sanitised += "=\"" + val + "\""

View File

@ -165,7 +165,7 @@ class Ui_settingsDialog(object):
self.lineEditMaxOutboundConnections.setSizePolicy(sizePolicy)
self.lineEditMaxOutboundConnections.setMaximumSize(QtCore.QSize(60, 16777215))
self.lineEditMaxOutboundConnections.setObjectName(_fromUtf8("lineEditMaxOutboundConnections"))
self.lineEditMaxOutboundConnections.setValidator(QtGui.QIntValidator(0, 4096, self.lineEditMaxOutboundConnections))
self.lineEditMaxOutboundConnections.setValidator(QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections))
self.gridLayout_9.addWidget(self.lineEditMaxOutboundConnections, 2, 2, 1, 1)
self.gridLayout_4.addWidget(self.groupBox_3, 2, 0, 1, 1)
self.groupBox_2 = QtGui.QGroupBox(self.tabNetworkSettings)

21
src/bitmessageqt/sound.py Normal file
View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# sound type constants
SOUND_NONE = 0
SOUND_KNOWN = 1
SOUND_UNKNOWN = 2
SOUND_CONNECTED = 3
SOUND_DISCONNECTED = 4
SOUND_CONNECTION_GREEN = 5
# returns true if the given sound category is a connection sound
# rather than a received message sound
def is_connection_sound(category):
return category in (
SOUND_CONNECTED,
SOUND_DISCONNECTED,
SOUND_CONNECTION_GREEN
)
extensions = ('wav', 'mp3', 'oga')

View File

@ -1,64 +0,0 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'specialaddressbehavior.ui'
#
# Created: Fri Apr 26 17:43:31 2013
# by: PyQt4 UI code generator 4.9.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_SpecialAddressBehaviorDialog(object):
def setupUi(self, SpecialAddressBehaviorDialog):
SpecialAddressBehaviorDialog.setObjectName(_fromUtf8("SpecialAddressBehaviorDialog"))
SpecialAddressBehaviorDialog.resize(386, 172)
self.gridLayout = QtGui.QGridLayout(SpecialAddressBehaviorDialog)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.radioButtonBehaveNormalAddress = QtGui.QRadioButton(SpecialAddressBehaviorDialog)
self.radioButtonBehaveNormalAddress.setChecked(True)
self.radioButtonBehaveNormalAddress.setObjectName(_fromUtf8("radioButtonBehaveNormalAddress"))
self.gridLayout.addWidget(self.radioButtonBehaveNormalAddress, 0, 0, 1, 1)
self.radioButtonBehaviorMailingList = QtGui.QRadioButton(SpecialAddressBehaviorDialog)
self.radioButtonBehaviorMailingList.setObjectName(_fromUtf8("radioButtonBehaviorMailingList"))
self.gridLayout.addWidget(self.radioButtonBehaviorMailingList, 1, 0, 1, 1)
self.label = QtGui.QLabel(SpecialAddressBehaviorDialog)
self.label.setWordWrap(True)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout.addWidget(self.label, 2, 0, 1, 1)
self.label_2 = QtGui.QLabel(SpecialAddressBehaviorDialog)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.gridLayout.addWidget(self.label_2, 3, 0, 1, 1)
self.lineEditMailingListName = QtGui.QLineEdit(SpecialAddressBehaviorDialog)
self.lineEditMailingListName.setEnabled(False)
self.lineEditMailingListName.setObjectName(_fromUtf8("lineEditMailingListName"))
self.gridLayout.addWidget(self.lineEditMailingListName, 4, 0, 1, 1)
self.buttonBox = QtGui.QDialogButtonBox(SpecialAddressBehaviorDialog)
self.buttonBox.setMinimumSize(QtCore.QSize(368, 0))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.gridLayout.addWidget(self.buttonBox, 5, 0, 1, 1)
self.retranslateUi(SpecialAddressBehaviorDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), SpecialAddressBehaviorDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), SpecialAddressBehaviorDialog.reject)
QtCore.QObject.connect(self.radioButtonBehaviorMailingList, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditMailingListName.setEnabled)
QtCore.QObject.connect(self.radioButtonBehaveNormalAddress, QtCore.SIGNAL(_fromUtf8("clicked(bool)")), self.lineEditMailingListName.setDisabled)
QtCore.QMetaObject.connectSlotsByName(SpecialAddressBehaviorDialog)
SpecialAddressBehaviorDialog.setTabOrder(self.radioButtonBehaveNormalAddress, self.radioButtonBehaviorMailingList)
SpecialAddressBehaviorDialog.setTabOrder(self.radioButtonBehaviorMailingList, self.lineEditMailingListName)
SpecialAddressBehaviorDialog.setTabOrder(self.lineEditMailingListName, self.buttonBox)
def retranslateUi(self, SpecialAddressBehaviorDialog):
SpecialAddressBehaviorDialog.setWindowTitle(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Special Address Behavior", None, QtGui.QApplication.UnicodeUTF8))
self.radioButtonBehaveNormalAddress.setText(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Behave as a normal address", None, QtGui.QApplication.UnicodeUTF8))
self.radioButtonBehaviorMailingList.setText(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Behave as a pseudo-mailing-list address", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public).", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("SpecialAddressBehaviorDialog", "Name of the pseudo-mailing-list:", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -13,15 +13,16 @@ from helper_sql import *
from l10n import getTranslationLanguage
from openclpow import openclAvailable, openclEnabled
import paths
from proofofwork import bmpow
import proofofwork
from pyelliptic.openssl import OpenSSL
import queues
import shared
import network.stats
import state
from version import softwareVersion
# this is BM support address going to Peter Surda
SUPPORT_ADDRESS = 'BM-2cTkCtMYkrSPwFTpgcBrMrf5d8oZwvMZWK'
OLD_SUPPORT_ADDRESS = 'BM-2cTkCtMYkrSPwFTpgcBrMrf5d8oZwvMZWK'
SUPPORT_ADDRESS = 'BM-2cUdgkDDAahwPAU6oD2A7DnjqZz3hgY832'
SUPPORT_LABEL = 'PyBitmessage support'
SUPPORT_MY_LABEL = 'My new address'
SUPPORT_SUBJECT = 'Support request'
@ -53,6 +54,7 @@ Connected hosts: {}
'''
def checkAddressBook(myapp):
sqlExecute('''DELETE from addressbook WHERE address=?''', OLD_SUPPORT_ADDRESS)
queryreturn = sqlQuery('''SELECT * FROM addressbook WHERE address=?''', SUPPORT_ADDRESS)
if queryreturn == []:
sqlExecute('''INSERT INTO addressbook VALUES (?,?)''', str(QtGui.QApplication.translate("Support", SUPPORT_LABEL)), SUPPORT_ADDRESS)
@ -85,9 +87,9 @@ def createSupportMessage(myapp):
return
myapp.ui.comboBoxSendFrom.setCurrentIndex(addrIndex)
myapp.ui.lineEditTo.setText(SUPPORT_ADDRESS)
version = softwareVersion
commit = paths.lastCommit()
commit = paths.lastCommit().get('commit')
if commit:
version += " GIT " + commit
@ -111,7 +113,7 @@ def createSupportMessage(myapp):
if paths.frozen:
frozen = paths.frozen
portablemode = "True" if state.appdata == paths.lookupExeFolder() else "False"
cpow = "True" if bmpow else "False"
cpow = "True" if proofofwork.bmpow else "False"
#cpow = QtGui.QApplication.translate("Support", cpow)
openclpow = str(BMConfigParser().safeGet('bitmessagesettings', 'opencl')) if openclEnabled() else "None"
#openclpow = QtGui.QApplication.translate("Support", openclpow)
@ -124,11 +126,15 @@ def createSupportMessage(myapp):
upnp = BMConfigParser().get('bitmessagesettings', 'upnp')
except:
upnp = "N/A"
connectedhosts = len(shared.connectedHostsList)
connectedhosts = len(network.stats.connectedHostsList())
myapp.ui.textEditMessage.setText(str(QtGui.QApplication.translate("Support", SUPPORT_MESSAGE)).format(version, os, architecture, pythonversion, opensslversion, frozen, portablemode, cpow, openclpow, locale, socks, upnp, connectedhosts))
# single msg tab
myapp.ui.tabWidgetSend.setCurrentIndex(0)
myapp.ui.tabWidgetSend.setCurrentIndex(
myapp.ui.tabWidgetSend.indexOf(myapp.ui.sendDirect)
)
# send tab
myapp.ui.tabWidget.setCurrentIndex(1)
myapp.ui.tabWidget.setCurrentIndex(
myapp.ui.tabWidget.indexOf(myapp.ui.send)
)

View File

@ -45,7 +45,8 @@ class UISignaler(QThread):
"displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),
toAddress, fromLabel, fromAddress, subject, message, ackdata)
elif command == 'updateNetworkStatusTab':
self.emit(SIGNAL("updateNetworkStatusTab()"))
outbound, add, destination = data
self.emit(SIGNAL("updateNetworkStatusTab(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), outbound, add, destination)
elif command == 'updateNumberOfMessagesProcessed':
self.emit(SIGNAL("updateNumberOfMessagesProcessed()"))
elif command == 'updateNumberOfPubkeysProcessed':

View File

@ -58,8 +58,8 @@ def identiconize(address):
idcon_render = Pydenticon(addBMIfNotPresent(address)+identiconsuffix, size*3)
rendering = idcon_render._render()
data = rendering.convert("RGBA").tostring("raw", "RGBA")
qim = QImage(data, size, size, QImage.Format_ARGB32)
pix = QPixmap.fromImage(qim)
qim = QtGui.QImage(data, size, size, QtGui.QImage.Format_ARGB32)
pix = QtGui.QPixmap.fromImage(qim)
idcon = QtGui.QIcon()
idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off)
return idcon

View File

@ -6,6 +6,33 @@ import os
from singleton import Singleton
import state
BMConfigDefaults = {
"bitmessagesettings": {
"maxaddrperstreamsend": 500,
"maxbootstrapconnections": 20,
"maxdownloadrate": 0,
"maxoutboundconnections": 8,
"maxtotalconnections": 200,
"maxuploadrate": 0,
},
"threads": {
"receive": 3,
},
"network": {
"bind": '',
"dandelion": 90,
},
"inventory": {
"storage": "sqlite",
"acceptmismatch": False,
},
"knownnodes": {
"maxnodes": 20000,
},
"zlib": {
'maxsize': 1048576
}
}
@Singleton
class BMConfigParser(ConfigParser.SafeConfigParser):
@ -13,40 +40,60 @@ class BMConfigParser(ConfigParser.SafeConfigParser):
if self._optcre is self.OPTCRE or value:
if not isinstance(value, basestring):
raise TypeError("option values must be strings")
if not self.validate(section, option, value):
raise ValueError("Invalid value %s" % str(value))
return ConfigParser.ConfigParser.set(self, section, option, value)
def get(self, section, option, raw=False, vars=None):
if section == "bitmessagesettings" and option == "timeformat":
try:
return ConfigParser.ConfigParser.get(self, section, option, raw, vars)
except ConfigParser.InterpolationError:
return ConfigParser.ConfigParser.get(self, section, option, True, vars)
return ConfigParser.ConfigParser.get(self, section, option, True, vars)
def get(self, section, option, raw=False, variables=None):
try:
if section == "bitmessagesettings" and option == "timeformat":
return ConfigParser.ConfigParser.get(self, section, option, raw, variables)
return ConfigParser.ConfigParser.get(self, section, option, True, variables)
except ConfigParser.InterpolationError:
return ConfigParser.ConfigParser.get(self, section, option, True, variables)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e:
try:
return BMConfigDefaults[section][option]
except (KeyError, ValueError, AttributeError):
raise e
def safeGetBoolean(self, section, field):
if self.has_option(section, field):
try:
return self.getboolean(section, field)
except ValueError:
return False
return False
try:
return self.getboolean(section, field)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, ValueError, AttributeError):
return False
def safeGetInt(self, section, field, default=0):
if self.has_option(section, field):
try:
return self.getint(section, field)
except ValueError:
return default
return default
def safeGet(self, section, option, default = None):
if self.has_option(section, option):
return self.get(section, option)
else:
try:
return self.getint(section, field)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, ValueError, AttributeError):
return default
def items(self, section, raw=False, vars=None):
return ConfigParser.ConfigParser.items(self, section, True, vars)
def safeGet(self, section, option, default = None):
try:
return self.get(section, option)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, ValueError, AttributeError):
return default
def items(self, section, raw=False, variables=None):
return ConfigParser.ConfigParser.items(self, section, True, variables)
def addresses(self):
return filter(lambda x: x.startswith('BM-'), BMConfigParser().sections())
def read(self, filenames):
ConfigParser.ConfigParser.read(self, filenames)
for section in self.sections():
for option in self.options(section):
try:
if not self.validate(section, option, ConfigParser.ConfigParser.get(self, section, option)):
try:
newVal = BMConfigDefaults[section][option]
except KeyError:
continue
ConfigParser.ConfigParser.set(self, section, option, newVal)
except ConfigParser.InterpolationError:
continue
def save(self):
fileName = os.path.join(state.appdata, 'keys.dat')
@ -65,3 +112,18 @@ class BMConfigParser(ConfigParser.SafeConfigParser):
# delete the backup
if fileNameExisted:
os.remove(fileNameBak)
def validate(self, section, option, value):
try:
return getattr(self, "validate_%s_%s" % (section, option))(value)
except AttributeError:
return True
def validate_bitmessagesettings_maxoutboundconnections(self, value):
try:
value = int(value)
except ValueError:
return False
if value < 0 or value > 8:
return False
return True

View File

@ -21,6 +21,7 @@ import helper_inbox
import helper_msgcoding
import helper_sent
from helper_sql import *
from helper_ackPayload import genAckPayload
import protocol
import queues
import state
@ -28,7 +29,6 @@ import tr
from debug import logger
import l10n
class objectProcessor(threading.Thread):
"""
The objectProcessor thread, of which there is only one, receives network
@ -56,6 +56,8 @@ class objectProcessor(threading.Thread):
while True:
objectType, data = queues.objectProcessorQueue.get()
self.checkackdata(data)
try:
if objectType == 0: # getpubkey
self.processgetpubkey(data)
@ -68,7 +70,12 @@ class objectProcessor(threading.Thread):
elif objectType == 'checkShutdownVariable': # is more of a command, not an object type. Is used to get this thread past the queue.get() so that it will check the shutdown variable.
pass
else:
logger.critical('Error! Bug! The class_objectProcessor was passed an object type it doesn\'t recognize: %s' % str(objectType))
if isinstance(objectType, int):
logger.info('Don\'t know how to handle object type 0x%08X', objectType)
else:
logger.info('Don\'t know how to handle object type %s', objectType)
except helper_msgcoding.DecompressionSizeException as e:
logger.error("The object is too big after decompression (stopped decompressing at %ib, your configured limit %ib). Ignoring", e.size, BMConfigParser().safeGetInt("zlib", "maxsize"))
except varintDecodeError as e:
logger.debug("There was a problem with a varint while processing an object. Some details: %s" % e)
except Exception as e:
@ -86,8 +93,31 @@ class objectProcessor(threading.Thread):
logger.debug('Saved %s objects from the objectProcessorQueue to disk. objectProcessorThread exiting.' % str(numberOfObjectsThatWereInTheObjectProcessorQueue))
state.shutdown = 2
break
def checkackdata(self, data):
# Let's check whether this is a message acknowledgement bound for us.
if len(data) < 32:
return
# bypass nonce and time, retain object type/version/stream + body
readPosition = 16
if data[readPosition:] in shared.ackdataForWhichImWatching:
logger.info('This object is an acknowledgement bound for me.')
del shared.ackdataForWhichImWatching[data[readPosition:]]
sqlExecute('UPDATE sent SET status=?, lastactiontime=? WHERE ackdata=?',
'ackreceived',
int(time.time()),
data[readPosition:])
queues.UISignalQueue.put(('updateSentItemStatusByAckdata', (data[readPosition:], tr._translate("MainWindow",'Acknowledgement of the message received %1').arg(l10n.formatTimestamp()))))
else:
logger.debug('This object is not an acknowledgement bound for me.')
def processgetpubkey(self, data):
if len(data) > 200:
logger.info('getpubkey is abnormally long. Sanity check failed. Ignoring object.')
return
readPosition = 20 # bypass the nonce, time, and object type
requestedAddressVersionNumber, addressVersionLength = decodeVarint(
data[readPosition:readPosition + 10])
@ -322,24 +352,11 @@ class objectProcessor(threading.Thread):
readPosition += streamNumberAsClaimedByMsgLength
inventoryHash = calculateInventoryHash(data)
initialDecryptionSuccessful = False
# Let's check whether this is a message acknowledgement bound for us.
if data[-32:] in shared.ackdataForWhichImWatching:
logger.info('This msg IS an acknowledgement bound for me.')
del shared.ackdataForWhichImWatching[data[-32:]]
sqlExecute('UPDATE sent SET status=?, lastactiontime=? WHERE ackdata=?',
'ackreceived',
int(time.time()),
data[-32:])
queues.UISignalQueue.put(('updateSentItemStatusByAckdata', (data[-32:], tr._translate("MainWindow",'Acknowledgement of the message received %1').arg(l10n.formatTimestamp()))))
return
else:
logger.info('This was NOT an acknowledgement bound for me.')
# This is not an acknowledgement bound for me. See if it is a message
# bound for me by trying to decrypt it with my private keys.
for key, cryptorObject in shared.myECCryptorObjects.items():
for key, cryptorObject in sorted(shared.myECCryptorObjects.items(), key=lambda x: random.random()):
try:
if initialDecryptionSuccessful: # continue decryption attempts to avoid timing attacks
cryptorObject.decrypt(data[readPosition:])
@ -492,7 +509,10 @@ class objectProcessor(threading.Thread):
if toLabel == '':
toLabel = toAddress
decodedMessage = helper_msgcoding.MsgDecode(messageEncodingType, message)
try:
decodedMessage = helper_msgcoding.MsgDecode(messageEncodingType, message)
except helper_msgcoding.MsgDecodeException:
return
subject = decodedMessage.subject
body = decodedMessage.body
@ -536,8 +556,10 @@ class objectProcessor(threading.Thread):
message = time.strftime("%a, %Y-%m-%d %H:%M:%S UTC", time.gmtime(
)) + ' Message ostensibly from ' + fromAddress + ':\n\n' + body
fromAddress = toAddress # The fromAddress for the broadcast that we are about to send is the toAddress (my address) for the msg message we are currently processing.
ackdataForBroadcast = OpenSSL.rand(
32) # We don't actually need the ackdataForBroadcast for acknowledgement since this is a broadcast message but we can use it to update the user interface when the POW is done generating.
# We don't actually need the ackdata for acknowledgement since this is a broadcast message but we can use it to update the user interface when the POW is done generating.
streamNumber = decodeAddress(fromAddress)[2]
ackdata = genAckPayload(streamNumber, 0)
toAddress = '[Broadcast subscribers]'
ripe = ''
@ -551,7 +573,7 @@ class objectProcessor(threading.Thread):
fromAddress,
subject,
message,
ackdataForBroadcast,
ackdata,
int(time.time()), # sentTime (this doesn't change)
int(time.time()), # lastActionTime
0,
@ -563,7 +585,7 @@ class objectProcessor(threading.Thread):
helper_sent.insert(t)
queues.UISignalQueue.put(('displayNewSentMessage', (
toAddress, '[Broadcast subscribers]', fromAddress, subject, message, ackdataForBroadcast)))
toAddress, '[Broadcast subscribers]', fromAddress, subject, message, ackdata)))
queues.workerQueue.put(('sendbroadcast', ''))
# Don't send ACK if invalid, blacklisted senders, invisible messages, disabled or chan
@ -612,7 +634,7 @@ class objectProcessor(threading.Thread):
"""
signedData = data[8:readPosition]
initialDecryptionSuccessful = False
for key, cryptorObject in shared.MyECSubscriptionCryptorObjects.items():
for key, cryptorObject in sorted(shared.MyECSubscriptionCryptorObjects.items(), key=lambda x: random.random()):
try:
if initialDecryptionSuccessful: # continue decryption attempts to avoid timing attacks
cryptorObject.decrypt(data[readPosition:])
@ -742,7 +764,10 @@ class objectProcessor(threading.Thread):
sendersAddressVersion, sendersStream, calculatedRipe)
logger.debug('fromAddress: ' + fromAddress)
decodedMessage = helper_msgcoding.MsgDecode(messageEncodingType, message)
try:
decodedMessage = helper_msgcoding.MsgDecode(messageEncodingType, message)
except helper_msgcoding.MsgDecodeException:
return
subject = decodedMessage.subject
body = decodedMessage.body

View File

@ -185,12 +185,10 @@ class outgoingSynSender(threading.Thread, StoppableThread):
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
return
someObjectsOfWhichThisRemoteNodeIsAlreadyAware = {} # This is not necessairly a complete list; we clear it from time to time to save memory.
sendDataThreadQueue = Queue.Queue() # Used to submit information to the send data thread for this connection.
sd = sendDataThread(sendDataThreadQueue)
sd.setup(self.sock, peer.host, peer.port, self.streamNumber,
someObjectsOfWhichThisRemoteNodeIsAlreadyAware)
sd.setup(self.sock, peer.host, peer.port, self.streamNumber)
sd.start()
rd = receiveDataThread()
@ -199,7 +197,6 @@ class outgoingSynSender(threading.Thread, StoppableThread):
peer.host,
peer.port,
self.streamNumber,
someObjectsOfWhichThisRemoteNodeIsAlreadyAware,
self.selfInitiatedConnections,
sendDataThreadQueue,
sd.objectHashHolderInstance)

View File

@ -32,7 +32,7 @@ import knownnodes
from debug import logger
import paths
import protocol
from inventory import Inventory, PendingDownload, PendingUpload
from inventory import Inventory, PendingDownloadQueue, PendingUpload
import queues
import state
import throttle
@ -56,7 +56,6 @@ class receiveDataThread(threading.Thread):
HOST,
port,
streamNumber,
someObjectsOfWhichThisRemoteNodeIsAlreadyAware,
selfInitiatedConnections,
sendDataThreadQueue,
objectHashHolderInstance):
@ -79,8 +78,8 @@ class receiveDataThread(threading.Thread):
self.initiatedConnection = True
for stream in self.streamNumber:
self.selfInitiatedConnections[stream][self] = 0
self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware = someObjectsOfWhichThisRemoteNodeIsAlreadyAware
self.objectHashHolderInstance = objectHashHolderInstance
self.downloadQueue = PendingDownloadQueue()
self.startTime = time.time()
def run(self):
@ -123,11 +122,11 @@ class receiveDataThread(threading.Thread):
select.select([self.sslSock if isSSL else self.sock], [], [], 10)
logger.debug('sock.recv retriable error')
continue
logger.error('sock.recv error. Closing receiveData thread (' + str(self.peer) + ', Thread ID: ' + str(id(self)) + ').' + str(err.errno) + "/" + str(err))
logger.error('sock.recv error. Closing receiveData thread, %s', str(err))
break
# print 'Received', repr(self.data)
if len(self.data) == dataLen: # If self.sock.recv returned no data:
logger.debug('Connection to ' + str(self.peer) + ' closed. Closing receiveData thread. (ID: ' + str(id(self)) + ')')
logger.debug('Connection to ' + str(self.peer) + ' closed. Closing receiveData thread')
break
else:
self.processData()
@ -147,7 +146,6 @@ class receiveDataThread(threading.Thread):
except Exception as err:
logger.error('Could not delete ' + str(self.hostIdent) + ' from shared.connectedHostsList.' + str(err))
PendingDownload().threadEnd()
queues.UISignalQueue.put(('updateNetworkStatusTab', 'no data'))
self.checkTimeOffsetNotification()
logger.debug('receiveDataThread ending. ID ' + str(id(self)) + '. The size of the shared.connectedHostsList is now ' + str(len(shared.connectedHostsList)))
@ -240,10 +238,20 @@ class receiveDataThread(threading.Thread):
self.data = self.data[payloadLength + protocol.Header.size:] # take this message out and then process the next message
if self.data == '': # if there are no more messages
toRequest = []
try:
self.sendgetdata(PendingDownload().pull(100))
except Queue.Full:
for i in range(len(self.downloadQueue.pending), 100):
while True:
hashId = self.downloadQueue.get(False)
if not hashId in Inventory():
toRequest.append(hashId)
break
# don't track download for duplicates
self.downloadQueue.task_done(hashId)
except Queue.Empty:
pass
if len(toRequest) > 0:
self.sendgetdata(toRequest)
self.processData()
def sendpong(self, payload):
@ -282,6 +290,8 @@ class receiveDataThread(threading.Thread):
try:
self.sslSock.do_handshake()
logger.debug("TLS handshake success")
if sys.version_info >= (2, 7, 9):
logger.debug("TLS protocol version: %s", self.sslSock.version())
break
except ssl.SSLError as e:
if sys.hexversion >= 0x02070900:
@ -302,9 +312,13 @@ class receiveDataThread(threading.Thread):
logger.debug("Waiting for SSL socket handhake write")
select.select([], [self.sslSock], [], 10)
continue
logger.error("SSL socket handhake failed: %s, shutting down connection", str(e))
logger.error("SSL socket handhake failed: shutting down connection, %s", str(e))
self.sendDataThreadQueue.put((0, 'shutdown','tls handshake fail %s' % (str(e))))
return False
except socket.error as err:
logger.debug('SSL socket handshake failed, shutting down connection, %s', str(err))
self.sendDataThreadQueue.put((0, 'shutdown','tls handshake fail'))
return False
except Exception:
logger.error("SSL socket handhake failed, shutting down connection", exc_info=True)
self.sendDataThreadQueue.put((0, 'shutdown','tls handshake fail'))
@ -403,7 +417,7 @@ class receiveDataThread(threading.Thread):
bigInvList = {}
for stream in self.streamNumber:
for hash in Inventory().unexpired_hashes_by_stream(stream):
if hash not in self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware and not self.objectHashHolderInstance.hasHash(hash):
if not self.objectHashHolderInstance.hasHash(hash):
bigInvList[hash] = 0
numberOfObjectsInInvMessage = 0
payload = ''
@ -472,6 +486,7 @@ class receiveDataThread(threading.Thread):
def recobject(self, data):
self.messageProcessingStartTime = time.time()
lengthOfTimeWeShouldUseToProcessThisMessage = shared.checkAndShareObjectWithPeers(data)
self.downloadQueue.task_done(calculateInventoryHash(data))
"""
Sleeping will help guarantee that we can process messages faster than a
@ -504,9 +519,8 @@ class receiveDataThread(threading.Thread):
for stream in self.streamNumber:
objectsNewToMe -= Inventory().hashes_by_stream(stream)
logger.info('inv message lists %s objects. Of those %s are new to me. It took %s seconds to figure that out.', numberOfItemsInInv, len(objectsNewToMe), time.time()-startTime)
for item in objectsNewToMe:
PendingDownload().add(item)
self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware[item] = 0 # helps us keep from sending inv messages to peers that already know about the objects listed therein
for item in random.sample(objectsNewToMe, len(objectsNewToMe)):
self.downloadQueue.put(item)
# Send a getdata message to our peer to request the object with the given
# hash
@ -626,17 +640,18 @@ class receiveDataThread(threading.Thread):
knownnodes.knownNodes[recaddrStream] = {}
peerFromAddrMessage = state.Peer(hostStandardFormat, recaddrPort)
if peerFromAddrMessage not in knownnodes.knownNodes[recaddrStream]:
knownnodes.trimKnownNodes(recaddrStream)
# only if recent
if timeSomeoneElseReceivedMessageFromThisNode > (int(time.time()) - 10800) and timeSomeoneElseReceivedMessageFromThisNode < (int(time.time()) + 10800):
logger.debug('added new node ' + str(peerFromAddrMessage) + ' to knownNodes in stream ' + str(recaddrStream))
# bootstrap provider?
if BMConfigParser().safeGetInt('bitmessagesettings', 'maxoutboundconnections') >= \
BMConfigParser().safeGetInt('bitmessagesettings', 'maxtotalconnections', 200):
knownnodes.trimKnownNodes(recaddrStream)
with knownnodes.knownNodesLock:
knownnodes.knownNodes[recaddrStream][peerFromAddrMessage] = int(time.time()) - 10800
knownnodes.knownNodes[recaddrStream][peerFromAddrMessage] = int(time.time()) - 86400 # penalise initially by 1 day
logger.debug('added new node ' + str(peerFromAddrMessage) + ' to knownNodes in stream ' + str(recaddrStream))
shared.needToWriteKnownNodesToDisk = True
# normal mode
else:
elif len(knownnodes.knownNodes[recaddrStream]) < 20000:
with knownnodes.knownNodesLock:
knownnodes.knownNodes[recaddrStream][peerFromAddrMessage] = timeSomeoneElseReceivedMessageFromThisNode
hostDetails = (
@ -644,7 +659,8 @@ class receiveDataThread(threading.Thread):
recaddrStream, recaddrServices, hostStandardFormat, recaddrPort)
protocol.broadcastToSendDataQueues((
recaddrStream, 'advertisepeer', hostDetails))
shared.needToWriteKnownNodesToDisk = True
logger.debug('added new node ' + str(peerFromAddrMessage) + ' to knownNodes in stream ' + str(recaddrStream))
shared.needToWriteKnownNodesToDisk = True
# only update if normal mode
elif BMConfigParser().safeGetInt('bitmessagesettings', 'maxoutboundconnections') < \
BMConfigParser().safeGetInt('bitmessagesettings', 'maxtotalconnections', 200):

View File

@ -39,8 +39,8 @@ class sendDataThread(threading.Thread):
sock,
HOST,
PORT,
streamNumber,
someObjectsOfWhichThisRemoteNodeIsAlreadyAware):
streamNumber
):
self.sock = sock
self.peer = state.Peer(HOST, PORT)
self.name = "sendData-" + self.peer.host.replace(":", ".") # log parser field separator
@ -52,7 +52,6 @@ class sendDataThread(threading.Thread):
1 # This must be set using setRemoteProtocolVersion command which is sent through the self.sendDataThreadQueue queue.
self.lastTimeISentData = int(
time.time()) # If this value increases beyond five minutes ago, we'll send a pong message to keep the connection alive.
self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware = someObjectsOfWhichThisRemoteNodeIsAlreadyAware
if streamNumber == -1: # This was an incoming connection.
self.initiatedConnection = False
else:
@ -105,8 +104,8 @@ class sendDataThread(threading.Thread):
select.select([], [self.sslSock if isSSL else self.sock], [], 10)
logger.debug('sock.recv retriable error')
continue
if e.errno in (errno.EPIPE, errno.ECONNRESET, errno.EHOSTUNREACH, errno.ETIMEDOUT):
logger.debug('Connection error (EPIPE/ECONNRESET/EHOSTUNREACH/ETIMEDOUT)')
if e.errno in (errno.EPIPE, errno.ECONNRESET, errno.EHOSTUNREACH, errno.ETIMEDOUT, errno.ECONNREFUSED):
logger.debug('Connection error: %s', str(e))
return False
raise
throttle.SendThrottle().wait(amountSent)
@ -165,8 +164,7 @@ class sendDataThread(threading.Thread):
if self.connectionIsOrWasFullyEstablished: # only send inv messages if we have send and heard a verack from the remote node
payload = ''
for hash in data:
if hash not in self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware:
payload += hash
payload += hash
if payload != '':
payload = encodeVarint(len(payload)/32) + payload
packet = protocol.CreatePacket('inv', payload)
@ -176,7 +174,6 @@ class sendDataThread(threading.Thread):
logger.error('sendinv: self.sock.sendall failed')
break
elif command == 'pong':
self.someObjectsOfWhichThisRemoteNodeIsAlreadyAware.clear() # To save memory, let us clear this data structure from time to time. As its function is to help us keep from sending inv messages to peers which sent us the same inv message mere seconds earlier, it will be fine to clear this data structure from time to time.
if self.lastTimeISentData < (int(time.time()) - 298):
# Send out a pong message to keep the connection alive.
logger.debug('Sending pong to ' + str(self.peer) + ' to keep connection alive.')

View File

@ -1,7 +1,7 @@
import gc
import threading
import shared
import time
import sys
import os
import tr#anslate
@ -9,10 +9,10 @@ from bmconfigparser import BMConfigParser
from helper_sql import *
from helper_threading import *
from inventory import Inventory
from network.connectionpool import BMConnectionPool
from debug import logger
import knownnodes
import queues
import protocol
import state
"""
@ -36,12 +36,15 @@ resends msg messages in 5 days (then 10 days, then 20 days, etc...)
class singleCleaner(threading.Thread, StoppableThread):
cycleLength = 300
expireDiscoveredPeers = 300
def __init__(self):
threading.Thread.__init__(self, name="singleCleaner")
self.initStop()
def run(self):
gc.disable()
timeWeLastClearedInventoryAndPubkeysTables = 0
try:
shared.maximumLengthOfTimeToBotherResendingMessages = (float(BMConfigParser().get('bitmessagesettings', 'stopresendingafterxdays')) * 24 * 60 * 60) + (float(BMConfigParser().get('bitmessagesettings', 'stopresendingafterxmonths')) * (60 * 60 * 24 *365)/12)
@ -49,18 +52,20 @@ class singleCleaner(threading.Thread, StoppableThread):
# Either the user hasn't set stopresendingafterxdays and stopresendingafterxmonths yet or the options are missing from the config file.
shared.maximumLengthOfTimeToBotherResendingMessages = float('inf')
# initial wait
if state.shutdown == 0:
self.stop.wait(singleCleaner.cycleLength)
while state.shutdown == 0:
queues.UISignalQueue.put((
'updateStatusBar', 'Doing housekeeping (Flushing inventory in memory to disk...)'))
Inventory().flush()
queues.UISignalQueue.put(('updateStatusBar', ''))
protocol.broadcastToSendDataQueues((
0, 'pong', 'no data')) # commands the sendData threads to send out a pong message if they haven't sent anything else in the last five minutes. The socket timeout-time is 10 minutes.
# If we are running as a daemon then we are going to fill up the UI
# queue which will never be handled by a UI. We should clear it to
# save memory.
if BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon'):
if shared.thisapp.daemon:
queues.UISignalQueue.queue.clear()
if timeWeLastClearedInventoryAndPubkeysTables < int(time.time()) - 7380:
timeWeLastClearedInventoryAndPubkeysTables = int(time.time())
@ -88,13 +93,24 @@ class singleCleaner(threading.Thread, StoppableThread):
# cleanup old nodes
now = int(time.time())
toDelete = []
with knownnodes.knownNodesLock:
for stream in knownnodes.knownNodes:
for node in knownnodes.knownNodes[stream].keys():
if now - knownnodes.knownNodes[stream][node] > 2419200: # 28 days
shared.needToWriteKownNodesToDisk = True
del knownnodes.knownNodes[stream][node]
keys = knownnodes.knownNodes[stream].keys()
for node in keys:
try:
# scrap old nodes
if now - knownnodes.knownNodes[stream][node]["lastseen"] > 2419200: # 28 days
shared.needToWriteKnownNodesToDisk = True
del knownnodes.knownNodes[stream][node]
continue
# scrap old nodes with low rating
if now - knownnodes.knownNodes[stream][node]["lastseen"] > 10800 and knownnodes.knownNodes[stream][node]["rating"] <= knownnodes.knownNodesForgetRating:
shared.needToWriteKnownNodesToDisk = True
del knownnodes.knownNodes[stream][node]
continue
except TypeError:
print "Error in %s" % (str(node))
keys = []
# Let us write out the knowNodes to disk if there is anything new to write out.
if shared.needToWriteKnownNodesToDisk:
@ -104,14 +120,33 @@ class singleCleaner(threading.Thread, StoppableThread):
if "Errno 28" in str(err):
logger.fatal('(while receiveDataThread knownnodes.needToWriteKnownNodesToDisk) Alert: Your disk or data storage volume is full. ')
queues.UISignalQueue.put(('alert', (tr._translate("MainWindow", "Disk full"), tr._translate("MainWindow", 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), True)))
if shared.daemon:
if shared.thisapp.daemon:
os._exit(0)
shared.needToWriteKnownNodesToDisk = False
# # clear download queues
# for thread in threading.enumerate():
# if thread.isAlive() and hasattr(thread, 'downloadQueue'):
# thread.downloadQueue.clear()
# inv/object tracking
for connection in BMConnectionPool().inboundConnections.values() + BMConnectionPool().outboundConnections.values():
connection.clean()
# discovery tracking
exp = time.time() - singleCleaner.expireDiscoveredPeers
reaper = (k for k, v in state.discoveredPeers.items() if v < exp)
for k in reaper:
try:
del state.discoveredPeers[k]
except KeyError:
pass
# TODO: cleanup pending upload / download
gc.collect()
if state.shutdown == 0:
self.stop.wait(300)
self.stop.wait(singleCleaner.cycleLength)
def resendPubkeyRequest(address):

View File

@ -33,6 +33,10 @@ class singleListener(threading.Thread, StoppableThread):
HOST = '' # Symbolic name meaning all available interfaces
# If not sockslisten, but onionhostname defined, only listen on localhost
if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'sockslisten') and ".onion" in BMConfigParser().get('bitmessagesettings', 'onionhostname'):
if family == socket.AF_INET6 and "." in BMConfigParser().get('bitmessagesettings', 'onionbindip'):
raise socket.error(errno.EINVAL, "Invalid mix of IPv4 and IPv6")
elif family == socket.AF_INET and ":" in BMConfigParser().get('bitmessagesettings', 'onionbindip'):
raise socket.error(errno.EINVAL, "Invalid mix of IPv4 and IPv6")
HOST = BMConfigParser().get('bitmessagesettings', 'onionbindip')
PORT = BMConfigParser().getint('bitmessagesettings', 'port')
sock = socket.socket(family, socket.SOCK_STREAM)
@ -146,19 +150,18 @@ class singleListener(threading.Thread, StoppableThread):
else:
break
someObjectsOfWhichThisRemoteNodeIsAlreadyAware = {} # This is not necessairly a complete list; we clear it from time to time to save memory.
sendDataThreadQueue = Queue.Queue() # Used to submit information to the send data thread for this connection.
socketObject.settimeout(20)
sd = sendDataThread(sendDataThreadQueue)
sd.setup(
socketObject, HOST, PORT, -1, someObjectsOfWhichThisRemoteNodeIsAlreadyAware)
socketObject, HOST, PORT, -1)
sd.start()
rd = receiveDataThread()
rd.daemon = True # close the main program even if there are threads left
rd.setup(
socketObject, HOST, PORT, -1, someObjectsOfWhichThisRemoteNodeIsAlreadyAware, self.selfInitiatedConnections, sendDataThreadQueue, sd.objectHashHolderInstance)
socketObject, HOST, PORT, -1, self.selfInitiatedConnections, sendDataThreadQueue, sd.objectHashHolderInstance)
rd.start()
logger.info('connected to ' + HOST + ' during INCOMING request.')

View File

@ -42,6 +42,7 @@ class singleWorker(threading.Thread, StoppableThread):
# QThread.__init__(self, parent)
threading.Thread.__init__(self, name="singleWorker")
self.initStop()
proofofwork.init()
def stopThread(self):
try:
@ -80,6 +81,16 @@ class singleWorker(threading.Thread, StoppableThread):
logger.info('Watching for ackdata ' + hexlify(ackdata))
shared.ackdataForWhichImWatching[ackdata] = 0
# Fix legacy (headerless) watched ackdata to include header
for oldack in shared.ackdataForWhichImWatching.keys():
if (len(oldack)==32):
# attach legacy header, always constant (msg/1/1)
newack = '\x00\x00\x00\x02\x01\x01' + oldack
shared.ackdataForWhichImWatching[newack] = 0
sqlExecute('UPDATE sent SET ackdata=? WHERE ackdata=?',
newack, oldack )
del shared.ackdataForWhichImWatching[oldack]
self.stop.wait(
10) # give some time for the GUI to start before we start on existing POW tasks.
@ -136,7 +147,7 @@ class singleWorker(threading.Thread, StoppableThread):
def doPOWForMyV2Pubkey(self, hash): # This function also broadcasts out the pubkey message once it is done with the POW
# Look up my stream number based on my address hash
"""configSections = shared.config.sections()
"""configSections = shared.config.addresses()
for addressInKeysFile in configSections:
if addressInKeysFile <> 'bitmessagesettings':
status,addressVersionNumber,streamNumber,hashFromThisParticularAddress = decodeAddress(addressInKeysFile)
@ -192,8 +203,7 @@ class singleWorker(threading.Thread, StoppableThread):
logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash))
protocol.broadcastToSendDataQueues((
streamNumber, 'advertiseobject', inventoryHash))
queues.invQueue.put((streamNumber, inventoryHash))
queues.UISignalQueue.put(('updateStatusBar', ''))
try:
BMConfigParser().set(
@ -283,8 +293,7 @@ class singleWorker(threading.Thread, StoppableThread):
logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash))
protocol.broadcastToSendDataQueues((
streamNumber, 'advertiseobject', inventoryHash))
queues.invQueue.put((streamNumber, inventoryHash))
queues.UISignalQueue.put(('updateStatusBar', ''))
try:
BMConfigParser().set(
@ -374,8 +383,7 @@ class singleWorker(threading.Thread, StoppableThread):
logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash))
protocol.broadcastToSendDataQueues((
streamNumber, 'advertiseobject', inventoryHash))
queues.invQueue.put((streamNumber, inventoryHash))
queues.UISignalQueue.put(('updateStatusBar', ''))
try:
BMConfigParser().set(
@ -504,8 +512,7 @@ class singleWorker(threading.Thread, StoppableThread):
objectType, streamNumber, payload, embeddedTime, tag)
PendingUpload().add(inventoryHash)
logger.info('sending inv (within sendBroadcast function) for object: ' + hexlify(inventoryHash))
protocol.broadcastToSendDataQueues((
streamNumber, 'advertiseobject', inventoryHash))
queues.invQueue.put((streamNumber, inventoryHash))
queues.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr._translate("MainWindow", "Broadcast sent on %1").arg(l10n.formatTimestamp()))))
@ -834,8 +841,7 @@ class singleWorker(threading.Thread, StoppableThread):
# not sending to a chan or one of my addresses
queues.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr._translate("MainWindow", "Message sent. Waiting for acknowledgement. Sent on %1").arg(l10n.formatTimestamp()))))
logger.info('Broadcasting inv for my msg(within sendmsg function):' + hexlify(inventoryHash))
protocol.broadcastToSendDataQueues((
toStreamNumber, 'advertiseobject', inventoryHash))
queues.invQueue.put((toStreamNumber, inventoryHash))
# Update the sent message in the sent table with the necessary information.
if BMConfigParser().has_section(toaddress) or not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK):
@ -937,8 +943,7 @@ class singleWorker(threading.Thread, StoppableThread):
objectType, streamNumber, payload, embeddedTime, '')
PendingUpload().add(inventoryHash)
logger.info('sending inv (for the getpubkey message)')
protocol.broadcastToSendDataQueues((
streamNumber, 'advertiseobject', inventoryHash))
queues.invQueue.put((streamNumber, inventoryHash))
# wait 10% past expiration
sleeptill = int(time.time() + TTL * 1.1)
@ -972,11 +977,10 @@ class singleWorker(threading.Thread, StoppableThread):
TTL = 28*24*60*60 # 4 weeks
TTL = int(TTL + random.randrange(-300, 300)) # Add some randomness to the TTL
embeddedTime = int(time.time() + TTL)
payload = pack('>Q', (embeddedTime))
payload += '\x00\x00\x00\x02' # object type: msg
payload += encodeVarint(1) # msg version
payload += encodeVarint(toStreamNumber) + ackdata
# type/version/stream already included
payload = pack('>Q', (embeddedTime)) + ackdata
target = 2 ** 64 / (defaults.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + defaults.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+defaults.networkDefaultPayloadLengthExtraBytes))/(2 ** 16))))
logger.info('(For ack message) Doing proof of work. TTL set to ' + str(TTL))

View File

@ -59,7 +59,7 @@ class smtpDeliver(threading.Thread, StoppableThread):
msg = MIMEText(body, 'plain', 'utf-8')
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = fromAddress + '@' + SMTPDOMAIN
toLabel = map (lambda y: BMConfigParser().safeGet(y, "label"), filter(lambda x: x == toAddress, BMConfigParser().sections()))
toLabel = map (lambda y: BMConfigParser().safeGet(y, "label"), filter(lambda x: x == toAddress, BMConfigParser().addresses()))
if len(toLabel) > 0:
msg['To'] = "\"%s\" <%s>" % (Header(toLabel[0], 'utf-8'), toAddress + '@' + SMTPDOMAIN)
else:

View File

@ -14,6 +14,7 @@ from addresses import decodeAddress
from bmconfigparser import BMConfigParser
from debug import logger
from helper_sql import sqlExecute
from helper_ackPayload import genAckPayload
from helper_threading import StoppableThread
from pyelliptic.openssl import OpenSSL
import queues
@ -65,7 +66,8 @@ class smtpServerPyBitmessage(smtpd.SMTPServer):
def send(self, fromAddress, toAddress, subject, message):
status, addressVersionNumber, streamNumber, ripe = decodeAddress(toAddress)
ackdata = OpenSSL.rand(32)
stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel')
ackdata = genAckPayload(streamNumber, stealthLevel)
t = ()
sqlExecute(
'''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
@ -115,7 +117,7 @@ class smtpServerPyBitmessage(smtpd.SMTPServer):
sender, domain = p.sub(r'\1', mailfrom).split("@")
if domain != SMTPDOMAIN:
raise Exception("Bad domain %s", domain)
if sender not in BMConfigParser().sections():
if sender not in BMConfigParser().addresses():
raise Exception("Nonexisting user %s", sender)
except Exception as err:
logger.debug("Bad envelope from %s: %s", mailfrom, repr(err))
@ -125,7 +127,7 @@ class smtpServerPyBitmessage(smtpd.SMTPServer):
sender, domain = msg_from.split("@")
if domain != SMTPDOMAIN:
raise Exception("Bad domain %s", domain)
if sender not in BMConfigParser().sections():
if sender not in BMConfigParser().addresses():
raise Exception("Nonexisting user %s", sender)
except Exception as err:
logger.error("Bad headers from %s: %s", msg_from, repr(err))

View File

@ -316,7 +316,7 @@ class sqlThread(threading.Thread):
# Adjust the required POW values for each of this user's addresses to conform to protocol v3 norms.
if BMConfigParser().getint('bitmessagesettings', 'settingsversion') == 9:
for addressInKeysFile in BMConfigParser().sections():
for addressInKeysFile in BMConfigParser().addressses():
try:
previousTotalDifficulty = float(BMConfigParser().getint(addressInKeysFile, 'noncetrialsperbyte')) / 320
previousSmallMessageDifficulty = float(BMConfigParser().getint(addressInKeysFile, 'payloadlengthextrabytes')) / 14000

View File

@ -20,7 +20,6 @@ import logging
import logging.config
import os
import sys
import traceback
import helper_startup
import state
helper_startup.loadConfig()
@ -30,8 +29,7 @@ helper_startup.loadConfig()
log_level = 'WARNING'
def log_uncaught_exceptions(ex_cls, ex, tb):
logging.critical(''.join(traceback.format_tb(tb)))
logging.critical('{0}: {1}'.format(ex_cls, ex))
logging.critical('Unhandled exception', exc_info=(ex_cls, ex, tb))
def configureLogging():
have_logging = False
@ -56,7 +54,7 @@ def configureLogging():
'version': 1,
'formatters': {
'default': {
'format': '%(asctime)s - %(levelname)s - %(message)s',
'format': u'%(asctime)s - %(levelname)s - %(message)s',
},
},
'handlers': {
@ -64,7 +62,7 @@ def configureLogging():
'class': 'logging.StreamHandler',
'formatter': 'default',
'level': log_level,
'stream': 'ext://sys.stdout'
'stream': 'ext://sys.stderr'
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',

View File

@ -12,15 +12,15 @@ def createDefaultKnownNodes(appdata):
stream1 = {}
#stream1[state.Peer('2604:2000:1380:9f:82e:148b:2746:d0c7', 8080)] = int(time.time())
stream1[state.Peer('5.45.99.75', 8444)] = int(time.time())
stream1[state.Peer('75.167.159.54', 8444)] = int(time.time())
stream1[state.Peer('95.165.168.168', 8444)] = int(time.time())
stream1[state.Peer('85.180.139.241', 8444)] = int(time.time())
stream1[state.Peer('158.222.211.81', 8080)] = int(time.time())
stream1[state.Peer('178.62.12.187', 8448)] = int(time.time())
stream1[state.Peer('24.188.198.204', 8111)] = int(time.time())
stream1[state.Peer('109.147.204.113', 1195)] = int(time.time())
stream1[state.Peer('178.11.46.221', 8444)] = int(time.time())
stream1[state.Peer('5.45.99.75', 8444)] = {"lastseen": int(time.time()), "rating": 0, "self": False}
stream1[state.Peer('75.167.159.54', 8444)] = {"lastseen": int(time.time()), "rating": 0, "self": False}
stream1[state.Peer('95.165.168.168', 8444)] = {"lastseen": int(time.time()), "rating": 0, "self": False}
stream1[state.Peer('85.180.139.241', 8444)] = {"lastseen": int(time.time()), "rating": 0, "self": False}
stream1[state.Peer('158.222.217.190', 8080)] = {"lastseen": int(time.time()), "rating": 0, "self": False}
stream1[state.Peer('178.62.12.187', 8448)] = {"lastseen": int(time.time()), "rating": 0, "self": False}
stream1[state.Peer('24.188.198.204', 8111)] = {"lastseen": int(time.time()), "rating": 0, "self": False}
stream1[state.Peer('109.147.204.113', 1195)] = {"lastseen": int(time.time()), "rating": 0, "self": False}
stream1[state.Peer('178.11.46.221', 8444)] = {"lastseen": int(time.time()), "rating": 0, "self": False}
############# Stream 2 #################
stream2 = {}

View File

@ -204,7 +204,9 @@ def check_msgpack():
try:
import msgpack
except ImportError:
logger.error('The msgpack package is not available. PyBitmessage requires msgpack.')
logger.error(
'The msgpack package is not available.'
'It is highly recommended for messages coding.')
if sys.platform.startswith('openbsd'):
logger.error('On OpenBSD, try running "pkg_add py-msgpack" as root.')
elif sys.platform.startswith('freebsd'):
@ -224,7 +226,6 @@ def check_msgpack():
else:
logger.error('If your package manager does not have this package, try running "pip install msgpack-python".')
return False
return True
def check_dependencies(verbose = False, optional = False):

0
src/fallback/__init__.py Normal file
View File

View File

View File

@ -0,0 +1,1057 @@
# u-msgpack-python v2.4.1 - v at sergeev.io
# https://github.com/vsergeev/u-msgpack-python
#
# u-msgpack-python is a lightweight MessagePack serializer and deserializer
# module, compatible with both Python 2 and 3, as well CPython and PyPy
# implementations of Python. u-msgpack-python is fully compliant with the
# latest MessagePack specification.com/msgpack/msgpack/blob/master/spec.md). In
# particular, it supports the new binary, UTF-8 string, and application ext
# types.
#
# MIT License
#
# Copyright (c) 2013-2016 vsergeev / Ivan (Vanya) A. Sergeev
#
# 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.
#
"""
u-msgpack-python v2.4.1 - v at sergeev.io
https://github.com/vsergeev/u-msgpack-python
u-msgpack-python is a lightweight MessagePack serializer and deserializer
module, compatible with both Python 2 and 3, as well CPython and PyPy
implementations of Python. u-msgpack-python is fully compliant with the
latest MessagePack specification.com/msgpack/msgpack/blob/master/spec.md). In
particular, it supports the new binary, UTF-8 string, and application ext
types.
License: MIT
"""
import struct
import collections
import sys
import io
__version__ = "2.4.1"
"Module version string"
version = (2, 4, 1)
"Module version tuple"
##############################################################################
# Ext Class
##############################################################################
# Extension type for application-defined types and data
class Ext:
"""
The Ext class facilitates creating a serializable extension object to store
an application-defined type and data byte array.
"""
def __init__(self, type, data):
"""
Construct a new Ext object.
Args:
type: application-defined type integer from 0 to 127
data: application-defined data byte array
Raises:
TypeError:
Specified ext type is outside of 0 to 127 range.
Example:
>>> foo = umsgpack.Ext(0x05, b"\x01\x02\x03")
>>> umsgpack.packb({u"special stuff": foo, u"awesome": True})
'\x82\xa7awesome\xc3\xadspecial stuff\xc7\x03\x05\x01\x02\x03'
>>> bar = umsgpack.unpackb(_)
>>> print(bar["special stuff"])
Ext Object (Type: 0x05, Data: 01 02 03)
>>>
"""
# Application ext type should be 0 <= type <= 127
if not isinstance(type, int) or not (type >= 0 and type <= 127):
raise TypeError("ext type out of range")
# Check data is type bytes
elif sys.version_info[0] == 3 and not isinstance(data, bytes):
raise TypeError("ext data is not type \'bytes\'")
elif sys.version_info[0] == 2 and not isinstance(data, str):
raise TypeError("ext data is not type \'str\'")
self.type = type
self.data = data
def __eq__(self, other):
"""
Compare this Ext object with another for equality.
"""
return (isinstance(other, self.__class__) and
self.type == other.type and
self.data == other.data)
def __ne__(self, other):
"""
Compare this Ext object with another for inequality.
"""
return not self.__eq__(other)
def __str__(self):
"""
String representation of this Ext object.
"""
s = "Ext Object (Type: 0x%02x, Data: " % self.type
s += " ".join(["0x%02x" % ord(self.data[i:i + 1])
for i in xrange(min(len(self.data), 8))])
if len(self.data) > 8:
s += " ..."
s += ")"
return s
def __hash__(self):
"""
Provide a hash of this Ext object.
"""
return hash((self.type, self.data))
class InvalidString(bytes):
"""Subclass of bytes to hold invalid UTF-8 strings."""
pass
##############################################################################
# Exceptions
##############################################################################
# Base Exception classes
class PackException(Exception):
"Base class for exceptions encountered during packing."
pass
class UnpackException(Exception):
"Base class for exceptions encountered during unpacking."
pass
# Packing error
class UnsupportedTypeException(PackException):
"Object type not supported for packing."
pass
# Unpacking error
class InsufficientDataException(UnpackException):
"Insufficient data to unpack the serialized object."
pass
class InvalidStringException(UnpackException):
"Invalid UTF-8 string encountered during unpacking."
pass
class ReservedCodeException(UnpackException):
"Reserved code encountered during unpacking."
pass
class UnhashableKeyException(UnpackException):
"""
Unhashable key encountered during map unpacking.
The serialized map cannot be deserialized into a Python dictionary.
"""
pass
class DuplicateKeyException(UnpackException):
"Duplicate key encountered during map unpacking."
pass
# Backwards compatibility
KeyNotPrimitiveException = UnhashableKeyException
KeyDuplicateException = DuplicateKeyException
#############################################################################
# Exported Functions and Glob
#############################################################################
# Exported functions and variables, set up in __init()
pack = None
packb = None
unpack = None
unpackb = None
dump = None
dumps = None
load = None
loads = None
compatibility = False
"""
Compatibility mode boolean.
When compatibility mode is enabled, u-msgpack-python will serialize both
unicode strings and bytes into the old "raw" msgpack type, and deserialize the
"raw" msgpack type into bytes. This provides backwards compatibility with the
old MessagePack specification.
Example:
>>> umsgpack.compatibility = True
>>>
>>> umsgpack.packb([u"some string", b"some bytes"])
b'\x92\xabsome string\xaasome bytes'
>>> umsgpack.unpackb(_)
[b'some string', b'some bytes']
>>>
"""
##############################################################################
# Packing
##############################################################################
# You may notice struct.pack("B", obj) instead of the simpler chr(obj) in the
# code below. This is to allow for seamless Python 2 and 3 compatibility, as
# chr(obj) has a str return type instead of bytes in Python 3, and
# struct.pack(...) has the right return type in both versions.
def _pack_integer(obj, fp, options):
if obj < 0:
if obj >= -32:
fp.write(struct.pack("b", obj))
elif obj >= -2**(8 - 1):
fp.write(b"\xd0" + struct.pack("b", obj))
elif obj >= -2**(16 - 1):
fp.write(b"\xd1" + struct.pack(">h", obj))
elif obj >= -2**(32 - 1):
fp.write(b"\xd2" + struct.pack(">i", obj))
elif obj >= -2**(64 - 1):
fp.write(b"\xd3" + struct.pack(">q", obj))
else:
raise UnsupportedTypeException("huge signed int")
else:
if obj <= 127:
fp.write(struct.pack("B", obj))
elif obj <= 2**8 - 1:
fp.write(b"\xcc" + struct.pack("B", obj))
elif obj <= 2**16 - 1:
fp.write(b"\xcd" + struct.pack(">H", obj))
elif obj <= 2**32 - 1:
fp.write(b"\xce" + struct.pack(">I", obj))
elif obj <= 2**64 - 1:
fp.write(b"\xcf" + struct.pack(">Q", obj))
else:
raise UnsupportedTypeException("huge unsigned int")
def _pack_nil(obj, fp, options):
fp.write(b"\xc0")
def _pack_boolean(obj, fp, options):
fp.write(b"\xc3" if obj else b"\xc2")
def _pack_float(obj, fp, options):
float_precision = options.get('force_float_precision', _float_precision)
if float_precision == "double":
fp.write(b"\xcb" + struct.pack(">d", obj))
elif float_precision == "single":
fp.write(b"\xca" + struct.pack(">f", obj))
else:
raise ValueError("invalid float precision")
def _pack_string(obj, fp, options):
obj = obj.encode('utf-8')
if len(obj) <= 31:
fp.write(struct.pack("B", 0xa0 | len(obj)) + obj)
elif len(obj) <= 2**8 - 1:
fp.write(b"\xd9" + struct.pack("B", len(obj)) + obj)
elif len(obj) <= 2**16 - 1:
fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj)
elif len(obj) <= 2**32 - 1:
fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj)
else:
raise UnsupportedTypeException("huge string")
def _pack_binary(obj, fp, options):
if len(obj) <= 2**8 - 1:
fp.write(b"\xc4" + struct.pack("B", len(obj)) + obj)
elif len(obj) <= 2**16 - 1:
fp.write(b"\xc5" + struct.pack(">H", len(obj)) + obj)
elif len(obj) <= 2**32 - 1:
fp.write(b"\xc6" + struct.pack(">I", len(obj)) + obj)
else:
raise UnsupportedTypeException("huge binary string")
def _pack_oldspec_raw(obj, fp, options):
if len(obj) <= 31:
fp.write(struct.pack("B", 0xa0 | len(obj)) + obj)
elif len(obj) <= 2**16 - 1:
fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj)
elif len(obj) <= 2**32 - 1:
fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj)
else:
raise UnsupportedTypeException("huge raw string")
def _pack_ext(obj, fp, options):
if len(obj.data) == 1:
fp.write(b"\xd4" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) == 2:
fp.write(b"\xd5" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) == 4:
fp.write(b"\xd6" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) == 8:
fp.write(b"\xd7" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) == 16:
fp.write(b"\xd8" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) <= 2**8 - 1:
fp.write(b"\xc7" +
struct.pack("BB", len(obj.data), obj.type & 0xff) + obj.data)
elif len(obj.data) <= 2**16 - 1:
fp.write(b"\xc8" +
struct.pack(">HB", len(obj.data), obj.type & 0xff) + obj.data)
elif len(obj.data) <= 2**32 - 1:
fp.write(b"\xc9" +
struct.pack(">IB", len(obj.data), obj.type & 0xff) + obj.data)
else:
raise UnsupportedTypeException("huge ext data")
def _pack_array(obj, fp, options):
if len(obj) <= 15:
fp.write(struct.pack("B", 0x90 | len(obj)))
elif len(obj) <= 2**16 - 1:
fp.write(b"\xdc" + struct.pack(">H", len(obj)))
elif len(obj) <= 2**32 - 1:
fp.write(b"\xdd" + struct.pack(">I", len(obj)))
else:
raise UnsupportedTypeException("huge array")
for e in obj:
pack(e, fp, **options)
def _pack_map(obj, fp, options):
if len(obj) <= 15:
fp.write(struct.pack("B", 0x80 | len(obj)))
elif len(obj) <= 2**16 - 1:
fp.write(b"\xde" + struct.pack(">H", len(obj)))
elif len(obj) <= 2**32 - 1:
fp.write(b"\xdf" + struct.pack(">I", len(obj)))
else:
raise UnsupportedTypeException("huge array")
for k, v in obj.items():
pack(k, fp, **options)
pack(v, fp, **options)
########################################
# Pack for Python 2, with 'unicode' type, 'str' type, and 'long' type
def _pack2(obj, fp, **options):
"""
Serialize a Python object into MessagePack bytes.
Args:
obj: a Python object
fp: a .write()-supporting file-like object
Kwargs:
ext_handlers (dict): dictionary of Ext handlers, mapping a custom type
to a callable that packs an instance of the type
into an Ext object
force_float_precision (str): "single" to force packing floats as
IEEE-754 single-precision floats,
"double" to force packing floats as
IEEE-754 double-precision floats.
Returns:
None.
Raises:
UnsupportedType(PackException):
Object type not supported for packing.
Example:
>>> f = open('test.bin', 'wb')
>>> umsgpack.pack({u"compact": True, u"schema": 0}, f)
>>>
"""
global compatibility
ext_handlers = options.get("ext_handlers")
if obj is None:
_pack_nil(obj, fp, options)
elif ext_handlers and obj.__class__ in ext_handlers:
_pack_ext(ext_handlers[obj.__class__](obj), fp, options)
elif isinstance(obj, bool):
_pack_boolean(obj, fp, options)
elif isinstance(obj, int) or isinstance(obj, long):
_pack_integer(obj, fp, options)
elif isinstance(obj, float):
_pack_float(obj, fp, options)
elif compatibility and isinstance(obj, unicode):
_pack_oldspec_raw(bytes(obj), fp, options)
elif compatibility and isinstance(obj, bytes):
_pack_oldspec_raw(obj, fp, options)
elif isinstance(obj, unicode):
_pack_string(obj, fp, options)
elif isinstance(obj, str):
_pack_binary(obj, fp, options)
elif isinstance(obj, list) or isinstance(obj, tuple):
_pack_array(obj, fp, options)
elif isinstance(obj, dict):
_pack_map(obj, fp, options)
elif isinstance(obj, Ext):
_pack_ext(obj, fp, options)
elif ext_handlers:
# Linear search for superclass
t = next((t for t in ext_handlers.keys() if isinstance(obj, t)), None)
if t:
_pack_ext(ext_handlers[t](obj), fp, options)
else:
raise UnsupportedTypeException(
"unsupported type: %s" % str(type(obj)))
else:
raise UnsupportedTypeException("unsupported type: %s" % str(type(obj)))
# Pack for Python 3, with unicode 'str' type, 'bytes' type, and no 'long' type
def _pack3(obj, fp, **options):
"""
Serialize a Python object into MessagePack bytes.
Args:
obj: a Python object
fp: a .write()-supporting file-like object
Kwargs:
ext_handlers (dict): dictionary of Ext handlers, mapping a custom type
to a callable that packs an instance of the type
into an Ext object
force_float_precision (str): "single" to force packing floats as
IEEE-754 single-precision floats,
"double" to force packing floats as
IEEE-754 double-precision floats.
Returns:
None.
Raises:
UnsupportedType(PackException):
Object type not supported for packing.
Example:
>>> f = open('test.bin', 'wb')
>>> umsgpack.pack({u"compact": True, u"schema": 0}, f)
>>>
"""
global compatibility
ext_handlers = options.get("ext_handlers")
if obj is None:
_pack_nil(obj, fp, options)
elif ext_handlers and obj.__class__ in ext_handlers:
_pack_ext(ext_handlers[obj.__class__](obj), fp, options)
elif isinstance(obj, bool):
_pack_boolean(obj, fp, options)
elif isinstance(obj, int):
_pack_integer(obj, fp, options)
elif isinstance(obj, float):
_pack_float(obj, fp, options)
elif compatibility and isinstance(obj, str):
_pack_oldspec_raw(obj.encode('utf-8'), fp, options)
elif compatibility and isinstance(obj, bytes):
_pack_oldspec_raw(obj, fp, options)
elif isinstance(obj, str):
_pack_string(obj, fp, options)
elif isinstance(obj, bytes):
_pack_binary(obj, fp, options)
elif isinstance(obj, list) or isinstance(obj, tuple):
_pack_array(obj, fp, options)
elif isinstance(obj, dict):
_pack_map(obj, fp, options)
elif isinstance(obj, Ext):
_pack_ext(obj, fp, options)
elif ext_handlers:
# Linear search for superclass
t = next((t for t in ext_handlers.keys() if isinstance(obj, t)), None)
if t:
_pack_ext(ext_handlers[t](obj), fp, options)
else:
raise UnsupportedTypeException(
"unsupported type: %s" % str(type(obj)))
else:
raise UnsupportedTypeException(
"unsupported type: %s" % str(type(obj)))
def _packb2(obj, **options):
"""
Serialize a Python object into MessagePack bytes.
Args:
obj: a Python object
Kwargs:
ext_handlers (dict): dictionary of Ext handlers, mapping a custom type
to a callable that packs an instance of the type
into an Ext object
force_float_precision (str): "single" to force packing floats as
IEEE-754 single-precision floats,
"double" to force packing floats as
IEEE-754 double-precision floats.
Returns:
A 'str' containing serialized MessagePack bytes.
Raises:
UnsupportedType(PackException):
Object type not supported for packing.
Example:
>>> umsgpack.packb({u"compact": True, u"schema": 0})
'\x82\xa7compact\xc3\xa6schema\x00'
>>>
"""
fp = io.BytesIO()
_pack2(obj, fp, **options)
return fp.getvalue()
def _packb3(obj, **options):
"""
Serialize a Python object into MessagePack bytes.
Args:
obj: a Python object
Kwargs:
ext_handlers (dict): dictionary of Ext handlers, mapping a custom type
to a callable that packs an instance of the type
into an Ext object
force_float_precision (str): "single" to force packing floats as
IEEE-754 single-precision floats,
"double" to force packing floats as
IEEE-754 double-precision floats.
Returns:
A 'bytes' containing serialized MessagePack bytes.
Raises:
UnsupportedType(PackException):
Object type not supported for packing.
Example:
>>> umsgpack.packb({u"compact": True, u"schema": 0})
b'\x82\xa7compact\xc3\xa6schema\x00'
>>>
"""
fp = io.BytesIO()
_pack3(obj, fp, **options)
return fp.getvalue()
#############################################################################
# Unpacking
#############################################################################
def _read_except(fp, n):
data = fp.read(n)
if len(data) < n:
raise InsufficientDataException()
return data
def _unpack_integer(code, fp, options):
if (ord(code) & 0xe0) == 0xe0:
return struct.unpack("b", code)[0]
elif code == b'\xd0':
return struct.unpack("b", _read_except(fp, 1))[0]
elif code == b'\xd1':
return struct.unpack(">h", _read_except(fp, 2))[0]
elif code == b'\xd2':
return struct.unpack(">i", _read_except(fp, 4))[0]
elif code == b'\xd3':
return struct.unpack(">q", _read_except(fp, 8))[0]
elif (ord(code) & 0x80) == 0x00:
return struct.unpack("B", code)[0]
elif code == b'\xcc':
return struct.unpack("B", _read_except(fp, 1))[0]
elif code == b'\xcd':
return struct.unpack(">H", _read_except(fp, 2))[0]
elif code == b'\xce':
return struct.unpack(">I", _read_except(fp, 4))[0]
elif code == b'\xcf':
return struct.unpack(">Q", _read_except(fp, 8))[0]
raise Exception("logic error, not int: 0x%02x" % ord(code))
def _unpack_reserved(code, fp, options):
if code == b'\xc1':
raise ReservedCodeException(
"encountered reserved code: 0x%02x" % ord(code))
raise Exception(
"logic error, not reserved code: 0x%02x" % ord(code))
def _unpack_nil(code, fp, options):
if code == b'\xc0':
return None
raise Exception("logic error, not nil: 0x%02x" % ord(code))
def _unpack_boolean(code, fp, options):
if code == b'\xc2':
return False
elif code == b'\xc3':
return True
raise Exception("logic error, not boolean: 0x%02x" % ord(code))
def _unpack_float(code, fp, options):
if code == b'\xca':
return struct.unpack(">f", _read_except(fp, 4))[0]
elif code == b'\xcb':
return struct.unpack(">d", _read_except(fp, 8))[0]
raise Exception("logic error, not float: 0x%02x" % ord(code))
def _unpack_string(code, fp, options):
if (ord(code) & 0xe0) == 0xa0:
length = ord(code) & ~0xe0
elif code == b'\xd9':
length = struct.unpack("B", _read_except(fp, 1))[0]
elif code == b'\xda':
length = struct.unpack(">H", _read_except(fp, 2))[0]
elif code == b'\xdb':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not string: 0x%02x" % ord(code))
# Always return raw bytes in compatibility mode
global compatibility
if compatibility:
return _read_except(fp, length)
data = _read_except(fp, length)
try:
return bytes.decode(data, 'utf-8')
except UnicodeDecodeError:
if options.get("allow_invalid_utf8"):
return InvalidString(data)
raise InvalidStringException("unpacked string is invalid utf-8")
def _unpack_binary(code, fp, options):
if code == b'\xc4':
length = struct.unpack("B", _read_except(fp, 1))[0]
elif code == b'\xc5':
length = struct.unpack(">H", _read_except(fp, 2))[0]
elif code == b'\xc6':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not binary: 0x%02x" % ord(code))
return _read_except(fp, length)
def _unpack_ext(code, fp, options):
if code == b'\xd4':
length = 1
elif code == b'\xd5':
length = 2
elif code == b'\xd6':
length = 4
elif code == b'\xd7':
length = 8
elif code == b'\xd8':
length = 16
elif code == b'\xc7':
length = struct.unpack("B", _read_except(fp, 1))[0]
elif code == b'\xc8':
length = struct.unpack(">H", _read_except(fp, 2))[0]
elif code == b'\xc9':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not ext: 0x%02x" % ord(code))
ext = Ext(ord(_read_except(fp, 1)), _read_except(fp, length))
# Unpack with ext handler, if we have one
ext_handlers = options.get("ext_handlers")
if ext_handlers and ext.type in ext_handlers:
ext = ext_handlers[ext.type](ext)
return ext
def _unpack_array(code, fp, options):
if (ord(code) & 0xf0) == 0x90:
length = (ord(code) & ~0xf0)
elif code == b'\xdc':
length = struct.unpack(">H", _read_except(fp, 2))[0]
elif code == b'\xdd':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not array: 0x%02x" % ord(code))
return [_unpack(fp, options) for i in xrange(length)]
def _deep_list_to_tuple(obj):
if isinstance(obj, list):
return tuple([_deep_list_to_tuple(e) for e in obj])
return obj
def _unpack_map(code, fp, options):
if (ord(code) & 0xf0) == 0x80:
length = (ord(code) & ~0xf0)
elif code == b'\xde':
length = struct.unpack(">H", _read_except(fp, 2))[0]
elif code == b'\xdf':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not map: 0x%02x" % ord(code))
d = {} if not options.get('use_ordered_dict') \
else collections.OrderedDict()
for _ in xrange(length):
# Unpack key
k = _unpack(fp, options)
if isinstance(k, list):
# Attempt to convert list into a hashable tuple
k = _deep_list_to_tuple(k)
elif not isinstance(k, collections.Hashable):
raise UnhashableKeyException(
"encountered unhashable key: %s, %s" % (str(k), str(type(k))))
elif k in d:
raise DuplicateKeyException(
"encountered duplicate key: %s, %s" % (str(k), str(type(k))))
# Unpack value
v = _unpack(fp, options)
try:
d[k] = v
except TypeError:
raise UnhashableKeyException(
"encountered unhashable key: %s" % str(k))
return d
def _unpack(fp, options):
code = _read_except(fp, 1)
return _unpack_dispatch_table[code](code, fp, options)
########################################
def _unpack2(fp, **options):
"""
Deserialize MessagePack bytes into a Python object.
Args:
fp: a .read()-supporting file-like object
Kwargs:
ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext
type to a callable that unpacks an instance of
Ext into an object
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
unordered dict (default False)
allow_invalid_utf8 (bool): unpack invalid strings into instances of
InvalidString, for access to the bytes
(default False)
Returns:
A Python object.
Raises:
InsufficientDataException(UnpackException):
Insufficient data to unpack the serialized object.
InvalidStringException(UnpackException):
Invalid UTF-8 string encountered during unpacking.
ReservedCodeException(UnpackException):
Reserved code encountered during unpacking.
UnhashableKeyException(UnpackException):
Unhashable key encountered during map unpacking.
The serialized map cannot be deserialized into a Python dictionary.
DuplicateKeyException(UnpackException):
Duplicate key encountered during map unpacking.
Example:
>>> f = open('test.bin', 'rb')
>>> umsgpack.unpackb(f)
{u'compact': True, u'schema': 0}
>>>
"""
return _unpack(fp, options)
def _unpack3(fp, **options):
"""
Deserialize MessagePack bytes into a Python object.
Args:
fp: a .read()-supporting file-like object
Kwargs:
ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext
type to a callable that unpacks an instance of
Ext into an object
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
unordered dict (default False)
allow_invalid_utf8 (bool): unpack invalid strings into instances of
InvalidString, for access to the bytes
(default False)
Returns:
A Python object.
Raises:
InsufficientDataException(UnpackException):
Insufficient data to unpack the serialized object.
InvalidStringException(UnpackException):
Invalid UTF-8 string encountered during unpacking.
ReservedCodeException(UnpackException):
Reserved code encountered during unpacking.
UnhashableKeyException(UnpackException):
Unhashable key encountered during map unpacking.
The serialized map cannot be deserialized into a Python dictionary.
DuplicateKeyException(UnpackException):
Duplicate key encountered during map unpacking.
Example:
>>> f = open('test.bin', 'rb')
>>> umsgpack.unpackb(f)
{'compact': True, 'schema': 0}
>>>
"""
return _unpack(fp, options)
# For Python 2, expects a str object
def _unpackb2(s, **options):
"""
Deserialize MessagePack bytes into a Python object.
Args:
s: a 'str' or 'bytearray' containing serialized MessagePack bytes
Kwargs:
ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext
type to a callable that unpacks an instance of
Ext into an object
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
unordered dict (default False)
allow_invalid_utf8 (bool): unpack invalid strings into instances of
InvalidString, for access to the bytes
(default False)
Returns:
A Python object.
Raises:
TypeError:
Packed data type is neither 'str' nor 'bytearray'.
InsufficientDataException(UnpackException):
Insufficient data to unpack the serialized object.
InvalidStringException(UnpackException):
Invalid UTF-8 string encountered during unpacking.
ReservedCodeException(UnpackException):
Reserved code encountered during unpacking.
UnhashableKeyException(UnpackException):
Unhashable key encountered during map unpacking.
The serialized map cannot be deserialized into a Python dictionary.
DuplicateKeyException(UnpackException):
Duplicate key encountered during map unpacking.
Example:
>>> umsgpack.unpackb(b'\x82\xa7compact\xc3\xa6schema\x00')
{u'compact': True, u'schema': 0}
>>>
"""
if not isinstance(s, (str, bytearray)):
raise TypeError("packed data must be type 'str' or 'bytearray'")
return _unpack(io.BytesIO(s), options)
# For Python 3, expects a bytes object
def _unpackb3(s, **options):
"""
Deserialize MessagePack bytes into a Python object.
Args:
s: a 'bytes' or 'bytearray' containing serialized MessagePack bytes
Kwargs:
ext_handlers (dict): dictionary of Ext handlers, mapping integer Ext
type to a callable that unpacks an instance of
Ext into an object
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
unordered dict (default False)
allow_invalid_utf8 (bool): unpack invalid strings into instances of
InvalidString, for access to the bytes
(default False)
Returns:
A Python object.
Raises:
TypeError:
Packed data type is neither 'bytes' nor 'bytearray'.
InsufficientDataException(UnpackException):
Insufficient data to unpack the serialized object.
InvalidStringException(UnpackException):
Invalid UTF-8 string encountered during unpacking.
ReservedCodeException(UnpackException):
Reserved code encountered during unpacking.
UnhashableKeyException(UnpackException):
Unhashable key encountered during map unpacking.
The serialized map cannot be deserialized into a Python dictionary.
DuplicateKeyException(UnpackException):
Duplicate key encountered during map unpacking.
Example:
>>> umsgpack.unpackb(b'\x82\xa7compact\xc3\xa6schema\x00')
{'compact': True, 'schema': 0}
>>>
"""
if not isinstance(s, (bytes, bytearray)):
raise TypeError("packed data must be type 'bytes' or 'bytearray'")
return _unpack(io.BytesIO(s), options)
#############################################################################
# Module Initialization
#############################################################################
def __init():
global pack
global packb
global unpack
global unpackb
global dump
global dumps
global load
global loads
global compatibility
global _float_precision
global _unpack_dispatch_table
global xrange
# Compatibility mode for handling strings/bytes with the old specification
compatibility = False
# Auto-detect system float precision
if sys.float_info.mant_dig == 53:
_float_precision = "double"
else:
_float_precision = "single"
# Map packb and unpackb to the appropriate version
if sys.version_info[0] == 3:
pack = _pack3
packb = _packb3
dump = _pack3
dumps = _packb3
unpack = _unpack3
unpackb = _unpackb3
load = _unpack3
loads = _unpackb3
xrange = range
else:
pack = _pack2
packb = _packb2
dump = _pack2
dumps = _packb2
unpack = _unpack2
unpackb = _unpackb2
load = _unpack2
loads = _unpackb2
# Build a dispatch table for fast lookup of unpacking function
_unpack_dispatch_table = {}
# Fix uint
for code in range(0, 0x7f + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer
# Fix map
for code in range(0x80, 0x8f + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_map
# Fix array
for code in range(0x90, 0x9f + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_array
# Fix str
for code in range(0xa0, 0xbf + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_string
# Nil
_unpack_dispatch_table[b'\xc0'] = _unpack_nil
# Reserved
_unpack_dispatch_table[b'\xc1'] = _unpack_reserved
# Boolean
_unpack_dispatch_table[b'\xc2'] = _unpack_boolean
_unpack_dispatch_table[b'\xc3'] = _unpack_boolean
# Bin
for code in range(0xc4, 0xc6 + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_binary
# Ext
for code in range(0xc7, 0xc9 + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_ext
# Float
_unpack_dispatch_table[b'\xca'] = _unpack_float
_unpack_dispatch_table[b'\xcb'] = _unpack_float
# Uint
for code in range(0xcc, 0xcf + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer
# Int
for code in range(0xd0, 0xd3 + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer
# Fixext
for code in range(0xd4, 0xd8 + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_ext
# String
for code in range(0xd9, 0xdb + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_string
# Array
_unpack_dispatch_table[b'\xdc'] = _unpack_array
_unpack_dispatch_table[b'\xdd'] = _unpack_array
# Map
_unpack_dispatch_table[b'\xde'] = _unpack_map
_unpack_dispatch_table[b'\xdf'] = _unpack_map
# Negative fixint
for code in range(0xe0, 0xff + 1):
_unpack_dispatch_table[struct.pack("B", code)] = _unpack_integer
__init()

40
src/helper_ackPayload.py Normal file
View File

@ -0,0 +1,40 @@
import hashlib
import highlevelcrypto
import random
import helper_random
from binascii import hexlify, unhexlify
from struct import pack, unpack
from addresses import encodeVarint
# This function generates payload objects for message acknowledgements
# Several stealth levels are available depending on the privacy needs;
# a higher level means better stealth, but also higher cost (size+POW)
# - level 0: a random 32-byte sequence with a message header appended
# - level 1: a getpubkey request for a (random) dummy key hash
# - level 2: a standard message, encrypted to a random pubkey
def genAckPayload(streamNumber=1, stealthLevel=0):
if (stealthLevel==2): # Generate privacy-enhanced payload
# Generate a dummy privkey and derive the pubkey
dummyPubKeyHex = highlevelcrypto.privToPub(hexlify(helper_random.randomBytes(32)))
# Generate a dummy message of random length
# (the smallest possible standard-formatted message is 234 bytes)
dummyMessage = helper_random.randomBytes(random.randint(234, 800))
# Encrypt the message using standard BM encryption (ECIES)
ackdata = highlevelcrypto.encrypt(dummyMessage, dummyPubKeyHex)
acktype = 2 # message
version = 1
elif (stealthLevel==1): # Basic privacy payload (random getpubkey)
ackdata = helper_random.randomBytes(32)
acktype = 0 # getpubkey
version = 4
else: # Minimum viable payload (non stealth)
ackdata = helper_random.randomBytes(32)
acktype = 2 # message
version = 1
ackobject = pack('>I', acktype) + encodeVarint(version) + encodeVarint(streamNumber) + ackdata
return ackobject

View File

@ -9,55 +9,69 @@ import knownnodes
import socks
import state
def addKnownNode(stream, peer, lastseen=None, self=False):
if lastseen is None:
lastseen = time.time()
knownnodes.knownNodes[stream][peer] = {
"lastseen": lastseen,
"rating": 0,
"self": self,
}
def knownNodes():
try:
# We shouldn't have to use the knownnodes.knownNodesLock because this had
# better be the only thread accessing knownNodes right now.
pickleFile = open(state.appdata + 'knownnodes.dat', 'rb')
loadedKnownNodes = pickle.load(pickleFile)
pickleFile.close()
# The old format of storing knownNodes was as a 'host: (port, time)'
# mapping. The new format is as 'Peer: time' pairs. If we loaded
# data in the old format, transform it to the new style.
for stream, nodes in loadedKnownNodes.items():
knownnodes.knownNodes[stream] = {}
for node_tuple in nodes.items():
try:
host, (port, lastseen) = node_tuple
peer = state.Peer(host, port)
except:
peer, lastseen = node_tuple
knownnodes.knownNodes[stream][peer] = lastseen
with open(state.appdata + 'knownnodes.dat', 'rb') as pickleFile:
with knownnodes.knownNodesLock:
knownnodes.knownNodes = pickle.load(pickleFile)
# the old format was {Peer:lastseen, ...}
# the new format is {Peer:{"lastseen":i, "rating":f}}
for stream in knownnodes.knownNodes.keys():
for node, params in knownnodes.knownNodes[stream].items():
if isinstance(params, (float, int)):
addKnownNode(stream, node, params)
except:
knownnodes.knownNodes = defaultKnownNodes.createDefaultKnownNodes(state.appdata)
# your own onion address, if setup
if BMConfigParser().has_option('bitmessagesettings', 'onionhostname') and ".onion" in BMConfigParser().get('bitmessagesettings', 'onionhostname'):
knownnodes.knownNodes[1][state.Peer(BMConfigParser().get('bitmessagesettings', 'onionhostname'), BMConfigParser().getint('bitmessagesettings', 'onionport'))] = int(time.time())
addKnownNode(1, state.Peer(BMConfigParser().get('bitmessagesettings', 'onionhostname'), BMConfigParser().getint('bitmessagesettings', 'onionport')), self=True)
if BMConfigParser().getint('bitmessagesettings', 'settingsversion') > 10:
logger.error('Bitmessage cannot read future versions of the keys file (keys.dat). Run the newer version of Bitmessage.')
raise SystemExit
def dns():
# DNS bootstrap. This could be programmed to use the SOCKS proxy to do the
# DNS lookup some day but for now we will just rely on the entries in
# defaultKnownNodes.py. Hopefully either they are up to date or the user
# has run Bitmessage recently without SOCKS turned on and received good
# bootstrap nodes using that method.
if BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'none':
def try_add_known_node(stream, addr, port, method=''):
try:
for item in socket.getaddrinfo('bootstrap8080.bitmessage.org', 80):
logger.info('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method')
knownnodes.knownNodes[1][state.Peer(item[4][0], 8080)] = int(time.time())
except:
logger.error('bootstrap8080.bitmessage.org DNS bootstrapping failed.')
try:
for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80):
logger.info ('Adding ' + item[4][0] + ' to knownNodes based on DNS bootstrap method')
knownnodes.knownNodes[1][state.Peer(item[4][0], 8444)] = int(time.time())
except:
logger.error('bootstrap8444.bitmessage.org DNS bootstrapping failed.')
elif BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'SOCKS5':
knownnodes.knownNodes[1][state.Peer('quzwelsuziwqgpt2.onion', 8444)] = int(time.time())
socket.inet_aton(addr)
except (TypeError, socket.error):
return
logger.info(
'Adding %s to knownNodes based on %s DNS bootstrap method',
addr, method)
addKnownNode(stream, state.Peer(addr, port))
proxy_type = BMConfigParser().get('bitmessagesettings', 'socksproxytype')
if proxy_type == 'none':
for port in [8080, 8444]:
try:
for item in socket.getaddrinfo(
'bootstrap%s.bitmessage.org' % port, 80):
try_add_known_node(1, item[4][0], port)
except:
logger.error(
'bootstrap%s.bitmessage.org DNS bootstrapping failed.',
port, exc_info=True
)
elif proxy_type == 'SOCKS5':
addKnownNode(1, state.Peer('quzwelsuziwqgpt2.onion', 8444))
logger.debug("Adding quzwelsuziwqgpt2.onion:8444 to knownNodes.")
for port in [8080, 8444]:
logger.debug("Resolving %i through SOCKS...", port)
@ -88,9 +102,9 @@ def dns():
except:
logger.error("SOCKS DNS resolving failed", exc_info=True)
else:
if ip is not None:
logger.info ('Adding ' + ip + ' to knownNodes based on SOCKS DNS bootstrap method')
knownnodes.knownNodes[1][state.Peer(ip, port)] = time.time()
try_add_known_node(1, ip, port, 'SOCKS')
else:
logger.info('DNS bootstrap skipped because the proxy type does not support DNS resolution.')
logger.info(
'DNS bootstrap skipped because the proxy type does not support'
' DNS resolution.'
)

View File

@ -4,8 +4,9 @@ import sys
from binascii import hexlify, unhexlify
from multiprocessing import current_process
from threading import current_thread, enumerate
import traceback
from bmconfigparser import BMConfigParser
import shared
from debug import logger
import queues
import shutdown
@ -29,10 +30,20 @@ def convertIntToString(n):
else:
return unhexlify('0' + a[2:])
def convertStringToInt(s):
return int(hexlify(s), 16)
def allThreadTraceback(frame):
id2name = dict([(th.ident, th.name) for th in enumerate()])
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), threadId))
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
if line:
code.append(" %s" % (line.strip()))
print "\n".join(code)
def signal_handler(signal, frame):
logger.error("Got signal %i in %s/%s", signal, current_process().name, current_thread().name)
if current_process().name == "RegExParser":
@ -40,12 +51,13 @@ def signal_handler(signal, frame):
raise SystemExit
if "PoolWorker" in current_process().name:
raise SystemExit
if current_thread().name != "MainThread":
if current_thread().name not in ("PyBitmessage", "MainThread"):
return
logger.error("Got signal %i", signal)
if BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon'):
if shared.thisapp.daemon:
shutdown.doCleanShutdown()
else:
allThreadTraceback(frame)
print 'Unfortunately you cannot use Ctrl+C when running the UI because the UI captures the signal.'
def isHostInPrivateIPRange(host):

View File

@ -1,10 +1,16 @@
#!/usr/bin/python2.7
import msgpack
try:
import msgpack
except ImportError:
try:
import umsgpack as msgpack
except ImportError:
import fallback.umsgpack.umsgpack as msgpack
import string
import zlib
import shared
from bmconfigparser import BMConfigParser
from debug import logger
import messagetypes
from tr import _translate
@ -15,6 +21,19 @@ BITMESSAGE_ENCODING_SIMPLE = 2
BITMESSAGE_ENCODING_EXTENDED = 3
class MsgEncodeException(Exception):
pass
class MsgDecodeException(Exception):
pass
class DecompressionSizeException(MsgDecodeException):
def __init__(self, size):
self.size = size
class MsgEncode(object):
def __init__(self, message, encoding=BITMESSAGE_ENCODING_SIMPLE):
self.data = None
@ -27,7 +46,7 @@ class MsgEncode(object):
elif self.encoding == BITMESSAGE_ENCODING_TRIVIAL:
self.encodeTrivial(message)
else:
raise ValueError("Unknown encoding %i" % (encoding))
raise MsgEncodeException("Unknown encoding %i" % (encoding))
def encodeExtended(self, message):
try:
@ -35,10 +54,10 @@ class MsgEncode(object):
self.data = zlib.compress(msgpack.dumps(msgObj.encode(message)), 9)
except zlib.error:
logger.error("Error compressing message")
raise
raise MsgEncodeException("Error compressing message")
except msgpack.exceptions.PackException:
logger.error("Error msgpacking message")
raise
raise MsgEncodeException("Error msgpacking message")
self.length = len(self.data)
def encodeSimple(self, message):
@ -62,29 +81,42 @@ class MsgDecode(object):
self.subject = _translate("MsgDecode", "Unknown encoding")
def decodeExtended(self, data):
dc = zlib.decompressobj()
tmp = ""
while len(tmp) <= BMConfigParser().safeGetInt("zlib", "maxsize"):
try:
got = dc.decompress(data, BMConfigParser().safeGetInt("zlib", "maxsize") + 1 - len(tmp))
# EOF
if got == "":
break
tmp += got
data = dc.unconsumed_tail
except zlib.error:
logger.error("Error decompressing message")
raise MsgDecodeException("Error decompressing message")
else:
raise DecompressionSizeException(len(tmp))
try:
tmp = msgpack.loads(zlib.decompress(data))
except zlib.error:
logger.error("Error decompressing message")
raise
tmp = msgpack.loads(tmp)
except (msgpack.exceptions.UnpackException,
msgpack.exceptions.ExtraData):
logger.error("Error msgunpacking message")
raise
raise MsgDecodeException("Error msgunpacking message")
try:
msgType = tmp[""]
except KeyError:
logger.error("Message type missing")
raise
raise MsgDecodeException("Message type missing")
msgObj = messagetypes.constructObject(tmp)
if msgObj is None:
raise ValueError("Malformed message")
raise MsgDecodeException("Malformed message")
try:
msgObj.process()
except:
raise ValueError("Malformed message")
raise MsgDecodeException("Malformed message")
if msgType == "message":
self.subject = msgObj.subject
self.body = msgObj.body

9
src/helper_random.py Normal file
View File

@ -0,0 +1,9 @@
import os
from pyelliptic.openssl import OpenSSL
def randomBytes(n):
try:
return os.urandom(n)
except NotImplementedError:
return OpenSSL.rand(n)

View File

@ -21,6 +21,37 @@ def sqlQuery(sqlStatement, *args):
return queryreturn
def sqlExecuteChunked(sqlStatement, idCount, *args):
# SQLITE_MAX_VARIABLE_NUMBER,
# unfortunately getting/setting isn't exposed to python
sqlExecuteChunked.chunkSize = 999
if idCount == 0 or idCount > len(args):
return 0
totalRowCount = 0
with sqlLock:
for i in range(
len(args) - idCount, len(args),
sqlExecuteChunked.chunkSize - (len(args) - idCount)
):
chunk_slice = args[
i:i+sqlExecuteChunked.chunkSize - (len(args) - idCount)
]
sqlSubmitQueue.put(
sqlStatement.format(','.join('?' * len(chunk_slice)))
)
# first static args, and then iterative chunk
sqlSubmitQueue.put(
args[0:len(args)-idCount] + chunk_slice
)
retVal = sqlReturnQueue.get()
totalRowCount += retVal[1]
sqlSubmitQueue.put('commit')
return totalRowCount
def sqlExecute(sqlStatement, *args):
sqlLock.acquire()
sqlSubmitQueue.put(sqlStatement)

View File

@ -1,5 +1,19 @@
from contextlib import contextmanager
import threading
try:
import prctl
def set_thread_name(name): prctl.set_name(name)
def _thread_name_hack(self):
set_thread_name(self.name)
threading.Thread.__bootstrap_original__(self)
threading.Thread.__bootstrap_original__ = threading.Thread._Thread__bootstrap
threading.Thread._Thread__bootstrap = _thread_name_hack
except ImportError:
def set_thread_name(name): threading.current_thread().name = name
class StoppableThread(object):
def initStop(self):
self.stop = threading.Event()
@ -7,4 +21,17 @@ class StoppableThread(object):
def stopThread(self):
self._stopped = True
self.stop.set()
self.stop.set()
class BusyError(threading.ThreadError):
pass
@contextmanager
def nonBlocking(lock):
locked = lock.acquire(False)
if not locked:
raise BusyError
try:
yield
finally:
lock.release()

View File

@ -1,4 +1,5 @@
from binascii import hexlify
from bmconfigparser import BMConfigParser
import pyelliptic
from pyelliptic import arithmetic as a, OpenSSL
def makeCryptor(privkey):
@ -35,8 +36,15 @@ def sign(msg,hexPrivkey):
# upgrade PyBitmessage gracefully.
# https://github.com/yann2192/pyelliptic/pull/33
# More discussion: https://github.com/yann2192/pyelliptic/issues/32
return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.digest_ecdsa_sha1) # SHA1
#return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256) # SHA256. We should switch to this eventually.
digestAlg = BMConfigParser().safeGet('bitmessagesettings', 'digestalg', 'sha1')
if digestAlg == "sha1":
# SHA1, this will eventually be deprecated
return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.digest_ecdsa_sha1)
elif digestAlg == "sha256":
# SHA256. Eventually this will become the default
return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256)
else:
raise ValueError("Unknown digest algorithm %s" % (digestAlg))
# Verifies with hex public key
def verify(msg,sig,hexPubkey):
# As mentioned above, we must upgrade gracefully to use SHA256. So

View File

@ -1,214 +1,91 @@
import collections
from importlib import import_module
from threading import current_thread, enumerate as threadingEnumerate, RLock
import Queue
import time
import sys
from bmconfigparser import BMConfigParser
from helper_sql import *
from singleton import Singleton
# TODO make this dynamic, and watch out for frozen, like with messagetypes
import storage.sqlite
import storage.filesystem
@Singleton
class Inventory(collections.MutableMapping):
class Inventory():
def __init__(self):
super(self.__class__, self).__init__()
self._inventory = {} #of objects (like msg payloads and pubkey payloads) Does not include protocol headers (the first 24 bytes of each packet).
#super(self.__class__, self).__init__()
self._moduleName = BMConfigParser().safeGet("inventory", "storage")
#import_module("." + self._moduleName, "storage")
#import_module("storage." + self._moduleName)
self._className = "storage." + self._moduleName + "." + self._moduleName.title() + "Inventory"
self._inventoryClass = eval(self._className)
self._realInventory = self._inventoryClass()
self.numberOfInventoryLookupsPerformed = 0
self._streams = collections.defaultdict(set) # key = streamNumer, value = a set which holds the inventory object hashes that we are aware of. This is used whenever we receive an inv message from a peer to check to see what items are new to us. We don't delete things out of it; instead, the singleCleaner thread clears and refills it every couple hours.
self.lock = RLock() # Guarantees that two receiveDataThreads don't receive and process the same message concurrently (probably sent by a malicious individual)
self.InventoryItem = collections.namedtuple('InventoryItem', 'type stream payload expires tag')
def __contains__(self, hash):
with self.lock:
self.numberOfInventoryLookupsPerformed += 1
if hash in self._inventory:
return True
return bool(sqlQuery('SELECT 1 FROM inventory WHERE hash=?', hash))
def __getitem__(self, hash):
with self.lock:
if hash in self._inventory:
return self._inventory[hash]
rows = sqlQuery('SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE hash=?', hash)
if not rows:
raise KeyError(hash)
return self.InventoryItem(*rows[0])
def __setitem__(self, hash, value):
with self.lock:
value = self.InventoryItem(*value)
self._inventory[hash] = value
self._streams[value.stream].add(hash)
PendingDownload().delete(hash)
def __delitem__(self, hash):
raise NotImplementedError
def __iter__(self):
with self.lock:
hashes = self._inventory.keys()[:]
hashes += (x for x, in sqlQuery('SELECT hash FROM inventory'))
return hashes.__iter__()
def __len__(self):
with self.lock:
return len(self._inventory) + sqlQuery('SELECT count(*) FROM inventory')[0][0]
def by_type_and_tag(self, type, tag):
with self.lock:
values = [value for value in self._inventory.values() if value.type == type and value.tag == tag]
values += (self.InventoryItem(*value) for value in sqlQuery('SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE objecttype=? AND tag=?', type, tag))
return values
def hashes_by_stream(self, stream):
with self.lock:
return self._streams[stream]
def unexpired_hashes_by_stream(self, stream):
with self.lock:
t = int(time.time())
hashes = [x for x, value in self._inventory.items() if value.stream == stream and value.expires > t]
hashes += (payload for payload, in sqlQuery('SELECT hash FROM inventory WHERE streamnumber=? AND expirestime>?', stream, t))
return hashes
def flush(self):
with self.lock: # If you use both the inventoryLock and the sqlLock, always use the inventoryLock OUTSIDE of the sqlLock.
with SqlBulkExecute() as sql:
for objectHash, value in self._inventory.items():
sql.execute('INSERT INTO inventory VALUES (?, ?, ?, ?, ?, ?)', objectHash, *value)
self._inventory.clear()
def clean(self):
with self.lock:
sqlExecute('DELETE FROM inventory WHERE expirestime<?',int(time.time()) - (60 * 60 * 3))
self._streams.clear()
for objectHash, value in self.items():
self._streams[value.stream].add(objectHash)
@Singleton
class PendingDownload(object):
# keep a track of objects that have been advertised to us but we haven't downloaded them yet
def __init__(self):
super(self.__class__, self).__init__()
self.lock = RLock()
self.hashes = {}
self.stopped = False
# don't request the same object more frequently than this
self.frequency = 60
# after requesting and not receiving an object more than this times, consider it expired
self.maxRequestCount = 3
self.pending = {}
def add(self, objectHash):
if self.stopped:
return
with self.lock:
if objectHash not in self.hashes:
self.hashes[objectHash] = {'peers':[], 'requested':0, 'requestedCount':0}
self.hashes[objectHash]['peers'].append(current_thread().peer)
def addPending(self, objectHash=None):
if self.stopped:
return
if current_thread().peer not in self.pending:
self.pending[current_thread().peer] = {'objects':[], 'requested':0, 'received':0}
if objectHash not in self.pending[current_thread().peer]['objects'] and not objectHash is None:
self.pending[current_thread().peer]['objects'].append(objectHash)
self.pending[current_thread().peer]['requested'] = time.time()
def len(self):
with self.lock:
return sum(1 for x in self.hashes.values() if len(x) > 0)
def pull(self, count=1):
if count < 1:
raise ValueError("Must be at least one")
objectHashes = []
unreachableObjects = []
if self.stopped:
return objectHashes
start = time.time()
# cheap inheritance copied from asyncore
def __getattr__(self, attr):
try:
for objectHash in self.hashes.keys():
with self.lock:
if len(objectHashes) >= count:
break
if current_thread().peer not in self.pending:
self.addPending()
if (self.pending[current_thread().peer]['requested'] >= time.time() - self.frequency or \
self.pending[current_thread().peer]['received'] >= time.time() - self.frequency) and \
len(self.pending[current_thread().peer]['objects']) >= count:
break
if len(self.hashes[objectHash]['peers']) == 0:
unreachableObjects.append(objectHash)
continue
# requested too long ago or not at all from any thread
if self.hashes[objectHash]['requested'] < time.time() - self.frequency:
# ready requested from this thread but haven't received yet
if objectHash in self.pending[current_thread().peer]['objects']:
# if still sending or receiving, request next
if self.pending[current_thread().peer]['received'] >= time.time() - self.frequency or \
self.pending[current_thread().peer]['requested'] >= time.time() - self.frequency:
continue
# haven't requested or received anything recently, re-request (i.e. continue)
# the current node doesn't have the object
elif current_thread().peer not in self.hashes[objectHash]['peers']:
continue
# already requested too many times, remove all signs of this object
if self.hashes[objectHash]['requestedCount'] >= self.maxRequestCount:
del self.hashes[objectHash]
for thread in self.pending.keys():
if objectHash in self.pending[thread]['objects']:
self.pending[thread]['objects'].remove(objectHash)
continue
# all ok, request
objectHashes.append(objectHash)
self.hashes[objectHash]['requested'] = time.time()
self.hashes[objectHash]['requestedCount'] += 1
self.pending[current_thread().peer]['requested'] = time.time()
self.addPending(objectHash)
except (RuntimeError, KeyError, ValueError):
# the for cycle sometimes breaks if you remove elements
if attr == "__contains__":
self.numberOfInventoryLookupsPerformed += 1
realRet = getattr(self._realInventory, attr)
except AttributeError:
raise AttributeError("%s instance has no attribute '%s'" %(self.__class__.__name__, attr))
else:
return realRet
class PendingDownloadQueue(Queue.Queue):
# keep a track of objects that have been advertised to us but we haven't downloaded them yet
maxWait = 300
def __init__(self, maxsize=0):
Queue.Queue.__init__(self, maxsize)
self.stopped = False
self.pending = {}
self.lock = RLock()
def task_done(self, hashId):
Queue.Queue.task_done(self)
try:
with self.lock:
del self.pending[hashId]
except KeyError:
pass
for objectHash in unreachableObjects:
def get(self, block=True, timeout=None):
retval = Queue.Queue.get(self, block, timeout)
# no exception was raised
if not self.stopped:
with self.lock:
if objectHash in self.hashes:
del self.hashes[objectHash]
# logger.debug("Pull took %.3f seconds", time.time() - start)
return objectHashes
self.pending[retval] = time.time()
return retval
def delete(self, objectHash):
def clear(self):
with self.lock:
if objectHash in self.hashes:
del self.hashes[objectHash]
if hasattr(current_thread(), 'peer') and current_thread().peer in self.pending:
self.pending[current_thread().peer]['received'] = time.time()
for thread in self.pending.keys():
with self.lock:
if thread in self.pending and objectHash in self.pending[thread]['objects']:
self.pending[thread]['objects'].remove(objectHash)
newPending = {}
for hashId in self.pending:
if self.pending[hashId] + PendingDownloadQueue.maxWait > time.time():
newPending[hashId] = self.pending[hashId]
self.pending = newPending
def stop(self):
with self.lock:
self.hashes = {}
self.pending = {}
@staticmethod
def totalSize():
size = 0
for thread in threadingEnumerate():
if thread.isAlive() and hasattr(thread, 'downloadQueue'):
size += thread.downloadQueue.qsize() + len(thread.downloadQueue.pending)
return size
def threadEnd(self):
while True:
try:
with self.lock:
if current_thread().peer in self.pending:
for objectHash in self.pending[current_thread().peer]['objects']:
if objectHash in self.hashes:
self.hashes[objectHash]['peers'].remove(current_thread().peer)
except (KeyError):
pass
else:
break
with self.lock:
try:
del self.pending[current_thread().peer]
except KeyError:
pass
@staticmethod
def stop():
for thread in threadingEnumerate():
if thread.isAlive() and hasattr(thread, 'downloadQueue'):
thread.downloadQueue.stopped = True
with thread.downloadQueue.lock:
thread.downloadQueue.pending = {}
class PendingUploadDeadlineException(Exception):
@ -303,7 +180,7 @@ class PendingUpload(object):
self.hashes[objectHash]['sendCount'] += 1
self.hashes[objectHash]['peers'].remove(current_thread().peer)
except KeyError:
pass
pass
self.clearHashes(objectHash)
def stop(self):

View File

@ -1,25 +1,49 @@
import pickle
import os
import threading
from bmconfigparser import BMConfigParser
import state
knownNodesLock = threading.Lock()
knownNodes = {}
knownNodesMax = 20000
knownNodesTrimAmount = 2000
# forget a node after rating is this low
knownNodesForgetRating = -0.5
def saveKnownNodes(dirName = None):
if dirName is None:
dirName = state.appdata
with knownNodesLock:
with open(dirName + 'knownnodes.dat', 'wb') as output:
with open(os.path.join(dirName, 'knownnodes.dat'), 'wb') as output:
pickle.dump(knownNodes, output)
def increaseRating(peer):
increaseAmount = 0.1
maxRating = 1
with knownNodesLock:
for stream in knownNodes.keys():
try:
knownNodes[stream][peer]["rating"] = min(knownNodes[stream][peer]["rating"] + increaseAmount, maxRating)
except KeyError:
pass
def decreaseRating(peer):
decreaseAmount = 0.1
minRating = -1
with knownNodesLock:
for stream in knownNodes.keys():
try:
knownNodes[stream][peer]["rating"] = max(knownNodes[stream][peer]["rating"] - decreaseAmount, minRating)
except KeyError:
pass
def trimKnownNodes(recAddrStream = 1):
if len(knownNodes[recAddrStream]) < knownNodesMax:
if len(knownNodes[recAddrStream]) < int(BMConfigParser().get("knownnodes", "maxnodes")):
return
with knownNodesLock:
oldestList = sorted(knownNodes[recAddrStream], key=knownNodes[recAddrStream].get)[:knownNodesTrimAmount]
oldestList = sorted(knownNodes[recAddrStream], key=lambda x: x['lastseen'])[:knownNodesTrimAmount]
for oldest in oldestList:
del knownNodes[recAddrStream][oldest]

View File

@ -11,10 +11,14 @@ class MsgBase(object):
def constructObject(data):
whitelist = ["message"]
if data[""] not in whitelist:
return None
try:
classBase = eval(data[""] + "." + data[""].title())
except NameError:
logger.error("Don't know how to handle message type: \"%s\"", data[""])
m = import_module("messagetypes." + data[""])
classBase = getattr(m, data[""].title())
except (NameError, ImportError):
logger.error("Don't know how to handle message type: \"%s\"", data[""], exc_info=True)
return None
try:
returnObj = classBase()

37
src/multiqueue.py Normal file
View File

@ -0,0 +1,37 @@
from collections import deque
import Queue
import random
class MultiQueue(Queue.Queue):
defaultQueueCount = 10
def __init__(self, maxsize=0, count=0):
if not count:
self.queueCount = MultiQueue.defaultQueueCount
else:
self.queueCount = count
Queue.Queue.__init__(self, maxsize)
# Initialize the queue representation
def _init(self, maxsize):
self.iter = 0
self.queues = []
for i in range(self.queueCount):
self.queues.append(deque())
def _qsize(self, len=len):
return len(self.queues[self.iter])
# Put a new item in the queue
def _put(self, item):
#self.queue.append(item)
self.queues[random.randrange(self.queueCount)].append((item))
# Get an item from the queue
def _get(self):
return self.queues[self.iter].popleft()
def iterate(self):
self.iter = (self.iter + 1) % self.queueCount
def totalSize(self):
return sum(len(x) for x in self.queues)

View File

@ -129,12 +129,14 @@ class namecoinConnection (object):
# 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):
def test(self):
try:
if self.nmctype == "namecoind":
res = self.callRPC ("getinfo", [])
vers = res["version"]
try:
vers = self.callRPC("getinfo", [])["version"]
except RPCError:
vers = self.callRPC("getnetworkinfo", [])["version"]
v3 = vers % 100
vers = vers / 100
v2 = vers % 100
@ -160,7 +162,11 @@ class namecoinConnection (object):
except Exception:
logger.info("Namecoin connection test failure")
return ('failed', "The connection to namecoin failed.")
return (
'failed',
tr._translate(
"MainWindow", "The connection to namecoin failed.")
)
# Helper routine that actually performs an JSON RPC call.
def callRPC (self, method, params):

36
src/network/addrthread.py Normal file
View File

@ -0,0 +1,36 @@
import Queue
import threading
import addresses
from helper_threading import StoppableThread
from network.connectionpool import BMConnectionPool
from queues import addrQueue
import protocol
import state
class AddrThread(threading.Thread, StoppableThread):
def __init__(self):
threading.Thread.__init__(self, name="AddrBroadcaster")
self.initStop()
self.name = "AddrBroadcaster"
def run(self):
while not state.shutdown:
chunk = []
while True:
try:
data = addrQueue.get(False)
chunk.append((data[0], data[1]))
if len(data) > 2:
source = BMConnectionPool().getConnectionByAddr(data[2])
except Queue.Empty:
break
except KeyError:
continue
#finish
addrQueue.iterate()
for i in range(len(chunk)):
addrQueue.task_done()
self.stop.wait(1)

View File

@ -1,52 +1,131 @@
import asyncore
import socket
import threading
import time
import asyncore_pollchoose as asyncore
from debug import logger
from helper_threading import BusyError, nonBlocking
import state
class AdvancedDispatcher(asyncore.dispatcher):
_buf_len = 131072
_buf_len = 131072 # 128kB
def __init__(self, sock):
asyncore.dispatcher.__init__(self, sock)
self.read_buf = ""
self.write_buf = ""
def __init__(self, sock=None):
if not hasattr(self, '_map'):
asyncore.dispatcher.__init__(self, sock)
self.read_buf = bytearray()
self.write_buf = bytearray()
self.state = "init"
self.lastTx = time.time()
self.sentBytes = 0
self.receivedBytes = 0
self.expectBytes = 0
self.readLock = threading.RLock()
self.writeLock = threading.RLock()
self.processingLock = threading.RLock()
def slice_read_buf(self, length=0):
self.read_buf = self.read_buf[length:]
def append_write_buf(self, data):
if data:
if isinstance(data, list):
with self.writeLock:
for chunk in data:
self.write_buf.extend(chunk)
else:
with self.writeLock:
self.write_buf.extend(data)
def slice_write_buf(self, length=0):
self.write_buf = self.read_buf[length:]
if length > 0:
with self.writeLock:
if length >= len(self.write_buf):
del self.write_buf[:]
else:
del self.write_buf[0:length]
def read_buf_sufficient(self, length=0):
if len(self.read_buf) < length:
return False
else:
return True
def slice_read_buf(self, length=0):
if length > 0:
with self.readLock:
if length >= len(self.read_buf):
del self.read_buf[:]
else:
del self.read_buf[0:length]
def process(self):
if len(self.read_buf) == 0:
return
while True:
while self.connected and not state.shutdown:
try:
if getattr(self, "state_" + str(self.state))() is False:
break
with nonBlocking(self.processingLock):
if not self.connected or state.shutdown:
break
if len(self.read_buf) < self.expectBytes:
return False
if not getattr(self, "state_" + str(self.state))():
break
except AttributeError:
# missing state
logger.error("Unknown state %s", self.state, exc_info=True)
raise
except BusyError:
return False
return False
def set_state(self, state, length):
def set_state(self, state, length=0, expectBytes=0):
self.expectBytes = expectBytes
self.slice_read_buf(length)
self.state = state
def writable(self):
return len(self.write_buf) > 0
self.uploadChunk = AdvancedDispatcher._buf_len
if asyncore.maxUploadRate > 0:
self.uploadChunk = int(asyncore.uploadBucket)
self.uploadChunk = min(self.uploadChunk, len(self.write_buf))
return asyncore.dispatcher.writable(self) and \
(self.connecting or (self.connected and self.uploadChunk > 0))
def readable(self):
return len(self.read_buf) < AdvancedDispatcher._buf_len
self.downloadChunk = AdvancedDispatcher._buf_len
if asyncore.maxDownloadRate > 0:
self.downloadChunk = int(asyncore.downloadBucket)
try:
if self.expectBytes > 0 and not self.fullyEstablished:
self.downloadChunk = min(self.downloadChunk, self.expectBytes - len(self.read_buf))
if self.downloadChunk < 0:
self.downloadChunk = 0
except AttributeError:
pass
return asyncore.dispatcher.readable(self) and \
(self.connecting or self.accepting or (self.connected and self.downloadChunk > 0))
def handle_read(self):
self.read_buf += self.recv(AdvancedDispatcher._buf_len)
self.process()
self.lastTx = time.time()
newData = self.recv(self.downloadChunk)
self.receivedBytes += len(newData)
asyncore.update_received(len(newData))
with self.readLock:
self.read_buf.extend(newData)
def handle_write(self):
written = self.send(self.write_buf)
self.lastTx = time.time()
written = self.send(self.write_buf[0:self.uploadChunk])
asyncore.update_sent(written)
self.sentBytes += written
self.slice_write_buf(written)
# self.process()
def handle_connect_event(self):
try:
asyncore.dispatcher.handle_connect_event(self)
except socket.error as e:
if e.args[0] not in asyncore._DISCONNECTED:
raise
def handle_connect(self):
self.lastTx = time.time()
def state_close(self):
return False
def handle_close(self):
with self.readLock:
self.read_buf = bytearray()
with self.writeLock:
self.write_buf = bytearray()
self.set_state("close")
self.close()

View File

@ -0,0 +1,35 @@
import threading
import time
from bmconfigparser import BMConfigParser
from debug import logger
from helper_threading import StoppableThread
from network.bmproto import BMProto
from network.connectionpool import BMConnectionPool
from network.udp import UDPSocket
import state
class AnnounceThread(threading.Thread, StoppableThread):
def __init__(self):
threading.Thread.__init__(self, name="Announcer")
self.initStop()
self.name = "Announcer"
logger.info("init announce thread")
def run(self):
lastSelfAnnounced = 0
while not self._stopped and state.shutdown == 0:
processed = 0
if lastSelfAnnounced < time.time() - UDPSocket.announceInterval:
self.announceSelf()
lastSelfAnnounced = time.time()
if processed == 0:
self.stop.wait(10)
def announceSelf(self):
for connection in BMConnectionPool().udpSockets.values():
if not connection.announcing:
continue
for stream in state.streamsInWhichIAmParticipating:
addr = (stream, state.Peer('127.0.0.1', BMConfigParser().safeGetInt("bitmessagesettings", "port")), time.time())
connection.append_write_buf(BMProto.assembleAddr([addr]))

View File

@ -0,0 +1,944 @@
# -*- Mode: Python -*-
# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
# Author: Sam Rushing <rushing@nightmare.com>
# ======================================================================
# Copyright 1996 by Sam Rushing
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Sam
# Rushing not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# ======================================================================
"""Basic infrastructure for asynchronous socket service clients and servers.
There are only two ways to have a program on a single processor do "more
than one thing at a time". Multi-threaded programming is the simplest and
most popular way to do it, but there is another very different technique,
that lets you have nearly all the advantages of multi-threading, without
actually using multiple threads. it's really only practical if your program
is largely I/O bound. If your program is CPU bound, then pre-emptive
scheduled threads are probably what you really need. Network servers are
rarely CPU-bound, however.
If your operating system supports the select() system call in its I/O
library (and nearly all do), then you can use it to juggle multiple
communication channels at once; doing other work while your I/O is taking
place in the "background." Although this strategy can seem strange and
complex, especially at first, it is in many ways easier to understand and
control than multi-threaded programming. The module documented here solves
many of the difficult problems for you, making the task of building
sophisticated high-performance network servers and clients a snap.
"""
# randomise object order for bandwidth balancing
import random
import select
import socket
import sys
import time
from threading import current_thread
import warnings
import os
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, EINVAL, \
ENOTCONN, ESHUTDOWN, EISCONN, EBADF, ECONNABORTED, EPIPE, EAGAIN, \
ECONNREFUSED, EHOSTUNREACH, ENETUNREACH, ENOTSOCK, EINTR, ETIMEDOUT, \
EADDRINUSE, \
errorcode
try:
from errno import WSAEWOULDBLOCK
except (ImportError, AttributeError):
WSAEWOULDBLOCK = EWOULDBLOCK
try:
from errno import WSAENOTSOCK
except (ImportError, AttributeError):
WSAENOTSOCK = ENOTSOCK
try:
from errno import WSAECONNRESET
except (ImportError, AttributeError):
WSAECONNRESET = ECONNRESET
try:
from errno import WSAEADDRINUSE
except (ImportError, AttributeError):
WSAEADDRINUSE = EADDRINUSE
_DISCONNECTED = frozenset((ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE,
EBADF, ECONNREFUSED, EHOSTUNREACH, ENETUNREACH, ETIMEDOUT,
WSAECONNRESET))
OP_READ = 1
OP_WRITE = 2
try:
socket_map
except NameError:
socket_map = {}
def _strerror(err):
try:
return os.strerror(err)
except (ValueError, OverflowError, NameError):
if err in errorcode:
return errorcode[err]
return "Unknown error %s" %err
class ExitNow(Exception):
pass
_reraised_exceptions = (ExitNow, KeyboardInterrupt, SystemExit)
maxDownloadRate = 0
downloadTimestamp = 0
downloadBucket = 0
receivedBytes = 0
maxUploadRate = 0
uploadTimestamp = 0
uploadBucket = 0
sentBytes = 0
def read(obj):
if not can_receive():
return
try:
obj.handle_read_event()
except _reraised_exceptions:
raise
except:
obj.handle_error()
def write(obj):
if not can_send():
return
try:
obj.handle_write_event()
except _reraised_exceptions:
raise
except:
obj.handle_error()
def set_rates(download, upload):
global maxDownloadRate, maxUploadRate, downloadBucket, uploadBucket, downloadTimestamp, uploadTimestamp
maxDownloadRate = float(download) * 1024
maxUploadRate = float(upload) * 1024
downloadBucket = maxDownloadRate
uploadBucket = maxUploadRate
downloadTimestamp = time.time()
uploadTimestamp = time.time()
def can_receive():
return maxDownloadRate == 0 or downloadBucket > 0
def can_send():
return maxUploadRate == 0 or uploadBucket > 0
def update_received(download=0):
global receivedBytes, downloadBucket, downloadTimestamp
currentTimestamp = time.time()
receivedBytes += download
if maxDownloadRate > 0:
bucketIncrease = maxDownloadRate * (currentTimestamp - downloadTimestamp)
downloadBucket += bucketIncrease
if downloadBucket > maxDownloadRate:
downloadBucket = int(maxDownloadRate)
downloadBucket -= download
downloadTimestamp = currentTimestamp
def update_sent(upload=0):
global sentBytes, uploadBucket, uploadTimestamp
currentTimestamp = time.time()
sentBytes += upload
if maxUploadRate > 0:
bucketIncrease = maxUploadRate * (currentTimestamp - uploadTimestamp)
uploadBucket += bucketIncrease
if uploadBucket > maxUploadRate:
uploadBucket = int(maxUploadRate)
uploadBucket -= upload
uploadTimestamp = currentTimestamp
def _exception(obj):
try:
obj.handle_expt_event()
except _reraised_exceptions:
raise
except:
obj.handle_error()
def readwrite(obj, flags):
try:
if flags & select.POLLIN and can_receive():
obj.handle_read_event()
if flags & select.POLLOUT and can_send():
obj.handle_write_event()
if flags & select.POLLPRI:
obj.handle_expt_event()
if flags & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
obj.handle_close()
except socket.error as e:
if e.args[0] not in _DISCONNECTED:
obj.handle_error()
else:
obj.handle_close()
except _reraised_exceptions:
raise
except:
obj.handle_error()
def select_poller(timeout=0.0, map=None):
"""A poller which uses select(), available on most platforms."""
if map is None:
map = socket_map
if map:
r = []; w = []; e = []
for fd, obj in list(map.items()):
is_r = obj.readable()
is_w = obj.writable()
if is_r:
r.append(fd)
# accepting sockets should not be writable
if is_w and not obj.accepting:
w.append(fd)
if is_r or is_w:
e.append(fd)
if [] == r == w == e:
time.sleep(timeout)
return
try:
r, w, e = select.select(r, w, e, timeout)
except KeyboardInterrupt:
return
except socket.error as err:
if err.args[0] in (EBADF, EINTR):
return
except Exception as err:
if err.args[0] in (WSAENOTSOCK, ):
return
for fd in random.sample(r, len(r)):
obj = map.get(fd)
if obj is None:
continue
read(obj)
for fd in random.sample(w, len(w)):
obj = map.get(fd)
if obj is None:
continue
write(obj)
for fd in e:
obj = map.get(fd)
if obj is None:
continue
_exception(obj)
else:
current_thread().stop.wait(timeout)
def poll_poller(timeout=0.0, map=None):
"""A poller which uses poll(), available on most UNIXen."""
if map is None:
map = socket_map
if timeout is not None:
# timeout is in milliseconds
timeout = int(timeout*1000)
try:
poll_poller.pollster
except AttributeError:
poll_poller.pollster = select.poll()
if map:
for fd, obj in list(map.items()):
flags = newflags = 0
if obj.readable():
flags |= select.POLLIN | select.POLLPRI
newflags |= OP_READ
else:
newflags &= ~ OP_READ
# accepting sockets should not be writable
if obj.writable() and not obj.accepting:
flags |= select.POLLOUT
newflags |= OP_WRITE
else:
newflags &= ~ OP_WRITE
if newflags != obj.poller_flags:
obj.poller_flags = newflags
try:
if obj.poller_registered:
poll_poller.pollster.modify(fd, flags)
else:
poll_poller.pollster.register(fd, flags)
obj.poller_registered = True
except IOError:
pass
try:
r = poll_poller.pollster.poll(timeout)
except KeyboardInterrupt:
r = []
except socket.error as err:
if err.args[0] in (EBADF, WSAENOTSOCK, EINTR):
return
for fd, flags in random.sample(r, len(r)):
obj = map.get(fd)
if obj is None:
continue
readwrite(obj, flags)
else:
current_thread().stop.wait(timeout)
# Aliases for backward compatibility
poll = select_poller
poll2 = poll3 = poll_poller
def epoll_poller(timeout=0.0, map=None):
"""A poller which uses epoll(), supported on Linux 2.5.44 and newer."""
if map is None:
map = socket_map
try:
epoll_poller.pollster
except AttributeError:
epoll_poller.pollster = select.epoll()
if map:
for fd, obj in map.items():
flags = newflags = 0
if obj.readable():
flags |= select.POLLIN | select.POLLPRI
newflags |= OP_READ
else:
newflags &= ~ OP_READ
# accepting sockets should not be writable
if obj.writable() and not obj.accepting:
flags |= select.POLLOUT
newflags |= OP_WRITE
else:
newflags &= ~ OP_WRITE
if newflags != obj.poller_flags:
obj.poller_flags = newflags
# Only check for exceptions if object was either readable
# or writable.
flags |= select.POLLERR | select.POLLHUP | select.POLLNVAL
try:
if obj.poller_registered:
epoll_poller.pollster.modify(fd, flags)
else:
epoll_poller.pollster.register(fd, flags)
obj.poller_registered = True
except IOError:
pass
try:
r = epoll_poller.pollster.poll(timeout)
except IOError as e:
if e.errno != EINTR:
raise
r = []
except select.error, err:
if err.args[0] != EINTR:
raise
r = []
for fd, flags in random.sample(r, len(r)):
obj = map.get(fd)
if obj is None:
continue
readwrite(obj, flags)
else:
current_thread().stop.wait(timeout)
def kqueue_poller(timeout=0.0, map=None):
"""A poller which uses kqueue(), BSD specific."""
if map is None:
map = socket_map
try:
kqueue_poller.pollster
except AttributeError:
kqueue_poller.pollster = select.kqueue()
if map:
updates = []
selectables = 0
for fd, obj in map.items():
kq_filter = 0
if obj.readable():
kq_filter |= 1
selectables += 1
if obj.writable() and not obj.accepting:
kq_filter |= 2
selectables += 1
if kq_filter != obj.poller_filter:
# unlike other pollers, READ and WRITE aren't OR able but have
# to be set and checked separately
if kq_filter & 1 != obj.poller_filter & 1:
poller_flags = select.KQ_EV_ADD
if kq_filter & 1:
poller_flags |= select.KQ_EV_ENABLE
else:
poller_flags |= select.KQ_EV_DISABLE
updates.append(select.kevent(fd, filter=select.KQ_FILTER_READ, flags=poller_flags))
if kq_filter & 2 != obj.poller_filter & 2:
poller_flags = select.KQ_EV_ADD
if kq_filter & 2:
poller_flags |= select.KQ_EV_ENABLE
else:
poller_flags |= select.KQ_EV_DISABLE
updates.append(select.kevent(fd, filter=select.KQ_FILTER_WRITE, flags=poller_flags))
obj.poller_filter = kq_filter
if not selectables:
# unlike other pollers, kqueue poll does not wait if there are no
# filters setup
current_thread().stop.wait(timeout)
return
events = kqueue_poller.pollster.control(updates, selectables, timeout)
if len(events) > 1:
events = random.sample(events, len(events))
for event in events:
fd = event.ident
obj = map.get(fd)
if obj is None:
continue
if event.flags & select.KQ_EV_ERROR:
_exception(obj)
continue
if event.flags & select.KQ_EV_EOF and event.data and event.fflags:
obj.handle_close()
continue
if event.filter == select.KQ_FILTER_READ:
read(obj)
if event.filter == select.KQ_FILTER_WRITE:
write(obj)
else:
current_thread().stop.wait(timeout)
def loop(timeout=30.0, use_poll=False, map=None, count=None,
poller=None):
if map is None:
map = socket_map
if count is None:
count = True
# code which grants backward compatibility with "use_poll"
# argument which should no longer be used in favor of
# "poller"
if poller is None:
if use_poll:
poller = poll_poller
elif hasattr(select, 'epoll'):
poller = epoll_poller
elif hasattr(select, 'kqueue'):
poller = kqueue_poller
elif hasattr(select, 'poll'):
poller = poll_poller
elif hasattr(select, 'select'):
poller = select_poller
if timeout == 0:
deadline = 0
else:
deadline = time.time() + timeout
while count:
# fill buckets first
update_sent()
update_received()
subtimeout = deadline - time.time()
if subtimeout <= 0:
break
# then poll
poller(subtimeout, map)
if type(count) is int:
count = count - 1
class dispatcher:
debug = False
connected = False
accepting = False
connecting = False
closing = False
addr = None
ignore_log_types = frozenset(['warning'])
poller_registered = False
poller_flags = 0
# don't do network IO with a smaller bucket than this
minTx = 1500
def __init__(self, sock=None, map=None):
if map is None:
self._map = socket_map
else:
self._map = map
self._fileno = None
if sock:
# Set to nonblocking just to make sure for cases where we
# get a socket from a blocking source.
sock.setblocking(0)
self.set_socket(sock, map)
self.connected = True
# The constructor no longer requires that the socket
# passed be connected.
try:
self.addr = sock.getpeername()
except socket.error as err:
if err.args[0] in (ENOTCONN, EINVAL):
# To handle the case where we got an unconnected
# socket.
self.connected = False
else:
# The socket is broken in some unknown way, alert
# the user and remove it from the map (to prevent
# polling of broken sockets).
self.del_channel(map)
raise
else:
self.socket = None
def __repr__(self):
status = [self.__class__.__module__+"."+self.__class__.__name__]
if self.accepting and self.addr:
status.append('listening')
elif self.connected:
status.append('connected')
if self.addr is not None:
try:
status.append('%s:%d' % self.addr)
except TypeError:
status.append(repr(self.addr))
return '<%s at %#x>' % (' '.join(status), id(self))
__str__ = __repr__
def add_channel(self, map=None):
#self.log_info('adding channel %s' % self)
if map is None:
map = self._map
map[self._fileno] = self
self.poller_flags = 0
self.poller_filter = 0
def del_channel(self, map=None):
fd = self._fileno
if map is None:
map = self._map
if fd in map:
#self.log_info('closing channel %d:%s' % (fd, self))
del map[fd]
if self._fileno:
try:
kqueue_poller.pollster.control([select.kevent(fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)], 0)
except (AttributeError, KeyError, TypeError, IOError, OSError):
pass
try:
kqueue_poller.pollster.control([select.kevent(fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)], 0)
except (AttributeError, KeyError, TypeError, IOError, OSError):
pass
try:
epoll_poller.pollster.unregister(fd)
except (AttributeError, KeyError, TypeError, IOError):
# no epoll used, or not registered
pass
try:
poll_poller.pollster.unregister(fd)
except (AttributeError, KeyError, TypeError, IOError):
# no poll used, or not registered
pass
self._fileno = None
self.poller_flags = 0
self.poller_filter = 0
self.poller_registered = False
def create_socket(self, family=socket.AF_INET, socket_type=socket.SOCK_STREAM):
self.family_and_type = family, socket_type
sock = socket.socket(family, socket_type)
sock.setblocking(0)
self.set_socket(sock)
def set_socket(self, sock, map=None):
self.socket = sock
## self.__dict__['socket'] = sock
self._fileno = sock.fileno()
self.add_channel(map)
def set_reuse_addr(self):
# try to re-use a server port if possible
try:
self.socket.setsockopt(
socket.SOL_SOCKET, socket.SO_REUSEADDR,
self.socket.getsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR) | 1
)
except socket.error:
pass
# ==================================================
# predicates for select()
# these are used as filters for the lists of sockets
# to pass to select().
# ==================================================
def readable(self):
if maxDownloadRate > 0:
return downloadBucket > dispatcher.minTx
return True
def writable(self):
if maxUploadRate > 0:
return uploadBucket > dispatcher.minTx
return True
# ==================================================
# socket object methods.
# ==================================================
def listen(self, num):
self.accepting = True
if os.name == 'nt' and num > 5:
num = 5
return self.socket.listen(num)
def bind(self, addr):
self.addr = addr
return self.socket.bind(addr)
def connect(self, address):
self.connected = False
self.connecting = True
err = self.socket.connect_ex(address)
if err in (EINPROGRESS, EALREADY, EWOULDBLOCK, WSAEWOULDBLOCK) \
or err == EINVAL and os.name in ('nt', 'ce'):
self.addr = address
return
if err in (0, EISCONN):
self.addr = address
self.handle_connect_event()
else:
raise socket.error(err, errorcode[err])
def accept(self):
# XXX can return either an address pair or None
try:
conn, addr = self.socket.accept()
except TypeError:
return None
except socket.error as why:
if why.args[0] in (EWOULDBLOCK, WSAEWOULDBLOCK, ECONNABORTED, EAGAIN, ENOTCONN):
return None
else:
raise
else:
return conn, addr
def send(self, data):
try:
result = self.socket.send(data)
return result
except socket.error as why:
if why.args[0] in (EAGAIN, EWOULDBLOCK, WSAEWOULDBLOCK):
return 0
elif why.args[0] in _DISCONNECTED:
self.handle_close()
return 0
else:
raise
def recv(self, buffer_size):
try:
data = self.socket.recv(buffer_size)
if not data:
# a closed connection is indicated by signaling
# a read condition, and having recv() return 0.
self.handle_close()
return b''
else:
return data
except socket.error as why:
# winsock sometimes raises ENOTCONN
if why.args[0] in (EAGAIN, EWOULDBLOCK, WSAEWOULDBLOCK):
return b''
if why.args[0] in _DISCONNECTED:
self.handle_close()
return b''
else:
raise
def close(self):
self.connected = False
self.accepting = False
self.connecting = False
self.del_channel()
try:
self.socket.close()
except socket.error as why:
if why.args[0] not in (ENOTCONN, EBADF):
raise
# cheap inheritance, used to pass all other attribute
# references to the underlying socket object.
def __getattr__(self, attr):
try:
retattr = getattr(self.socket, attr)
except AttributeError:
raise AttributeError("%s instance has no attribute '%s'"
%(self.__class__.__name__, attr))
else:
msg = "%(me)s.%(attr)s is deprecated; use %(me)s.socket.%(attr)s " \
"instead" % {'me' : self.__class__.__name__, 'attr' : attr}
warnings.warn(msg, DeprecationWarning, stacklevel=2)
return retattr
# log and log_info may be overridden to provide more sophisticated
# logging and warning methods. In general, log is for 'hit' logging
# and 'log_info' is for informational, warning and error logging.
def log(self, message):
sys.stderr.write('log: %s\n' % str(message))
def log_info(self, message, log_type='info'):
if log_type not in self.ignore_log_types:
print('%s: %s' % (log_type, message))
def handle_read_event(self):
if self.accepting:
# accepting sockets are never connected, they "spawn" new
# sockets that are connected
self.handle_accept()
elif not self.connected:
if self.connecting:
self.handle_connect_event()
self.handle_read()
else:
self.handle_read()
def handle_connect_event(self):
err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
if err != 0:
raise socket.error(err, _strerror(err))
self.handle_connect()
self.connected = True
self.connecting = False
def handle_write_event(self):
if self.accepting:
# Accepting sockets shouldn't get a write event.
# We will pretend it didn't happen.
return
if not self.connected:
if self.connecting:
self.handle_connect_event()
self.handle_write()
def handle_expt_event(self):
# handle_expt_event() is called if there might be an error on the
# socket, or if there is OOB data
# check for the error condition first
err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
if err != 0:
# we can get here when select.select() says that there is an
# exceptional condition on the socket
# since there is an error, we'll go ahead and close the socket
# like we would in a subclassed handle_read() that received no
# data
self.handle_close()
elif sys.platform.startswith("win"):
# async connect failed
self.handle_close()
else:
self.handle_expt()
def handle_error(self):
nil, t, v, tbinfo = compact_traceback()
# sometimes a user repr method will crash.
try:
self_repr = repr(self)
except:
self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
self.log_info(
'uncaptured python exception, closing channel %s (%s:%s %s)' % (
self_repr,
t,
v,
tbinfo
),
'error'
)
self.handle_close()
def handle_expt(self):
self.log_info('unhandled incoming priority event', 'warning')
def handle_read(self):
self.log_info('unhandled read event', 'warning')
def handle_write(self):
self.log_info('unhandled write event', 'warning')
def handle_connect(self):
self.log_info('unhandled connect event', 'warning')
def handle_accept(self):
pair = self.accept()
if pair is not None:
self.handle_accepted(*pair)
def handle_accepted(self, sock, addr):
sock.close()
self.log_info('unhandled accepted event on %s' % (addr), 'warning')
def handle_close(self):
self.log_info('unhandled close event', 'warning')
self.close()
# ---------------------------------------------------------------------------
# adds simple buffered output capability, useful for simple clients.
# [for more sophisticated usage use asynchat.async_chat]
# ---------------------------------------------------------------------------
class dispatcher_with_send(dispatcher):
def __init__(self, sock=None, map=None):
dispatcher.__init__(self, sock, map)
self.out_buffer = b''
def initiate_send(self):
num_sent = 0
num_sent = dispatcher.send(self, self.out_buffer[:512])
self.out_buffer = self.out_buffer[num_sent:]
def handle_write(self):
self.initiate_send()
def writable(self):
return (not self.connected) or len(self.out_buffer)
def send(self, data):
if self.debug:
self.log_info('sending %s' % repr(data))
self.out_buffer = self.out_buffer + data
self.initiate_send()
# ---------------------------------------------------------------------------
# used for debugging.
# ---------------------------------------------------------------------------
def compact_traceback():
t, v, tb = sys.exc_info()
tbinfo = []
if not tb: # Must have a traceback
raise AssertionError("traceback does not exist")
while tb:
tbinfo.append((
tb.tb_frame.f_code.co_filename,
tb.tb_frame.f_code.co_name,
str(tb.tb_lineno)
))
tb = tb.tb_next
# just to be safe
del tb
file, function, line = tbinfo[-1]
info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
return (file, function, line), t, v, info
def close_all(map=None, ignore_all=False):
if map is None:
map = socket_map
for x in list(map.values()):
try:
x.close()
except OSError as e:
if e.args[0] == EBADF:
pass
elif not ignore_all:
raise
except _reraised_exceptions:
raise
except:
if not ignore_all:
raise
map.clear()
# Asynchronous File I/O:
#
# After a little research (reading man pages on various unixen, and
# digging through the linux kernel), I've determined that select()
# isn't meant for doing asynchronous file i/o.
# Heartening, though - reading linux/mm/filemap.c shows that linux
# supports asynchronous read-ahead. So _MOST_ of the time, the data
# will be sitting in memory for us already when we go to read it.
#
# What other OS's (besides NT) support async file i/o? [VMS?]
#
# Regardless, this is useful for pipes, and stdin/stdout...
if os.name == 'posix':
import fcntl
class file_wrapper:
# Here we override just enough to make a file
# look like a socket for the purposes of asyncore.
# The passed fd is automatically os.dup()'d
def __init__(self, fd):
self.fd = os.dup(fd)
def recv(self, *args):
return os.read(self.fd, *args)
def send(self, *args):
return os.write(self.fd, *args)
def getsockopt(self, level, optname, buflen=None):
if (level == socket.SOL_SOCKET and
optname == socket.SO_ERROR and
not buflen):
return 0
raise NotImplementedError("Only asyncore specific behaviour "
"implemented.")
read = recv
write = send
def close(self):
os.close(self.fd)
def fileno(self):
return self.fd
class file_dispatcher(dispatcher):
def __init__(self, fd, map=None):
dispatcher.__init__(self, None, map)
self.connected = True
try:
fd = fd.fileno()
except AttributeError:
pass
self.set_file(fd)
# set it to non-blocking mode
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
flags = flags | os.O_NONBLOCK
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
def set_file(self, fd):
self.socket = file_wrapper(fd)
self._fileno = self.socket.fileno()
self.add_channel()

113
src/network/bmobject.py Normal file
View File

@ -0,0 +1,113 @@
from binascii import hexlify
import time
from addresses import calculateInventoryHash
from debug import logger
from inventory import Inventory
from network.dandelion import Dandelion
import protocol
import state
class BMObjectInsufficientPOWError(Exception):
errorCodes = ("Insufficient proof of work")
class BMObjectInvalidDataError(Exception):
errorCodes = ("Data invalid")
class BMObjectExpiredError(Exception):
errorCodes = ("Object expired")
class BMObjectUnwantedStreamError(Exception):
errorCodes = ("Object in unwanted stream")
class BMObjectInvalidError(Exception):
errorCodes = ("Invalid object")
class BMObjectAlreadyHaveError(Exception):
errorCodes = ("Already have this object")
class BMObject(object):
# max TTL, 28 days and 3 hours
maxTTL = 28 * 24 * 60 * 60 + 10800
# min TTL, 3 hour (in the past
minTTL = -3600
def __init__(self, nonce, expiresTime, objectType, version, streamNumber, data, payloadOffset):
self.nonce = nonce
self.expiresTime = expiresTime
self.objectType = objectType
self.version = version
self.streamNumber = streamNumber
self.inventoryHash = calculateInventoryHash(data)
# copy to avoid memory issues
self.data = bytearray(data)
self.tag = self.data[payloadOffset:payloadOffset+32]
def checkProofOfWorkSufficient(self):
# Let us check to make sure that the proof of work is sufficient.
if not protocol.isProofOfWorkSufficient(self.data):
logger.info('Proof of work is insufficient.')
raise BMObjectInsufficientPOWError()
def checkEOLSanity(self):
# EOL sanity check
if self.expiresTime - int(time.time()) > BMObject.maxTTL:
logger.info('This object\'s End of Life time is too far in the future. Ignoring it. Time is %i', self.expiresTime)
# TODO: remove from download queue
raise BMObjectExpiredError()
if self.expiresTime - int(time.time()) < BMObject.minTTL:
logger.info('This object\'s End of Life time was too long ago. Ignoring the object. Time is %i', self.expiresTime)
# TODO: remove from download queue
raise BMObjectExpiredError()
def checkStream(self):
if self.streamNumber not in state.streamsInWhichIAmParticipating:
logger.debug('The streamNumber %i isn\'t one we are interested in.', self.streamNumber)
raise BMObjectUnwantedStreamError()
def checkAlreadyHave(self):
# if it's a stem duplicate, pretend we don't have it
if Dandelion().hasHash(self.inventoryHash):
return
if self.inventoryHash in Inventory():
raise BMObjectAlreadyHaveError()
def checkObjectByType(self):
if self.objectType == protocol.OBJECT_GETPUBKEY:
self.checkGetpubkey()
elif self.objectType == protocol.OBJECT_PUBKEY:
self.checkPubkey()
elif self.objectType == protocol.OBJECT_MSG:
self.checkMessage()
elif self.objectType == protocol.OBJECT_BROADCAST:
self.checkBroadcast()
# other objects don't require other types of tests
def checkMessage(self):
return
def checkGetpubkey(self):
if len(self.data) < 42:
logger.info('getpubkey message doesn\'t contain enough data. Ignoring.')
raise BMObjectInvalidError()
def checkPubkey(self):
if len(self.data) < 146 or len(self.data) > 440: # sanity check
logger.info('pubkey object too short or too long. Ignoring.')
raise BMObjectInvalidError()
def checkBroadcast(self):
if len(self.data) < 180:
logger.debug('The payload length of this broadcast packet is unreasonably low. Someone is probably trying funny business. Ignoring message.')
raise BMObjectInvalidError()
# this isn't supported anymore
if self.version < 2:
raise BMObjectInvalidError()

572
src/network/bmproto.py Normal file
View File

@ -0,0 +1,572 @@
import base64
import hashlib
import random
import socket
import struct
import time
from bmconfigparser import BMConfigParser
from debug import logger
from inventory import Inventory
import knownnodes
from network.advanceddispatcher import AdvancedDispatcher
from network.dandelion import Dandelion
from network.bmobject import BMObject, BMObjectInsufficientPOWError, BMObjectInvalidDataError, \
BMObjectExpiredError, BMObjectUnwantedStreamError, BMObjectInvalidError, BMObjectAlreadyHaveError
import network.connectionpool
from network.node import Node
from network.objectracker import ObjectTracker
from network.proxy import Proxy, ProxyError, GeneralProxyError
import addresses
from queues import objectProcessorQueue, portCheckerQueue, invQueue, addrQueue
import shared
import state
import protocol
class BMProtoError(ProxyError):
errorCodes = ("Protocol error")
class BMProtoInsufficientDataError(BMProtoError):
errorCodes = ("Insufficient data")
class BMProtoExcessiveDataError(BMProtoError):
errorCodes = ("Too much data")
class BMProto(AdvancedDispatcher, ObjectTracker):
# ~1.6 MB which is the maximum possible size of an inv message.
maxMessageSize = 1600100
# 2**18 = 256kB is the maximum size of an object payload
maxObjectPayloadSize = 2**18
# protocol specification says max 1000 addresses in one addr command
maxAddrCount = 1000
# protocol specification says max 50000 objects in one inv command
maxObjectCount = 50000
# address is online if online less than this many seconds ago
addressAlive = 10800
# maximum time offset
maxTimeOffset = 3600
def __init__(self, address=None, sock=None):
AdvancedDispatcher.__init__(self, sock)
self.isOutbound = False
# packet/connection from a local IP
self.local = False
def bm_proto_reset(self):
self.magic = None
self.command = None
self.payloadLength = 0
self.checksum = None
self.payload = None
self.invalid = False
self.payloadOffset = 0
self.expectBytes = protocol.Header.size
self.object = None
def state_bm_header(self):
self.magic, self.command, self.payloadLength, self.checksum = protocol.Header.unpack(self.read_buf[:protocol.Header.size])
self.command = self.command.rstrip('\x00')
if self.magic != 0xE9BEB4D9:
# skip 1 byte in order to sync
self.set_state("bm_header", length=1)
self.bm_proto_reset()
logger.debug("Bad magic")
if self.socket.type == socket.SOCK_STREAM:
self.close_reason = "Bad magic"
self.set_state("close")
return False
if self.payloadLength > BMProto.maxMessageSize:
self.invalid = True
self.set_state("bm_command", length=protocol.Header.size, expectBytes=self.payloadLength)
return True
def state_bm_command(self):
self.payload = self.read_buf[:self.payloadLength]
if self.checksum != hashlib.sha512(self.payload).digest()[0:4]:
logger.debug("Bad checksum, ignoring")
self.invalid = True
retval = True
if not self.fullyEstablished and self.command not in ("error", "version", "verack"):
logger.error("Received command %s before connection was fully established, ignoring", self.command)
self.invalid = True
if not self.invalid:
try:
retval = getattr(self, "bm_command_" + str(self.command).lower())()
except AttributeError:
# unimplemented command
logger.debug("unimplemented command %s", self.command)
except BMProtoInsufficientDataError:
logger.debug("packet length too short, skipping")
except BMProtoExcessiveDataError:
logger.debug("too much data, skipping")
except BMObjectInsufficientPOWError:
logger.debug("insufficient PoW, skipping")
except BMObjectInvalidDataError:
logger.debug("object invalid data, skipping")
except BMObjectExpiredError:
logger.debug("object expired, skipping")
except BMObjectUnwantedStreamError:
logger.debug("object not in wanted stream, skipping")
except BMObjectInvalidError:
logger.debug("object invalid, skipping")
except BMObjectAlreadyHaveError:
logger.debug("%s:%i already got object, skipping", self.destination.host, self.destination.port)
except struct.error:
logger.debug("decoding error, skipping")
elif self.socket.type == socket.SOCK_DGRAM:
# broken read, ignore
pass
else:
#print "Skipping command %s due to invalid data" % (self.command)
logger.debug("Closing due to invalid command %s", self.command)
self.close_reason = "Invalid command %s" % (self.command)
self.set_state("close")
return False
if retval:
self.set_state("bm_header", length=self.payloadLength)
self.bm_proto_reset()
# else assume the command requires a different state to follow
return True
def decode_payload_string(self, length):
value = self.payload[self.payloadOffset:self.payloadOffset+length]
self.payloadOffset += length
return value
def decode_payload_varint(self):
value, offset = addresses.decodeVarint(self.payload[self.payloadOffset:])
self.payloadOffset += offset
return value
def decode_payload_node(self):
services, host, port = self.decode_payload_content("Q16sH")
if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
host = socket.inet_ntop(socket.AF_INET, str(host[12:16]))
elif host[0:6] == '\xfd\x87\xd8\x7e\xeb\x43':
# Onion, based on BMD/bitcoind
host = base64.b32encode(host[6:]).lower() + ".onion"
else:
host = socket.inet_ntop(socket.AF_INET6, str(host))
if host == "":
# This can happen on Windows systems which are not 64-bit compatible
# so let us drop the IPv6 address.
host = socket.inet_ntop(socket.AF_INET, str(host[12:16]))
return Node(services, host, port)
def decode_payload_content(self, pattern = "v"):
# L = varint indicating the length of the next array
# l = varint indicating the length of the next item
# v = varint (or array)
# H = uint16
# I = uint32
# Q = uint64
# i = net_addr (without time and stream number)
# s = string
# 0-9 = length of the next item
# , = end of array
def decode_simple(self, char="v"):
if char == "v":
return self.decode_payload_varint()
if char == "i":
return self.decode_payload_node()
if char == "H":
self.payloadOffset += 2
return struct.unpack(">H", self.payload[self.payloadOffset-2:self.payloadOffset])[0]
if char == "I":
self.payloadOffset += 4
return struct.unpack(">I", self.payload[self.payloadOffset-4:self.payloadOffset])[0]
if char == "Q":
self.payloadOffset += 8
return struct.unpack(">Q", self.payload[self.payloadOffset-8:self.payloadOffset])[0]
size = None
isArray = False
# size
# iterator starting from size counting to 0
# isArray?
# subpattern
# position of parser in subpattern
# retval (array)
parserStack = [[1, 1, False, pattern, 0, []]]
#try:
# sys._getframe(200)
# logger.error("Stack depth warning, pattern: %s", pattern)
# return
#except ValueError:
# pass
while True:
i = parserStack[-1][3][parserStack[-1][4]]
if i in "0123456789" and (size is None or parserStack[-1][3][parserStack[-1][4]-1] not in "lL"):
try:
size = size * 10 + int(i)
except TypeError:
size = int(i)
isArray = False
elif i in "Ll" and size is None:
size = self.decode_payload_varint()
if i == "L":
isArray = True
else:
isArray = False
elif size is not None:
if isArray:
parserStack.append([size, size, isArray, parserStack[-1][3][parserStack[-1][4]:], 0, []])
parserStack[-2][4] = len(parserStack[-2][3])
else:
for j in range(parserStack[-1][4], len(parserStack[-1][3])):
if parserStack[-1][3][j] not in "lL0123456789":
break
parserStack.append([size, size, isArray, parserStack[-1][3][parserStack[-1][4]:j+1], 0, []])
parserStack[-2][4] += len(parserStack[-1][3]) - 1
size = None
continue
elif i == "s":
#if parserStack[-2][2]:
# parserStack[-1][5].append(self.payload[self.payloadOffset:self.payloadOffset + parserStack[-1][0]])
#else:
parserStack[-1][5] = self.payload[self.payloadOffset:self.payloadOffset + parserStack[-1][0]]
self.payloadOffset += parserStack[-1][0]
parserStack[-1][1] = 0
parserStack[-1][2] = True
#del parserStack[-1]
size = None
elif i in "viHIQ":
parserStack[-1][5].append(decode_simple(self, parserStack[-1][3][parserStack[-1][4]]))
size = None
else:
size = None
for depth in range(len(parserStack) - 1, -1, -1):
parserStack[depth][4] += 1
if parserStack[depth][4] >= len(parserStack[depth][3]):
parserStack[depth][1] -= 1
parserStack[depth][4] = 0
if depth > 0:
if parserStack[depth][2]:
parserStack[depth - 1][5].append(parserStack[depth][5])
else:
parserStack[depth - 1][5].extend(parserStack[depth][5])
parserStack[depth][5] = []
if parserStack[depth][1] <= 0:
if depth == 0:
# we're done, at depth 0 counter is at 0 and pattern is done parsing
return parserStack[depth][5]
del parserStack[-1]
continue
break
break
if self.payloadOffset > self.payloadLength:
logger.debug("Insufficient data %i/%i", self.payloadOffset, self.payloadLength)
raise BMProtoInsufficientDataError()
def bm_command_error(self):
fatalStatus, banTime, inventoryVector, errorText = self.decode_payload_content("vvlsls")
logger.error("%s:%i error: %i, %s", self.destination.host, self.destination.port, fatalStatus, errorText)
return True
def bm_command_getdata(self):
items = self.decode_payload_content("l32s")
# skip?
if time.time() < self.skipUntil:
return True
#TODO make this more asynchronous
random.shuffle(items)
for i in map(str, items):
if Dandelion().hasHash(i) and \
self != Dandelion().objectChildStem(i):
self.antiIntersectionDelay()
logger.info('%s asked for a stem object we didn\'t offer to it.', self.destination)
break
else:
try:
self.append_write_buf(protocol.CreatePacket('object', Inventory()[i].payload))
except KeyError:
self.antiIntersectionDelay()
logger.info('%s asked for an object we don\'t have.', self.destination)
break
# I think that aborting after the first missing/stem object is more secure
# when using random reordering, as the recipient won't know exactly which objects we refuse to deliver
return True
def _command_inv(self, dandelion=False):
items = self.decode_payload_content("l32s")
if len(items) >= BMProto.maxObjectCount:
logger.error("Too many items in %sinv message!", "d" if dandelion else "")
raise BMProtoExcessiveDataError()
else:
pass
# ignore dinv if dandelion turned off
if dandelion and not state.dandelion:
return True
for i in map(str, items):
if i in Inventory() and not Dandelion().hasHash(i):
continue
if dandelion and not Dandelion().hasHash(i):
Dandelion().addHash(i, self)
self.handleReceivedInventory(i)
return True
def bm_command_inv(self):
return self._command_inv(False)
def bm_command_dinv(self):
"""
Dandelion stem announce
"""
return self._command_inv(True)
def bm_command_object(self):
objectOffset = self.payloadOffset
nonce, expiresTime, objectType, version, streamNumber = self.decode_payload_content("QQIvv")
self.object = BMObject(nonce, expiresTime, objectType, version, streamNumber, self.payload, self.payloadOffset)
if len(self.payload) - self.payloadOffset > BMProto.maxObjectPayloadSize:
logger.info('The payload length of this object is too large (%s bytes). Ignoring it.' % len(self.payload) - self.payloadOffset)
raise BMProtoExcessiveDataError()
try:
self.object.checkProofOfWorkSufficient()
self.object.checkEOLSanity()
self.object.checkAlreadyHave()
except (BMObjectExpiredError, BMObjectAlreadyHaveError, BMObjectInsufficientPOWError) as e:
BMProto.stopDownloadingObject(self.object.inventoryHash)
raise e
try:
self.object.checkStream()
except (BMObjectUnwantedStreamError,) as e:
BMProto.stopDownloadingObject(self.object.inventoryHash, BMConfigParser().get("inventory", "acceptmismatch"))
if not BMConfigParser().get("inventory", "acceptmismatch"):
raise e
try:
self.object.checkObjectByType()
objectProcessorQueue.put((self.object.objectType, buffer(self.object.data)))
except BMObjectInvalidError as e:
BMProto.stopDownloadingObject(self.object.inventoryHash, True)
else:
try:
del state.missingObjects[self.object.inventoryHash]
except KeyError:
pass
if self.object.inventoryHash in Inventory() and Dandelion().hasHash(self.object.inventoryHash):
Dandelion().removeHash(self.object.inventoryHash, "cycle detection")
Inventory()[self.object.inventoryHash] = (
self.object.objectType, self.object.streamNumber, buffer(self.payload[objectOffset:]), self.object.expiresTime, buffer(self.object.tag))
self.handleReceivedObject(self.object.streamNumber, self.object.inventoryHash)
invQueue.put((self.object.streamNumber, self.object.inventoryHash, self.destination))
return True
def _decode_addr(self):
return self.decode_payload_content("LQIQ16sH")
def bm_command_addr(self):
addresses = self._decode_addr()
for i in addresses:
seenTime, stream, services, ip, port = i
decodedIP = protocol.checkIPAddress(str(ip))
if stream not in state.streamsInWhichIAmParticipating:
continue
if decodedIP is not False and seenTime > time.time() - BMProto.addressAlive:
peer = state.Peer(decodedIP, port)
try:
if knownnodes.knownNodes[stream][peer]["lastseen"] > seenTime:
continue
except KeyError:
pass
if len(knownnodes.knownNodes[stream]) < int(BMConfigParser().get("knownnodes", "maxnodes")):
with knownnodes.knownNodesLock:
try:
knownnodes.knownNodes[stream][peer]["lastseen"] = seenTime
except (TypeError, KeyError):
knownnodes.knownNodes[stream][peer] = {
"lastseen": seenTime,
"rating": 0,
"self": False,
}
addrQueue.put((stream, peer, self.destination))
return True
def bm_command_portcheck(self):
portCheckerQueue.put(state.Peer(self.destination, self.peerNode.port))
return True
def bm_command_ping(self):
self.append_write_buf(protocol.CreatePacket('pong'))
return True
def bm_command_pong(self):
# nothing really
return True
def bm_command_verack(self):
self.verackReceived = True
if self.verackSent:
if self.isSSL:
self.set_state("tls_init", length=self.payloadLength, expectBytes=0)
return False
self.set_state("connection_fully_established", length=self.payloadLength, expectBytes=0)
return False
return True
def bm_command_version(self):
self.remoteProtocolVersion, self.services, self.timestamp, self.sockNode, self.peerNode, self.nonce, \
self.userAgent, self.streams = self.decode_payload_content("IQQiiQlsLv")
self.nonce = struct.pack('>Q', self.nonce)
self.timeOffset = self.timestamp - int(time.time())
logger.debug("remoteProtocolVersion: %i", self.remoteProtocolVersion)
logger.debug("services: 0x%08X", self.services)
logger.debug("time offset: %i", self.timestamp - int(time.time()))
logger.debug("my external IP: %s", self.sockNode.host)
logger.debug("remote node incoming address: %s:%i", self.destination.host, self.peerNode.port)
logger.debug("user agent: %s", self.userAgent)
logger.debug("streams: [%s]", ",".join(map(str,self.streams)))
if not self.peerValidityChecks():
# TODO ABORT
return True
#shared.connectedHostsList[self.destination] = self.streams[0]
self.append_write_buf(protocol.CreatePacket('verack'))
self.verackSent = True
if not self.isOutbound:
self.append_write_buf(protocol.assembleVersionMessage(self.destination.host, self.destination.port, \
network.connectionpool.BMConnectionPool().streams, True, nodeid=self.nodeid))
#print "%s:%i: Sending version" % (self.destination.host, self.destination.port)
if ((self.services & protocol.NODE_SSL == protocol.NODE_SSL) and
protocol.haveSSL(not self.isOutbound)):
self.isSSL = True
if self.verackReceived:
if self.isSSL:
self.set_state("tls_init", length=self.payloadLength, expectBytes=0)
return False
self.set_state("connection_fully_established", length=self.payloadLength, expectBytes=0)
return False
return True
def peerValidityChecks(self):
if self.remoteProtocolVersion < 3:
self.append_write_buf(protocol.assembleErrorMessage(fatal=2,
errorText="Your is using an old protocol. Closing connection."))
logger.debug ('Closing connection to old protocol version %s, node: %s',
str(self.remoteProtocolVersion), str(self.destination))
return False
if self.timeOffset > BMProto.maxTimeOffset:
self.append_write_buf(protocol.assembleErrorMessage(fatal=2,
errorText="Your time is too far in the future compared to mine. Closing connection."))
logger.info("%s's time is too far in the future (%s seconds). Closing connection to it.",
self.destination, self.timeOffset)
shared.timeOffsetWrongCount += 1
return False
elif self.timeOffset < -BMProto.maxTimeOffset:
self.append_write_buf(protocol.assembleErrorMessage(fatal=2,
errorText="Your time is too far in the past compared to mine. Closing connection."))
logger.info("%s's time is too far in the past (timeOffset %s seconds). Closing connection to it.",
self.destination, self.timeOffset)
shared.timeOffsetWrongCount += 1
return False
else:
shared.timeOffsetWrongCount = 0
if not self.streams:
self.append_write_buf(protocol.assembleErrorMessage(fatal=2,
errorText="We don't have shared stream interests. Closing connection."))
logger.debug ('Closed connection to %s because there is no overlapping interest in streams.',
str(self.destination))
return False
if self.destination in network.connectionpool.BMConnectionPool().inboundConnections:
try:
if not protocol.checkSocksIP(self.destination.host):
self.append_write_buf(protocol.assembleErrorMessage(fatal=2,
errorText="Too many connections from your IP. Closing connection."))
logger.debug ('Closed connection to %s because we are already connected to that IP.',
str(self.destination))
return False
except:
pass
if not self.isOutbound:
# incoming from a peer we're connected to as outbound, or server full
# report the same error to counter deanonymisation
if state.Peer(self.destination.host, self.peerNode.port) in \
network.connectionpool.BMConnectionPool().inboundConnections or \
len(network.connectionpool.BMConnectionPool().inboundConnections) + \
len(network.connectionpool.BMConnectionPool().outboundConnections) > \
BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections") + \
BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections"):
self.append_write_buf(protocol.assembleErrorMessage(fatal=2,
errorText="Server full, please try again later."))
logger.debug ("Closed connection to %s due to server full or duplicate inbound/outbound.",
str(self.destination))
return False
if network.connectionpool.BMConnectionPool().isAlreadyConnected(self.nonce):
self.append_write_buf(protocol.assembleErrorMessage(fatal=2,
errorText="I'm connected to myself. Closing connection."))
logger.debug ("Closed connection to %s because I'm connected to myself.",
str(self.destination))
return False
return True
@staticmethod
def assembleAddr(peerList):
if isinstance(peerList, state.Peer):
peerList = (peerList)
if not peerList:
return b''
retval = b''
for i in range(0, len(peerList), BMProto.maxAddrCount):
payload = addresses.encodeVarint(len(peerList[i:i + BMProto.maxAddrCount]))
for address in peerList[i:i + BMProto.maxAddrCount]:
stream, peer, timestamp = address
payload += struct.pack(
'>Q', timestamp) # 64-bit time
payload += struct.pack('>I', stream)
payload += struct.pack(
'>q', 1) # service bit flags offered by this node
payload += protocol.encodeHost(peer.host)
payload += struct.pack('>H', peer.port) # remote port
retval += protocol.CreatePacket('addr', payload)
return retval
@staticmethod
def stopDownloadingObject(hashId, forwardAnyway=False):
for connection in network.connectionpool.BMConnectionPool().inboundConnections.values() + \
network.connectionpool.BMConnectionPool().outboundConnections.values():
try:
del connection.objectsNewToMe[hashId]
except KeyError:
pass
if not forwardAnyway:
try:
with connection.objectsNewToThemLock:
del connection.objectsNewToThem[hashId]
except KeyError:
pass
try:
del state.missingObjects[hashId]
except KeyError:
pass
def handle_close(self):
self.set_state("close")
if not (self.accepting or self.connecting or self.connected):
# already disconnected
return
try:
logger.debug("%s:%i: closing, %s", self.destination.host, self.destination.port, self.close_reason)
except AttributeError:
try:
logger.debug("%s:%i: closing", self.destination.host, self.destination.port)
except AttributeError:
logger.debug("Disconnected socket closing")
AdvancedDispatcher.handle_close(self)

View File

@ -0,0 +1,58 @@
from queues import Queue
import random
from bmconfigparser import BMConfigParser
import knownnodes
import protocol
from queues import portCheckerQueue
import state
def getDiscoveredPeer():
try:
peer = random.choice(state.discoveredPeers.keys())
except (IndexError, KeyError):
raise ValueError
try:
del state.discoveredPeers[peer]
except KeyError:
pass
return peer
def chooseConnection(stream):
haveOnion = BMConfigParser().safeGet("bitmessagesettings", "socksproxytype")[0:5] == 'SOCKS'
if state.trustedPeer:
return state.trustedPeer
try:
retval = portCheckerQueue.get(False)
portCheckerQueue.task_done()
return retval
except Queue.Empty:
pass
# with a probability of 0.5, connect to a discovered peer
if random.choice((False, True)) and not haveOnion:
# discovered peers are already filtered by allowed streams
return getDiscoveredPeer()
for _ in range(50):
peer = random.choice(knownnodes.knownNodes[stream].keys())
try:
rating = knownnodes.knownNodes[stream][peer]["rating"]
except TypeError:
print "Error in %s" % (peer)
rating = 0
if haveOnion:
# onion addresses have a higher priority when SOCKS
if peer.host.endswith('.onion') and rating > 0:
rating = 1
else:
encodedAddr = protocol.encodeHost(peer.host)
# don't connect to local IPs when using SOCKS
if not protocol.checkIPAddress(encodedAddr, False):
continue
if rating > 1:
rating = 1
try:
if 0.05/(1.0-rating) > random.random():
return peer
except ZeroDivisionError:
return peer
raise ValueError

View File

@ -0,0 +1,259 @@
from ConfigParser import NoOptionError, NoSectionError
import errno
import socket
import time
import random
import re
from bmconfigparser import BMConfigParser
from debug import logger
import helper_bootstrap
from network.proxy import Proxy
from network.tcp import TCPServer, Socks5BMConnection, Socks4aBMConnection, TCPConnection
from network.udp import UDPSocket
from network.connectionchooser import chooseConnection
import network.asyncore_pollchoose as asyncore
import protocol
from singleton import Singleton
import state
@Singleton
class BMConnectionPool(object):
def __init__(self):
asyncore.set_rates(
BMConfigParser().safeGetInt("bitmessagesettings", "maxdownloadrate"),
BMConfigParser().safeGetInt("bitmessagesettings", "maxuploadrate"))
self.outboundConnections = {}
self.inboundConnections = {}
self.listeningSockets = {}
self.udpSockets = {}
self.streams = []
self.lastSpawned = 0
self.spawnWait = 2
self.bootstrapped = False
def connectToStream(self, streamNumber):
self.streams.append(streamNumber)
def getConnectionByAddr(self, addr):
if addr in self.inboundConnections:
return self.inboundConnections[addr]
try:
if addr.host in self.inboundConnections:
return self.inboundConnections[addr.host]
except AttributeError:
pass
if addr in self.outboundConnections:
return self.outboundConnections[addr]
try:
if addr.host in self.udpSockets:
return self.udpSockets[addr.host]
except AttributeError:
pass
raise KeyError
def isAlreadyConnected(self, nodeid):
for i in self.inboundConnections.values() + self.outboundConnections.values():
try:
if nodeid == i.nodeid:
return True
except AttributeError:
pass
return False
def addConnection(self, connection):
if isinstance(connection, UDPSocket):
return
if connection.isOutbound:
self.outboundConnections[connection.destination] = connection
else:
if connection.destination.host in self.inboundConnections:
self.inboundConnections[connection.destination] = connection
else:
self.inboundConnections[connection.destination.host] = connection
def removeConnection(self, connection):
if isinstance(connection, UDPSocket):
del self.udpSockets[connection.listening.host]
elif isinstance(connection, TCPServer):
del self.listeningSockets[state.Peer(connection.destination.host, connection.destination.port)]
elif connection.isOutbound:
try:
del self.outboundConnections[connection.destination]
except KeyError:
pass
else:
try:
del self.inboundConnections[connection.destination]
except KeyError:
try:
del self.inboundConnections[connection.destination.host]
except KeyError:
pass
connection.close()
def getListeningIP(self):
if BMConfigParser().safeGet("bitmessagesettings", "onionhostname").endswith(".onion"):
host = BMConfigParser().safeGet("bitmessagesettings", "onionbindip")
else:
host = '127.0.0.1'
if BMConfigParser().safeGetBoolean("bitmessagesettings", "sockslisten") or \
BMConfigParser().get("bitmessagesettings", "socksproxytype") == "none":
# python doesn't like bind + INADDR_ANY?
#host = socket.INADDR_ANY
host = BMConfigParser().get("network", "bind")
return host
def startListening(self, bind=None):
if bind is None:
bind = self.getListeningIP()
port = BMConfigParser().safeGetInt("bitmessagesettings", "port")
# correct port even if it changed
ls = TCPServer(host=bind, port=port)
self.listeningSockets[ls.destination] = ls
def startUDPSocket(self, bind=None):
if bind is None:
host = self.getListeningIP()
udpSocket = UDPSocket(host=host, announcing=True)
else:
if bind is False:
udpSocket = UDPSocket(announcing=False)
else:
udpSocket = UDPSocket(host=bind, announcing=True)
self.udpSockets[udpSocket.listening.host] = udpSocket
def loop(self):
# defaults to empty loop if outbound connections are maxed
spawnConnections = False
acceptConnections = True
if BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect'):
acceptConnections = False
elif BMConfigParser().safeGetBoolean('bitmessagesettings', 'sendoutgoingconnections'):
spawnConnections = True
if BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and \
(not BMConfigParser().getboolean('bitmessagesettings', 'sockslisten') and \
".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname')):
acceptConnections = False
if spawnConnections:
if not self.bootstrapped:
helper_bootstrap.dns()
self.bootstrapped = True
Proxy.proxy = (BMConfigParser().safeGet("bitmessagesettings", "sockshostname"),
BMConfigParser().safeGetInt("bitmessagesettings", "socksport"))
# TODO AUTH
# TODO reset based on GUI settings changes
try:
if not BMConfigParser().get("network", "onionsocksproxytype").startswith("SOCKS"):
raise NoOptionError
Proxy.onionproxy = (BMConfigParser().get("network", "onionsockshostname"),
BMConfigParser().getint("network", "onionsocksport"))
except (NoOptionError, NoSectionError):
Proxy.onionproxy = None
established = sum(1 for c in self.outboundConnections.values() if (c.connected and c.fullyEstablished))
pending = len(self.outboundConnections) - established
if established < BMConfigParser().safeGetInt("bitmessagesettings", "maxoutboundconnections"):
for i in range(state.maximumNumberOfHalfOpenConnections - pending):
try:
chosen = chooseConnection(random.choice(self.streams))
except ValueError:
continue
if chosen in self.outboundConnections:
continue
if chosen.host in self.inboundConnections:
continue
# don't connect to self
if chosen in state.ownAddresses:
continue
#for c in self.outboundConnections:
# if chosen == c.destination:
# continue
#for c in self.inboundConnections:
# if chosen.host == c.destination.host:
# continue
try:
if chosen.host.endswith(".onion") and Proxy.onionproxy is not None:
if BMConfigParser().get("network", "onionsocksproxytype") == "SOCKS5":
self.addConnection(Socks5BMConnection(chosen))
elif BMConfigParser().get("network", "onionsocksproxytype") == "SOCKS4a":
self.addConnection(Socks4aBMConnection(chosen))
elif BMConfigParser().safeGet("bitmessagesettings", "socksproxytype") == "SOCKS5":
self.addConnection(Socks5BMConnection(chosen))
elif BMConfigParser().safeGet("bitmessagesettings", "socksproxytype") == "SOCKS4a":
self.addConnection(Socks4aBMConnection(chosen))
else:
self.addConnection(TCPConnection(chosen))
except socket.error as e:
if e.errno == errno.ENETUNREACH:
continue
except (NoSectionError, NoOptionError):
# shouldn't happen
pass
self.lastSpawned = time.time()
else:
for i in (
self.inboundConnections.values() +
self.outboundConnections.values()
):
i.set_state("close")
# FIXME: rating will be increased after next connection
i.handle_close()
if acceptConnections:
if not self.listeningSockets:
if BMConfigParser().safeGet("network", "bind") == '':
self.startListening()
else:
for bind in re.sub("[^\w.]+", " ", BMConfigParser().safeGet("network", "bind")).split():
self.startListening(bind)
logger.info('Listening for incoming connections.')
if not self.udpSockets:
if BMConfigParser().safeGet("network", "bind") == '':
self.startUDPSocket()
else:
for bind in re.sub("[^\w.]+", " ", BMConfigParser().safeGet("network", "bind")).split():
self.startUDPSocket(bind)
self.startUDPSocket(False)
logger.info('Starting UDP socket(s).')
else:
if self.listeningSockets:
for i in self.listeningSockets.values():
i.close_reason = "Stopping listening"
i.accepting = i.connecting = i.connected = False
logger.info('Stopped listening for incoming connections.')
if self.udpSockets:
for i in self.udpSockets.values():
i.close_reason = "Stopping UDP socket"
i.accepting = i.connecting = i.connected = False
logger.info('Stopped udp sockets.')
loopTime = float(self.spawnWait)
if self.lastSpawned < time.time() - self.spawnWait:
loopTime = 2.0
asyncore.loop(timeout=loopTime, count=1000)
reaper = []
for i in self.inboundConnections.values() + self.outboundConnections.values():
minTx = time.time() - 20
if i.fullyEstablished:
minTx -= 300 - 20
if i.lastTx < minTx:
if i.fullyEstablished:
i.append_write_buf(protocol.CreatePacket('ping'))
else:
i.close_reason = "Timeout (%is)" % (time.time() - i.lastTx)
i.set_state("close")
for i in self.inboundConnections.values() + self.outboundConnections.values() + self.listeningSockets.values() + self.udpSockets.values():
if not (i.accepting or i.connecting or i.connected):
reaper.append(i)
else:
try:
if i.state == "close":
reaper.append(i)
except AttributeError:
pass
for i in reaper:
self.removeConnection(i)

141
src/network/dandelion.py Normal file
View File

@ -0,0 +1,141 @@
from collections import namedtuple
from random import choice, sample, expovariate
from threading import RLock
from time import time
from bmconfigparser import BMConfigParser
import network.connectionpool
from debug import logging
from queues import invQueue
from singleton import Singleton
import state
# randomise routes after 600 seconds
REASSIGN_INTERVAL = 600
# trigger fluff due to expiration
FLUFF_TRIGGER_FIXED_DELAY = 10
FLUFF_TRIGGER_MEAN_DELAY = 30
MAX_STEMS = 2
Stem = namedtuple('Stem', ['child', 'stream', 'timeout'])
@Singleton
class Dandelion():
def __init__(self):
# currently assignable child stems
self.stem = []
# currently assigned parent <-> child mappings
self.nodeMap = {}
# currently existing objects in stem mode
self.hashMap = {}
# when to rerandomise routes
self.refresh = time() + REASSIGN_INTERVAL
self.lock = RLock()
def poissonTimeout(self, start=None, average=0):
if start is None:
start = time()
if average == 0:
average = FLUFF_TRIGGER_MEAN_DELAY
return start + expovariate(1.0/average) + FLUFF_TRIGGER_FIXED_DELAY
def addHash(self, hashId, source=None, stream=1):
if not state.dandelion:
return
with self.lock:
self.hashMap[hashId] = Stem(
self.getNodeStem(source),
stream,
self.poissonTimeout())
def setHashStream(self, hashId, stream=1):
with self.lock:
if hashId in self.hashMap:
self.hashMap[hashId] = Stem(
self.hashMap[hashId].child,
stream,
self.poissonTimeout())
def removeHash(self, hashId, reason="no reason specified"):
logging.debug("%s entering fluff mode due to %s.", ''.join('%02x'%ord(i) for i in hashId), reason)
with self.lock:
try:
del self.hashMap[hashId]
except KeyError:
pass
def hasHash(self, hashId):
return hashId in self.hashMap
def objectChildStem(self, hashId):
return self.hashMap[hashId].child
def maybeAddStem(self, connection):
# fewer than MAX_STEMS outbound connections at last reshuffle?
with self.lock:
if len(self.stem) < MAX_STEMS:
self.stem.append(connection)
for k in (k for k, v in self.nodeMap.iteritems() if v is None):
self.nodeMap[k] = connection
for k, v in {k: v for k, v in self.hashMap.iteritems() if v.child is None}.iteritems():
self.hashMap[k] = Stem(connection, v.stream, self.poissonTimeout())
invQueue.put((v.stream, k, v.child))
def maybeRemoveStem(self, connection):
# is the stem active?
with self.lock:
if connection in self.stem:
self.stem.remove(connection)
# active mappings to pointing to the removed node
for k in (k for k, v in self.nodeMap.iteritems() if v == connection):
self.nodeMap[k] = None
for k, v in {k: v for k, v in self.hashMap.iteritems() if v.child == connection}.iteritems():
self.hashMap[k] = Stem(None, v.stream, self.poissonTimeout())
def pickStem(self, parent=None):
try:
# pick a random from available stems
stem = choice(range(len(self.stem)))
if self.stem[stem] == parent:
# one stem available and it's the parent
if len(self.stem) == 1:
return None
# else, pick the other one
return self.stem[1 - stem]
# all ok
return self.stem[stem]
except IndexError:
# no stems available
return None
def getNodeStem(self, node=None):
with self.lock:
try:
return self.nodeMap[node]
except KeyError:
self.nodeMap[node] = self.pickStem(node)
return self.nodeMap[node]
def expire(self):
with self.lock:
deadline = time()
# only expire those that have a child node, i.e. those without a child not will stick around
toDelete = [[v.stream, k, v.child] for k, v in self.hashMap.iteritems() if v.timeout < deadline and v.child]
for row in toDelete:
self.removeHash(row[1], 'expiration')
invQueue.put((row[0], row[1], row[2]))
def reRandomiseStems(self):
with self.lock:
try:
# random two connections
self.stem = sample(network.connectionpool.BMConnectionPool().outboundConnections.values(), MAX_STEMS)
# not enough stems available
except ValueError:
self.stem = network.connectionpool.BMConnectionPool().outboundConnections.values()
self.nodeMap = {}
# hashMap stays to cater for pending stems
self.refresh = time() + REASSIGN_INTERVAL

View File

@ -0,0 +1,74 @@
import random
import threading
import time
import addresses
from dandelion import Dandelion
from debug import logger
from helper_threading import StoppableThread
from inventory import Inventory
from network.connectionpool import BMConnectionPool
import protocol
from state import missingObjects
class DownloadThread(threading.Thread, StoppableThread):
minPending = 200
maxRequestChunk = 1000
requestTimeout = 60
cleanInterval = 60
requestExpires = 3600
def __init__(self):
threading.Thread.__init__(self, name="Downloader")
self.initStop()
self.name = "Downloader"
logger.info("init download thread")
self.lastCleaned = time.time()
def cleanPending(self):
deadline = time.time() - DownloadThread.requestExpires
try:
toDelete = [k for k, v in missingObjects.iteritems() if v < deadline]
except RuntimeError:
pass
else:
for i in toDelete:
del missingObjects[i]
self.lastCleaned = time.time()
def run(self):
while not self._stopped:
requested = 0
# Choose downloading peers randomly
connections = [x for x in BMConnectionPool().inboundConnections.values() + BMConnectionPool().outboundConnections.values() if x.fullyEstablished]
random.shuffle(connections)
try:
requestChunk = max(int(min(DownloadThread.maxRequestChunk, len(missingObjects)) / len(connections)), 1)
except ZeroDivisionError:
requestChunk = 1
for i in connections:
now = time.time()
try:
request = i.objectsNewToMe.randomKeys(requestChunk)
except KeyError:
continue
payload = bytearray()
payload.extend(addresses.encodeVarint(len(request)))
for chunk in request:
if chunk in Inventory() and not Dandelion().hasHash(chunk):
try:
del i.objectsNewToMe[chunk]
except KeyError:
pass
continue
payload.extend(chunk)
missingObjects[chunk] = now
if not payload:
continue
i.append_write_buf(protocol.CreatePacket('getdata', payload))
logger.debug("%s:%i Requesting %i objects", i.destination.host, i.destination.port, len(request))
requested += len(request)
if time.time() >= self.lastCleaned + DownloadThread.cleanInterval:
self.cleanPending()
if not requested:
self.stop.wait(1)

49
src/network/http-old.py Normal file
View File

@ -0,0 +1,49 @@
import asyncore
import socket
import time
requestCount = 0
parallel = 50
duration = 60
class HTTPClient(asyncore.dispatcher):
port = 12345
def __init__(self, host, path, connect=True):
if not hasattr(self, '_map'):
asyncore.dispatcher.__init__(self)
if connect:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, HTTPClient.port))
self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % path
def handle_close(self):
global requestCount
requestCount += 1
self.close()
def handle_read(self):
# print self.recv(8192)
self.recv(8192)
def writable(self):
return (len(self.buffer) > 0)
def handle_write(self):
sent = self.send(self.buffer)
self.buffer = self.buffer[sent:]
if __name__ == "__main__":
# initial fill
for i in range(parallel):
HTTPClient('127.0.0.1', '/')
start = time.time()
while (time.time() - start < duration):
if (len(asyncore.socket_map) < parallel):
for i in range(parallel - len(asyncore.socket_map)):
HTTPClient('127.0.0.1', '/')
print "Active connections: %i" % (len(asyncore.socket_map))
asyncore.loop(count=len(asyncore.socket_map)/2)
if requestCount % 100 == 0:
print "Processed %i total messages" % (requestCount)

View File

@ -1,49 +1,86 @@
import asyncore
import socket
import time
requestCount = 0
parallel = 50
duration = 60
from advanceddispatcher import AdvancedDispatcher
import asyncore_pollchoose as asyncore
from proxy import Proxy, ProxyError, GeneralProxyError
from socks5 import Socks5Connection, Socks5Resolver, Socks5AuthError, Socks5Error
from socks4a import Socks4aConnection, Socks4aResolver, Socks4aError
class HttpError(ProxyError): pass
class HTTPClient(asyncore.dispatcher):
port = 12345
class HttpConnection(AdvancedDispatcher):
def __init__(self, host, path="/"):
AdvancedDispatcher.__init__(self)
self.path = path
self.destination = (host, 80)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect(self.destination)
print "connecting in background to %s:%i" % (self.destination[0], self.destination[1])
def __init__(self, host, path, connect=True):
if not hasattr(self, '_map'):
asyncore.dispatcher.__init__(self)
if connect:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, HTTPClient.port))
self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % path
def state_init(self):
self.append_write_buf("GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n" % (self.path, self.destination[0]))
print "Sending %ib" % (len(self.write_buf))
self.set_state("http_request_sent", 0)
return False
def handle_close(self):
global requestCount
requestCount += 1
self.close()
def state_http_request_sent(self):
if len(self.read_buf) > 0:
print "Received %ib" % (len(self.read_buf))
self.read_buf = b""
if not self.connected:
self.set_state("close", 0)
return False
def handle_read(self):
# print self.recv(8192)
self.recv(8192)
def writable(self):
return (len(self.buffer) > 0)
class Socks5HttpConnection(Socks5Connection, HttpConnection):
def __init__(self, host, path="/"):
self.path = path
Socks5Connection.__init__(self, address=(host, 80))
def state_socks_handshake_done(self):
HttpConnection.state_init(self)
return False
class Socks4aHttpConnection(Socks4aConnection, HttpConnection):
def __init__(self, host, path="/"):
Socks4aConnection.__init__(self, address=(host, 80))
self.path = path
def state_socks_handshake_done(self):
HttpConnection.state_init(self)
return False
def handle_write(self):
sent = self.send(self.buffer)
self.buffer = self.buffer[sent:]
if __name__ == "__main__":
# initial fill
for i in range(parallel):
HTTPClient('127.0.0.1', '/')
start = time.time()
while (time.time() - start < duration):
if (len(asyncore.socket_map) < parallel):
for i in range(parallel - len(asyncore.socket_map)):
HTTPClient('127.0.0.1', '/')
print "Active connections: %i" % (len(asyncore.socket_map))
asyncore.loop(count=len(asyncore.socket_map)/2)
if requestCount % 100 == 0:
print "Processed %i total messages" % (requestCount)
for host in ("bootstrap8080.bitmessage.org", "bootstrap8444.bitmessage.org"):
proxy = Socks5Resolver(host=host)
while len(asyncore.socket_map) > 0:
print "loop %s, len %i" % (proxy.state, len(asyncore.socket_map))
asyncore.loop(timeout=1, count=1)
proxy.resolved()
proxy = Socks4aResolver(host=host)
while len(asyncore.socket_map) > 0:
print "loop %s, len %i" % (proxy.state, len(asyncore.socket_map))
asyncore.loop(timeout=1, count=1)
proxy.resolved()
for host in ("bitmessage.org",):
direct = HttpConnection(host)
while len(asyncore.socket_map) > 0:
# print "loop, state = %s" % (direct.state)
asyncore.loop(timeout=1, count=1)
proxy = Socks5HttpConnection(host)
while len(asyncore.socket_map) > 0:
# print "loop, state = %s" % (proxy.state)
asyncore.loop(timeout=1, count=1)
proxy = Socks4aHttpConnection(host)
while len(asyncore.socket_map) > 0:
# print "loop, state = %s" % (proxy.state)
asyncore.loop(timeout=1, count=1)

87
src/network/invthread.py Normal file
View File

@ -0,0 +1,87 @@
import Queue
from random import randint, shuffle
import threading
from time import time
import addresses
from bmconfigparser import BMConfigParser
from helper_threading import StoppableThread
from network.connectionpool import BMConnectionPool
from network.dandelion import Dandelion
from queues import invQueue
import protocol
import state
class InvThread(threading.Thread, StoppableThread):
def __init__(self):
threading.Thread.__init__(self, name="InvBroadcaster")
self.initStop()
self.name = "InvBroadcaster"
def handleLocallyGenerated(self, stream, hashId):
Dandelion().addHash(hashId, stream=stream)
for connection in BMConnectionPool().inboundConnections.values() + \
BMConnectionPool().outboundConnections.values():
if state.dandelion and connection != Dandelion().objectChildStem(hashId):
continue
connection.objectsNewToThem[hashId] = time()
def run(self):
while not state.shutdown:
chunk = []
while True:
# Dandelion fluff trigger by expiration
Dandelion().expire()
try:
data = invQueue.get(False)
chunk.append((data[0], data[1]))
# locally generated
if len(data) == 2 or data[2] is None:
self.handleLocallyGenerated(data[0], data[1])
except Queue.Empty:
break
if chunk:
for connection in BMConnectionPool().inboundConnections.values() + \
BMConnectionPool().outboundConnections.values():
fluffs = []
stems = []
for inv in chunk:
if inv[0] not in connection.streams:
continue
try:
with connection.objectsNewToThemLock:
del connection.objectsNewToThem[inv[1]]
except KeyError:
continue
try:
if connection == Dandelion().objectChildStem(inv[1]):
# Fluff trigger by RNG
# auto-ignore if config set to 0, i.e. dandelion is off
if randint(1, 100) >= state.dandelion:
fluffs.append(inv[1])
# send a dinv only if the stem node supports dandelion
elif connection.services & protocol.NODE_DANDELION > 0:
stems.append(inv[1])
else:
fluffs.append(inv[1])
except KeyError:
fluffs.append(inv[1])
if fluffs:
shuffle(fluffs)
connection.append_write_buf(protocol.CreatePacket('inv', \
addresses.encodeVarint(len(fluffs)) + "".join(fluffs)))
if stems:
shuffle(stems)
connection.append_write_buf(protocol.CreatePacket('dinv', \
addresses.encodeVarint(len(stems)) + "".join(stems)))
invQueue.iterate()
for i in range(len(chunk)):
invQueue.task_done()
if Dandelion().refresh < time():
Dandelion().reRandomiseStems()
self.stop.wait(1)

View File

@ -0,0 +1,40 @@
import threading
from bmconfigparser import BMConfigParser
from debug import logger
from helper_threading import StoppableThread
import network.asyncore_pollchoose as asyncore
from network.connectionpool import BMConnectionPool
import state
class BMNetworkThread(threading.Thread, StoppableThread):
def __init__(self):
threading.Thread.__init__(self, name="Asyncore")
self.initStop()
self.name = "Asyncore"
logger.info("init asyncore thread")
def run(self):
while not self._stopped and state.shutdown == 0:
BMConnectionPool().loop()
def stopThread(self):
super(BMNetworkThread, self).stopThread()
for i in BMConnectionPool().listeningSockets.values():
try:
i.close()
except:
pass
for i in BMConnectionPool().outboundConnections.values():
try:
i.close()
except:
pass
for i in BMConnectionPool().inboundConnections.values():
try:
i.close()
except:
pass
# just in case
asyncore.close_all()

3
src/network/node.py Normal file
View File

@ -0,0 +1,3 @@
import collections
Node = collections.namedtuple('Node', ['services', 'host', 'port'])

129
src/network/objectracker.py Normal file
View File

@ -0,0 +1,129 @@
import time
from threading import RLock
from inventory import Inventory
import network.connectionpool
from network.dandelion import Dandelion
from randomtrackingdict import RandomTrackingDict
from state import missingObjects
haveBloom = False
try:
# pybloomfiltermmap
from pybloomfilter import BloomFilter
haveBloom = True
except ImportError:
try:
# pybloom
from pybloom import BloomFilter
haveBloom = True
except ImportError:
pass
# it isn't actually implemented yet so no point in turning it on
haveBloom = False
class ObjectTracker(object):
invCleanPeriod = 300
invInitialCapacity = 50000
invErrorRate = 0.03
trackingExpires = 3600
initialTimeOffset = 60
def __init__(self):
self.objectsNewToMe = RandomTrackingDict()
self.objectsNewToThem = {}
self.objectsNewToThemLock = RLock()
self.initInvBloom()
self.initAddrBloom()
self.lastCleaned = time.time()
def initInvBloom(self):
if haveBloom:
# lock?
self.invBloom = BloomFilter(capacity=ObjectTracker.invInitialCapacity,
error_rate=ObjectTracker.invErrorRate)
def initAddrBloom(self):
if haveBloom:
# lock?
self.addrBloom = BloomFilter(capacity=ObjectTracker.invInitialCapacity,
error_rate=ObjectTracker.invErrorRate)
def clean(self):
if self.lastCleaned < time.time() - ObjectTracker.invCleanPeriod:
if haveBloom:
# FIXME
if PendingDownloadQueue().size() == 0:
self.initInvBloom()
self.initAddrBloom()
else:
# release memory
deadline = time.time() - ObjectTracker.trackingExpires
with self.objectsNewToThemLock:
self.objectsNewToThem = {k: v for k, v in self.objectsNewToThem.iteritems() if v >= deadline}
self.lastCleaned = time.time()
def hasObj(self, hashid):
if haveBloom:
return hashid in self.invBloom
else:
return hashid in self.objectsNewToMe
def handleReceivedInventory(self, hashId):
if haveBloom:
self.invBloom.add(hashId)
try:
with self.objectsNewToThemLock:
del self.objectsNewToThem[hashId]
except KeyError:
pass
if hashId not in missingObjects:
missingObjects[hashId] = time.time()
self.objectsNewToMe[hashId] = True
def handleReceivedObject(self, streamNumber, hashid):
for i in network.connectionpool.BMConnectionPool().inboundConnections.values() + network.connectionpool.BMConnectionPool().outboundConnections.values():
if not i.fullyEstablished:
continue
try:
del i.objectsNewToMe[hashid]
except KeyError:
if streamNumber in i.streams and \
(not Dandelion().hasHash(hashid) or \
Dandelion().objectChildStem(hashid) == i):
with i.objectsNewToThemLock:
i.objectsNewToThem[hashid] = time.time()
# update stream number, which we didn't have when we just received the dinv
# also resets expiration of the stem mode
Dandelion().setHashStream(hashid, streamNumber)
if i == self:
try:
with i.objectsNewToThemLock:
del i.objectsNewToThem[hashid]
except KeyError:
pass
def hasAddr(self, addr):
if haveBloom:
return addr in self.invBloom
def addAddr(self, hashid):
if haveBloom:
self.addrBloom.add(hashid)
# addr sending -> per node upload queue, and flush every minute or so
# inv sending -> if not in bloom, inv immediately, otherwise put into a per node upload queue and flush every minute or so
# data sending -> a simple queue
# no bloom
# - if inv arrives
# - if we don't have it, add tracking and download queue
# - if we do have it, remove from tracking
# tracking downloads
# - per node hash of items the node has but we don't
# tracking inv
# - per node hash of items that neither the remote node nor we have
#

View File

@ -1,16 +1,44 @@
# SOCKS5 only
import asyncore
import socket
import struct
import time
from advanceddispatcher import AdvancedDispatcher
import asyncore_pollchoose as asyncore
from debug import logger
import network.connectionpool
import state
class ProxyError(Exception):
errorCodes = ("UnknownError")
def __init__(self, code=-1):
self.code = code
try:
self.message = self.__class__.errorCodes[self.code]
except IndexError:
self.message = self.__class__.errorCodes[-1]
super(ProxyError, self).__init__(self.message)
class GeneralProxyError(ProxyError):
errorCodes = ("Success",
"Invalid data",
"Not connected",
"Not available",
"Bad proxy type",
"Bad input",
"Timed out",
"Network unreachable",
"Connection refused",
"Host unreachable")
class Proxy(AdvancedDispatcher):
# these are global, and if you change config during runtime, all active/new
# instances should change too
_proxy = ["", 1080]
_proxy = ("127.0.0.1", 9050)
_auth = None
_onion_proxy = None
_onion_auth = None
_remote_dns = True
@property
@ -19,8 +47,9 @@ class Proxy(AdvancedDispatcher):
@proxy.setter
def proxy(self, address):
if (not type(address) in (list,tuple)) or (len(address) < 2) or (type(address[0]) != type('')) or (type(address[1]) != int):
raise
if not isinstance(address, tuple) or (len(address) < 2) or \
(not isinstance(address[0], str) or not isinstance(address[1], int)):
raise ValueError
self.__class__._proxy = address
@property
@ -31,160 +60,48 @@ class Proxy(AdvancedDispatcher):
def auth(self, authTuple):
self.__class__._auth = authTuple
def __init__(self, address=None):
if (not type(address) in (list,tuple)) or (len(address) < 2) or (type(address[0]) != type('')) or (type(address[1]) != int):
raise
AdvancedDispatcher.__init__(self, self.sock)
@property
def onion_proxy(self):
return self.__class__._onion_proxy
@onion_proxy.setter
def onion_proxy(self, address):
if address is not None and (not isinstance(address, tuple) or (len(address) < 2) or \
(not isinstance(address[0], str) or not isinstance(address[1], int))):
raise ValueError
self.__class__._onion_proxy = address
@property
def onion_auth(self):
return self.__class__._onion_auth
@onion_auth.setter
def onion_auth(self, authTuple):
self.__class__._onion_auth = authTuple
def __init__(self, address):
if not isinstance(address, state.Peer):
raise ValueError
AdvancedDispatcher.__init__(self)
self.destination = address
self.isOutbound = True
self.fullyEstablished = False
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.sslSocket.setblocking(0)
self.connect(self.proxy)
class SOCKS5(Proxy):
def __init__(self, address=None, sock=None):
Proxy.__init__(self, address)
self.state = "init"
if address.host.endswith(".onion") and self.onion_proxy is not None:
self.connect(self.onion_proxy)
else:
self.connect(self.proxy)
def handle_connect(self):
self.process()
def state_init(self):
if self._auth:
self.write_buf += struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)
else:
self.write_buf += struct.pack('BBB', 0x05, 0x01, 0x00)
self.set_state("auth_1", 0)
def state_auth_1(self):
if not self.read_buf_sufficient(2):
return False
ret = struct.unpack('BB', self.read_buf)
self.read_buf = self.read_buf[2:]
if ret[0] != 5:
# general error
raise
elif ret[1] == 0:
# no auth required
self.set_state("auth_done", 2)
elif ret[1] == 2:
# username/password
self.write_buf += struct.pack('BB', 1, len(self._auth[0])) + \
self._auth[0] + struct.pack('B', len(self._auth[1])) + \
self._auth[1]
self.set_state("auth_1", 2)
else:
if ret[1] == 0xff:
# auth error
raise
else:
# other error
raise
def state_auth_needed(self):
if not self.read_buf_sufficient(2):
return False
ret = struct.unpack('BB', self.read_buf)
if ret[0] != 1:
# general error
raise
if ret[1] != 0:
# auth error
raise
# all ok
self.set_state = ("auth_done", 2)
def state_pre_connect(self):
if not self.read_buf_sufficient(4):
return False
# Get the response
if self.read_buf[0:1] != chr(0x05).encode():
# general error
self.close()
raise
elif self.read_buf[1:2] != chr(0x00).encode():
# Connection failed
self.close()
if ord(self.read_buf[1:2])<=8:
# socks 5 erro
raise
#raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
else:
raise
#raise Socks5Error((9, _socks5errors[9]))
# Get the bound address/port
elif self.read_buf[3:4] == chr(0x01).encode():
self.set_state("proxy_addr_1", 4)
elif self.read_buf[3:4] == chr(0x03).encode():
self.set_state("proxy_addr_2_1", 4)
else:
self.close()
raise GeneralProxyError((1,_generalerrors[1]))
def state_proxy_addr_1(self):
if not self.read_buf_sufficient(4):
return False
self.boundaddr = self.read_buf[0:4]
self.set_state("proxy_port", 4)
def state_proxy_addr_2_1(self):
if not self.read_buf_sufficient(1):
return False
self.address_length = ord(self.read_buf[0:1])
self.set_state("proxy_addr_2_2", 1)
def state_proxy_addr_2_2(self):
if not self.read_buf_sufficient(self.address_length):
return False
self.boundaddr = read_buf
self.set_state("proxy_port", self.address_length)
def state_proxy_port(self):
if not self.read_buf_sufficient(2):
return False
self.boundport = struct.unpack(">H", self.read_buf[0:2])[0]
self.__proxysockname = (self.boundaddr, self.boundport)
if self.ipaddr != None:
self.__proxypeername = (socket.inet_ntoa(self.ipaddr), self.destination[1])
else:
self.__proxypeername = (self.destination[1], destport)
class SOCKS5Connection(SOCKS5):
def __init__(self, address):
SOCKS5.__init__(self, address)
def state_auth_done(self):
# Now we can request the actual connection
self.write_buf += struct.pack('BBB', 0x05, 0x01, 0x00)
# If the given destination address is an IP address, we'll
# use the IPv4 address request even if remote resolving was specified.
self.set_state("init")
try:
self.ipaddr = socket.inet_aton(self.destination[0])
self.write_buf += chr(0x01).encode() + ipaddr
except socket.error:
# Well it's not an IP number, so it's probably a DNS name.
if Proxy._remote_dns:
# Resolve remotely
self.ipaddr = None
self.write_buf += chr(0x03).encode() + chr(len(self.destination[0])).encode() + self.destination[0]
else:
# Resolve locally
self.ipaddr = socket.inet_aton(socket.gethostbyname(self.destination[0]))
self.write_buf += chr(0x01).encode() + ipaddr
self.write_buf += struct.pack(">H", self.destination[1])
self.set_state = ("pre_connect", 0)
AdvancedDispatcher.handle_connect(self)
except socket.error as e:
if e.errno in asyncore._DISCONNECTED:
logger.debug("%s:%i: Connection failed: %s", self.destination.host, self.destination.port, str(e))
return
self.state_init()
class SOCKS5Resolver(SOCKS5):
def __init__(self, host):
self.host = host
self.port = 8444
SOCKS5.__init__(self, [self.host, self.port])
def state_auth_done(self):
# Now we can request the actual connection
self.write_buf += struct.pack('BBB', 0x05, 0xF0, 0x00)
self.write_buf += chr(0x03).encode() + chr(len(self.host)).encode() + self.host
self.write_buf += struct.pack(">H", self.port)
self.state = "pre_connect"
def state_proxy_handshake_done(self):
self.connectedAt = time.time()
return False

View File

@ -0,0 +1,53 @@
import errno
import Queue
import socket
import sys
import threading
import time
import addresses
from bmconfigparser import BMConfigParser
from debug import logger
from helper_threading import StoppableThread
from inventory import Inventory
from network.connectionpool import BMConnectionPool
from network.bmproto import BMProto
from queues import receiveDataQueue
import protocol
import state
class ReceiveQueueThread(threading.Thread, StoppableThread):
def __init__(self, num=0):
threading.Thread.__init__(self, name="ReceiveQueue_%i" %(num))
self.initStop()
self.name = "ReceiveQueue_%i" % (num)
logger.info("init receive queue thread %i", num)
def run(self):
while not self._stopped and state.shutdown == 0:
try:
dest = receiveDataQueue.get(block=True, timeout=1)
except Queue.Empty:
continue
if self._stopped or state.shutdown:
break
# cycle as long as there is data
# methods should return False if there isn't enough data, or the connection is to be aborted
# state_* methods should return False if there isn't enough data,
# or the connection is to be aborted
try:
BMConnectionPool().getConnectionByAddr(dest).process()
# KeyError = connection object not found
# AttributeError = state isn't implemented
except (KeyError, AttributeError):
pass
except socket.error as err:
if err.errno == errno.EBADF:
BMConnectionPool().getConnectionByAddr(dest).set_state("close", 0)
else:
logger.error("Socket error: %s", str(err))
receiveDataQueue.task_done()

111
src/network/socks4a.py Normal file
View File

@ -0,0 +1,111 @@
import socket
import struct
from proxy import Proxy, ProxyError, GeneralProxyError
class Socks4aError(ProxyError):
errorCodes = ("Request granted",
"Request rejected or failed",
"Request rejected because SOCKS server cannot connect to identd on the client",
"Request rejected because the client program and identd report different user-ids",
"Unknown error")
class Socks4a(Proxy):
def __init__(self, address=None):
Proxy.__init__(self, address)
self.ipaddr = None
self.destport = address[1]
def state_init(self):
self.set_state("auth_done", 0)
return True
def state_pre_connect(self):
# Get the response
if self.read_buf[0:1] != chr(0x00).encode():
# bad data
self.close()
raise GeneralProxyError(1)
elif self.read_buf[1:2] != chr(0x5A).encode():
# Connection failed
self.close()
if ord(self.read_buf[1:2]) in (91, 92, 93):
# socks 4 error
raise Socks4aError(ord(self.read_buf[1:2]) - 90)
else:
raise Socks4aError(4)
# Get the bound address/port
self.boundport = struct.unpack(">H", self.read_buf[2:4])[0]
self.boundaddr = self.read_buf[4:]
self.__proxysockname = (self.boundaddr, self.boundport)
if self.ipaddr:
self.__proxypeername = (socket.inet_ntoa(self.ipaddr), self.destination[1])
else:
self.__proxypeername = (self.destination[0], self.destport)
self.set_state("proxy_handshake_done", length=8)
return True
def proxy_sock_name(self):
return socket.inet_ntoa(self.__proxysockname[0])
class Socks4aConnection(Socks4a):
def __init__(self, address):
Socks4a.__init__(self, address=address)
def state_auth_done(self):
# Now we can request the actual connection
rmtrslv = False
self.append_write_buf(struct.pack('>BBH', 0x04, 0x01, self.destination[1]))
# If the given destination address is an IP address, we'll
# use the IPv4 address request even if remote resolving was specified.
try:
self.ipaddr = socket.inet_aton(self.destination[0])
self.append_write_buf(self.ipaddr)
except socket.error:
# Well it's not an IP number, so it's probably a DNS name.
if Proxy._remote_dns:
# Resolve remotely
rmtrslv = True
self.ipaddr = None
self.append_write_buf(struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01))
else:
# Resolve locally
self.ipaddr = socket.inet_aton(socket.gethostbyname(self.destination[0]))
self.append_write_buf(self.ipaddr)
if self._auth:
self.append_write_buf(self._auth[0])
self.append_write_buf(chr(0x00).encode())
if rmtrslv:
self.append_write_buf(self.destination[0] + chr(0x00).encode())
self.set_state("pre_connect", length=0, expectBytes=8)
return True
def state_pre_connect(self):
try:
return Socks4a.state_pre_connect(self)
except Socks4aError as e:
self.close_reason = e.message
self.set_state("close")
class Socks4aResolver(Socks4a):
def __init__(self, host):
self.host = host
self.port = 8444
Socks4a.__init__(self, address=(self.host, self.port))
def state_auth_done(self):
# Now we can request the actual connection
self.append_write_buf(struct.pack('>BBH', 0x04, 0xF0, self.destination[1]))
self.append_write_buf(struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01))
if self._auth:
self.append_write_buf(self._auth[0])
self.append_write_buf(chr(0x00).encode())
self.append_write_buf(self.host + chr(0x00).encode())
self.set_state("pre_connect", length=0, expectBytes=8)
return True
def resolved(self):
print "Resolved %s as %s" % (self.host, self.proxy_sock_name())

176
src/network/socks5.py Normal file
View File

@ -0,0 +1,176 @@
import socket
import struct
from proxy import Proxy, ProxyError, GeneralProxyError
class Socks5AuthError(ProxyError):
errorCodes = ("Succeeded",
"Authentication is required",
"All offered authentication methods were rejected",
"Unknown username or invalid password",
"Unknown error")
class Socks5Error(ProxyError):
errorCodes = ("Succeeded",
"General SOCKS server failure",
"Connection not allowed by ruleset",
"Network unreachable",
"Host unreachable",
"Connection refused",
"TTL expired",
"Command not supported",
"Address type not supported",
"Unknown error")
class Socks5(Proxy):
def __init__(self, address=None):
Proxy.__init__(self, address)
self.ipaddr = None
self.destport = address[1]
def state_init(self):
if self._auth:
self.append_write_buf(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
else:
self.append_write_buf(struct.pack('BBB', 0x05, 0x01, 0x00))
self.set_state("auth_1", length=0, expectBytes=2)
return True
def state_auth_1(self):
ret = struct.unpack('BB', self.read_buf)
if ret[0] != 5:
# general error
raise GeneralProxyError(1)
elif ret[1] == 0:
# no auth required
self.set_state("auth_done", length=2)
elif ret[1] == 2:
# username/password
self.append_write_buf(struct.pack('BB', 1, len(self._auth[0])) + \
self._auth[0] + struct.pack('B', len(self._auth[1])) + \
self._auth[1])
self.set_state("auth_needed", length=2, expectBytes=2)
else:
if ret[1] == 0xff:
# auth error
raise Socks5AuthError(2)
else:
# other error
raise GeneralProxyError(1)
return True
def state_auth_needed(self):
ret = struct.unpack('BB', self.read_buf[0:2])
if ret[0] != 1:
# general error
raise GeneralProxyError(1)
if ret[1] != 0:
# auth error
raise Socks5AuthError(3)
# all ok
self.set_state("auth_done", length=2)
return True
def state_pre_connect(self):
# Get the response
if self.read_buf[0:1] != chr(0x05).encode():
self.close()
raise GeneralProxyError(1)
elif self.read_buf[1:2] != chr(0x00).encode():
# Connection failed
self.close()
if ord(self.read_buf[1:2])<=8:
raise Socks5Error(ord(self.read_buf[1:2]))
else:
raise Socks5Error(9)
# Get the bound address/port
elif self.read_buf[3:4] == chr(0x01).encode():
self.set_state("proxy_addr_1", length=4, expectBytes=4)
elif self.read_buf[3:4] == chr(0x03).encode():
self.set_state("proxy_addr_2_1", length=4, expectBytes=1)
else:
self.close()
raise GeneralProxyError(1)
return True
def state_proxy_addr_1(self):
self.boundaddr = self.read_buf[0:4]
self.set_state("proxy_port", length=4, expectBytes=2)
return True
def state_proxy_addr_2_1(self):
self.address_length = ord(self.read_buf[0:1])
self.set_state("proxy_addr_2_2", length=1, expectBytes=self.address_length)
return True
def state_proxy_addr_2_2(self):
self.boundaddr = self.read_buf[0:self.address_length]
self.set_state("proxy_port", length=self.address_length, expectBytes=2)
return True
def state_proxy_port(self):
self.boundport = struct.unpack(">H", self.read_buf[0:2])[0]
self.__proxysockname = (self.boundaddr, self.boundport)
if self.ipaddr is not None:
self.__proxypeername = (socket.inet_ntoa(self.ipaddr), self.destination[1])
else:
self.__proxypeername = (self.destination[0], self.destport)
self.set_state("proxy_handshake_done", length=2)
return True
def proxy_sock_name(self):
return socket.inet_ntoa(self.__proxysockname[0])
class Socks5Connection(Socks5):
def __init__(self, address):
Socks5.__init__(self, address=address)
def state_auth_done(self):
# Now we can request the actual connection
self.append_write_buf(struct.pack('BBB', 0x05, 0x01, 0x00))
# If the given destination address is an IP address, we'll
# use the IPv4 address request even if remote resolving was specified.
try:
self.ipaddr = socket.inet_aton(self.destination[0])
self.append_write_buf(chr(0x01).encode() + self.ipaddr)
except socket.error:
# Well it's not an IP number, so it's probably a DNS name.
if Proxy._remote_dns:
# Resolve remotely
self.ipaddr = None
self.append_write_buf(chr(0x03).encode() + chr(len(self.destination[0])).encode() + self.destination[0])
else:
# Resolve locally
self.ipaddr = socket.inet_aton(socket.gethostbyname(self.destination[0]))
self.append_write_buf(chr(0x01).encode() + self.ipaddr)
self.append_write_buf(struct.pack(">H", self.destination[1]))
self.set_state("pre_connect", length=0, expectBytes=4)
return True
def state_pre_connect(self):
try:
return Socks5.state_pre_connect(self)
except Socks5Error as e:
self.close_reason = e.message
self.set_state("close")
class Socks5Resolver(Socks5):
def __init__(self, host):
self.host = host
self.port = 8444
Socks5.__init__(self, address=(self.host, self.port))
def state_auth_done(self):
# Now we can request the actual connection
self.append_write_buf(struct.pack('BBB', 0x05, 0xF0, 0x00))
self.append_write_buf(chr(0x03).encode() + chr(len(self.host)).encode() + str(self.host))
self.append_write_buf(struct.pack(">H", self.port))
self.set_state("pre_connect", length=0, expectBytes=4)
return True
def resolved(self):
print "Resolved %s as %s" % (self.host, self.proxy_sock_name())

70
src/network/stats.py Normal file
View File

@ -0,0 +1,70 @@
import time
from network.connectionpool import BMConnectionPool
import asyncore_pollchoose as asyncore
from state import missingObjects
lastReceivedTimestamp = time.time()
lastReceivedBytes = 0
currentReceivedSpeed = 0
lastSentTimestamp = time.time()
lastSentBytes = 0
currentSentSpeed = 0
def connectedHostsList():
retval = []
for i in BMConnectionPool().inboundConnections.values() + \
BMConnectionPool().outboundConnections.values():
if not i.fullyEstablished:
continue
try:
retval.append(i)
except AttributeError:
pass
return retval
def sentBytes():
return asyncore.sentBytes
def uploadSpeed():
global lastSentTimestamp, lastSentBytes, currentSentSpeed
currentTimestamp = time.time()
if int(lastSentTimestamp) < int(currentTimestamp):
currentSentBytes = asyncore.sentBytes
currentSentSpeed = int((currentSentBytes - lastSentBytes) / (currentTimestamp - lastSentTimestamp))
lastSentBytes = currentSentBytes
lastSentTimestamp = currentTimestamp
return currentSentSpeed
def receivedBytes():
return asyncore.receivedBytes
def downloadSpeed():
global lastReceivedTimestamp, lastReceivedBytes, currentReceivedSpeed
currentTimestamp = time.time()
if int(lastReceivedTimestamp) < int(currentTimestamp):
currentReceivedBytes = asyncore.receivedBytes
currentReceivedSpeed = int((currentReceivedBytes - lastReceivedBytes) /
(currentTimestamp - lastReceivedTimestamp))
lastReceivedBytes = currentReceivedBytes
lastReceivedTimestamp = currentTimestamp
return currentReceivedSpeed
def pendingDownload():
return len(missingObjects)
#tmp = {}
#for connection in BMConnectionPool().inboundConnections.values() + \
# BMConnectionPool().outboundConnections.values():
# for k in connection.objectsNewToMe.keys():
# tmp[k] = True
#return len(tmp)
def pendingUpload():
#tmp = {}
#for connection in BMConnectionPool().inboundConnections.values() + \
# BMConnectionPool().outboundConnections.values():
# for k in connection.objectsNewToThem.keys():
# tmp[k] = True
#This probably isn't the correct logic so it's disabled
#return len(tmp)
return 0

325
src/network/tcp.py Normal file
View File

@ -0,0 +1,325 @@
import base64
from binascii import hexlify
import hashlib
import math
import time
from pprint import pprint
import socket
import struct
import random
import traceback
from addresses import calculateInventoryHash
from debug import logger
from helper_random import randomBytes
from inventory import Inventory
import knownnodes
from network.advanceddispatcher import AdvancedDispatcher
from network.bmproto import BMProtoError, BMProtoInsufficientDataError, BMProtoExcessiveDataError, BMProto
from network.bmobject import BMObject, BMObjectInsufficientPOWError, BMObjectInvalidDataError, BMObjectExpiredError, BMObjectUnwantedStreamError, BMObjectInvalidError, BMObjectAlreadyHaveError
import network.connectionpool
from network.dandelion import Dandelion
from network.node import Node
import network.asyncore_pollchoose as asyncore
from network.proxy import Proxy, ProxyError, GeneralProxyError
from network.objectracker import ObjectTracker
from network.socks5 import Socks5Connection, Socks5Resolver, Socks5AuthError, Socks5Error
from network.socks4a import Socks4aConnection, Socks4aResolver, Socks4aError
from network.tls import TLSDispatcher
import addresses
from bmconfigparser import BMConfigParser
from queues import invQueue, objectProcessorQueue, portCheckerQueue, UISignalQueue, receiveDataQueue
import shared
import state
import protocol
class TCPConnection(BMProto, TLSDispatcher):
def __init__(self, address=None, sock=None):
BMProto.__init__(self, address=address, sock=sock)
self.verackReceived = False
self.verackSent = False
self.streams = [0]
self.fullyEstablished = False
self.connectedAt = 0
self.skipUntil = 0
if address is None and sock is not None:
self.destination = state.Peer(sock.getpeername()[0], sock.getpeername()[1])
self.isOutbound = False
TLSDispatcher.__init__(self, sock, server_side=True)
self.connectedAt = time.time()
logger.debug("Received connection from %s:%i", self.destination.host, self.destination.port)
self.nodeid = randomBytes(8)
elif address is not None and sock is not None:
TLSDispatcher.__init__(self, sock, server_side=False)
self.isOutbound = True
logger.debug("Outbound proxy connection to %s:%i", self.destination.host, self.destination.port)
else:
self.destination = address
self.isOutbound = True
if ":" in address.host:
self.create_socket(socket.AF_INET6, socket.SOCK_STREAM)
else:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
TLSDispatcher.__init__(self, sock, server_side=False)
self.connect(self.destination)
logger.debug("Connecting to %s:%i", self.destination.host, self.destination.port)
encodedAddr = protocol.encodeHost(self.destination.host)
if protocol.checkIPAddress(encodedAddr, True) and not protocol.checkSocksIP(self.destination.host):
self.local = True
else:
self.local = False
#shared.connectedHostsList[self.destination] = 0
ObjectTracker.__init__(self)
self.bm_proto_reset()
self.set_state("bm_header", expectBytes=protocol.Header.size)
def antiIntersectionDelay(self, initial = False):
# estimated time for a small object to propagate across the whole network
delay = math.ceil(math.log(max(len(knownnodes.knownNodes[x]) for x in knownnodes.knownNodes) + 2, 20)) * (0.2 + invQueue.queueCount/2.0)
# take the stream with maximum amount of nodes
# +2 is to avoid problems with log(0) and log(1)
# 20 is avg connected nodes count
# 0.2 is avg message transmission time
if delay > 0:
if initial:
self.skipUntil = self.connectedAt + delay
if self.skipUntil > time.time():
logger.debug("Initial skipping processing getdata for %.2fs", self.skipUntil - time.time())
else:
logger.debug("Skipping processing getdata due to missing object for %.2fs", delay)
self.skipUntil = time.time() + delay
def state_connection_fully_established(self):
self.set_connection_fully_established()
self.set_state("bm_header")
self.bm_proto_reset()
return True
def set_connection_fully_established(self):
if not self.isOutbound and not self.local:
shared.clientHasReceivedIncomingConnections = True
UISignalQueue.put(('setStatusIcon', 'green'))
UISignalQueue.put(('updateNetworkStatusTab', (self.isOutbound, True, self.destination)))
self.antiIntersectionDelay(True)
self.fullyEstablished = True
if self.isOutbound:
knownnodes.increaseRating(self.destination)
if self.isOutbound:
Dandelion().maybeAddStem(self)
self.sendAddr()
self.sendBigInv()
def sendAddr(self):
# We are going to share a maximum number of 1000 addrs (per overlapping
# stream) with our peer. 500 from overlapping streams, 250 from the
# left child stream, and 250 from the right child stream.
maxAddrCount = BMConfigParser().safeGetInt("bitmessagesettings", "maxaddrperstreamsend", 500)
# init
addressCount = 0
payload = b''
templist = []
addrs = {}
for stream in self.streams:
with knownnodes.knownNodesLock:
if len(knownnodes.knownNodes[stream]) > 0:
filtered = {k: v for k, v in knownnodes.knownNodes[stream].items()
if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)}
elemCount = len(filtered)
if elemCount > maxAddrCount:
elemCount = maxAddrCount
# only if more recent than 3 hours
addrs[stream] = random.sample(filtered.items(), elemCount)
# sent 250 only if the remote isn't interested in it
if len(knownnodes.knownNodes[stream * 2]) > 0 and stream not in self.streams:
filtered = {k: v for k, v in knownnodes.knownNodes[stream*2].items()
if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)}
elemCount = len(filtered)
if elemCount > maxAddrCount / 2:
elemCount = int(maxAddrCount / 2)
addrs[stream * 2] = random.sample(filtered.items(), elemCount)
if len(knownnodes.knownNodes[(stream * 2) + 1]) > 0 and stream not in self.streams:
filtered = {k: v for k, v in knownnodes.knownNodes[stream*2+1].items()
if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)}
elemCount = len(filtered)
if elemCount > maxAddrCount / 2:
elemCount = int(maxAddrCount / 2)
addrs[stream * 2 + 1] = random.sample(filtered.items(), elemCount)
for substream in addrs.keys():
for peer, params in addrs[substream]:
templist.append((substream, peer, params["lastseen"]))
if len(templist) > 0:
self.append_write_buf(BMProto.assembleAddr(templist))
def sendBigInv(self):
def sendChunk():
if objectCount == 0:
return
logger.debug('Sending huge inv message with %i objects to just this one peer', objectCount)
self.append_write_buf(protocol.CreatePacket('inv', addresses.encodeVarint(objectCount) + payload))
# Select all hashes for objects in this stream.
bigInvList = {}
for stream in self.streams:
# may lock for a long time, but I think it's better than thousands of small locks
with self.objectsNewToThemLock:
for objHash in Inventory().unexpired_hashes_by_stream(stream):
# don't advertise stem objects on bigInv
if Dandelion().hasHash(objHash):
continue
bigInvList[objHash] = 0
self.objectsNewToThem[objHash] = time.time()
objectCount = 0
payload = b''
# Now let us start appending all of these hashes together. They will be
# sent out in a big inv message to our new peer.
for hash, storedValue in bigInvList.items():
payload += hash
objectCount += 1
if objectCount >= BMProto.maxObjectCount:
sendChunk()
payload = b''
objectCount = 0
# flush
sendChunk()
def handle_connect(self):
try:
AdvancedDispatcher.handle_connect(self)
except socket.error as e:
if e.errno in asyncore._DISCONNECTED:
logger.debug("%s:%i: Connection failed: %s" % (self.destination.host, self.destination.port, str(e)))
return
self.nodeid = randomBytes(8)
self.append_write_buf(protocol.assembleVersionMessage(self.destination.host, self.destination.port, \
network.connectionpool.BMConnectionPool().streams, False, nodeid=self.nodeid))
#print "%s:%i: Sending version" % (self.destination.host, self.destination.port)
self.connectedAt = time.time()
receiveDataQueue.put(self.destination)
def handle_read(self):
TLSDispatcher.handle_read(self)
if self.isOutbound and self.fullyEstablished:
for s in self.streams:
try:
with knownnodes.knownNodesLock:
knownnodes.knownNodes[s][self.destination]["lastseen"] = time.time()
except KeyError:
pass
receiveDataQueue.put(self.destination)
def handle_write(self):
TLSDispatcher.handle_write(self)
def handle_close(self):
if self.isOutbound and not self.fullyEstablished:
knownnodes.decreaseRating(self.destination)
if self.fullyEstablished:
UISignalQueue.put(('updateNetworkStatusTab', (self.isOutbound, False, self.destination)))
if self.isOutbound:
Dandelion().maybeRemoveStem(self)
BMProto.handle_close(self)
class Socks5BMConnection(Socks5Connection, TCPConnection):
def __init__(self, address):
Socks5Connection.__init__(self, address=address)
TCPConnection.__init__(self, address=address, sock=self.socket)
self.set_state("init")
def state_proxy_handshake_done(self):
Socks5Connection.state_proxy_handshake_done(self)
self.nodeid = randomBytes(8)
self.append_write_buf(protocol.assembleVersionMessage(self.destination.host, self.destination.port, \
network.connectionpool.BMConnectionPool().streams, False, nodeid=self.nodeid))
self.set_state("bm_header", expectBytes=protocol.Header.size)
return True
class Socks4aBMConnection(Socks4aConnection, TCPConnection):
def __init__(self, address):
Socks4aConnection.__init__(self, address=address)
TCPConnection.__init__(self, address=address, sock=self.socket)
self.set_state("init")
def state_proxy_handshake_done(self):
Socks4aConnection.state_proxy_handshake_done(self)
self.nodeid = randomBytes(8)
self.append_write_buf(protocol.assembleVersionMessage(self.destination.host, self.destination.port, \
network.connectionpool.BMConnectionPool().streams, False, nodeid=self.nodeid))
self.set_state("bm_header", expectBytes=protocol.Header.size)
return True
class TCPServer(AdvancedDispatcher):
def __init__(self, host='127.0.0.1', port=8444):
if not hasattr(self, '_map'):
AdvancedDispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
for attempt in range(50):
try:
if attempt > 0:
port = random.randint(32767, 65535)
self.bind((host, port))
except socket.error as e:
if e.errno in (asyncore.EADDRINUSE, asyncore.WSAEADDRINUSE):
continue
else:
if attempt > 0:
BMConfigParser().set("bitmessagesettings", "port", str(port))
BMConfigParser().save()
break
self.destination = state.Peer(host, port)
self.bound = True
self.listen(5)
def is_bound(self):
try:
return self.bound
except AttributeError:
return False
def handle_accept(self):
pair = self.accept()
if pair is not None:
sock, addr = pair
state.ownAddresses[state.Peer(sock.getsockname()[0], sock.getsockname()[1])] = True
if len(network.connectionpool.BMConnectionPool().inboundConnections) + \
len(network.connectionpool.BMConnectionPool().outboundConnections) > \
BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections") + \
BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections") + 10:
# 10 is a sort of buffer, in between it will go through the version handshake
# and return an error to the peer
logger.warning("Server full, dropping connection")
sock.close()
return
try:
network.connectionpool.BMConnectionPool().addConnection(TCPConnection(sock=sock))
except socket.error:
pass
if __name__ == "__main__":
# initial fill
for host in (("127.0.0.1", 8448),):
direct = TCPConnection(host)
while len(asyncore.socket_map) > 0:
print "loop, state = %s" % (direct.state)
asyncore.loop(timeout=10, count=1)
continue
proxy = Socks5BMConnection(host)
while len(asyncore.socket_map) > 0:
# print "loop, state = %s" % (proxy.state)
asyncore.loop(timeout=10, count=1)
proxy = Socks4aBMConnection(host)
while len(asyncore.socket_map) > 0:
# print "loop, state = %s" % (proxy.state)
asyncore.loop(timeout=10, count=1)

View File

@ -2,57 +2,53 @@
SSL/TLS negotiation.
"""
import asyncore
import os
import socket
import ssl
import sys
from debug import logger
from network.advanceddispatcher import AdvancedDispatcher
import network.asyncore_pollchoose as asyncore
from queues import receiveDataQueue
import paths
import protocol
class TLSHandshake(asyncore.dispatcher):
"""
Negotiates a SSL/TLS connection before handing itself spawning a
dispatcher that can deal with the overlying protocol as soon as the
handshake has been completed.
`handoff` is a function/method called when the handshake has completed.
`address` is a tuple consisting of hostname/address and port to connect to
if nothing is passed in `sock`, which can take an already-connected socket.
`certfile` can take a path to a certificate bundle, and `server_side`
indicates whether the socket is intended to be a server-side or client-side
socket.
"""
_DISCONNECTED_SSL = frozenset((ssl.SSL_ERROR_EOF,))
class TLSDispatcher(AdvancedDispatcher):
def __init__(self, address=None, sock=None,
certfile=None, keyfile=None, server_side=False, ciphers=None, init_parent=True):
if not hasattr(self, '_map'):
asyncore.dispatcher.__init__(self, sock)
certfile=None, keyfile=None, server_side=False, ciphers=protocol.sslProtocolCiphers):
self.want_read = self.want_write = True
self.certfile = certfile
self.keyfile = keyfile
if certfile is None:
self.certfile = os.path.join(paths.codePath(), 'sslkeys', 'cert.pem')
else:
self.certfile = certfile
if keyfile is None:
self.keyfile = os.path.join(paths.codePath(), 'sslkeys', 'key.pem')
else:
self.keyfile = keyfile
self.server_side = server_side
self.ciphers = ciphers
self.tlsStarted = False
self.tlsDone = False
if sock is None:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
# logger.info('Connecting to %s%d', address[0], address[1])
self.connect(address)
elif self.connected:
# Initiate the handshake for an already-connected socket.
self.handle_connect()
self.tlsVersion = "N/A"
self.isSSL = False
def handle_connect(self):
def state_tls_init(self):
self.isSSL = True
self.tlsStarted = True
# Once the connection has been established, it's safe to wrap the
# socket.
if sys.version_info >= (2,7,9):
context = ssl.create_default_context(purpose = ssl.Purpose.SERVER_AUTH if self.server_side else ssl.Purpose.CLIENT_AUTH)
context.set_ciphers(self.ciphers)
# context.set_ecdh_curve("secp256k1")
context.set_ecdh_curve("secp256k1")
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
# also exclude TLSv1 and TLSv1.1 in the future
context.options |= ssl.OP_NOSSLv2 | ssl.OP_NOSSLv3
self.sslSock = context.wrap_socket(self.sock, server_side = self.server_side, do_handshake_on_connect=False)
context.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_SINGLE_ECDH_USE | ssl.OP_CIPHER_SERVER_PREFERENCE
self.sslSocket = context.wrap_socket(self.socket, server_side = self.server_side, do_handshake_on_connect=False)
else:
self.sslSocket = ssl.wrap_socket(self.socket,
server_side=self.server_side,
@ -63,39 +59,115 @@ class TLSHandshake(asyncore.dispatcher):
do_handshake_on_connect=False)
self.sslSocket.setblocking(0)
self.want_read = self.want_write = True
self.set_state("tls_handshake")
return False
# if hasattr(self.socket, "context"):
# self.socket.context.set_ecdh_curve("secp256k1")
def state_tls_handshake(self):
return False
def writable(self):
return self.want_write
try:
if self.tlsStarted and not self.tlsDone and not self.write_buf:
return self.want_write
return AdvancedDispatcher.writable(self)
except AttributeError:
return AdvancedDispatcher.writable(self)
def readable(self):
return self.want_read
try:
# during TLS handshake, and after flushing write buffer, return status of last handshake attempt
if self.tlsStarted and not self.tlsDone and not self.write_buf:
#print "tls readable, %r" % (self.want_read)
return self.want_read
# prior to TLS handshake, receiveDataThread should emulate synchronous behaviour
elif not self.fullyEstablished and (self.expectBytes == 0 or not self.write_buf_empty()):
return False
return AdvancedDispatcher.readable(self)
except AttributeError:
return AdvancedDispatcher.readable(self)
def handle_read(self):
if not self.tlsDone:
self._handshake()
try:
# wait for write buffer flush
if self.tlsStarted and not self.tlsDone and not self.write_buf:
#logger.debug("%s:%i TLS handshaking (read)", self.destination.host, self.destination.port)
self.tls_handshake()
else:
#logger.debug("%s:%i Not TLS handshaking (read)", self.destination.host, self.destination.port)
return AdvancedDispatcher.handle_read(self)
except AttributeError:
return AdvancedDispatcher.handle_read(self)
except ssl.SSLError as err:
if err.errno == ssl.SSL_ERROR_WANT_READ:
return
elif err.errno in _DISCONNECTED_SSL:
self.handle_close()
return
logger.info("SSL Error: %s", str(err))
self.handle_close()
return
def handle_write(self):
if not self.tlsDone:
self._handshake()
def _handshake(self):
"""
Perform the handshake.
"""
try:
# wait for write buffer flush
if self.tlsStarted and not self.tlsDone and not self.write_buf:
#logger.debug("%s:%i TLS handshaking (write)", self.destination.host, self.destination.port)
self.tls_handshake()
else:
#logger.debug("%s:%i Not TLS handshaking (write)", self.destination.host, self.destination.port)
return AdvancedDispatcher.handle_write(self)
except AttributeError:
return AdvancedDispatcher.handle_write(self)
except ssl.SSLError as err:
if err.errno == ssl.SSL_ERROR_WANT_WRITE:
return 0
elif err.errno in _DISCONNECTED_SSL:
self.handle_close()
return 0
logger.info("SSL Error: %s", str(err))
self.handle_close()
return
def tls_handshake(self):
# wait for flush
if self.write_buf:
return False
# Perform the handshake.
try:
#print "handshaking (internal)"
self.sslSocket.do_handshake()
except ssl.SSLError, err:
except ssl.SSLError as err:
#print "%s:%i: handshake fail" % (self.destination.host, self.destination.port)
self.want_read = self.want_write = False
if err.args[0] == ssl.SSL_ERROR_WANT_READ:
#print "want read"
self.want_read = True
elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
if err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
#print "want write"
self.want_write = True
if not (self.want_write or self.want_read):
raise
except socket.error as err:
if err.errno in asyncore._DISCONNECTED:
self.handle_close()
else:
raise
else:
if sys.version_info >= (2, 7, 9):
self.tlsVersion = self.sslSocket.version()
logger.debug("%s:%i: TLS handshake success, TLS protocol version: %s",
self.destination.host, self.destination.port, self.sslSocket.version())
else:
self.tlsVersion = "TLSv1"
logger.debug("%s:%i: TLS handshake success", self.destination.host, self.destination.port)
# The handshake has completed, so remove this channel and...
self.del_channel()
self.set_socket(self.sslSocket)
self.tlsDone = True
self.bm_proto_reset()
self.set_state("connection_fully_established")
receiveDataQueue.put(self.destination)
return False

Some files were not shown because too many files have changed in this diff Show More