Merge branch 'master' into master
This commit is contained in:
commit
58147fb211
26
INSTALL.md
26
INSTALL.md
|
@ -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
14
PULL_REQUEST_TEMPLATE.md
Normal 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).
|
|
@ -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
215
checkdeps.py
Normal 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"
|
|
@ -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 |
|
@ -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
|
||||
|
||||
|
|
60
packages/collectd/pybitmessagestatus.py
Normal file
60
packages/collectd/pybitmessagestatus.py
Normal 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)
|
18
packages/systemd/bitmessage.service
Normal file
18
packages/systemd/bitmessage.service
Normal 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
323
setup.py
|
@ -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}
|
||||
)
|
||||
|
|
115
src/api.py
115
src/api.py
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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 © 2012-2016 Jonathan Warren<br/>Copyright © 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))
|
||||
|
|
@ -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><html><head/><body><p>Copyright © 2012-2014 Jonathan Warren<br/>Copyright © 2013-2014 The Bitmessage Developers</p></body></html></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><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></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"><html><head/><body><p><a href="https://github.com/Bitmessage/PyBitmessage/tree/:branch:"><span style="text-decoration:none; color:#0000ff;">PyBitmessage :version:</span></a></p></body></html></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><html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 The Bitmessage Developers</p></body></html></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><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></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>
|
||||
|
|
|
@ -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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -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/>
|
||||
|
|
354
src/bitmessageqt/address_dialogs.py
Normal file
354
src/bitmessageqt/address_dialogs.py
Normal 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()
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -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_())
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -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")))
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -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/>
|
||||
|
|
|
@ -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))
|
||||
|
|
@ -22,7 +22,8 @@ class SafeHTMLParser(HTMLParser):
|
|||
replaces_pre = [["&", "&"], ["\"", """], ["<", "<"], [">", ">"]]
|
||||
replaces_post = [["\n", "<br/>"], ["\t", " "], [" ", " "], [" ", " "], ["<br/> ", "<br/> "]]
|
||||
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="([^"]+)&')
|
||||
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 + "\""
|
||||
|
|
|
@ -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
21
src/bitmessageqt/sound.py
Normal 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')
|
|
@ -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))
|
||||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.')
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.')
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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
0
src/fallback/__init__.py
Normal file
0
src/fallback/umsgpack/__init__.py
Normal file
0
src/fallback/umsgpack/__init__.py
Normal file
1057
src/fallback/umsgpack/umsgpack.py
Normal file
1057
src/fallback/umsgpack/umsgpack.py
Normal 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
40
src/helper_ackPayload.py
Normal 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
|
|
@ -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.'
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
9
src/helper_random.py
Normal 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)
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
263
src/inventory.py
263
src/inventory.py
|
@ -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):
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
37
src/multiqueue.py
Normal 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)
|
|
@ -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
36
src/network/addrthread.py
Normal 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)
|
|
@ -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()
|
||||
|
|
35
src/network/announcethread.py
Normal file
35
src/network/announcethread.py
Normal 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]))
|
944
src/network/asyncore_pollchoose.py
Normal file
944
src/network/asyncore_pollchoose.py
Normal 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
113
src/network/bmobject.py
Normal 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
572
src/network/bmproto.py
Normal 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)
|
58
src/network/connectionchooser.py
Normal file
58
src/network/connectionchooser.py
Normal 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
|
259
src/network/connectionpool.py
Normal file
259
src/network/connectionpool.py
Normal 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
141
src/network/dandelion.py
Normal 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
|
74
src/network/downloadthread.py
Normal file
74
src/network/downloadthread.py
Normal 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
49
src/network/http-old.py
Normal 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)
|
|
@ -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
87
src/network/invthread.py
Normal 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)
|
40
src/network/networkthread.py
Normal file
40
src/network/networkthread.py
Normal 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
3
src/network/node.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
import collections
|
||||
|
||||
Node = collections.namedtuple('Node', ['services', 'host', 'port'])
|
129
src/network/objectracker.py
Normal file
129
src/network/objectracker.py
Normal 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
|
||||
#
|
|
@ -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
|
||||
|
|
53
src/network/receivequeuethread.py
Normal file
53
src/network/receivequeuethread.py
Normal 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
111
src/network/socks4a.py
Normal 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
176
src/network/socks5.py
Normal 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
70
src/network/stats.py
Normal 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
325
src/network/tcp.py
Normal 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)
|
|
@ -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
Reference in New Issue
Block a user