diff --git a/INSTALL.md b/INSTALL.md
index 07468759..e66915fd 100644
--- a/INSTALL.md
+++ b/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
diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..91c3bc96
--- /dev/null
+++ b/PULL_REQUEST_TEMPLATE.md
@@ -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).
\ No newline at end of file
diff --git a/README.md b/README.md
index 7a161d04..0ea6144b 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/checkdeps.py b/checkdeps.py
new file mode 100644
index 00000000..05f00944
--- /dev/null
+++ b/checkdeps.py
@@ -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"
diff --git a/desktop/can-icon.svg b/desktop/can-icon.svg
index b4c2bd89..7c854e34 100644
--- a/desktop/can-icon.svg
+++ b/desktop/can-icon.svg
@@ -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">
@@ -45,7 +45,7 @@
image/svg+xml
-
+
@@ -55,129 +55,95 @@
id="layer1">
-
-
-
-
-
-
-
-
-
-
-
+ transform="translate(10.559462,156.88343)">
-
+ sodipodi:nodetypes="ccccc"
+ inkscape:export-xdpi="4.57552"
+ inkscape:export-ydpi="4.57552" />
+ 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" />
-
+ 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" />
-
-
+ 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" />
+ 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" />
+ inkscape:export-xdpi="4.57552"
+ inkscape:export-ydpi="4.57552" />
+ inkscape:export-xdpi="4.57552"
+ inkscape:export-ydpi="4.57552" />
+ inkscape:export-xdpi="4.57552"
+ inkscape:export-ydpi="4.57552" />
+ inkscape:export-xdpi="4.57552"
+ inkscape:export-ydpi="4.57552" />
+
+
+
diff --git a/dev/powinterrupttest.py b/dev/powinterrupttest.py
index 2f8cfcac..cc4c2197 100644
--- a/dev/powinterrupttest.py
+++ b/dev/powinterrupttest.py
@@ -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
diff --git a/packages/collectd/pybitmessagestatus.py b/packages/collectd/pybitmessagestatus.py
new file mode 100644
index 00000000..1db9f5b1
--- /dev/null
+++ b/packages/collectd/pybitmessagestatus.py
@@ -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)
diff --git a/packages/systemd/bitmessage.service b/packages/systemd/bitmessage.service
new file mode 100644
index 00000000..1a9f7f47
--- /dev/null
+++ b/packages/systemd/bitmessage.service
@@ -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
diff --git a/setup.py b/setup.py
index 2c769a63..ba34f6df 100644
--- a/setup.py
+++ b/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}
+ )
diff --git a/src/api.py b/src/api.py
index d6d0b266..edb9e23d 100644
--- a/src/api.py
+++ b/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)
+
+
diff --git a/src/bitmessagecli.py b/src/bitmessagecli.py
index 068a5597..d7f4e5a9 100644
--- a/src/bitmessagecli.py
+++ b/src/bitmessagecli.py
@@ -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()
diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py
index d710720f..fc1d74b2 100644
--- a/src/bitmessagecurses/__init__.py
+++ b/src/bitmessagecurses/__init__.py
@@ -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()
diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py
index c98f7592..accd5740 100755
--- a/src/bitmessagemain.py
+++ b/src/bitmessagemain.py
@@ -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()
diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py
index fc9b4d9a..7bbd7dd9 100644
--- a/src/bitmessageqt/__init__.py
+++ b/src/bitmessageqt/__init__.py
@@ -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'))
diff --git a/src/bitmessageqt/about.py b/src/bitmessageqt/about.py
deleted file mode 100644
index a3483675..00000000
--- a/src/bitmessageqt/about.py
+++ /dev/null
@@ -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", "
Copyright © 2012-2016 Jonathan Warren Copyright © 2013-2016 The Bitmessage Developers
", None))
- self.label_3.setText(_translate("aboutDialog", "Distributed under the MIT/X11 software license; see http://www.opensource.org/licenses/mit-license.php
", None))
- self.label_5.setText(_translate("aboutDialog", "This is Beta software.", None))
-
diff --git a/src/bitmessageqt/about.ui b/src/bitmessageqt/about.ui
index 3deab41b..8ec7159c 100644
--- a/src/bitmessageqt/about.ui
+++ b/src/bitmessageqt/about.ui
@@ -6,117 +6,91 @@
0
0
- 360
- 315
+ 430
+ 340
About
-
-
-
- 20
- 280
- 311
- 32
-
-
-
- Qt::Horizontal
-
-
- QDialogButtonBox::Ok
-
-
-
-
-
- 70
- 126
- 111
- 20
-
-
-
-
- 75
- true
-
-
-
- PyBitmessage
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
-
-
- 190
- 126
- 161
- 20
-
-
-
- version ?
-
-
-
-
-
- 10
- 150
- 341
- 41
-
-
-
- <html><head/><body><p>Copyright © 2012-2014 Jonathan Warren<br/>Copyright © 2013-2014 The Bitmessage Developers</p></body></html>
-
-
- Qt::AlignCenter
-
-
-
-
-
- 20
- 200
- 331
- 71
-
-
-
- <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>
-
-
- true
-
-
- true
-
-
-
-
-
- 10
- 190
- 341
- 20
-
-
-
- This is Beta software.
-
-
- Qt::AlignCenter
-
-
+
+ -
+
+
+
+
+
+ :/newPrefix/images/can-icon-24px.png
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+
+ 75
+ 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>
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 The Bitmessage Developers</p></body></html>
+
+
+ Qt::AlignLeft
+
+
+
+ -
+
+
+ This is Beta software.
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ <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>
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Ok
+
+
+
+
-
+
+
+
buttonBox
diff --git a/src/bitmessageqt/account.py b/src/bitmessageqt/account.py
index 611f5039..92d497f8 100644
--- a/src/bitmessageqt/account.py
+++ b/src/bitmessageqt/account.py
@@ -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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
diff --git a/src/bitmessageqt/addaddressdialog.py b/src/bitmessageqt/addaddressdialog.py
deleted file mode 100644
index 5ed19e0a..00000000
--- a/src/bitmessageqt/addaddressdialog.py
+++ /dev/null
@@ -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))
-
diff --git a/src/bitmessageqt/addaddressdialog.ui b/src/bitmessageqt/addaddressdialog.ui
index d7963e0a..09701fa4 100644
--- a/src/bitmessageqt/addaddressdialog.ui
+++ b/src/bitmessageqt/addaddressdialog.ui
@@ -7,9 +7,15 @@
0
0
368
- 162
+ 232
+
+
+ 368
+ 200
+
+
Add new entry
@@ -25,7 +31,7 @@
-
-
+
-
@@ -47,7 +53,7 @@
- -
+
-
Qt::Horizontal
@@ -57,6 +63,19 @@
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
diff --git a/src/bitmessageqt/address_dialogs.py b/src/bitmessageqt/address_dialogs.py
new file mode 100644
index 00000000..2ea5cef8
--- /dev/null
+++ b/src/bitmessageqt/address_dialogs.py
@@ -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()
diff --git a/src/bitmessageqt/addressvalidator.py b/src/bitmessageqt/addressvalidator.py
index 56352b72..f9de70a2 100644
--- a/src/bitmessageqt/addressvalidator.py
+++ b/src/bitmessageqt/addressvalidator.py
@@ -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
diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py
index f001487e..cb3578c0 100644
--- a/src/bitmessageqt/bitmessageui.py
+++ b/src/bitmessageqt/bitmessageui.py
@@ -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))
diff --git a/src/bitmessageqt/connect.py b/src/bitmessageqt/connect.py
deleted file mode 100644
index 1e224afb..00000000
--- a/src/bitmessageqt/connect.py
+++ /dev/null
@@ -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))
-
diff --git a/src/bitmessageqt/connect.ui b/src/bitmessageqt/connect.ui
index 74173860..8b76f5ac 100644
--- a/src/bitmessageqt/connect.ui
+++ b/src/bitmessageqt/connect.ui
@@ -38,7 +38,14 @@
- -
+
-
+
+
+ Work offline
+
+
+
+ -
Qt::Horizontal
@@ -51,7 +58,7 @@
- -
+
-
Qt::Horizontal
diff --git a/src/bitmessageqt/dialogs.py b/src/bitmessageqt/dialogs.py
index d8133eb1..cb82f348 100644
--- a/src/bitmessageqt/dialogs.py
+++ b/src/bitmessageqt/dialogs.py
@@ -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))
diff --git a/src/bitmessageqt/emailgateway.py b/src/bitmessageqt/emailgateway.py
deleted file mode 100644
index 54ca4529..00000000
--- a/src/bitmessageqt/emailgateway.py
+++ /dev/null
@@ -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))
diff --git a/src/bitmessageqt/emailgateway.ui b/src/bitmessageqt/emailgateway.ui
index 927df46a..77a66dec 100644
--- a/src/bitmessageqt/emailgateway.ui
+++ b/src/bitmessageqt/emailgateway.ui
@@ -7,20 +7,20 @@
0
0
386
- 172
+ 240
Email gateway
- -
+
-
true
- Desired email address (including @mailchuck.com)
+ Desired email address (including @mailchuck.com):
@@ -50,28 +50,60 @@
- -
-
+ -
+
true
@mailchuck.com
+
+ 0
+
-
- Email gateway alows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
+ Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
true
+ -
+
+
+ false
+
+
+ Account status at email gateway
+
+
+ false
+
+
+
+ -
+
+
+ false
+
+
+ Change account settings at email gateway
+
+
+ false
+
+
+
-
+
+ false
+
Unregister from email gateway
@@ -84,7 +116,10 @@
radioButtonRegister
- lineEditEmailAddress
+ lineEditEmail
+ radioButtonStatus
+ radioButtonSettings
+ radioButtonUnregister
buttonBox
@@ -124,7 +159,7 @@
radioButtonRegister
clicked(bool)
- lineEditEmailAddress
+ lineEditEmail
setEnabled(bool)
@@ -140,7 +175,7 @@
radioButtonUnregister
clicked(bool)
- lineEditEmailAddress
+ lineEditEmail
setDisabled(bool)
@@ -153,5 +188,37 @@
+
+ radioButtonStatus
+ clicked(bool)
+ lineEditEmail
+ setDisabled(bool)
+
+
+ 20
+ 20
+
+
+ 20
+ 20
+
+
+
+
+ radioButtonSettings
+ clicked(bool)
+ lineEditEmail
+ setDisabled(bool)
+
+
+ 20
+ 20
+
+
+ 20
+ 20
+
+
+
diff --git a/src/bitmessageqt/foldertree.py b/src/bitmessageqt/foldertree.py
index 7cc42229..11227fca 100644
--- a/src/bitmessageqt/foldertree.py
+++ b/src/bitmessageqt/foldertree.py
@@ -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:
diff --git a/src/bitmessageqt/help.py b/src/bitmessageqt/help.py
deleted file mode 100644
index ff876514..00000000
--- a/src/bitmessageqt/help.py
+++ /dev/null
@@ -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", "https://bitmessage.org/wiki/PyBitmessage_Help ", 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))
-
diff --git a/src/bitmessageqt/iconglossary.py b/src/bitmessageqt/iconglossary.py
deleted file mode 100644
index 32d92db6..00000000
--- a/src/bitmessageqt/iconglossary.py
+++ /dev/null
@@ -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_())
-
diff --git a/src/bitmessageqt/iconglossary.ui b/src/bitmessageqt/iconglossary.ui
index 870a90ee..1bac94c8 100644
--- a/src/bitmessageqt/iconglossary.ui
+++ b/src/bitmessageqt/iconglossary.ui
@@ -76,7 +76,7 @@
-
- You are using TCP port ?. (This can be changed in the settings).
+ You are using TCP port ?. (This can be changed in the settings).
diff --git a/src/bitmessageqt/messageview.py b/src/bitmessageqt/messageview.py
index 40830a70..4d2e768d 100644
--- a/src/bitmessageqt/messageview.py
+++ b/src/bitmessageqt/messageview.py
@@ -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)
diff --git a/src/bitmessageqt/networkstatus.py b/src/bitmessageqt/networkstatus.py
index 7506b652..06b1e0ce 100644
--- a/src/bitmessageqt/networkstatus.py
+++ b/src/bitmessageqt/networkstatus.py
@@ -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)))
diff --git a/src/bitmessageqt/networkstatus.ui b/src/bitmessageqt/networkstatus.ui
index 6ae988bf..e0c01b57 100644
--- a/src/bitmessageqt/networkstatus.ui
+++ b/src/bitmessageqt/networkstatus.ui
@@ -7,7 +7,7 @@
0
0
602
- 252
+ 254
@@ -18,12 +18,12 @@
-
-
+
20
- QLayout::SetDefaultConstraint
+ QLayout::SetNoConstraint
-
@@ -31,7 +31,7 @@
20
- QLayout::SetFixedSize
+ QLayout::SetMinimumSize
-
@@ -85,17 +85,26 @@
QFrame::Plain
+
+ QAbstractItemView::NoEditTriggers
+
false
- true
+ false
QAbstractItemView::NoSelection
+
+ true
+
- false
+ true
+
+
+ 80
false
@@ -108,12 +117,42 @@
- Stream #
+ Peer
+
+
+ IP address or hostname
- Connections
+ Rating
+
+
+ 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
+
+
+
+
+ User agent
+
+
+ Peer's self-reported software
+
+
+
+
+ TLS
+
+
+ Connection encryption
+
+
+
+
+ Stream #
+
+
+ List of streams negotiated between you and the peer
@@ -125,6 +164,9 @@
4
+
+ QLayout::SetNoConstraint
+
-
diff --git a/src/bitmessageqt/newaddressdialog.py b/src/bitmessageqt/newaddressdialog.py
deleted file mode 100644
index afe6fa2d..00000000
--- a/src/bitmessageqt/newaddressdialog.py
+++ /dev/null
@@ -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", " Pros: You can recreate your addresses on any computer from memory. You need-not worry about backing up your keys.dat file as long as you can remember your passphrase. Cons: You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost. You must remember the address version number and the stream number along with your passphrase. 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.
", 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))
-
diff --git a/src/bitmessageqt/newchandialog.py b/src/bitmessageqt/newchandialog.py
index a7784206..ed683b13 100644
--- a/src/bitmessageqt/newchandialog.py
+++ b/src/bitmessageqt/newchandialog.py
@@ -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")))
diff --git a/src/bitmessageqt/newchandialog.ui b/src/bitmessageqt/newchandialog.ui
index 0abe061c..59dbb2bb 100644
--- a/src/bitmessageqt/newchandialog.ui
+++ b/src/bitmessageqt/newchandialog.ui
@@ -53,7 +53,7 @@
-
- Chan passhphrase/name:
+ Chan passphrase/name:
diff --git a/src/bitmessageqt/newsubscriptiondialog.py b/src/bitmessageqt/newsubscriptiondialog.py
deleted file mode 100644
index a63cce4a..00000000
--- a/src/bitmessageqt/newsubscriptiondialog.py
+++ /dev/null
@@ -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))
-
diff --git a/src/bitmessageqt/newsubscriptiondialog.ui b/src/bitmessageqt/newsubscriptiondialog.ui
index ed8615f4..ec67efa3 100644
--- a/src/bitmessageqt/newsubscriptiondialog.ui
+++ b/src/bitmessageqt/newsubscriptiondialog.ui
@@ -7,9 +7,15 @@
0
0
368
- 173
+ 254
+
+
+ 368
+ 200
+
+
Add new entry
@@ -22,7 +28,7 @@
-
-
+
-
@@ -32,7 +38,7 @@
-
-
+
-
@@ -44,17 +50,17 @@
- -
+
-
false
- CheckBox
+ Enter an address above.
- -
+
-
Qt::Horizontal
@@ -64,6 +70,19 @@
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
diff --git a/src/bitmessageqt/regenerateaddresses.py b/src/bitmessageqt/regenerateaddresses.py
deleted file mode 100644
index 7129b632..00000000
--- a/src/bitmessageqt/regenerateaddresses.py
+++ /dev/null
@@ -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))
-
diff --git a/src/bitmessageqt/safehtmlparser.py b/src/bitmessageqt/safehtmlparser.py
index a78991d3..d1d7910c 100644
--- a/src/bitmessageqt/safehtmlparser.py
+++ b/src/bitmessageqt/safehtmlparser.py
@@ -22,7 +22,8 @@ class SafeHTMLParser(HTMLParser):
replaces_pre = [["&", "&"], ["\"", """], ["<", "<"], [">", ">"]]
replaces_post = [["\n", " "], ["\t", " "], [" ", " "], [" ", " "], [" ", " "]]
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' 8:
+ return False
+ return True
diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py
index 446e09ab..181ce30e 100644
--- a/src/class_objectProcessor.py
+++ b/src/class_objectProcessor.py
@@ -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
diff --git a/src/class_outgoingSynSender.py b/src/class_outgoingSynSender.py
index c4f385c3..9b3eac14 100644
--- a/src/class_outgoingSynSender.py
+++ b/src/class_outgoingSynSender.py
@@ -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)
diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py
index 90364228..4e86196c 100644
--- a/src/class_receiveDataThread.py
+++ b/src/class_receiveDataThread.py
@@ -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):
diff --git a/src/class_sendDataThread.py b/src/class_sendDataThread.py
index 7de63a02..792fedd0 100644
--- a/src/class_sendDataThread.py
+++ b/src/class_sendDataThread.py
@@ -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.')
diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py
index 921e84ed..ca77881c 100644
--- a/src/class_singleCleaner.py
+++ b/src/class_singleCleaner.py
@@ -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):
diff --git a/src/class_singleListener.py b/src/class_singleListener.py
index 5332929c..7626542d 100644
--- a/src/class_singleListener.py
+++ b/src/class_singleListener.py
@@ -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.')
diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py
index b45c4aca..322bb20e 100644
--- a/src/class_singleWorker.py
+++ b/src/class_singleWorker.py
@@ -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))
diff --git a/src/class_smtpDeliver.py b/src/class_smtpDeliver.py
index 9492f123..bb659ebe 100644
--- a/src/class_smtpDeliver.py
+++ b/src/class_smtpDeliver.py
@@ -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:
diff --git a/src/class_smtpServer.py b/src/class_smtpServer.py
index 13882a94..b62a7130 100644
--- a/src/class_smtpServer.py
+++ b/src/class_smtpServer.py
@@ -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))
diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py
index 9bd7e8e7..18606e74 100644
--- a/src/class_sqlThread.py
+++ b/src/class_sqlThread.py
@@ -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
diff --git a/src/debug.py b/src/debug.py
index 663bbeeb..79c6e64e 100644
--- a/src/debug.py
+++ b/src/debug.py
@@ -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',
diff --git a/src/defaultKnownNodes.py b/src/defaultKnownNodes.py
index 4557c2e9..05b65014 100644
--- a/src/defaultKnownNodes.py
+++ b/src/defaultKnownNodes.py
@@ -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 = {}
diff --git a/src/depends.py b/src/depends.py
index 49c5c7a8..d66663b1 100755
--- a/src/depends.py
+++ b/src/depends.py
@@ -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):
diff --git a/src/fallback/__init__.py b/src/fallback/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/fallback/umsgpack/__init__.py b/src/fallback/umsgpack/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/fallback/umsgpack/umsgpack.py b/src/fallback/umsgpack/umsgpack.py
new file mode 100644
index 00000000..cd7a2037
--- /dev/null
+++ b/src/fallback/umsgpack/umsgpack.py
@@ -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()
diff --git a/src/helper_ackPayload.py b/src/helper_ackPayload.py
new file mode 100644
index 00000000..ef99ec2a
--- /dev/null
+++ b/src/helper_ackPayload.py
@@ -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
diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py
index 33a533ca..0ba86348 100644
--- a/src/helper_bootstrap.py
+++ b/src/helper_bootstrap.py
@@ -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.'
+ )
diff --git a/src/helper_generic.py b/src/helper_generic.py
index f2a293cd..588ae8f1 100644
--- a/src/helper_generic.py
+++ b/src/helper_generic.py
@@ -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):
diff --git a/src/helper_msgcoding.py b/src/helper_msgcoding.py
index 2ae44eea..f8bc95a6 100644
--- a/src/helper_msgcoding.py
+++ b/src/helper_msgcoding.py
@@ -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
diff --git a/src/helper_random.py b/src/helper_random.py
new file mode 100644
index 00000000..a0fb08f1
--- /dev/null
+++ b/src/helper_random.py
@@ -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)
diff --git a/src/helper_sql.py b/src/helper_sql.py
index d27401cf..fec67bef 100644
--- a/src/helper_sql.py
+++ b/src/helper_sql.py
@@ -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)
diff --git a/src/helper_threading.py b/src/helper_threading.py
index 599d297d..3b7ba378 100644
--- a/src/helper_threading.py
+++ b/src/helper_threading.py
@@ -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()
\ No newline at end of file
+ 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()
diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py
index 50f13cce..8729ec5c 100644
--- a/src/highlevelcrypto.py
+++ b/src/highlevelcrypto.py
@@ -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
diff --git a/src/inventory.py b/src/inventory.py
index 1087e655..598021fb 100644
--- a/src/inventory.py
+++ b/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):
diff --git a/src/knownnodes.py b/src/knownnodes.py
index ffb14edc..aa080128 100644
--- a/src/knownnodes.py
+++ b/src/knownnodes.py
@@ -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]
diff --git a/src/messagetypes/__init__.py b/src/messagetypes/__init__.py
index c3911dfd..1a5223df 100644
--- a/src/messagetypes/__init__.py
+++ b/src/messagetypes/__init__.py
@@ -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()
diff --git a/src/multiqueue.py b/src/multiqueue.py
new file mode 100644
index 00000000..62b0fa87
--- /dev/null
+++ b/src/multiqueue.py
@@ -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)
diff --git a/src/namecoin.py b/src/namecoin.py
index 2cfa5a1d..9b3c3c3e 100644
--- a/src/namecoin.py
+++ b/src/namecoin.py
@@ -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):
diff --git a/src/network/addrthread.py b/src/network/addrthread.py
new file mode 100644
index 00000000..5b0ea638
--- /dev/null
+++ b/src/network/addrthread.py
@@ -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)
diff --git a/src/network/advanceddispatcher.py b/src/network/advanceddispatcher.py
index 8258412a..6f857398 100644
--- a/src/network/advanceddispatcher.py
+++ b/src/network/advanceddispatcher.py
@@ -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()
diff --git a/src/network/announcethread.py b/src/network/announcethread.py
new file mode 100644
index 00000000..a94eeb36
--- /dev/null
+++ b/src/network/announcethread.py
@@ -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]))
diff --git a/src/network/asyncore_pollchoose.py b/src/network/asyncore_pollchoose.py
new file mode 100644
index 00000000..cd19063a
--- /dev/null
+++ b/src/network/asyncore_pollchoose.py
@@ -0,0 +1,944 @@
+# -*- Mode: Python -*-
+# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
+# Author: Sam Rushing
+
+# ======================================================================
+# 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()
diff --git a/src/network/bmobject.py b/src/network/bmobject.py
new file mode 100644
index 00000000..2e7dd092
--- /dev/null
+++ b/src/network/bmobject.py
@@ -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()
diff --git a/src/network/bmproto.py b/src/network/bmproto.py
new file mode 100644
index 00000000..28277f52
--- /dev/null
+++ b/src/network/bmproto.py
@@ -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)
diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py
new file mode 100644
index 00000000..819dfeb1
--- /dev/null
+++ b/src/network/connectionchooser.py
@@ -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
diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py
new file mode 100644
index 00000000..408d56e0
--- /dev/null
+++ b/src/network/connectionpool.py
@@ -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)
diff --git a/src/network/dandelion.py b/src/network/dandelion.py
new file mode 100644
index 00000000..06ecca24
--- /dev/null
+++ b/src/network/dandelion.py
@@ -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
diff --git a/src/network/downloadthread.py b/src/network/downloadthread.py
new file mode 100644
index 00000000..7eee2761
--- /dev/null
+++ b/src/network/downloadthread.py
@@ -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)
diff --git a/src/network/http-old.py b/src/network/http-old.py
new file mode 100644
index 00000000..56d24915
--- /dev/null
+++ b/src/network/http-old.py
@@ -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)
diff --git a/src/network/http.py b/src/network/http.py
index 56d24915..55cb81a1 100644
--- a/src/network/http.py
+++ b/src/network/http.py
@@ -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)
diff --git a/src/network/invthread.py b/src/network/invthread.py
new file mode 100644
index 00000000..d0d758fb
--- /dev/null
+++ b/src/network/invthread.py
@@ -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)
diff --git a/src/network/networkthread.py b/src/network/networkthread.py
new file mode 100644
index 00000000..5a709c8b
--- /dev/null
+++ b/src/network/networkthread.py
@@ -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()
diff --git a/src/network/node.py b/src/network/node.py
new file mode 100644
index 00000000..ab9f5fbe
--- /dev/null
+++ b/src/network/node.py
@@ -0,0 +1,3 @@
+import collections
+
+Node = collections.namedtuple('Node', ['services', 'host', 'port'])
diff --git a/src/network/objectracker.py b/src/network/objectracker.py
new file mode 100644
index 00000000..66b0685b
--- /dev/null
+++ b/src/network/objectracker.py
@@ -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
+#
diff --git a/src/network/proxy.py b/src/network/proxy.py
index d9830431..43298f63 100644
--- a/src/network/proxy.py
+++ b/src/network/proxy.py
@@ -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
diff --git a/src/network/receivequeuethread.py b/src/network/receivequeuethread.py
new file mode 100644
index 00000000..5399b972
--- /dev/null
+++ b/src/network/receivequeuethread.py
@@ -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()
diff --git a/src/network/socks4a.py b/src/network/socks4a.py
new file mode 100644
index 00000000..978ede04
--- /dev/null
+++ b/src/network/socks4a.py
@@ -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())
diff --git a/src/network/socks5.py b/src/network/socks5.py
new file mode 100644
index 00000000..52050ec9
--- /dev/null
+++ b/src/network/socks5.py
@@ -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())
diff --git a/src/network/stats.py b/src/network/stats.py
new file mode 100644
index 00000000..80925f7c
--- /dev/null
+++ b/src/network/stats.py
@@ -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
diff --git a/src/network/tcp.py b/src/network/tcp.py
new file mode 100644
index 00000000..33c4b6ca
--- /dev/null
+++ b/src/network/tcp.py
@@ -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)
diff --git a/src/network/tls.py b/src/network/tls.py
index 8f104c55..379dae99 100644
--- a/src/network/tls.py
+++ b/src/network/tls.py
@@ -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
diff --git a/src/network/udp.py b/src/network/udp.py
new file mode 100644
index 00000000..0dba5a3f
--- /dev/null
+++ b/src/network/udp.py
@@ -0,0 +1,176 @@
+import time
+import Queue
+import socket
+
+from debug import logger
+from network.advanceddispatcher import AdvancedDispatcher
+from network.bmproto import BMProtoError, BMProtoInsufficientDataError, BMProto
+from network.bmobject import BMObject, BMObjectInsufficientPOWError, BMObjectInvalidDataError, BMObjectExpiredError, BMObjectInvalidError, BMObjectAlreadyHaveError
+import network.asyncore_pollchoose as asyncore
+from network.objectracker import ObjectTracker
+
+from queues import objectProcessorQueue, UISignalQueue, receiveDataQueue
+import state
+import protocol
+
+class UDPSocket(BMProto):
+ port = 8444
+ announceInterval = 60
+
+ def __init__(self, host=None, sock=None, announcing=False):
+ super(BMProto, self).__init__(sock=sock)
+ self.verackReceived = True
+ self.verackSent = True
+ # TODO sort out streams
+ self.streams = [1]
+ self.fullyEstablished = True
+ self.connectedAt = 0
+ self.skipUntil = 0
+ if sock is None:
+ if host is None:
+ host = ''
+ if ":" in host:
+ self.create_socket(socket.AF_INET6, socket.SOCK_DGRAM)
+ else:
+ self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self.set_socket_reuse()
+ logger.info("Binding UDP socket to %s:%i", host, UDPSocket.port)
+ self.socket.bind((host, UDPSocket.port))
+ #BINDTODEVICE is only available on linux and requires root
+ #try:
+ #print "binding to %s" % (host)
+ #self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, host)
+ #except AttributeError:
+ else:
+ self.socket = sock
+ self.set_socket_reuse()
+ self.listening = state.Peer(self.socket.getsockname()[0], self.socket.getsockname()[1])
+ self.destination = state.Peer(self.socket.getsockname()[0], self.socket.getsockname()[1])
+ ObjectTracker.__init__(self)
+ self.connecting = False
+ self.connected = True
+ self.announcing = announcing
+ self.set_state("bm_header", expectBytes=protocol.Header.size)
+
+ def set_socket_reuse(self):
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ try:
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ except AttributeError:
+ pass
+
+ def state_bm_command(self):
+ return BMProto.state_bm_command(self)
+
+ # disable most commands before doing research / testing
+ # only addr (peer discovery), error and object are implemented
+
+ def bm_command_error(self):
+ return BMProto.bm_command_error(self)
+
+ def bm_command_getdata(self):
+ return True
+# return BMProto.bm_command_getdata(self)
+
+ def bm_command_inv(self):
+ return True
+# return BMProto.bm_command_inv(self)
+
+ def bm_command_object(self):
+ return BMProto.bm_command_object(self)
+
+ def bm_command_addr(self):
+# BMProto.bm_command_object(self)
+ addresses = self._decode_addr()
+ # only allow peer discovery from private IPs in order to avoid attacks from random IPs on the internet
+ if not self.local:
+ return True
+ remoteport = False
+ for i in addresses:
+ seenTime, stream, services, ip, port = i
+ decodedIP = protocol.checkIPAddress(str(ip))
+ if stream not in state.streamsInWhichIAmParticipating:
+ continue
+ if seenTime < time.time() - BMProto.maxTimeOffset or seenTime > time.time() + BMProto.maxTimeOffset:
+ continue
+ if decodedIP is False:
+ # if the address isn't local, interpret it as the hosts' own announcement
+ remoteport = port
+ if remoteport is False:
+ return True
+ logger.debug("received peer discovery from %s:%i (port %i):", self.destination.host, self.destination.port, remoteport)
+ if self.local:
+ state.discoveredPeers[state.Peer(self.destination.host, remoteport)] = time.time()
+ return True
+
+ def bm_command_portcheck(self):
+ return True
+
+ def bm_command_ping(self):
+ return True
+
+ def bm_command_pong(self):
+ return True
+
+ def bm_command_verack(self):
+ return True
+
+ def bm_command_version(self):
+ return True
+
+ def handle_connect(self):
+ return
+
+ def writable(self):
+ return self.write_buf
+
+ def readable(self):
+ return len(self.read_buf) < AdvancedDispatcher._buf_len
+
+ def handle_read(self):
+ try:
+ (recdata, addr) = self.socket.recvfrom(AdvancedDispatcher._buf_len)
+ except socket.error as e:
+ logger.error("socket error: %s", str(e))
+ return
+
+ self.destination = state.Peer(addr[0], addr[1])
+ encodedAddr = protocol.encodeHost(addr[0])
+ if protocol.checkIPAddress(encodedAddr, True):
+ self.local = True
+ else:
+ self.local = False
+ # overwrite the old buffer to avoid mixing data and so that self.local works correctly
+ self.read_buf[0:] = recdata
+ self.bm_proto_reset()
+ receiveDataQueue.put(self.listening)
+
+ def handle_write(self):
+ try:
+ retval = self.socket.sendto(self.write_buf, ('', UDPSocket.port))
+ except socket.error as e:
+ logger.error("socket error on sendato: %s", str(e))
+ retval = 0
+ self.slice_write_buf(retval)
+
+
+if __name__ == "__main__":
+ # initial fill
+
+ for host in (("127.0.0.1", 8448),):
+ direct = BMConnection(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)
diff --git a/src/openclpow.py b/src/openclpow.py
index 59375329..894a5b77 100644
--- a/src/openclpow.py
+++ b/src/openclpow.py
@@ -1,110 +1,111 @@
-#!/usr/bin/env python2.7
-from struct import pack, unpack
-import time
-import hashlib
-import random
-import os
-
-from bmconfigparser import BMConfigParser
-import paths
-from state import shutdown
-from debug import logger
-
-libAvailable = True
-ctx = False
-queue = False
-program = False
-gpus = []
-enabledGpus = []
-vendors = []
-hash_dt = None
-
-try:
- import numpy
- import pyopencl as cl
-except:
- libAvailable = False
-
-def initCL():
- global ctx, queue, program, hash_dt
- del enabledGpus[:]
- del vendors[:]
- del gpus[:]
- ctx = False
- try:
- hash_dt = numpy.dtype([('target', numpy.uint64), ('v', numpy.str_, 73)])
- try:
- for platform in cl.get_platforms():
- gpus.extend(platform.get_devices(device_type=cl.device_type.GPU))
- if BMConfigParser().safeGet("bitmessagesettings", "opencl") == platform.vendor:
- enabledGpus.extend(platform.get_devices(device_type=cl.device_type.GPU))
- if platform.vendor not in vendors:
- vendors.append(platform.vendor)
- except:
- pass
- if (len(enabledGpus) > 0):
- ctx = cl.Context(devices=enabledGpus)
- queue = cl.CommandQueue(ctx)
- f = open(os.path.join(paths.codePath(), "bitmsghash", 'bitmsghash.cl'), 'r')
- fstr = ''.join(f.readlines())
- program = cl.Program(ctx, fstr).build(options="")
- logger.info("Loaded OpenCL kernel")
- else:
- logger.info("No OpenCL GPUs found")
- del enabledGpus[:]
- except Exception as e:
- logger.error("OpenCL fail: ", exc_info=True)
- del enabledGpus[:]
-
-def openclAvailable():
- return (len(gpus) > 0)
-
-def openclEnabled():
- return (len(enabledGpus) > 0)
-
-def do_opencl_pow(hash, target):
- output = numpy.zeros(1, dtype=[('v', numpy.uint64, 1)])
- if (len(enabledGpus) == 0):
- return output[0][0]
-
- data = numpy.zeros(1, dtype=hash_dt, order='C')
- data[0]['v'] = ("0000000000000000" + hash).decode("hex")
- data[0]['target'] = target
-
- hash_buf = cl.Buffer(ctx, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=data)
- dest_buf = cl.Buffer(ctx, cl.mem_flags.WRITE_ONLY, output.nbytes)
-
- kernel = program.kernel_sha512
- worksize = kernel.get_work_group_info(cl.kernel_work_group_info.WORK_GROUP_SIZE, enabledGpus[0])
-
- kernel.set_arg(0, hash_buf)
- kernel.set_arg(1, dest_buf)
-
- start = time.time()
- progress = 0
- globamt = worksize*2000
-
- while output[0][0] == 0 and shutdown == 0:
- kernel.set_arg(2, pack("Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8])
- print "{} - value {} < {}".format(nonce, trialValue, target)
-
+#!/usr/bin/env python2.7
+from struct import pack, unpack
+import time
+import hashlib
+import random
+import os
+
+from bmconfigparser import BMConfigParser
+import paths
+from state import shutdown
+from debug import logger
+
+libAvailable = True
+ctx = False
+queue = False
+program = False
+gpus = []
+enabledGpus = []
+vendors = []
+hash_dt = None
+
+try:
+ import numpy
+ import pyopencl as cl
+except:
+ libAvailable = False
+
+def initCL():
+ global ctx, queue, program, hash_dt, libAvailable
+ if libAvailable is False:
+ return
+ del enabledGpus[:]
+ del vendors[:]
+ del gpus[:]
+ ctx = False
+ try:
+ hash_dt = numpy.dtype([('target', numpy.uint64), ('v', numpy.str_, 73)])
+ try:
+ for platform in cl.get_platforms():
+ gpus.extend(platform.get_devices(device_type=cl.device_type.GPU))
+ if BMConfigParser().safeGet("bitmessagesettings", "opencl") == platform.vendor:
+ enabledGpus.extend(platform.get_devices(device_type=cl.device_type.GPU))
+ if platform.vendor not in vendors:
+ vendors.append(platform.vendor)
+ except:
+ pass
+ if (len(enabledGpus) > 0):
+ ctx = cl.Context(devices=enabledGpus)
+ queue = cl.CommandQueue(ctx)
+ f = open(os.path.join(paths.codePath(), "bitmsghash", 'bitmsghash.cl'), 'r')
+ fstr = ''.join(f.readlines())
+ program = cl.Program(ctx, fstr).build(options="")
+ logger.info("Loaded OpenCL kernel")
+ else:
+ logger.info("No OpenCL GPUs found")
+ del enabledGpus[:]
+ except Exception as e:
+ logger.error("OpenCL fail: ", exc_info=True)
+ del enabledGpus[:]
+
+def openclAvailable():
+ return (len(gpus) > 0)
+
+def openclEnabled():
+ return (len(enabledGpus) > 0)
+
+def do_opencl_pow(hash, target):
+ output = numpy.zeros(1, dtype=[('v', numpy.uint64, 1)])
+ if (len(enabledGpus) == 0):
+ return output[0][0]
+
+ data = numpy.zeros(1, dtype=hash_dt, order='C')
+ data[0]['v'] = ("0000000000000000" + hash).decode("hex")
+ data[0]['target'] = target
+
+ hash_buf = cl.Buffer(ctx, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=data)
+ dest_buf = cl.Buffer(ctx, cl.mem_flags.WRITE_ONLY, output.nbytes)
+
+ kernel = program.kernel_sha512
+ worksize = kernel.get_work_group_info(cl.kernel_work_group_info.WORK_GROUP_SIZE, enabledGpus[0])
+
+ kernel.set_arg(0, hash_buf)
+ kernel.set_arg(1, dest_buf)
+
+ start = time.time()
+ progress = 0
+ globamt = worksize*2000
+
+ while output[0][0] == 0 and shutdown == 0:
+ kernel.set_arg(2, pack("Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8])
+ print "{} - value {} < {}".format(nonce, trialValue, target)
+
diff --git a/src/paths.py b/src/paths.py
index 0f843edf..325fcd8b 100644
--- a/src/paths.py
+++ b/src/paths.py
@@ -1,5 +1,7 @@
from os import environ, path
import sys
+import re
+from datetime import datetime
# When using py2exe or py2app, the variable frozen is added to the sys
# namespace. This can be used to setup a different code path for
@@ -95,13 +97,18 @@ def tail(f, lines=20):
all_read_text = ''.join(reversed(blocks))
return '\n'.join(all_read_text.splitlines()[-total_lines_wanted:])
+
def lastCommit():
githeadfile = path.join(codePath(), '..', '.git', 'logs', 'HEAD')
- version = ""
- if (path.isfile(githeadfile)):
+ result = {}
+ if path.isfile(githeadfile):
try:
with open(githeadfile, 'rt') as githead:
- version = tail(githead, 1).split()[1]
- except IOError:
+ line = tail(githead, 1)
+ result['commit'] = line.split()[1]
+ result['time'] = datetime.fromtimestamp(
+ float(re.search(r'>\s*(.*?)\s', line).group(1))
+ )
+ except (IOError, AttributeError, TypeError):
pass
- return version
+ return result
diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/plugins/indicator_libmessaging.py b/src/plugins/indicator_libmessaging.py
new file mode 100644
index 00000000..36178663
--- /dev/null
+++ b/src/plugins/indicator_libmessaging.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+
+import gi
+gi.require_version('MessagingMenu', '1.0') # noqa:E402
+from gi.repository import MessagingMenu
+
+from pybitmessage.bitmessageqt.utils import str_broadcast_subscribers
+from pybitmessage.tr import _translate
+
+
+class IndicatorLibmessaging(object):
+ def __init__(self, form):
+ try:
+ self.app = MessagingMenu.App(desktop_id='pybitmessage.desktop')
+ self.app.register()
+ self.app.connect('activate-source', self.activate)
+ except:
+ self.app = None
+ return
+
+ self._menu = {
+ 'send': unicode(_translate('MainWindow', 'Send')),
+ 'messages': unicode(_translate('MainWindow', 'Messages')),
+ 'subscriptions': unicode(_translate('MainWindow', 'Subscriptions'))
+ }
+
+ self.new_message_item = self.new_broadcast_item = None
+ self.form = form
+ self.show_unread()
+
+ def __del__(self):
+ if self.app:
+ self.app.unregister()
+
+ def activate(self, app, source):
+ self.form.appIndicatorInbox(
+ self.new_message_item if source == 'messages'
+ else self.new_broadcast_item
+ )
+
+ # show the number of unread messages and subscriptions
+ # on the messaging menu
+ def show_unread(self, draw_attention=False):
+ for source, count in zip(
+ ('messages', 'subscriptions'),
+ self.form.getUnread()
+ ):
+ if count > 0:
+ if self.app.has_source(source):
+ self.app.set_source_count(source, count)
+ else:
+ self.app.append_source_with_count(
+ source, None, self._menu[source], count)
+ if draw_attention:
+ self.app.draw_attention(source)
+
+ # update the Ubuntu messaging menu
+ def __call__(self, draw_attention, item=None, to_label=None):
+ if not self.app:
+ return
+ # remember this item to that the activate() can find it
+ if item:
+ if to_label == str_broadcast_subscribers:
+ self.new_broadcast_item = item
+ else:
+ self.new_message_item = item
+
+ self.show_unread(draw_attention)
+
+
+connect_plugin = IndicatorLibmessaging
diff --git a/src/plugins/notification_notify2.py b/src/plugins/notification_notify2.py
new file mode 100644
index 00000000..3fd935c4
--- /dev/null
+++ b/src/plugins/notification_notify2.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+
+import gi
+gi.require_version('Notify', '0.7')
+from gi.repository import Notify
+
+Notify.init('pybitmessage')
+
+def connect_plugin(title, subtitle, category, label, icon):
+ if not icon:
+ icon = 'mail-message-new' if category == 2 else 'pybitmessage'
+ connect_plugin.notification.update(title, subtitle, icon)
+ connect_plugin.notification.show()
+
+connect_plugin.notification = Notify.Notification.new("Init", "Init")
diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py
new file mode 100644
index 00000000..6601adaf
--- /dev/null
+++ b/src/plugins/plugin.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+
+import pkg_resources
+
+
+def get_plugins(group, point='', name=None, fallback=None):
+ """
+ Iterate through plugins (`connect_plugin` attribute of entry point)
+ which name starts with `point` or equals to `name`.
+ If `fallback` kwarg specified, plugin with that name yield last.
+ """
+ for ep in pkg_resources.iter_entry_points('bitmessage.' + group):
+ if name and ep.name == name or ep.name.startswith(point):
+ try:
+ plugin = ep.load().connect_plugin
+ if ep.name == fallback:
+ _fallback = plugin
+ else:
+ yield plugin
+ except (AttributeError,
+ ImportError,
+ ValueError,
+ pkg_resources.DistributionNotFound,
+ pkg_resources.UnknownExtra):
+ continue
+ try:
+ yield _fallback
+ except NameError:
+ pass
+
+
+def get_plugin(*args, **kwargs):
+ """Returns first available plugin `from get_plugins()` if any."""
+ for plugin in get_plugins(*args, **kwargs):
+ return plugin
diff --git a/src/plugins/qrcodeui.py b/src/plugins/qrcodeui.py
new file mode 100644
index 00000000..25b1e0b1
--- /dev/null
+++ b/src/plugins/qrcodeui.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+from PyQt4 import QtGui, QtCore
+import qrcode
+
+from pybitmessage.tr import translateText
+
+try:
+ _fromUtf8 = QtCore.QString.fromUtf8
+except AttributeError:
+ _fromUtf8 = lambda s: s
+
+
+# http://stackoverflow.com/questions/20452486
+class Image(qrcode.image.base.BaseImage):
+ def __init__(self, border, width, box_size):
+ self.border = border
+ self.width = width
+ self.box_size = box_size
+ size = (width + border * 2) * box_size
+ self._image = QtGui.QImage(
+ size, size, QtGui.QImage.Format_RGB16)
+ self._image.fill(QtCore.Qt.white)
+
+ def pixmap(self):
+ return QtGui.QPixmap.fromImage(self._image)
+
+ def drawrect(self, row, col):
+ painter = QtGui.QPainter(self._image)
+ painter.fillRect(
+ (col + self.border) * self.box_size,
+ (row + self.border) * self.box_size,
+ self.box_size, self.box_size,
+ QtCore.Qt.black)
+
+ def save(self, stream, kind=None):
+ pass
+
+
+class Ui_qrcodeDialog(object):
+ def setupUi(self, qrcodeDialog):
+ qrcodeDialog.setObjectName(_fromUtf8("qrcodeDialog"))
+ self.image = QtGui.QLabel(qrcodeDialog)
+ self.label = QtGui.QLabel(qrcodeDialog)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.label.setFont(font)
+ self.label.setAlignment(
+ QtCore.Qt.AlignCenter | QtCore.Qt.AlignVCenter)
+ self.buttonBox = QtGui.QDialogButtonBox(qrcodeDialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
+ layout = QtGui.QVBoxLayout(qrcodeDialog)
+ layout.addWidget(self.image)
+ layout.addWidget(self.label)
+ layout.addWidget(self.buttonBox)
+
+ self.retranslateUi(qrcodeDialog)
+ QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(
+ _fromUtf8("accepted()")), qrcodeDialog.accept)
+ QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(
+ _fromUtf8("rejected()")), qrcodeDialog.reject)
+ QtCore.QMetaObject.connectSlotsByName(qrcodeDialog)
+
+ def retranslateUi(self, qrcodeDialog):
+ qrcodeDialog.setWindowTitle(QtGui.QApplication.translate(
+ "qrcodeDialog", "QR-code",
+ None, QtGui.QApplication.UnicodeUTF8
+ ))
+
+ def render(self, text):
+ self.label.setText(text)
+ self.image.setPixmap(
+ qrcode.make(text, image_factory=Image).pixmap())
+
+
+class qrcodeDialog(QtGui.QDialog):
+
+ def __init__(self, parent):
+ QtGui.QWidget.__init__(self, parent)
+ self.ui = Ui_qrcodeDialog()
+ self.ui.setupUi(self)
+ self.parent = parent
+ QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self))
+
+
+def connect_plugin(form):
+ def on_action_ShowQR():
+ form.qrcodeDialogInstance = qrcodeDialog(form)
+ form.qrcodeDialogInstance.ui.render(
+ str(form.getCurrentAccount())
+ )
+ form.qrcodeDialogInstance.exec_()
+
+ form.actionShowQRCode = \
+ form.ui.addressContextMenuToolbarYourIdentities.addAction(
+ translateText("MainWindow", "Show QR-code"),
+ on_action_ShowQR
+ )
+ form.popMenuYourIdentities.addAction(form.actionShowQRCode)
diff --git a/src/plugins/sound_canberra.py b/src/plugins/sound_canberra.py
new file mode 100644
index 00000000..094901ed
--- /dev/null
+++ b/src/plugins/sound_canberra.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+from pybitmessage.bitmessageqt import sound
+
+import pycanberra
+
+_canberra = pycanberra.Canberra()
+
+_theme = {
+ sound.SOUND_UNKNOWN: 'message-new-email',
+ sound.SOUND_CONNECTED: 'network-connectivity-established',
+ sound.SOUND_DISCONNECTED: 'network-connectivity-lost',
+ sound.SOUND_CONNECTION_GREEN: 'network-connectivity-established'
+}
+
+
+def connect_plugin(category, label=None):
+ try:
+ _canberra.play(0, pycanberra.CA_PROP_EVENT_ID, _theme[category], None)
+ except (KeyError, pycanberra.CanberraException):
+ pass
diff --git a/src/plugins/sound_gstreamer.py b/src/plugins/sound_gstreamer.py
new file mode 100644
index 00000000..062da3f9
--- /dev/null
+++ b/src/plugins/sound_gstreamer.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+import gi
+gi.require_version('Gst', '1.0')
+from gi.repository import Gst # noqa: E402
+
+Gst.init(None)
+_player = Gst.ElementFactory.make("playbin", "player")
+
+
+def connect_plugin(sound_file):
+ _player.set_state(Gst.State.NULL)
+ _player.set_property("uri", "file://" + sound_file)
+ _player.set_state(Gst.State.PLAYING)
diff --git a/src/plugins/sound_playfile.py b/src/plugins/sound_playfile.py
new file mode 100644
index 00000000..c8216d07
--- /dev/null
+++ b/src/plugins/sound_playfile.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+
+try:
+ import winsound
+
+ def connect_plugin(sound_file):
+ winsound.PlaySound(sound_file, winsound.SND_FILENAME)
+except ImportError:
+ import os
+ import subprocess
+
+ play_cmd = {}
+
+ def _subprocess(*args):
+ FNULL = open(os.devnull, 'wb')
+ subprocess.call(
+ args, stdout=FNULL, stderr=subprocess.STDOUT, close_fds=True)
+
+ def connect_plugin(sound_file):
+ global play_cmd
+
+ ext = os.path.splitext(sound_file)[-1]
+ try:
+ return _subprocess(play_cmd[ext], sound_file)
+ except (KeyError, AttributeError):
+ pass
+
+ programs = ['gst123', 'gst-play-1.0']
+ if ext == '.wav':
+ programs.append('aplay')
+ elif ext == '.mp3':
+ programs += ['mpg123', 'mpg321', 'mpg321-mpg123']
+ for cmd in programs:
+ try:
+ _subprocess(cmd, sound_file)
+ except OSError:
+ pass # log here!
+ else:
+ play_cmd[ext] = cmd
+ break
diff --git a/src/proofofwork.py b/src/proofofwork.py
index 493b8b0c..df6ed295 100644
--- a/src/proofofwork.py
+++ b/src/proofofwork.py
@@ -19,6 +19,8 @@ import state
bitmsglib = 'bitmsghash.so'
+bmpow = None
+
def _set_idle():
if 'linux' in sys.platform:
os.nice(20)
@@ -237,6 +239,9 @@ def resetPoW():
# init
def init():
global bitmsglib, bso, bmpow
+
+ openclpow.initCL()
+
if "win32" == sys.platform:
if ctypes.sizeof(ctypes.c_voidp) == 4:
bitmsglib = 'bitmsghash32.dll'
@@ -266,9 +271,18 @@ def init():
else:
try:
bso = ctypes.CDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib))
- logger.info("Loaded C PoW DLL %s", bitmsglib)
+ except OSError:
+ import glob
+ try:
+ bso = ctypes.CDLL(glob.glob(os.path.join(
+ paths.codePath(), "bitmsghash", "bitmsghash*.so"
+ ))[0])
+ except (OSError, IndexError):
+ bso = None
except:
bso = None
+ else:
+ logger.info("Loaded C PoW DLL %s", bitmsglib)
if bso:
try:
bmpow = bso.BitmessagePOW
@@ -277,7 +291,5 @@ def init():
bmpow = None
else:
bmpow = None
-
-init()
-if bmpow is None:
- buildCPoW()
+ if bmpow is None:
+ buildCPoW()
diff --git a/src/protocol.py b/src/protocol.py
index 9d66ec2f..e5b2c5c2 100644
--- a/src/protocol.py
+++ b/src/protocol.py
@@ -23,10 +23,24 @@ from version import softwareVersion
#Service flags
NODE_NETWORK = 1
NODE_SSL = 2
+NODE_DANDELION = 8
#Bitfield flags
BITFIELD_DOESACK = 1
+#Error types
+STATUS_WARNING = 0
+STATUS_ERROR = 1
+STATUS_FATAL = 2
+
+#Object types
+OBJECT_GETPUBKEY = 0
+OBJECT_PUBKEY = 1
+OBJECT_MSG = 2
+OBJECT_BROADCAST = 3
+OBJECT_I2P = 0x493250
+OBJECT_ADDR = 0x61646472
+
eightBytesOfRandomDataUsedToDetectConnectionsToSelf = pack(
'>Q', random.randrange(1, 18446744073709551615))
@@ -34,6 +48,8 @@ eightBytesOfRandomDataUsedToDetectConnectionsToSelf = pack(
#New code should use CreatePacket instead of Header.pack
Header = Struct('!L12sL4s')
+VersionPacket = Struct('>LqQ20s4s36sH')
+
# Bitfield
def getBitfield(address):
@@ -54,7 +70,7 @@ def isBitSetWithinBitfield(fourByteString, n):
x, = unpack('>L', fourByteString)
return x & 2**n != 0
-# data handling
+# ip addresses
def encodeHost(host):
if host.find('.onion') > -1:
@@ -73,6 +89,58 @@ def networkType(host):
else:
return 'IPv6'
+def checkIPAddress(host, private=False):
+ if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
+ hostStandardFormat = socket.inet_ntop(socket.AF_INET, host[12:])
+ return checkIPv4Address(host[12:], hostStandardFormat, private)
+ elif host[0:6] == '\xfd\x87\xd8\x7e\xeb\x43':
+ # Onion, based on BMD/bitcoind
+ hostStandardFormat = base64.b32encode(host[6:]).lower() + ".onion"
+ if private:
+ return False
+ return hostStandardFormat
+ else:
+ hostStandardFormat = socket.inet_ntop(socket.AF_INET6, host)
+ if hostStandardFormat == "":
+ # This can happen on Windows systems which are not 64-bit compatible
+ # so let us drop the IPv6 address.
+ return False
+ return checkIPv6Address(host, hostStandardFormat, private)
+
+def checkIPv4Address(host, hostStandardFormat, private=False):
+ if host[0] == '\x7F': # 127/8
+ if not private:
+ logger.debug('Ignoring IP address in loopback range: ' + hostStandardFormat)
+ return hostStandardFormat if private else False
+ if host[0] == '\x0A': # 10/8
+ if not private:
+ logger.debug('Ignoring IP address in private range: ' + hostStandardFormat)
+ return hostStandardFormat if private else False
+ if host[0:2] == '\xC0\xA8': # 192.168/16
+ if not private:
+ logger.debug('Ignoring IP address in private range: ' + hostStandardFormat)
+ return hostStandardFormat if private else False
+ if host[0:2] >= '\xAC\x10' and host[0:2] < '\xAC\x20': # 172.16/12
+ if not private:
+ logger.debug('Ignoring IP address in private range:' + hostStandardFormat)
+ return hostStandardFormat if private else False
+ return False if private else hostStandardFormat
+
+def checkIPv6Address(host, hostStandardFormat, private=False):
+ if host == ('\x00' * 15) + '\x01':
+ if not private:
+ logger.debug('Ignoring loopback address: ' + hostStandardFormat)
+ return False
+ if host[0] == '\xFE' and (ord(host[1]) & 0xc0) == 0x80:
+ if not private:
+ logger.debug ('Ignoring local address: ' + hostStandardFormat)
+ return hostStandardFormat if private else False
+ if (ord(host[0]) & 0xfe) == 0xfc:
+ if not private:
+ logger.debug ('Ignoring unique local address: ' + hostStandardFormat)
+ return hostStandardFormat if private else False
+ return False if private else hostStandardFormat
+
# checks
def haveSSL(server = False):
@@ -121,10 +189,15 @@ def CreatePacket(command, payload=''):
b[Header.size:] = payload
return bytes(b)
-def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server = False):
+def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server = False, nodeid = None):
payload = ''
payload += pack('>L', 3) # protocol version.
- payload += pack('>q', NODE_NETWORK|(NODE_SSL if haveSSL(server) else 0)) # bitflags of the services I offer.
+ # bitflags of the services I offer.
+ payload += pack('>q',
+ NODE_NETWORK |
+ (NODE_SSL if haveSSL(server) else 0) |
+ (NODE_DANDELION if state.dandelion else 0)
+ )
payload += pack('>q', int(time.time()))
payload += pack(
@@ -136,7 +209,12 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server
payload += encodeHost(remoteHost)
payload += pack('>H', remotePort) # remote IPv6 and port
- payload += pack('>q', NODE_NETWORK|(NODE_SSL if haveSSL(server) else 0)) # bitflags of the services I offer.
+ # bitflags of the services I offer.
+ payload += pack('>q',
+ NODE_NETWORK |
+ (NODE_SSL if haveSSL(server) else 0) |
+ (NODE_DANDELION if state.dandelion else 0)
+ )
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack(
'>L', 2130706433) # = 127.0.0.1. This will be ignored by the remote host. The actual remote connected IP will be used.
# we have a separate extPort and
@@ -152,7 +230,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server
payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'port'))
random.seed()
- payload += eightBytesOfRandomDataUsedToDetectConnectionsToSelf
+ if nodeid is not None:
+ payload += nodeid[0:8]
+ else:
+ payload += eightBytesOfRandomDataUsedToDetectConnectionsToSelf
userAgent = '/PyBitmessage:' + softwareVersion + '/'
payload += encodeVarint(len(userAgent))
payload += userAgent
diff --git a/src/queues.py b/src/queues.py
index 335863e9..e8923dbd 100644
--- a/src/queues.py
+++ b/src/queues.py
@@ -1,10 +1,16 @@
import Queue
+
from class_objectProcessorQueue import ObjectProcessorQueue
+from multiqueue import MultiQueue
workerQueue = Queue.Queue()
UISignalQueue = Queue.Queue()
addressGeneratorQueue = Queue.Queue()
# receiveDataThreads dump objects they hear on the network into this queue to be processed.
objectProcessorQueue = ObjectProcessorQueue()
+invQueue = MultiQueue()
+addrQueue = MultiQueue()
+portCheckerQueue = Queue.Queue()
+receiveDataQueue = Queue.Queue()
apiAddressGeneratorReturnQueue = Queue.Queue(
) # The address generator thread uses this queue to get information back to the API thread.
diff --git a/src/randomtrackingdict.py b/src/randomtrackingdict.py
new file mode 100644
index 00000000..83d35cdf
--- /dev/null
+++ b/src/randomtrackingdict.py
@@ -0,0 +1,134 @@
+import random
+from threading import RLock
+from time import time
+
+class RandomTrackingDict(object):
+ maxPending = 10
+ pendingTimeout = 60
+ def __init__(self): # O(1)
+ self.dictionary = {}
+ self.indexDict = []
+ self.len = 0
+ self.pendingLen = 0
+ self.lastPoll = 0
+ self.lock = RLock()
+
+ def __len__(self):
+ return self.len
+
+ def __contains__(self, key):
+ return key in self.dictionary
+
+ def __getitem__(self, key):
+ return self.dictionary[key][1]
+
+ def _swap(self, i1, i2):
+ with self.lock:
+ key1 = self.indexDict[i1]
+ key2 = self.indexDict[i2]
+ self.indexDict[i1] = key2
+ self.indexDict[i2] = key1
+ self.dictionary[key1][0] = i2
+ self.dictionary[key2][0] = i1
+ # for quick reassignment
+ return i2
+
+ def __setitem__(self, key, value):
+ with self.lock:
+ if key in self.dictionary:
+ self.dictionary[key][1] = value
+ else:
+ self.indexDict.append(key)
+ self.dictionary[key] = [self.len, value]
+ self._swap(self.len, self.len - self.pendingLen)
+ self.len += 1
+
+ def __delitem__(self, key):
+ if not key in self.dictionary:
+ raise KeyError
+ with self.lock:
+ index = self.dictionary[key][0]
+ # not pending
+ if index < self.len - self.pendingLen:
+ # left of pending part
+ index = self._swap(index, self.len - self.pendingLen - 1)
+ # pending
+ else:
+ self.pendingLen -= 1
+ # end
+ self._swap(index, self.len - 1)
+ # if the following del is batched, performance of this single
+ # operation can improve 4x, but it's already very fast so we'll
+ # ignore it for the time being
+ del self.indexDict[-1]
+ del self.dictionary[key]
+ self.len -= 1
+
+ def setMaxPending(self, maxPending):
+ self.maxPending = maxPending
+
+ def setPendingTimeout(self, pendingTimeout):
+ self.pendingTimeout = pendingTimeout
+
+ def randomKeys(self, count=1):
+ if self.len == 0 or ((self.pendingLen >= self.maxPending or
+ self.pendingLen == self.len) and self.lastPoll +
+ self.pendingTimeout > time()):
+ raise KeyError
+ # reset if we've requested all
+ with self.lock:
+ if self.pendingLen == self.len:
+ self.pendingLen = 0
+ available = self.len - self.pendingLen
+ if count > available:
+ count = available
+ randomIndex = random.sample(range(self.len - self.pendingLen), count)
+ retval = [self.indexDict[i] for i in randomIndex]
+
+ for i in sorted(randomIndex, reverse=True):
+ # swap with one below lowest pending
+ self._swap(i, self.len - self.pendingLen - 1)
+ self.pendingLen += 1
+ self.lastPoll = time()
+ return retval
+
+if __name__ == '__main__':
+ def randString():
+ retval = b''
+ for _ in range(32):
+ retval += chr(random.randint(0,255))
+ return retval
+
+ a = []
+ k = RandomTrackingDict()
+ d = {}
+
+# print "populating normal dict"
+# a.append(time())
+# for i in range(50000):
+# d[randString()] = True
+# a.append(time())
+ print "populating random tracking dict"
+ a.append(time())
+ for i in range(50000):
+ k[randString()] = True
+ a.append(time())
+ print "done"
+ while len(k) > 0:
+ retval = k.randomKeys(1000)
+ if not retval:
+ print "error getting random keys"
+ #a.append(time())
+ try:
+ k.randomKeys(100)
+ print "bad"
+ except KeyError:
+ pass
+ #a.append(time())
+ for i in retval:
+ del k[i]
+ #a.append(time())
+ a.append(time())
+
+ for x in range(len(a) - 1):
+ print "%i: %.3f" % (x, a[x+1] - a[x])
diff --git a/src/shared.py b/src/shared.py
index c28392f6..e2f3c1cc 100644
--- a/src/shared.py
+++ b/src/shared.py
@@ -22,7 +22,7 @@ from bmconfigparser import BMConfigParser
import highlevelcrypto
#import helper_startup
from helper_sql import *
-from inventory import Inventory, PendingDownload
+from inventory import Inventory
from queues import objectProcessorQueue
import protocol
import state
@@ -49,7 +49,7 @@ clientHasReceivedIncomingConnections = False #used by API command clientStatus
numberOfMessagesProcessed = 0
numberOfBroadcastsProcessed = 0
numberOfPubkeysProcessed = 0
-daemon = False
+
needToWriteKnownNodesToDisk = False # If True, the singleCleaner will write it to disk eventually.
maximumLengthOfTimeToBotherResendingMessages = 0
timeOffsetWrongCount = 0
@@ -110,29 +110,27 @@ def reloadMyAddressHashes():
#myPrivateKeys.clear()
keyfileSecure = checkSensitiveFilePermissions(state.appdata + 'keys.dat')
- configSections = BMConfigParser().sections()
hasEnabledKeys = False
- for addressInKeysFile in configSections:
- if addressInKeysFile <> 'bitmessagesettings':
- isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled')
- if isEnabled:
- hasEnabledKeys = True
- status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile)
- if addressVersionNumber == 2 or addressVersionNumber == 3 or addressVersionNumber == 4:
- # Returns a simple 32 bytes of information encoded in 64 Hex characters,
- # or null if there was an error.
- privEncryptionKey = hexlify(decodeWalletImportFormat(
- BMConfigParser().get(addressInKeysFile, 'privencryptionkey')))
+ for addressInKeysFile in BMConfigParser().addresses():
+ isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled')
+ if isEnabled:
+ hasEnabledKeys = True
+ status,addressVersionNumber,streamNumber,hash = decodeAddress(addressInKeysFile)
+ if addressVersionNumber == 2 or addressVersionNumber == 3 or addressVersionNumber == 4:
+ # Returns a simple 32 bytes of information encoded in 64 Hex characters,
+ # or null if there was an error.
+ privEncryptionKey = hexlify(decodeWalletImportFormat(
+ BMConfigParser().get(addressInKeysFile, 'privencryptionkey')))
- if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters
- myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey)
- myAddressesByHash[hash] = addressInKeysFile
- tag = hashlib.sha512(hashlib.sha512(encodeVarint(
- addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest()[32:]
- myAddressesByTag[tag] = addressInKeysFile
+ if len(privEncryptionKey) == 64:#It is 32 bytes encoded as 64 hex characters
+ myECCryptorObjects[hash] = highlevelcrypto.makeCryptor(privEncryptionKey)
+ myAddressesByHash[hash] = addressInKeysFile
+ tag = hashlib.sha512(hashlib.sha512(encodeVarint(
+ addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest()[32:]
+ myAddressesByTag[tag] = addressInKeysFile
- else:
- logger.error('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2, 3, or 4.\n')
+ else:
+ logger.error('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2, 3, or 4.\n')
if not keyfileSecure:
fixSensitiveFilePermissions(state.appdata + 'keys.dat', hasEnabledKeys)
@@ -342,22 +340,18 @@ def checkAndShareObjectWithPeers(data):
"""
if len(data) > 2 ** 18:
logger.info('The payload length of this object is too large (%s bytes). Ignoring it.' % len(data))
- PendingDownload().delete(calculateInventoryHash(data))
return 0
# Let us check to make sure that the proof of work is sufficient.
if not protocol.isProofOfWorkSufficient(data):
logger.info('Proof of work is insufficient.')
- PendingDownload().delete(calculateInventoryHash(data))
return 0
endOfLifeTime, = unpack('>Q', data[8:16])
if endOfLifeTime - int(time.time()) > 28 * 24 * 60 * 60 + 10800: # The TTL may not be larger than 28 days + 3 hours of wiggle room
logger.info('This object\'s End of Life time is too far in the future. Ignoring it. Time is %s' % endOfLifeTime)
- PendingDownload().delete(calculateInventoryHash(data))
return 0
if endOfLifeTime - int(time.time()) < - 3600: # The EOL time was more than an hour ago. That's too much.
logger.info('This object\'s End of Life time was more than an hour ago. Ignoring the object. Time is %s' % endOfLifeTime)
- PendingDownload().delete(calculateInventoryHash(data))
return 0
intObjectType, = unpack('>I', data[16:20])
try:
@@ -436,8 +430,6 @@ def _checkAndShareGetpubkeyWithPeers(data):
if len(data) < 42:
logger.info('getpubkey message doesn\'t contain enough data. Ignoring.')
return
- if len(data) > 200:
- logger.info('getpubkey is abnormally long. Sanity check failed. Ignoring object.')
embeddedTime, = unpack('>Q', data[8:16])
readPosition = 20 # bypass the nonce, time, and object type
requestedAddressVersionNumber, addressVersionLength = decodeVarint(
diff --git a/src/shutdown.py b/src/shutdown.py
index a066104c..f447148b 100644
--- a/src/shutdown.py
+++ b/src/shutdown.py
@@ -3,22 +3,17 @@ import Queue
import threading
import time
-from class_outgoingSynSender import outgoingSynSender
-from class_sendDataThread import sendDataThread
-from bmconfigparser import BMConfigParser
from debug import logger
from helper_sql import sqlQuery, sqlStoredProcedure
from helper_threading import StoppableThread
from knownnodes import saveKnownNodes
from inventory import Inventory
-import protocol
from queues import addressGeneratorQueue, objectProcessorQueue, UISignalQueue, workerQueue
import shared
import state
def doCleanShutdown():
state.shutdown = 1 #Used to tell proof of work worker threads and the objectProcessorThread to exit.
- protocol.broadcastToSendDataQueues((0, 'shutdown', 'no data'))
objectProcessorQueue.put(('checkShutdownVariable', 'no data'))
for thread in threading.enumerate():
if thread.isAlive() and isinstance(thread, StoppableThread):
@@ -51,9 +46,7 @@ def doCleanShutdown():
time.sleep(.25)
for thread in threading.enumerate():
- if isinstance(thread, sendDataThread):
- thread.sendDataThreadQueue.put((0, 'shutdown','no data'))
- if thread is not threading.currentThread() and isinstance(thread, StoppableThread) and not isinstance(thread, outgoingSynSender):
+ if thread is not threading.currentThread() and isinstance(thread, StoppableThread):
logger.debug("Waiting for thread %s", thread.name)
thread.join()
@@ -66,7 +59,7 @@ def doCleanShutdown():
except Queue.Empty:
break
- if BMConfigParser().safeGetBoolean('bitmessagesettings','daemon'):
+ if shared.thisapp.daemon:
logger.info('Clean shutdown complete.')
shared.thisapp.cleanup()
os._exit(0)
diff --git a/src/singleinstance.py b/src/singleinstance.py
index a6ac4552..ed1048ba 100644
--- a/src/singleinstance.py
+++ b/src/singleinstance.py
@@ -44,7 +44,7 @@ class singleinstance:
# file already exists, we try to remove (in case previous execution was interrupted)
if os.path.exists(self.lockfile):
os.unlink(self.lockfile)
- self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
+ self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_TRUNC)
except OSError:
type, e, tb = sys.exc_info()
if e.errno == 13:
@@ -52,8 +52,11 @@ class singleinstance:
sys.exit(-1)
print(e.errno)
raise
+ else:
+ pidLine = "%i\n" % self.lockPid
+ os.write(self.fd, pidLine)
else: # non Windows
- self.fp = open(self.lockfile, 'w')
+ self.fp = open(self.lockfile, 'a+')
try:
if self.daemon and self.lockPid != os.getpid():
fcntl.lockf(self.fp, fcntl.LOCK_EX) # wait for parent to finish
@@ -63,12 +66,26 @@ class singleinstance:
except IOError:
print 'Another instance of this application is already running'
sys.exit(-1)
+ else:
+ pidLine = "%i\n" % self.lockPid
+ self.fp.truncate(0)
+ self.fp.write(pidLine)
+ self.fp.flush()
def cleanup(self):
if not self.initialized:
return
if self.daemon and self.lockPid == os.getpid():
# these are the two initial forks while daemonizing
+ try:
+ if sys.platform == 'win32':
+ if hasattr(self, 'fd'):
+ os.close(self.fd)
+ else:
+ fcntl.lockf(self.fp, fcntl.LOCK_UN)
+ except Exception, e:
+ pass
+
return
print "Cleaning up lockfile"
try:
diff --git a/src/state.py b/src/state.py
index c7b79e07..73e4f789 100644
--- a/src/state.py
+++ b/src/state.py
@@ -21,6 +21,14 @@ curses = False
sqlReady = False # set to true by sqlTread when ready for processing
+maximumNumberOfHalfOpenConnections = 0
+
+invThread = None
+addrThread = None
+downloadThread = None
+
+ownAddresses = {}
+
# If the trustedpeer option is specified in keys.dat then this will
# contain a Peer which will be connected to instead of using the
# addresses advertised by other peers. The client will only connect to
@@ -33,6 +41,11 @@ sqlReady = False # set to true by sqlTread when ready for processing
# security.
trustedPeer = None
+discoveredPeers = {}
+
+# tracking pending downloads globally, for stats
+missingObjects = {}
+
Peer = collections.namedtuple('Peer', ['host', 'port'])
def resetNetworkProtocolAvailability():
@@ -40,3 +53,5 @@ def resetNetworkProtocolAvailability():
networkProtocolAvailability = {'IPv4': None, 'IPv6': None, 'onion': None}
resetNetworkProtocolAvailability()
+
+dandelion = 0
diff --git a/src/storage/__init__.py b/src/storage/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/storage/filesystem.py b/src/storage/filesystem.py
new file mode 100644
index 00000000..d64894a9
--- /dev/null
+++ b/src/storage/filesystem.py
@@ -0,0 +1,175 @@
+from binascii import hexlify, unhexlify
+from os import listdir, makedirs, path, remove, rmdir
+import string
+from threading import RLock
+import time
+import traceback
+
+from paths import lookupAppdataFolder
+from storage import InventoryStorage, InventoryItem
+
+class FilesystemInventory(InventoryStorage):
+ topDir = "inventory"
+ objectDir = "objects"
+ metadataFilename = "metadata"
+ dataFilename = "data"
+
+ def __init__(self):
+ super(self.__class__, self).__init__()
+ self.baseDir = path.join(lookupAppdataFolder(), FilesystemInventory.topDir)
+ for createDir in [self.baseDir, path.join(self.baseDir, "objects")]:
+ if path.exists(createDir):
+ if not path.isdir(createDir):
+ raise IOError("%s exists but it's not a directory" % (createDir))
+ else:
+ makedirs(createDir)
+ self.lock = RLock() # Guarantees that two receiveDataThreads don't receive and process the same message concurrently (probably sent by a malicious individual)
+ self._inventory = {}
+ self._load()
+
+ def __contains__(self, hash):
+ retval = False
+ for streamDict in self._inventory.values():
+ if hash in streamDict:
+ return True
+ return False
+
+ def __getitem__(self, hash):
+ for streamDict in self._inventory.values():
+ try:
+ retval = streamDict[hash]
+ except KeyError:
+ continue
+ if retval.payload is None:
+ retval = InventoryItem(retval.type, retval.stream, self.getData(hash), retval.expires, retval.tag)
+ return retval
+ raise KeyError(hash)
+
+ def __setitem__(self, hash, value):
+ with self.lock:
+ value = InventoryItem(*value)
+ try:
+ makedirs(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash)))
+ except OSError:
+ pass
+ try:
+ with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.metadataFilename), 'w') as f:
+ f.write("%s,%s,%s,%s," % (value.type, value.stream, value.expires, hexlify(value.tag)))
+ with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.dataFilename), 'w') as f:
+ f.write(value.payload)
+ except IOError:
+ raise KeyError
+ try:
+ self._inventory[value.stream][hash] = value
+ except KeyError:
+ self._inventory[value.stream] = {}
+ self._inventory[value.stream][hash] = value
+
+ def delHashId(self, hash):
+ for stream in self._inventory.keys():
+ try:
+ del self._inventory[stream][hash]
+ except KeyError:
+ pass
+ with self.lock:
+ try:
+ remove(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.metadataFilename))
+ except IOError:
+ pass
+ try:
+ remove(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.dataFilename))
+ except IOError:
+ pass
+ try:
+ rmdir(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash)))
+ except IOError:
+ pass
+
+ def __iter__(self):
+ elems = []
+ for streamDict in self._inventory.values():
+ elems.extend (streamDict.keys())
+ return elems.__iter__()
+
+ def __len__(self):
+ retval = 0
+ for streamDict in self._inventory.values():
+ retval += len(streamDict)
+ return retval
+
+ def _load(self):
+ newInventory = {}
+ for hashId in self.object_list():
+ try:
+ objectType, streamNumber, expiresTime, tag = self.getMetadata(hashId)
+ try:
+ newInventory[streamNumber][hashId] = InventoryItem(objectType, streamNumber, None, expiresTime, tag)
+ except KeyError:
+ newInventory[streamNumber] = {}
+ newInventory[streamNumber][hashId] = InventoryItem(objectType, streamNumber, None, expiresTime, tag)
+ except KeyError:
+ print "error loading %s" % (hexlify(hashId))
+ pass
+ self._inventory = newInventory
+# for i, v in self._inventory.items():
+# print "loaded stream: %s, %i items" % (i, len(v))
+
+ def stream_list(self):
+ return self._inventory.keys()
+
+ def object_list(self):
+ return [unhexlify(x) for x in listdir(path.join(self.baseDir, FilesystemInventory.objectDir))]
+
+ def getData(self, hashId):
+ try:
+ with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashId), FilesystemInventory.dataFilename), 'r') as f:
+ return f.read()
+ except IOError:
+ raise AttributeError
+
+ def getMetadata(self, hashId):
+ try:
+ with open(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hashId), FilesystemInventory.metadataFilename), 'r') as f:
+ objectType, streamNumber, expiresTime, tag, undef = string.split(f.read(), ",", 4)
+ return [int(objectType), int(streamNumber), int(expiresTime), unhexlify(tag)]
+ except IOError:
+ raise KeyError
+
+ def by_type_and_tag(self, objectType, tag):
+ retval = []
+ for stream, streamDict in self._inventory:
+ for hashId, item in streamDict:
+ if item.type == objectType and item.tag == tag:
+ try:
+ if item.payload is None:
+ item.payload = self.getData(hashId)
+ except IOError:
+ continue
+ retval.append(InventoryItem(item.type, item.stream, item.payload, item.expires, item.tag))
+ return retval
+
+ def hashes_by_stream(self, stream):
+ try:
+ return self._inventory[stream].keys()
+ except KeyError:
+ return []
+
+ def unexpired_hashes_by_stream(self, stream):
+ t = int(time.time())
+ try:
+ return [x for x, value in self._inventory[stream].items() if value.expires > t]
+ except KeyError:
+ return []
+
+ def flush(self):
+ self._load()
+
+ def clean(self):
+ minTime = int(time.time()) - (60 * 60 * 30)
+ deletes = []
+ for stream, streamDict in self._inventory.items():
+ for hashId, item in streamDict.items():
+ if item.expires < minTime:
+ deletes.append(hashId)
+ for hashId in deletes:
+ self.delHashId(hashId)
diff --git a/src/storage/sqlite.py b/src/storage/sqlite.py
new file mode 100644
index 00000000..438cbdcb
--- /dev/null
+++ b/src/storage/sqlite.py
@@ -0,0 +1,81 @@
+import collections
+from threading import current_thread, enumerate as threadingEnumerate, RLock
+import Queue
+import sqlite3
+import time
+
+from helper_sql import *
+from storage import InventoryStorage, InventoryItem
+
+class SqliteInventory(InventoryStorage):
+ 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).
+ self._objects = {} # cache for existing objects, used for quick lookups if we have an object. This is used for example 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.
+ self.lock = RLock() # Guarantees that two receiveDataThreads don't receive and process the same message concurrently (probably sent by a malicious individual)
+
+ def __contains__(self, hash):
+ with self.lock:
+ if hash in self._objects:
+ return True
+ rows = sqlQuery('SELECT streamnumber FROM inventory WHERE hash=?', sqlite3.Binary(hash))
+ if not rows:
+ return False
+ self._objects[hash] = rows[0][0]
+ return True
+
+ 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=?', sqlite3.Binary(hash))
+ if not rows:
+ raise KeyError(hash)
+ return InventoryItem(*rows[0])
+
+ def __setitem__(self, hash, value):
+ with self.lock:
+ value = InventoryItem(*value)
+ self._inventory[hash] = value
+ self._objects[hash] = value.stream
+
+ 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, objectType, tag):
+ with self.lock:
+ values = [value for value in self._inventory.values() if value.type == objectType and value.tag == tag]
+ values += (InventoryItem(*value) for value in sqlQuery('SELECT objecttype, streamnumber, payload, expirestime, tag FROM inventory WHERE objecttype=? AND tag=?', objectType, sqlite3.Binary(tag)))
+ return values
+
+ 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 += (str(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 (?, ?, ?, ?, ?, ?)', sqlite3.Binary(objectHash), *value)
+ self._inventory.clear()
+
+ def clean(self):
+ with self.lock:
+ sqlExecute('DELETE FROM inventory WHERE expirestime',int(time.time()) - (60 * 60 * 3))
+ self._objects.clear()
+ for objectHash, value in self._inventory.items():
+ self._objects[objectHash] = value.stream
+
diff --git a/src/storage/storage.py b/src/storage/storage.py
new file mode 100644
index 00000000..08c85708
--- /dev/null
+++ b/src/storage/storage.py
@@ -0,0 +1,48 @@
+import collections
+
+InventoryItem = collections.namedtuple('InventoryItem', 'type stream payload expires tag')
+
+class Storage(object):
+ pass
+# def __init__(self):
+# super(self.__class__, self).__init__()
+
+class InventoryStorage(Storage, collections.MutableMapping):
+ def __init__(self):
+# super(self.__class__, self).__init__()
+ self.numberOfInventoryLookupsPerformed = 0
+
+ def __contains__(self, hash):
+ raise NotImplementedError
+
+ def __getitem__(self, hash):
+ raise NotImplementedError
+
+ def __setitem__(self, hash, value):
+ raise NotImplementedError
+
+ def __delitem__(self, hash):
+ raise NotImplementedError
+
+ def __iter__(self):
+ raise NotImplementedError
+
+ def __len__(self):
+ raise NotImplementedError
+
+ def by_type_and_tag(self, objectType, tag):
+ raise NotImplementedError
+
+ def unexpired_hashes_by_stream(self, stream):
+ raise NotImplementedError
+
+ def flush(self):
+ raise NotImplementedError
+
+ def clean(self):
+ raise NotImplementedError
+
+class MailboxStorage(Storage, collections.MutableMapping):
+ def __init__(self):
+# super(self.__class__, self).__init__()
+ pass
diff --git a/src/tr.py b/src/tr.py
index c185690c..cf7f16ac 100644
--- a/src/tr.py
+++ b/src/tr.py
@@ -1,6 +1,6 @@
import os
-from bmconfigparser import BMConfigParser
+import shared
# This is used so that the translateText function can be used when we are in daemon mode and not using any QT functions.
class translateClass:
@@ -17,7 +17,11 @@ def _translate(context, text, disambiguation = None, encoding = None, n = None):
return translateText(context, text, n)
def translateText(context, text, n = None):
- if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon'):
+ try:
+ is_daemon = shared.thisapp.daemon
+ except AttributeError: # inside the plugin
+ is_daemon = False
+ if not is_daemon:
try:
from PyQt4 import QtCore, QtGui
except Exception as err:
diff --git a/src/translations/bitmessage.pro b/src/translations/bitmessage.pro
index 22044d46..f5d6b946 100644
--- a/src/translations/bitmessage.pro
+++ b/src/translations/bitmessage.pro
@@ -16,36 +16,40 @@ SOURCES = ../addresses.py\
../helper_msgcoding.py\
../helper_sent.py\
../helper_startup.py\
+ ../namecoin.py\
../proofofwork.py\
../shared.py\
../upnp.py\
../bitmessageqt/__init__.py\
- ../bitmessageqt/about.py\
../bitmessageqt/account.py\
- ../bitmessageqt/addaddressdialog.py\
+ ../bitmessageqt/address_dialogs.py\
../bitmessageqt/bitmessageui.py\
../bitmessageqt/blacklist.py\
- ../bitmessageqt/connect.py\
- ../bitmessageqt/emailgateway.py\
+ ../bitmessageqt/dialogs.py\
../bitmessageqt/foldertree.py\
- ../bitmessageqt/help.py\
- ../bitmessageqt/iconglossary.py\
../bitmessageqt/languagebox.py\
../bitmessageqt/messagecompose.py\
../bitmessageqt/messageview.py\
../bitmessageqt/networkstatus.py\
- ../bitmessageqt/newaddressdialog.py\
../bitmessageqt/newchandialog.py\
- ../bitmessageqt/newsubscriptiondialog.py\
- ../bitmessageqt/regenerateaddresses.py\
../bitmessageqt/safehtmlparser.py\
../bitmessageqt/settings.py\
- ../bitmessageqt/specialaddressbehavior.py
+ ../plugins/qrcodeui.py
FORMS = \
+ ../bitmessageqt/about.ui\
+ ../bitmessageqt/addaddressdialog.ui\
../bitmessageqt/blacklist.ui\
+ ../bitmessageqt/connect.ui\
+ ../bitmessageqt/emailgateway.ui\
+ ../bitmessageqt/help.ui\
+ ../bitmessageqt/iconglossary.ui\
../bitmessageqt/networkstatus.ui\
- ../bitmessageqt/newchandialog.ui
+ ../bitmessageqt/newaddressdialog.ui\
+ ../bitmessageqt/newchandialog.ui\
+ ../bitmessageqt/newsubscriptiondialog.ui\
+ ../bitmessageqt/regenerateaddresses.ui\
+ ../bitmessageqt/specialaddressbehavior.ui
TRANSLATIONS = \
bitmessage_ar.ts \
@@ -64,6 +68,7 @@ TRANSLATIONS = \
bitmessage_pt.ts \
bitmessage_sk.ts \
bitmessage_ru.ts \
+ bitmessage_uk.ts \
bitmessage_zh_cn.ts
CODECFORTR = UTF-8
diff --git a/src/translations/bitmessage_de.qm b/src/translations/bitmessage_de.qm
index 3865eea8..ef443a61 100644
Binary files a/src/translations/bitmessage_de.qm and b/src/translations/bitmessage_de.qm differ
diff --git a/src/translations/bitmessage_de.ts b/src/translations/bitmessage_de.ts
index d8263551..69cdd2a8 100644
--- a/src/translations/bitmessage_de.ts
+++ b/src/translations/bitmessage_de.ts
@@ -2,17 +2,17 @@
AddAddressDialog
-
+
Add new entry
Neuen Eintrag erstellen
-
+
Label
Name oder Bezeichnung
-
+
Address
Adresse
@@ -20,70 +20,99 @@
EmailGatewayDialog
-
+
Email gateway
E-Mail Schnittstelle
-
+
Register on email gateway
- An E-Mailschnittstelle registrieren
+ An E-Mail Schnittstelle registrieren
-
+
Account status at email gateway
Statusanfrage der E-Mail Schnittstelle
-
+
Change account settings at email gateway
Einstellungen der E-Mail Schnittstelle ändern
-
+
Unregister from email gateway
Von der E-Mail Schnittstelle abmelden
-
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
Die E-Mail Schnittstelle ermöglicht es, mit anderen E-Mail Nutzern zu kommunizieren. Zur Zeit ist nur die Mailchuck-E-Mail Schnittstelle (@mailchuck.com) verfügbar.
-
+
Desired email address (including @mailchuck.com):
Gewünschte E-Mailaddresse (inkl. @mailchuck.com):
+
+
+ @mailchuck.com
+ @mailchuck.com
+
+
+
+ Registration failed:
+ Registrierung fehlgeschlagen:
+
+
+
+ The requested email address is not available, please try a new one.
+ Die gewünschte E-Mailaddresse ist nicht verfügbar, bitte probieren Sie eine neue.
+
+
+
+ Sending email gateway registration request
+ Der Registrierungsantrag für die E-Mail Schnittstelle wird versandt.
+
+
+
+ Sending email gateway unregistration request
+ E-Mail Schnittestellen-Abmeldeantrag wird versandt
+
+
+
+ Sending email gateway status request
+ E-Mail Schnittestellen Statusantrag wird versandt
+
EmailGatewayRegistrationDialog
-
+
Registration failed:
- Registrierung fehlgeschlagen:
+
-
+
The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:
- Die gewünschte E-Mailaddresse ist nicht verfügbar, bitte probieren Sie eine neue. Die gewünschte E-Mailaddresse (inkl. @mailchuck.com) unten ausfüllen:
+
Email gateway registration
- E-Mail Schnittstellen Registrierung
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
Please type the desired email address (including @mailchuck.com) below:
- Die E-Mail Schnittstelle ermöglicht es, mit anderen E-Mail-Nutzern zu kommunizieren. Zur Zeit ist nur die Mailchuck-E-Mail-Schnittstelle verfügbar (@mailchuck.com).
-Bitte geben Sie die gewünschte E-Mail-Adresse (inkl. @mailchuck.com) unten ein:
+
Mailchuck
-
+
# You can use this to configure your email gateway account
# Uncomment the setting you want to use
# Here are the options:
@@ -174,52 +203,52 @@ Bitte geben Sie die gewünschte E-Mail-Adresse (inkl. @mailchuck.com) unten ein:
MainWindow
-
+
Reply to sender
Dem Absender antworten
-
+
Reply to channel
Antworten in den Chan
-
+
Add sender to your Address Book
Absender zum Adressbuch hinzufügen
-
+
Add sender to your Blacklist
Absender in die Blacklist eintragen
-
+
Move to Trash
In den Papierkorb verschieben
-
+
Undelete
Wiederherstellen
-
+
View HTML code as formatted text
HTML als formatierten Text anzeigen
-
+
Save message as...
Nachricht speichern unter...
-
+
Mark Unread
Als ungelesen markieren
-
+
New
Neu
@@ -244,12 +273,12 @@ Bitte geben Sie die gewünschte E-Mail-Adresse (inkl. @mailchuck.com) unten ein:
Adresse in die Zwischenablage kopieren
-
+
Special address behavior...
Spezielles Verhalten der Adresse...
-
+
Email gateway
E-Mail Schnittstelle
@@ -259,37 +288,37 @@ Bitte geben Sie die gewünschte E-Mail-Adresse (inkl. @mailchuck.com) unten ein:
Löschen
-
+
Send message to this address
Nachricht an diese Adresse senden
-
+
Subscribe to this address
Diese Adresse abonnieren
-
+
Add New Address
Neue Adresse hinzufügen
-
+
Copy destination address to clipboard
Zieladresse in die Zwischenablage kopieren
-
+
Force send
Senden erzwingen
-
+
One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now?
Eine Ihrer Adressen, %1, ist eine alte Adresse der Version 1 und wird nicht mehr unterstützt. Soll sie jetzt gelöscht werden?
-
+
Waiting for their encryption key. Will request it again soon.
Warte auf den Verschlüsselungscode. Wird bald erneut angefordert.
@@ -299,17 +328,17 @@ Bitte geben Sie die gewünschte E-Mail-Adresse (inkl. @mailchuck.com) unten ein:
-
+
Queued.
In Warteschlange.
-
+
Message sent. Waiting for acknowledgement. Sent at %1
Nachricht gesendet. Warte auf Bestätigung. Zeitpunkt der Sendung: %1
-
+
Message sent. Sent at %1
Nachricht gesendet. Zeitpunkt der Sendung: %1
@@ -319,136 +348,136 @@ Bitte geben Sie die gewünschte E-Mail-Adresse (inkl. @mailchuck.com) unten ein:
-
+
Acknowledgement of the message received %1
Bestätigung der Nachricht erhalten %1
-
+
Broadcast queued.
Rundruf in Warteschlange.
-
+
Broadcast on %1
Rundruf um %1
-
+
Problem: The work demanded by the recipient is more difficult than you are willing to do. %1
Problem: Die vom Empfänger geforderte Arbeit ist schwerer als Sie bereit sind, zu berechnen. %1
-
+
Problem: The recipient's encryption key is no good. Could not encrypt message. %1
Problem: Der Verschlüsselungscode des Empfängers ist nicht in Ordnung. Nachricht konnte nicht verschlüsselt werden. %1
-
+
Forced difficulty override. Send should start soon.
Schwierigkeitslimit überschrieben. Senden sollte bald beginnen.
-
+
Unknown status: %1 %2
Unbekannter Status: %1 %2
-
+
Not Connected
Nicht verbunden
-
+
Show Bitmessage
Bitmessage anzeigen
-
+
Send
Senden
-
+
Subscribe
Abonnieren
-
+
Channel
Chan
-
+
Quit
Beenden
-
+
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.
- Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im gleichen Ordner wie das Programm liegt. Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen.
+ Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im gleichen Ordner wie das Programm liegt. Es ist empfehlenswert, vorher ein Backup dieser Datei anzulegen.
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file.
Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im Ordner
%1 liegt.
-Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen.
+Es ist empfehlenswert, vorher ein Backup dieser Datei anzulegen.
-
+
Open keys.dat?
Die keys.dat öffnen?
-
+
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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
- Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im gleichen Ordner wie das Programm liegt. Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die Datei jetzt öffnen? (Stellen Sie sicher, dass Sie Bitmessage beendet haben, bevor Sie etwas ändern.)
+ Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten, die im gleichen Ordner wie das Programm liegt. Es ist empfehlenswert, vorher ein Backup dieser Datei anzulegen. Möchten Sie die Datei jetzt öffnen? (Stellen Sie sicher, dass Sie Bitmessage beendet haben, bevor Sie etwas ändern.)
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
Sie können Ihre Schlüssel verwalten, indem Sie die keys.dat bearbeiten,
die im Ordner %1 liegt.
-Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die Datei jetzt öffnen?
+Es ist empfehlenswert, vorher ein Backup dieser Datei anzulegen. Möchten Sie die Datei jetzt öffnen?
(Stellen Sie sicher, dass Sie Bitmessage beendet haben, bevor Sie etwas ändern.)
-
+
Delete trash?
Papierkorb leeren?
-
+
Are you sure you want to delete all trashed messages?
Sind Sie sicher, dass Sie alle Nachrichten im Papierkorb löschen möchten?
-
+
bad passphrase
Falsches Passwort
-
+
You must type your passphrase. If you don't have one then this is not the form for you.
Sie müssen Ihr Passwort eingeben. Wenn Sie keins haben, ist dies das falsche Formular für Sie.
-
+
Bad address version number
Falsche Addressenversionsnummer
-
+
Your address version number must be a number: either 3 or 4.
Die Addressenversionsnummer muss eine Zahl sein, entweder 3 oder 4.
-
+
Your address version number must be either 3 or 4.
Die Addressenversionnsnummer muss entweder 3 oder 4 sein.
@@ -518,22 +547,22 @@ Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die
-
+
Connection lost
Verbindung verloren
-
+
Connected
Verbunden
-
+
Message trashed
Nachricht in den Papierkorb verschoben
-
+
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
@@ -541,19 +570,19 @@ Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die
Die Haltbarkeit, oder Time-To-Live, ist die Dauer, für die das Netzwerk die Nachricht speichern wird. Der Empfänger muss sie während dieser Zeit empfangen. Wenn Ihr Bitmessage-Client keine Empfangsbestätigung erhält, wird die Nachricht automatisch erneut verschickt. Je länger die Time-To-Live, desto mehr Arbeit muss Ihr Rechner verrichten, um die Nachricht zu senden. Eine Time-To-Live von vier oder fünf Tagen ist meist ausreichend.
-
+
Message too long
Narchricht zu lang
-
+
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.
Die Nachricht, die Sie zu senden versuchen, ist %1 Byte zu lang. (Maximum 261.644 Bytes). Bitte verringern Sie ihre Größe vor dem Senden.
-
+
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.
- Fehler: Ihr Konto war an keiner E-Mailschnittstelle registriert. Registrierung als %1 wird versandt, bitte vor einem erneutem Sendeversuch auf die Registrierungsverarbeitung warten.
+ Fehler: Ihr Konto war an keiner E-Mail Schnittstelle registriert. Registrierung als %1 wird versandt, bitte vor einem erneutem Sendeversuch auf die Registrierungsverarbeitung warten.
@@ -596,7 +625,7 @@ Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die
-
+
Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab.
Fehler: Sie müssen eine Absenderadresse auswählen. Sollten Sie keine haben, wechseln Sie zum Reiter "Ihre Identitäten".
@@ -626,39 +655,39 @@ Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die
Warnung: Sie sind aktuell nicht verbunden. Bitmessage wird die nötige Arbeit zum versenden verrichten, aber erst senden, wenn Sie verbunden sind.
-
+
Message queued.
Nachricht befindet sich in der Warteschleife.
-
+
Your 'To' field is empty.
Ihr "Empfänger"-Feld ist leer.
-
+
Right click one or more entries in your address book and select 'Send message to this address'.
Klicken Sie mit rechts auf einen oder mehrere Einträge aus Ihrem Adressbuch und wählen Sie "Nachricht an diese Adresse senden".
-
+
Fetched address from namecoin identity.
Adresse aus Namecoin Identität geholt.
-
+
New Message
Neue Nachricht
-
+
From
- Von
+
-
+
Sending email gateway registration request
- Der Registrierungsantrag für die E-Mail Schnittstelle wird versandt.
+
@@ -671,107 +700,107 @@ Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die
Die von Ihnen eingegebene Adresse ist ungültig, sie wird ignoriert.
-
+
Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.
Fehler: Sie können eine Adresse nicht doppelt im Adressbuch speichern. Sie können jedoch die bereits eingetragene umbenennen.
-
+
Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.
Fehler: Dieselbe Adresse kann nicht doppelt in die Abonnements eingetragen werden. Sie können jedoch die bereits eingetragene umbenennen.
-
+
Restart
Neustart
-
+
You must restart Bitmessage for the port number change to take effect.
Sie müssen Bitmessage neu starten, um den geänderten Port zu verwenden.
-
+
Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any).
Bitmessage wird ab sofort den Proxy-Server verwenden, aber eventuell möchten Sie Bitmessage neu starten um bereits bestehende Verbindungen zu schließen.
-
+
Number needed
Zahl erforderlich
-
+
Your maximum download and upload rate must be numbers. Ignoring what you typed.
Ihre maximale Herungerlade- und Hochladegeschwindigkeit müssen Zahlen sein. Die eingetragenen Werte werden ignoriert.
-
+
Will not resend ever
Wird nie wiederversendet
-
+
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.
Bitte beachten Sie, dass der eingetratene Dauer kürzer ist als die, die Bitmessage auf das erste Wiederversenden wartet. Deswegen werden Ihre Nachrichten nie wiederversendet.
-
+
Sending email gateway unregistration request
- E-Mail Schnittestellen-Abmeldeantrag wird versandt
+
-
+
Sending email gateway status request
- E-Mail Schnittestellen Statusantrag wird versandt
+
-
+
Passphrase mismatch
Kennwort stimmt nicht überein
-
+
The passphrase you entered twice doesn't match. Try again.
Die von Ihnen eingegebenen Kennwörter sind nicht identisch. Bitte neu versuchen.
-
+
Choose a passphrase
Wählen Sie ein Kennwort
-
+
You really do need a passphrase.
- Sie benötigen wirklich ein Kennwort.
+ Sie benötigen unbedingt ein Kennwort.
-
+
Address is gone
Adresse ist verloren
-
+
Bitmessage cannot find your address %1. Perhaps you removed it?
Bitmessage kann Ihre Adresse %1 nicht finden. Haben Sie sie gelöscht?
-
+
Address disabled
Adresse deaktiviert
-
+
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.
Fehler: Die Adresse von der Sie versuchen zu senden ist deaktiviert. Sie müssen sie unter dem Reiter "Ihre Identitäten" aktivieren bevor Sie fortfahren.
-
+
Entry added to the Address Book. Edit the label to your liking.
- Eintrag dem Adressbuch hinzugefügt. Editieren Sie den Eintrag nach Belieben.
+
-
+
Entry added to the blacklist. Edit the label to your liking.
Eintrag in die Blacklist hinzugefügt. Die Beschriftung können Sie ändern.
@@ -781,32 +810,32 @@ Es ist empfehlenswert, vorher ein Backup dieser Datei anlegen. Möchten Sie die
Fehler: Dieselbe Addresse kann nicht doppelt in die Blacklist eingetragen werden. Sie können jedoch die bereits eingetragene umbenennen.
-
+
Moved items to trash.
Objekt(e) in den Papierkorb verschoben.
-
+
Undeleted item.
Nachricht wiederhergestellt.
-
+
Save As...
Speichern unter...
-
+
Write error.
Fehler beim Speichern.
-
+
No addresses selected.
Keine Adresse ausgewählt.
-
+
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.
Are you sure you want to delete the subscription?
@@ -815,7 +844,7 @@ Are you sure you want to delete the subscription?
Sind Sie sicher, dass Sie das Abonnement löschen möchten?
-
+
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.
Are you sure you want to delete the channel?
@@ -824,282 +853,282 @@ Are you sure you want to delete the channel?
Sind Sie sicher, dass Sie das Chan löschen möchten?
-
+
Do you really want to remove this avatar?
Wollen Sie diesen Avatar wirklich entfernen?
-
+
You have already set an avatar for this address. Do you really want to overwrite it?
Sie haben bereits einen Avatar für diese Adresse gewählt. Wollen Sie ihn wirklich überschreiben?
-
+
Start-on-login not yet supported on your OS.
Mit Betriebssystem starten, noch nicht von Ihrem Betriebssystem unterstützt
-
+
Minimize-to-tray not yet supported on your OS.
Ins System Tray minimieren von Ihrem Betriebssytem noch nicht unterstützt.
-
+
Tray notifications not yet supported on your OS.
Trach-Benachrichtigungen von Ihrem Betriebssystem noch nicht unterstützt.
-
+
Testing...
teste...
-
+
This is a chan address. You cannot use it as a pseudo-mailing list.
- Dies ist eine Chan-Adresse. Sie können sie nicht als Pseudo-Mailingliste verwenden.
+
-
+
The address should start with ''BM-''
Die Adresse sollte mit "BM-" beginnen
-
+
The address is not typed or copied correctly (the checksum failed).
Die Adresse wurde nicht korrekt getippt oder kopiert (Prüfsumme falsch).
-
+
The version number of this address is higher than this software can support. Please upgrade Bitmessage.
- Die Versionsnummer dieser Adresse ist höher als diese Software unterstützt. Bitte installieren Sie die neuste Bitmessage Version.
+ Die Versionsnummer dieser Adresse ist höher als diese Software unterstützt. Bitte installieren Sie die neueste Bitmessage Version.
-
+
The address contains invalid characters.
Diese Adresse beinhaltet ungültige Zeichen.
-
+
Some data encoded in the address is too short.
- Die in der Adresse codierten Daten sind zu kurz.
+ Die in der Adresse kodierten Daten sind zu kurz.
-
+
Some data encoded in the address is too long.
- Die in der Adresse codierten Daten sind zu lang.
+ Die in der Adresse kodierten Daten sind zu lang.
-
+
Some data encoded in the address is malformed.
Einige in der Adresse kodierten Daten sind ungültig.
-
+
Enter an address above.
- Eine Addresse oben ausfüllen.
+
-
+
Address is an old type. We cannot display its past broadcasts.
Alter Addressentyp. Wir können deren vorige Rundrufe nicht anzeigen.
-
+
There are no recent broadcasts from this address to display.
Es gibt keine neuen Rundrufe von dieser Adresse die angezeigt werden können.
-
+
You are using TCP port %1. (This can be changed in the settings).
- Sie benutzen TCP-Port %1 (Dieser kann in den Einstellungen verändert werden).
+
-
+
Bitmessage
Bitmessage
-
+
Identities
Identitäten
-
+
New Identity
Neue Identität
-
+
Search
Suchen
-
+
All
Alle
-
+
To
An
-
+
From
Von
-
+
Subject
Betreff
-
+
Message
Nachricht
-
+
Received
Erhalten
-
+
Messages
Nachrichten
-
+
Address book
Addressbuch
-
+
Address
Adresse
-
+
Add Contact
Kontakt hinzufügen
-
+
Fetch Namecoin ID
Hole Namecoin ID
-
+
Subject:
Betreff:
-
+
From:
Von:
-
+
To:
An:
-
+
Send ordinary Message
Ordentliche Nachricht senden
-
+
Send Message to your Subscribers
Rundruf an Ihre Abonnenten senden
-
+
TTL:
- Haltbarkeit:
+ Lebenszeit:
-
+
Subscriptions
Abonnements
-
+
Add new Subscription
Neues Abonnement anlegen
-
+
Chans
Chans
-
+
Add Chan
Chan hinzufügen
-
+
File
Datei
-
+
Settings
Einstellungen
-
+
Help
Hilfe
-
+
Import keys
Schlüssel importieren
-
+
Manage keys
Schlüssel verwalten
-
+
Ctrl+Q
Strg+Q
-
+
F1
F1
-
+
Contact support
Unterstützung anfordern
-
+
About
Über
-
+
Regenerate deterministic addresses
Deterministische Adressen neu generieren
-
+
Delete all trashed messages
Alle Nachrichten im Papierkorb löschen
-
+
Join / Create chan
Chan beitreten / erstellen
-
+
All accounts
Alle Identitäten
@@ -1119,37 +1148,37 @@ Sind Sie sicher, dass Sie das Chan löschen möchten?
Neuen Eintrag erstellen
-
+
Display the %1 recent broadcast(s) from this address.
- Die letzten %1 Rundruf(e) von dieser Addresse anzeigen.
+
-
+
New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest
Neue Version von PyBitmessage steht zur Verfügung: %1. Sie können sie von https://github.com/Bitmessage/PyBitmessage/releases/latest herunterladen.
-
+
Waiting for PoW to finish... %1%
Warte auf Abschluss von Berechnungen (PoW)... %1%
-
+
Shutting down Pybitmessage... %1%
PyBitmessage wird beendet... %1%
-
+
Waiting for objects to be sent... %1%
Warte auf Versand von Objekten... %1%
-
+
Saving settings... %1%
Einstellungen werden gespeichert... %1%
-
+
Shutting down core... %1%
Kern wird beendet... %1%
@@ -1159,27 +1188,27 @@ Sind Sie sicher, dass Sie das Chan löschen möchten?
Beende Benachrichtigungen... %1%
-
+
Shutdown imminent... %1%
Unmittelbar vor Beendung... %1%
-
+
%n hour(s)
%n Stunde %n Stunden
-
+
%n day(s)
%n Tag %n Tage
-
+
Shutting down PyBitmessage... %1%
PyBitmessage wird beendet... %1%
-
+
Sent
Gesendet
@@ -1224,253 +1253,382 @@ Sind Sie sicher, dass Sie das Chan löschen möchten?
Warnung: Datenträger ist voll. Bitmessage wird jetzt beendet.
-
+
Error! Could not find sender address (your address) in the keys.dat file.
Fehler! Konnte die Absenderadresse (Ihre Adresse) in der keys.dat-Datei nicht finden.
-
+
Doing work necessary to send broadcast...
Arbeit wird verrichtet, um Rundruf zu verschicken...
-
+
Broadcast sent on %1
Rundruf verschickt um %1
-
+
Encryption key was requested earlier.
Verschlüsselungscode wurde früher angefordert.
-
+
Sending a request for the recipient's encryption key.
Anfrage nach dem Verschlüsselungscode des Empfängers wird versendet.
-
+
Looking up the receiver's public key
Suche nach dem öffentlichen Schlüssel des Empfängers
-
+
Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1
Problem: Der Empfänger benutzt ein mobiles Gerät und erfordert eine unverschlüsselte Empfängeraddresse. Dies ist in Ihren Einstellungen jedoch nicht zulässig. 1%
-
+
Doing work necessary to send message.
There is no required difficulty for version 2 addresses like this.
Arbeit für Nachrichtenversand wird verrichtet.
Version-2-Addressen wie die des Empfängers haben keine Schweirigkeitserforderungen.
-
+
Doing work necessary to send message.
Receiver's required difficulty: %1 and %2
Arbeit für Nachrichtenversand wird errichtet. Vom Empfänger geforderte Schwierigkeit: %1 und %2
-
+
Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3
Problem: Die vom Empfänger verlangte Arbeit (%1 und %2) ist schwieriger, als Sie in den Einstellungen erlaubt haben. %3
-
+
Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1
Problem: Sie versuchen, eine Nachricht an sich zu versenden, aber Ihr Schlüssel befindet sich nicht in der keys.dat-Datei. Die Nachricht kann nicht verschlüsselt werden. 1%
-
+
Doing work necessary to send message.
Arbeit wird verrichtet, um die Nachricht zu verschicken.
-
+
Message sent. Waiting for acknowledgement. Sent on %1
Nachricht gesendet. Auf Bestätigung wird gewartet. Zeitpunkt der Sendung: %1
-
+
Doing work necessary to request encryption key.
Arbeit wird verrichtet, um den Schlüssel nachzufragen...
-
+
Broadcasting the public key request. This program will auto-retry if they are offline.
Anfrage nach dem öffentlichen Schlüssel läuft. Wenn der Besitzer nicht mit dem Netzwerk verbunden ist, wird ein Wiederholungsversuch unternommen.
-
+
Sending public key request. Waiting for reply. Requested at %1
Nachfrage nach dem öffentlichen Schlüssel läuft, auf Antwort wird gewartet. Nachgefragt am %1
-
+
UPnP port mapping established on port %1
UPnP Port-Mapping eingerichtet auf Port %1
-
+
UPnP port mapping removed
UPnP Port-Mapping entfernt
-
+
Mark all messages as read
Alle Nachrichten als gelesen markieren
-
+
Are you sure you would like to mark all messages read?
Sind Sie sicher, dass Sie alle Nachrichten als gelesen markieren möchten?
-
+
Doing work necessary to send broadcast.
Führe Arbeit aus, die notwendig ist zum Senden des Rundspruches.
-
+
Proof of work pending
Arbeitsbeweis wird berechnet
-
+
%n object(s) pending proof of work
%n Objekt wartet auf Berechnungen %n Objekte warten auf Berechnungen
-
+
%n object(s) waiting to be distributed
%n Objekt wartet darauf, verteilt zu werden %n Objekte warten darauf, verteilt zu werden
-
+
Wait until these tasks finish?
Warten bis diese Aufgaben erledigt sind?
-
+
Problem communicating with proxy: %1. Please check your network settings.
Kommunikationsfehler mit dem Proxy: %1. Bitte überprüfen Sie Ihre Netzwerkeinstellungen.
-
+
SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings.
SOCKS5-Authentizierung fehlgeschlagen: %1. Bitte überprüfen Sie Ihre SOCKS5-Einstellungen.
-
+
The time on your computer, %1, may be wrong. Please verify your settings.
Die Uhrzeit ihres Computers, %1, ist möglicherweise falsch. Bitte überprüfen Sie Ihre einstellungen.
-
+
+ The name %1 was not found.
+ Der Name %1 wurde nicht gefunden.
+
+
+
+ The namecoin query failed (%1)
+ Namecoin-abfrage fehlgeschlagen (%1)
+
+
+
+ The namecoin query failed.
+ Namecoin-abfrage fehlgeschlagen.
+
+
+
+ The name %1 has no valid JSON data.
+ Der Name %1 beinhaltet keine gültige JSON-Daten.
+
+
+
+ The name %1 has no associated Bitmessage address.
+ Der Name %1 hat keine zugewiesene Bitmessageaddresse.
+
+
+
+ Success! Namecoind version %1 running.
+ Erfolg! Namecoind Version %1 läuft.
+
+
+
+ Success! NMControll is up and running.
+ Erfolg! NMControl läuft.
+
+
+
+ Couldn't understand NMControl.
+ Kann NMControl nicht verstehen.
+
+
+
Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.
Ihre Grafikkarte hat inkorrekt berechnet, OpenCL wird deaktiviert. Bitte benachrichtigen Sie die Entwickler.
-
+
+ Set notification sound...
+ Benachrichtigungsklang einstellen ...
+
+
+
+
+ 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
+
+
+Willkommen zu einfachem und sicherem Bitmessage
+* senden Sie Nachrichten an andere Leute
+* senden Sie Rundrufe wie bei Twitter oder
+* diskutieren Sie mit anderen Leuten in Chans
+
+
+
not recommended for chans
für Chans nicht empfohlen
-
+
+ Quiet Mode
+ stiller Modus
+
+
+
Problems connecting? Try enabling UPnP in the Network Settings
Verbindungsprobleme? Versuchen Sie UPnP in den Netzwerkeinstellungen einzuschalten
-
+
+ You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?
+ Sie versuchen, eine E-Mail anstelle einer Bitmessage zu senden. Dies erfordert eine Registrierung bei einer Schnittstelle. Registrierung versuchen?
+
+
+
Error: Bitmessage addresses start with BM- Please check the recipient address %1
Fehler: Bitmessage Adressen starten mit BM- Bitte überprüfen Sie die Empfängeradresse %1
-
+
Error: The recipient address %1 is not typed or copied correctly. Please check it.
Fehler: Die Empfängeradresse %1 wurde nicht korrekt getippt oder kopiert. Bitte überprüfen.
-
+
Error: The recipient address %1 contains invalid characters. Please check it.
Fehler: Die Empfängeradresse %1 beinhaltet ungültig Zeichen. Bitte überprüfen.
-
+
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.
Fehler: Die Empfängerdresseversion von %1 ist zu hoch. Entweder Sie müssen Ihre Bitmessage Software aktualisieren oder Ihr Bekannter ist sehr clever.
-
+
Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance.
Fehler: Einige Daten die in der Empfängerdresse %1 codiert sind, sind zu kurz. Es könnte sein, dass etwas mit der Software Ihres Bekannten nicht stimmt.
-
+
Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance.
Fehler: Einige Daten die in der Empfängeradresse %1 codiert sind, sind zu lang. Es könnte sein, dass etwas mit der Software Ihres Bekannten nicht stimmt.
-
+
Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance.
Fehler: Einige codierte Daten in der Empfängeradresse %1 sind ungültig. Es könnte etwas mit der Software Ihres Bekannten sein.
-
+
Error: Something is wrong with the recipient address %1.
Fehler: Mit der Empfängeradresse %1 stimmt etwas nicht.
-
+
+ Error: %1
+ Fehler: %1
+
+
+
+ From %1
+ Von %1
+
+
+
Synchronisation pending
Synchronisierung läuft
-
+
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?
Bitmessage ist nicht synchronisiert, %n Objekt wurde noch nicht heruntergeladen. Wenn Sie das Programm jetzt beenden, kann es zu Zustellverzögerungen kommen. Warten bis die Synchronisierung abgeschlossen ist? Bitmessage ist nicht synchronisiert, %n Objekte wurden noch nicht heruntergeladen. Wenn Sie das Programm jetzt beenden, kann es zu Zustellverzögerungen kommen. Warten bis die Synchronisierung abgeschlossen ist?
-
+
Not connected
Nicht verbunden
-
+
Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes?
Bitmessage ist nicht mit dem Netzwerk verbunden. Wenn Sie das Programm jetzt beenden, kann es zu Zustellverzögerungen kommen. Warten bis eine Verbindung besteht und die Synchronisierung abgeschlossen ist?
-
+
Waiting for network connection...
Warte auf Netzwerkverbindung...
-
+
Waiting for finishing synchronisation...
Warte auf Synchronisationsabschluss...
+
+
+ You have already set a notification sound for this address book entry. Do you really want to overwrite it?
+ Sie haben bereits einen Benachrichtigungsklang für diesen Adressbucheintrag gesetzt. Möchten Sie ihn wirklich überschreiben?
+
+
+
+ Error occurred: could not load message from disk.
+ Fehler: Nachricht konnte nicht geladen werden.
+
+
+
+ Display the %n recent broadcast(s) from this address.
+ Den letzten %1 Rundruf von dieser Addresse anzeigen. Die letzten %1 Rundrufe von dieser Addresse anzeigen.
+
+
+
+ Go online
+ Mit dem Netzwerk verbinden
+
+
+
+ Go offline
+ Trennen vom Netzwerk
+
+
+
+ Clear
+ Löschen
+
+
+
+ inbox
+ Eingang
+
+
+
+ new
+ Neu
+
+
+
+ sent
+
+
+
+
+ trash
+
+
MessageView
-
+
Follow external link
Externen Link folgen
-
+
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?
Der Link "%1" wird in Browser geöffnet. Es kann ein Sicherheitsrisiko darstellen, es könnte Sie de-anonymisieren oder schädliche Aktivitäten durchführen. Sind Sie sicher?
-
+
HTML detected, click here to display
HTML gefunden, klicken Sie hier um anzuzeigen
-
+
Click here to disable HTML
Klicken Sie hier um HTML-Anzeige zu deaktivieren
@@ -1478,14 +1636,14 @@ Receiver's required difficulty: %1 and %2
MsgDecode
-
+
The message has an unknown encoding.
Perhaps you should upgrade Bitmessage.
Diese Bitmessage verwendtet eine unbekannte codierung.
Womöglich sollten Sie Bitmessage upgraden.
-
+
Unknown encoding
Codierung unbekannt
@@ -1493,99 +1651,99 @@ Womöglich sollten Sie Bitmessage upgraden.
NewAddressDialog
-
+
Create new Address
Neue Adresse erstellen
-
+
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.
The 'Random Number' option is selected by default but deterministic addresses have several pros and cons:
Sie können so viele Adressen generieren wie Sie möchten. Es ist sogar empfohlen neue Adressen zu verwenden und alte fallen zu lassen. Sie können Adressen durch Zufallszahlen erstellen lassen, oder unter Verwendung eines Kennwortsatzes. Wenn Sie einen Kennwortsatz verwenden, nennt man dies eine "deterministische" Adresse.
Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministische Adressen einige Vor- und Nachteile:
-
+
<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>
<html><head/><body><p><span style=" font-weight:600;">Vorteile:<br/></span>Sie können ihre Adresse an jedem Computer aus dem Gedächtnis regenerieren. <br/>Sie brauchen sich keine Sorgen um das Sichern ihrer Schlüssel machen solange Sie sich den Kennwortsatz merken. <br/><span style=" font-weight:600;">Nachteile:<br/></span>Sie müssen sich den Kennwortsatz merken (oder aufschreiben) wenn Sie in der Lage sein wollen ihre Schlüssel wiederherzustellen. <br/>Sie müssen sich die Adressversion und die Datenstrom Nummer zusammen mit dem Kennwortsatz merken. <br/>Wenn Sie einen schwachen Kennwortsatz wählen und jemand kann ihn erraten oder durch ausprobieren herausbekommen, kann dieser Ihre Nachrichten lesen, oder in ihrem Namen Nachrichten senden..</p></body></html>
-
+
Use a random number generator to make an address
Lassen Sie eine Adresse mittels Zufallsgenerator erstellen
-
+
Use a passphrase to make addresses
Benutzen Sie einen Kennwortsatz um eine Adresse erstellen zu lassen
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
Verwenden Sie einige Minuten extra Rechenleistung um die Adresse(n) ein bis zwei Zeichen kürzer zu machen
-
+
Make deterministic addresses
Deterministische Adresse erzeugen
-
+
Address version number: 4
Adress-Versionsnummer: 4
-
+
In addition to your passphrase, you must remember these numbers:
Zusätzlich zu Ihrem Kennwortsatz müssen Sie sich diese Zahlen merken:
-
+
Passphrase
Kennwortsatz
-
+
Number of addresses to make based on your passphrase:
Anzahl Adressen die basierend auf diesem Kennwortsatz erzeugt werden sollen:
-
+
Stream number: 1
Datenstrom Nummer: 1
-
+
Retype passphrase
Kennwortsatz erneut eingeben
-
+
Randomly generate address
Zufällig generierte Adresse
-
+
Label (not shown to anyone except you)
Bezeichnung (Wird niemandem außer Ihnen gezeigt)
-
+
Use the most available stream
Verwendung des am besten verfügbaren Datenstroms
-
+
(best if this is the first of many addresses you will create)
(Zum Generieren der ersten Adresse empfohlen)
-
+
Use the same stream as an existing address
Verwendung des gleichen Datenstroms wie eine bestehende Adresse
-
+
(saves you some bandwidth and processing power)
(Dies erspart Ihnen etwas an Bandbreite und Rechenleistung)
@@ -1593,22 +1751,22 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
NewSubscriptionDialog
-
+
Add new entry
Neuen Eintrag erstellen
-
+
Label
Name oder Bezeichnung
-
+
Address
Adresse
-
+
Enter an address above.
Bitte geben Sie oben eine Adresse ein.
@@ -1616,55 +1774,60 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
SpecialAddressBehaviorDialog
-
+
Special Address Behavior
Spezielles Adressverhalten
-
+
Behave as a normal address
Wie eine normale Adresse verhalten
-
+
Behave as a pseudo-mailing-list address
Wie eine Pseudo-Mailinglistenadresse verhalten
-
+
Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public).
Nachrichten an eine Pseudo-Mailinglistenadresse werden automatisch an alle Abonnenten weitergeleitet (Der Inhalt ist dann öffentlich).
-
+
Name of the pseudo-mailing-list:
Name der Pseudo-Mailingliste:
+
+
+ This is a chan address. You cannot use it as a pseudo-mailing list.
+ Dies ist eine Chan-Adresse. Sie können sie nicht als Pseudo-Mailingliste verwenden.
+
aboutDialog
-
+
About
Über
-
+
PyBitmessage
- PyBitmessage
+
-
+
version ?
- Version ?
+
-
+
<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>
<html><head/><body><p>Veröffentlicht unter der MIT/X11 Software-Lizenz, siehe unter <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>
-
+
This is Beta software.
Dies ist Beta-Software.
@@ -1673,10 +1836,10 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
<html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
-
-
- <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
- <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 Die Bitmessage-Entwickler</p></body></html>
+
+
+ <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 The Bitmessage Developers</p></body></html>
+ <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 The Bitmessage Developers</p></body></html>
@@ -1720,40 +1883,45 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
connectDialog
-
+
Bitmessage
Bitmessage
-
+
Bitmessage won't connect to anyone until you let it.
Bitmessage wird sich nicht verbinden, wenn Sie es nicht möchten.
-
+
Connect now
Jetzt verbinden
-
+
Let me configure special network settings first
Zunächst spezielle Netzwerkeinstellungen vornehmen
+
+
+ Work offline
+ Nicht verbinden
+
helpDialog
-
+
Help
Hilfe
-
+
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
-
+
As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki:
Bitmessage ist ein kollaboratives Projekt. Hilfe finden Sie online im Bitmessage-Wiki:
@@ -1761,30 +1929,35 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
iconGlossaryDialog
-
+
Icon Glossary
Icon Glossar
-
+
You have no connections with other peers.
Sie haben keine Verbindung mit anderen Netzwerkteilnehmern.
-
+
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.
Sie haben mindestes eine Verbindung mit einem Netzwerkteilnehmer über eine ausgehende Verbindung, aber Sie haben noch keine eingehende Verbindung. Ihre Firewall oder Ihr Router ist vermutlich nicht richtig konfiguriert, um eingehende TCP-Verbindungen an Ihren Computer weiterzuleiten. Bitmessage wird gut funktionieren, jedoch helfen Sie dem Netzwerk, wenn Sie eingehende Verbindungen erlauben. Es hilft auch Ihnen, schneller und mehr Verbindungen ins Netzwerk aufzubauen.
You are using TCP port ?. (This can be changed in the settings).
- Sie benutzen TCP-Port ?. (Dies kann in den Einstellungen verändert werden).
+
-
+
You do have connections with other peers and your firewall is correctly configured.
Sie haben Verbindungen mit anderen Netzwerkteilnehmern und Ihre Firewall ist richtig konfiguriert.
+
+
+ You are using TCP port %1. (This can be changed in the settings).
+ Sie benutzen TCP-Port %1 (Dieser kann in den Einstellungen verändert werden).
+
networkstatus
@@ -1794,110 +1967,155 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
Verbindungen insgesamt:
-
+
Since startup:
Seit Start:
-
+
Processed 0 person-to-person messages.
0 Person-zu-Person-Nachrichten verarbeitet.
-
+
Processed 0 public keys.
0 öffentliche Schlüssel verarbeitet.
-
+
Processed 0 broadcasts.
0 Rundrufe verarbeitet.
-
+
Inventory lookups per second: 0
Inventory lookups pro Sekunde: 0
-
+
Objects to be synced:
Zu synchronisierende Objektanzahl:
-
+
Stream #
Datenstrom #
Connections
- Verbindungen
+
-
+
Since startup on %1
Seit Start der Anwendung am %1
-
+
Down: %1/s Total: %2
Herunter: %1/s Insg.: %2
-
+
Up: %1/s Total: %2
Hoch: %1/s Insg.: %2
-
+
Total Connections: %1
Verbindungen insgesamt: %1
-
+
Inventory lookups per second: %1
Inventory lookups pro Sekunde: %1
-
+
Up: 0 kB/s
Hoch: 0 kB/s
-
+
Down: 0 kB/s
Herunter: 0 kB/s
-
+
Network Status
Netzwerkstatus
-
+
byte(s)
Byte Bytes
-
+
Object(s) to be synced: %n
%n Objekt zu synchronisieren. %n Objekte zu synchronisieren.
-
+
Processed %n person-to-person message(s).
%n Person-zu-Person-Nachricht bearbeitet. %n Person-zu-Person-Nachrichten bearbeitet.
-
+
Processed %n broadcast message(s).
%n Rundruf-Nachricht bearbeitet. %n Rundruf-Nachrichten bearbeitet.
-
+
Processed %n public key(s).
%n öffentlicher Schlüssel verarbeitet. %n öffentliche Schlüssel verarbeitet.
+
+
+ Peer
+ Peer
+
+
+
+ IP address or hostname
+ IP-Adresse oder Hostname
+
+
+
+ Rating
+ Bewertung
+
+
+
+ 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
+ PyBitmessage verfolgt die Erfolgsrate von Verbindungsversuchen zu einzelnen Knoten. Die Bewertung reicht von -1 bis 1 und beeinflusst die Wahrscheinlichkeit, den Knoten zukünftig auszuwählen
+
+
+
+ User agent
+ Benutzer-Agent
+
+
+
+ Peer's self-reported software
+ Peer's selbstberichtete Software
+
+
+
+ TLS
+ TLS
+
+
+
+ Connection encryption
+ Verbindungsverschlüsselung
+
+
+
+ List of streams negotiated between you and the peer
+ Liste der zwischen Ihnen und dem Peer ausgehandelter Datenströme
+
newChanDialog
@@ -1953,18 +2171,18 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
- Chan passhphrase/name:
+ Chan passphrase/name:
Chan-Name
Optional, for advanced usage
- Optional, für fortgeschrittene
+ Optional, für Fortgeschrittene
Chan address
- Chan-adresse
+ Chan-Adresse
@@ -1975,7 +2193,7 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
newchandialog
-
+
Successfully created / joined chan %1
Chan %1 erfolgreich erstellt/beigetreten
@@ -1993,70 +2211,78 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
proofofwork
-
+
C PoW module built successfully.
C-PoW-Modul erfolgreich erstellt.
-
+
Failed to build C PoW module. Please build it manually.
Erstellung vom C-PoW-Modul fehlgeschlagen. Bitte erstellen Sie es manuell.
-
+
C PoW module unavailable. Please build it.
C-PoW-Modul nicht verfügbar. Bitte erstellen Sie es.
+
+ qrcodeDialog
+
+
+ QR-code
+ QR-Code
+
+
regenerateAddressesDialog
-
+
Regenerate Existing Addresses
Bestehende Adresse regenerieren
-
+
Regenerate existing addresses
Bestehende Adresse regenerieren
-
+
Passphrase
Kennwortsatz
-
+
Number of addresses to make based on your passphrase:
- Anzahl der Adressen, die basierend auf diesem Kennwortsatz erzeugt werden sollen:
+ Anzahl Adressen die basierend auf diesem Kennwortsatz erzeugt werden sollen:
-
+
Address version number:
Adress-Versionsnummer:
-
+
Stream number:
Stream-Nummer:
-
+
1
1
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
- Verwenden Sie einige Minuten extra Rechenleistung, um die Adresse(n) ein bis zwei Zeichen kürzer zu machen
+ Verwenden Sie einige Minuten extra Rechenleistung um die Adresse(n) ein bis zwei Zeichen kürzer zu machen
-
+
You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time.
Sie müssen diese Option auswählen (oder nicht auswählen) wie Sie es gemacht haben, als Sie Ihre Adresse das erste Mal erstellt haben.
-
+
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.
Wenn Sie bereits deterministische Adressen erstellt haben, aber diese durch einen Unfall (zum Beispiel durch eine defekte Festplatte) verloren haben, können Sie sie hier regenerieren. Dies funktioniert nur dann, wenn Sie bei der erstmaligen Erstellung Ihrer Adressen nicht den Zufallsgenerator verwendet haben.
@@ -2327,7 +2553,7 @@ Die Zufallszahlen-Option ist standardmässig gewählt, jedoch haben deterministi
<html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html>
- <html><head/><body><p>Wenn der Empfänger eine Nachricht nicht bis zum Verfalldatum herunterlädt, zum Beisplel weil er für längere Zeit nicht mit dem Netz verbunden ist, wird die Nachricht erneut versendet. Dies passiert solange, bis eine Empfangsbestätigung erhalten wird. Hier können Sie dieses Verhalten ändern, indem Sie Bitmessage die Wiederversandversuche nach einer bestimmten Anzahl von Tagen oder Monaten aufgeben lassen.</p><p>Für die Standardeinstellung (ohne zeitliche Einschränkung) lassen Sie diese Eingabefelder leer.</p></body></html>
+ <html><head/><body><p>Wenn der Empfänger eine Nachricht nicht bis zum Verfallsdatum herunterlädt, zum Beispiel weil er für längere Zeit nicht mit dem Netz verbunden ist, wird die Nachricht erneut versendet. Dies passiert solange, bis eine Empfangsbestätigung erhalten wird. Hier können Sie dieses Verhalten ändern, indem Sie Bitmessage die Wiederversandversuche nach einer bestimmten Anzahl von Tagen oder Monaten aufgeben lassen.</p><p>Für die Standardeinstellung (ohne zeitliche Einschränkung) lassen Sie diese Eingabefelder leer.</p></body></html>
diff --git a/src/translations/bitmessage_eo.qm b/src/translations/bitmessage_eo.qm
index 85a6ed57..ea03b1b5 100644
Binary files a/src/translations/bitmessage_eo.qm and b/src/translations/bitmessage_eo.qm differ
diff --git a/src/translations/bitmessage_eo.ts b/src/translations/bitmessage_eo.ts
index 7d0c3454..beff4aca 100644
--- a/src/translations/bitmessage_eo.ts
+++ b/src/translations/bitmessage_eo.ts
@@ -2,17 +2,17 @@
AddAddressDialog
-
+
Add new entry
Aldoni novan elementon
-
+
Label
Etikedo
-
+
Address
Adreso
@@ -20,70 +20,99 @@
EmailGatewayDialog
-
+
Email gateway
Retpoŝta kluzo
-
+
Register on email gateway
- Registri je retpoŝta kluzo
+ Registri ĉe retpoŝta kluzo
-
+
Account status at email gateway
Stato de retpoŝt-kluza konto
-
+
Change account settings at email gateway
Ŝanĝu agordojn de konto ĉe retpoŝta kluzo
-
+
Unregister from email gateway
Malregistri de retpoŝta kluzo
-
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
Retpoŝta kluzo ebligas al vi komunikadi kun retpoŝtaj uzantoj. Nuntempe, nur la retpoŝta kluzo de Mailchuck (@mailchuck.com) estas disponebla.
-
+
Desired email address (including @mailchuck.com):
Dezirata retpoŝta adreso (kune kun @mailchuck.com):
+
+
+ @mailchuck.com
+ @mailchuck.com
+
+
+
+ Registration failed:
+ Registrado malsukcesis:
+
+
+
+ The requested email address is not available, please try a new one.
+ La dezirata retpoŝtadreso ne estas disponebla, bonvolu provi alian.
+
+
+
+ Sending email gateway registration request
+ Sendado de peto pri registrado ĉe retpoŝta kluzo
+
+
+
+ Sending email gateway unregistration request
+ Sendado de peto pri malregistrado de retpoŝta kluzo
+
+
+
+ Sending email gateway status request
+ Sendado de peto pri stato de retpoŝta kluzo
+
EmailGatewayRegistrationDialog
-
+
Registration failed:
- Registrado malsukcesis:
+
-
+
The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:
- La dezirata retpoŝtadreso ne estas disponebla, bonvolu provi alian. Entajpu novan deziratan adreson (kune kun @mailchuck.com) sube:
+
Email gateway registration
- Registrado je retpoŝta kluzo
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
Please type the desired email address (including @mailchuck.com) below:
- Retpoŝta kluzo ebligas al vi komunikadi kun retpoŝtaj uzantoj. Nuntempe, nur la retpoŝta kluzo de Mailchuck (@mailchuck.com) estas disponebla.
-Bonvolu entajpi deziratan retpoŝtadreson (kune kun @mailchuck.com) sube:
+
Mailchuck
-
+
# You can use this to configure your email gateway account
# Uncomment the setting you want to use
# Here are the options:
@@ -131,7 +160,7 @@ Bonvolu entajpi deziratan retpoŝtadreson (kune kun @mailchuck.com) sube:
MainWindow
-
+
Reply to sender
Respondi al sendinto
-
+
Reply to channel
Respondi al kanalo
-
+
Add sender to your Address Book
Aldoni sendinton al via adresaro
-
+
Add sender to your Blacklist
Aldoni sendinton al via nigra listo
-
+
Move to Trash
Movi al rubujo
-
+
Undelete
- Malforviŝi
+ Malforigi
-
+
View HTML code as formatted text
Montri HTML-n kiel aranĝitan tekston
-
+
Save message as...
- Konservi mesaĝon kiel...
+ Konservi mesaĝon kiel…
-
+
Mark Unread
Marki kiel nelegitan
-
+
New
Nova
@@ -228,7 +257,7 @@ Bonvolu entajpi deziratan retpoŝtadreson (kune kun @mailchuck.com) sube:
Set avatar...
- Agordi avataron...
+ Agordi avataron…
@@ -236,12 +265,12 @@ Bonvolu entajpi deziratan retpoŝtadreson (kune kun @mailchuck.com) sube:Kopii adreson al tondejo
-
+
Special address behavior...
- Speciala sinteno de adreso...
+ Speciala sinteno de adreso…
-
+
Email gateway
Retpoŝta kluzo
@@ -251,37 +280,37 @@ Bonvolu entajpi deziratan retpoŝtadreson (kune kun @mailchuck.com) sube:Forigi
-
+
Send message to this address
Sendi mesaĝon al tiu adreso
-
+
Subscribe to this address
Aboni tiun adreson
-
+
Add New Address
Aldoni novan adreson
-
+
Copy destination address to clipboard
Kopii cel-adreson al tondejo
-
+
Force send
Devigi sendadon
-
+
One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now?
- Iu de viaj adresoj, %1, estas malnova versio 1 adreso. Versioj 1 adresoj ne estas jam subtenataj. Ĉu ni povas forviŝi ĝin?
+ Iu de viaj adresoj, %1, estas malnova versio 1 adreso. Versioj 1 adresoj ne estas jam subtenataj. Ĉu ni povas forigi ĝin?
-
+
Waiting for their encryption key. Will request it again soon.
Atendado je ilia ĉifroŝlosilo. Baldaŭ petos ĝin denove.
@@ -291,17 +320,17 @@ Bonvolu entajpi deziratan retpoŝtadreson (kune kun @mailchuck.com) sube:
-
+
Queued.
En atendovico.
-
+
Message sent. Waiting for acknowledgement. Sent at %1
Mesaĝo sendita. Atendado je konfirmo. Sendita je %1
-
+
Message sent. Sent at %1
Mesaĝo sendita. Sendita je %1
@@ -311,135 +340,135 @@ Bonvolu entajpi deziratan retpoŝtadreson (kune kun @mailchuck.com) sube:
-
+
Acknowledgement of the message received %1
Ricevis konfirmon de la mesaĝo je %1
-
+
Broadcast queued.
Elsendo en atendovico.
-
+
Broadcast on %1
Elsendo je %1
-
+
Problem: The work demanded by the recipient is more difficult than you are willing to do. %1
Problemo: la demandita laboro de la ricevonto estas pli malfacila ol vi pretas fari. %1
-
+
Problem: The recipient's encryption key is no good. Could not encrypt message. %1
Problemo: la ĉifroŝlosilo de la ricevonto estas rompita. Ne povis ĉifri la mesaĝon. %1
-
+
Forced difficulty override. Send should start soon.
Devigita superado de limito de malfacilaĵo. Sendado devus baldaŭ komenci.
-
+
Unknown status: %1 %2
Nekonata stato: %1 %2
-
+
Not Connected
Ne konektita
-
+
Show Bitmessage
Montri Bitmesaĝon
-
+
Send
Sendi
-
+
Subscribe
Aboni
-
+
Channel
Kanalo
-
+
Quit
Eliri
-
+
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.
- Vi povas administri viajn ŝlosilojn redaktante la dosieron keys.dat en la sama dosierujo kiel tiu programo. Estas grava, ke vi faru sekurkopion de tiu dosiero.
+ Vi povas administri viajn ŝlosilojn per redakti la dosieron keys.dat en la sama dosierujo kiel tiu programo. Estas grava, ke vi faru sekurkopion de tiu dosiero.
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file.
- Vi povas administri viajn ŝlosilojn redaktante la dosieron keys.dat en la dosierujo
+ Vi povas administri viajn ŝlosilojn per redakti la dosieron keys.dat en la dosierujo
%1.
Estas grava, ke vi faru sekurkopion de tiu dosiero.
-
+
Open keys.dat?
Ĉu malfermi keys.dat?
-
+
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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
- Vi povas administri viajn ŝlosilojn redaktante la dosieron keys.dat en la sama dosierujo kiel tiu programo. Estas grava ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dosieron nun? (Bonvolu certigi ke Bitmesaĝo estas fermita antaŭ fari ŝanĝojn.)
+ Vi povas administri viajn ŝlosilojn per redakti la dosieron keys.dat en la sama dosierujo kiel tiu programo. Estas grava ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dosieron nun? (Bonvolu certigi ke Bitmesaĝo estas fermita antaŭ fari ŝanĝojn.)
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
- Vi povas administri viajn ŝlosilojn redaktante la dosieron keys.dat en la dosierujo
+ Vi povas administri viajn ŝlosilojn per redakti la dosieron keys.dat en la dosierujo
%1.
Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dosieron nun? (Bonvolu certigi ke Bitmesaĝo estas fermita antaŭ fari ŝanĝojn.)
-
+
Delete trash?
Ĉu malplenigi rubujon?
-
+
Are you sure you want to delete all trashed messages?
Ĉu vi certe volas forviŝi ĉiujn mesaĝojn el la rubujo?
-
+
bad passphrase
malprava pasvorto
-
+
You must type your passphrase. If you don't have one then this is not the form for you.
Vi devas tajpi vian pasvorton. Se vi ne havas pasvorton, tiu ĉi ne estas la prava formularo por vi.
-
+
Bad address version number
Erara numero de adresversio
-
+
Your address version number must be a number: either 3 or 4.
Via numero de adresversio devas esti: aŭ 3 aŭ 4.
-
+
Your address version number must be either 3 or 4.
Via numero de adresversio devas esti: aŭ 3 aŭ 4.
@@ -509,40 +538,40 @@ Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dos
-
+
Connection lost
Perdis konekton
-
+
Connected
Konektita
-
+
Message trashed
Movis mesaĝon al rubujo
-
+
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.
- La vivdaŭro signifas ĝis kiam la reto tenos la mesaĝon. La ricevonto devos elŝuti ĝin dum tiu tempo. Se via Bitmesaĝa kliento ne ricevos konfirmon, ĝi resendos mesaĝon aŭtomate. Ju pli longa vivdaŭro, des pli laboron via komputilo bezonos fari por sendi mesaĝon. Vivdaŭro proksimume kvin aŭ kvar horoj estas ofte konvena.
+ La vivdaŭro signifas ĝis kiam la reto tenos la mesaĝon. La ricevonto devos elŝuti ĝin dum tiu tempo. Se via bitmesaĝa kliento ne ricevos konfirmon, ĝi resendos mesaĝon aŭtomate. Ju pli longa vivdaŭro, des pli laboron via komputilo bezonos fari por sendi mesaĝon. Vivdaŭro proksimume kvin aŭ kvar horoj estas ofte konvena.
-
+
Message too long
Mesaĝo tro longa
-
+
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.
La mesaĝon kiun vi provis sendi estas tro longa je %1 bitokoj. (La maksimumo estas 261644 bitokoj.) Bonvolu mallongigi ĝin antaŭ sendado.
-
+
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.
Eraro: Via konto ne estas registrita je retpoŝta kluzo. Registranta nun kiel %1, bonvolu atendi ĝis la registrado finos antaŭ vi reprovos sendi iun ajn.
@@ -587,69 +616,69 @@ Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dos
-
+
Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab.
Eraro: Vi devas elekti sendontan adreson. Se vi ne havas iun, iru al langeto 'Viaj identigoj'.
-
+
Address version number
Numero de adresversio
-
+
Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.
Dum prilaborado de adreso adreso %1, Bitmesaĝo ne povas kompreni numerojn %2 de adresversioj. Eble ĝisdatigu Bitmesaĝon al la plej nova versio.
-
+
Stream number
Fluo numero
-
+
Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version.
Dum prilaborado de adreso %1, Bitmesaĝo ne povas priservi %2 fluojn numerojn. Eble ĝisdatigu Bitmesaĝon al la plej nova versio.
-
+
Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect.
Atentu: Vi ne estas nun konektita. Bitmesaĝo faros necesan laboron por sendi mesaĝon, tamen ĝi ne sendos ĝin antaŭ vi konektos.
-
+
Message queued.
Mesaĝo envicigita.
-
+
Your 'To' field is empty.
Via "Ricevonto"-kampo malplenas.
-
+
Right click one or more entries in your address book and select 'Send message to this address'.
Dekstre alklaku kelka(j)n elemento(j)n en via adresaro kaj elektu 'Sendi mesaĝon al tiu adreso'.
-
+
Fetched address from namecoin identity.
- Venigis adreson de Namecoin-a identigo.
+ Venigis adreson de namecoin-a identigo.
-
+
New Message
Nova mesaĝo
-
+
From
- De
+
-
+
Sending email gateway registration request
- Sendado de peto pri registrado je retpoŝta kluzo
+
@@ -662,430 +691,430 @@ Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dos
La adreso kiun vi enmetis estas malĝusta. Ignoras ĝin.
-
+
Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.
Eraro: Vi ne povas duoble aldoni la saman adreson al via adresaro. Provu renomi la ekzistan se vi volas.
-
+
Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.
Eraro: Vi ne povas aldoni duoble la saman adreson al viaj abonoj. Eble renomi la ekzistan se vi volas.
-
+
Restart
Restartigi
-
+
You must restart Bitmessage for the port number change to take effect.
Vi devas restartigi Bitmesaĝon por ke la ŝanĝo de la numero de pordo (Port Number) efektivigu.
-
+
Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any).
- Bitmesaĝo uzos vian prokurilon (proxy) ekde nun, sed eble vi volas permane restartigi Bitmesaĝon nun, por ke ĝi fermu eblajn ekzistajn konektojn.
+ Bitmesaĝo uzos retperanton (proxy) ekde nun, sed eble vi volas permane restartigi Bitmesaĝon nun, por ke ĝi fermu eblajn ekzistajn konektojn.
-
+
Number needed
Numero bezonata
-
+
Your maximum download and upload rate must be numbers. Ignoring what you typed.
Maksimumaj elŝutrapido kaj alŝutrapido devas esti numeroj. Ignoras kion vi enmetis.
-
+
Will not resend ever
Resendos neniam
-
+
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.
Rigardu, ke la templimon vi enmetis estas pli malgrandan ol tempo dum kiu Bitmesaĝo atendas por resendi unuafoje, do viaj mesaĝoj estos senditaj neniam.
-
+
Sending email gateway unregistration request
- Sendado de peto pri malregistrado de retpoŝta kluzo
+
-
+
Sending email gateway status request
- Sendado de peto pri stato de retpoŝta kluzo
+
-
+
Passphrase mismatch
Pasfrazoj malsamas
-
+
The passphrase you entered twice doesn't match. Try again.
- La pasfrazo kiun vi duoble enmetis malsamas. Provu denove.
+ Entajpitaj pasfrazoj malsamas. Provu denove.
-
+
Choose a passphrase
Elektu pasfrazon
-
+
You really do need a passphrase.
Vi ja vere bezonas pasfrazon.
-
+
Address is gone
Adreso foriris
-
+
Bitmessage cannot find your address %1. Perhaps you removed it?
Bitmesaĝo ne povas trovi vian adreson %1. Ĉu eble vi forviŝis ĝin?
-
+
Address disabled
Adreso malŝaltita
-
+
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.
Eraro: La adreso kun kiu vi provas sendi estas malŝaltita. Vi devos ĝin ŝalti en la langeto 'Viaj identigoj' antaŭ uzi ĝin.
-
+
Entry added to the Address Book. Edit the label to your liking.
- Aldonis elementon al adresaro. Redaktu la etikedon laŭvole.
+
-
+
Entry added to the blacklist. Edit the label to your liking.
Aldonis elementon al la nigra listo. Redaktu la etikedon laŭvole.
-
+
Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want.
Eraro: Vi ne povas duoble aldoni la saman adreson al via nigra listo. Provu renomi la jaman se vi volas.
-
+
Moved items to trash.
Movis elementojn al rubujo.
-
+
Undeleted item.
- Malforviŝis elementon.
+ Malforigis elementon.
-
+
Save As...
- Konservi kiel...
+ Konservi kiel…
-
+
Write error.
Skriberaro.
-
+
No addresses selected.
Neniu adreso elektita.
-
+
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.
Are you sure you want to delete the subscription?
- Se vi forviŝos abonon, mesaĝojn kiujn vi jam ricevis, igos neatingeblajn. Eble vi devus anstataŭ malaktivigi abonon. Malaktivaj abonoj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis.
+ Se vi forigos abonon, mesaĝojn kiujn vi jam ricevis, igos neatingeblajn. Eble vi devus anstataŭ malaktivigi abonon. Malaktivaj abonoj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis.
-Ĉu vi certe volas forviŝi la abonon?
+Ĉu vi certe volas forigi la abonon?
-
+
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.
Are you sure you want to delete the channel?
- Se vi forviŝos kanalon, mesaĝojn kiujn vi jam ricevis, igos neatingeblajn. Eble vi devus anstataŭ malaktivigi kanalon. Malaktivaj kanaloj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis.
+ Se vi forigos kanalon, mesaĝojn kiujn vi jam ricevis, igos neatingeblajn. Eble vi devus anstataŭ malaktivigi kanalon. Malaktivaj kanaloj ne ricevos novajn mesaĝojn, tamen vi plu povos legi mesaĝojn kiujn ci jam ricevis.
-Ĉu vi certe volas forviŝi la kanalon?
+Ĉu vi certe volas forigi la kanalon?
-
+
Do you really want to remove this avatar?
Ĉu vi certe volas forviŝi tiun ĉi avataron?
-
+
You have already set an avatar for this address. Do you really want to overwrite it?
Vi jam agordis avataron por tiu ĉi adreso. Ĉu vi vere volas superskribi ĝin?
-
+
Start-on-login not yet supported on your OS.
Starto-dum-ensaluto ne estas ankoraŭ ebla en via operaciumo.
-
+
Minimize-to-tray not yet supported on your OS.
Plejetigo al taskopleto ne estas ankoraŭ ebla en via operaciumo.
-
+
Tray notifications not yet supported on your OS.
Taskopletaj sciigoj ne estas ankoraŭ eblaj en via operaciumo.
-
+
Testing...
- Testado...
+ Testado…
-
+
This is a chan address. You cannot use it as a pseudo-mailing list.
- Tio ĉi estas kanaladreso. Vi ne povas ĝin uzi kiel kvazaŭ-dissendolisto.
+
-
+
The address should start with ''BM-''
- La adreso komencu kun "BM-"
+ La adreso komencu kun “BM-”
-
+
The address is not typed or copied correctly (the checksum failed).
- La adreso ne estis prave tajpita aŭ kopiita (kontrolsumo malsukcesis).
+ La adreso ne estis ĝuste tajpita aŭ kopiita (kontrolsumo malsukcesis).
-
+
The version number of this address is higher than this software can support. Please upgrade Bitmessage.
- La numero de adresversio estas pli alta ol tiun, kiun la programo poveblas subteni. Bonvolu ĝisdatigi Bitmesaĝon.
+ La numero de adresversio estas pli alta ol tiu, kiun la programo poveblas subteni. Bonvolu ĝisdatigi Bitmesaĝon.
-
+
The address contains invalid characters.
La adreso enhavas malpermesitajn simbolojn.
-
+
Some data encoded in the address is too short.
- Kelkaj datumoj koditaj en la adreso estas tro mallongaj.
+ Iuj datumoj koditaj en la adreso estas tro mallongaj.
-
+
Some data encoded in the address is too long.
- Kelkaj datumoj koditaj en la adreso estas tro longaj.
+ Iuj datumoj koditaj en la adreso estas tro longaj.
-
+
Some data encoded in the address is malformed.
- Kelkaj datumoj koditaj en la adreso estas misformitaj.
+ Iuj datumoj koditaj en la adreso estas misformitaj.
-
+
Enter an address above.
- Enmetu adreson supre.
+
-
+
Address is an old type. We cannot display its past broadcasts.
- Malnova speco de adreso. Ne povas montri ĝiajn antaŭajn elsendojn.
+ Malnova tipo de adreso. Ne povas montri ĝiajn antaŭajn elsendojn.
-
+
There are no recent broadcasts from this address to display.
Neniaj lastatempaj elsendoj de tiu ĉi adreso por montri.
-
+
You are using TCP port %1. (This can be changed in the settings).
- Vi estas uzanta TCP pordo %1 (Tio ĉi estas ŝanĝebla en la agordoj).
+
-
+
Bitmessage
Bitmesaĝo
-
+
Identities
Identigoj
-
+
New Identity
Nova identigo
-
+
Search
Serĉi
-
+
All
Ĉio
-
+
To
Al
-
+
From
De
-
+
Subject
Temo
-
+
Message
Mesaĝo
-
+
Received
Ricevita je
-
+
Messages
Mesaĝoj
-
+
Address book
Etikedo
-
+
Address
Adreso
-
+
Add Contact
Aldoni kontakton
-
+
Fetch Namecoin ID
- Venigu Namecoin ID
+ Venigi Namecoin ID
-
+
Subject:
Temo:
-
+
From:
De:
-
+
To:
Al:
-
+
Send ordinary Message
Sendi ordinaran mesaĝon
-
+
Send Message to your Subscribers
Sendi mesaĝon al viaj abonantoj
-
+
TTL:
Vivdaŭro:
-
+
Subscriptions
Abonoj
-
+
Add new Subscription
Aldoni novan abonon
-
+
Chans
Kanaloj
-
+
Add Chan
Aldoni kanalon
-
+
File
Dosiero
-
+
Settings
Agordoj
-
+
Help
Helpo
-
+
Import keys
Enporti ŝlosilojn
-
+
Manage keys
Administri ŝlosilojn
-
+
Ctrl+Q
Stir+Q
-
+
F1
F1
-
+
Contact support
Peti pri helpo
-
+
About
Pri
-
+
Regenerate deterministic addresses
Regeneri antaŭkalkuleblan adreson
-
+
Delete all trashed messages
Forviŝi ĉiujn mesaĝojn el rubujo
-
+
Join / Create chan
Aniĝi / Krei kanalon
@@ -1110,67 +1139,67 @@ Are you sure you want to delete the channel?
Aldoni novan elementon
-
+
Display the %1 recent broadcast(s) from this address.
- Montri la %1 lasta(j)n elsendo(j)n de tiu adreso.
+ Montri %1 lasta(j)n elsendo(j)n de tiu ĉi adreso.
-
+
New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest
La nova versio de PyBitmessage estas disponebla: %1. Elŝutu ĝin de https://github.com/Bitmessage/PyBitmessage/releases/latest
-
+
Waiting for PoW to finish... %1%
- Atendado ĝis laborpruvo finos... %1%
+ Atendado ĝis laborpruvo finiĝos… %1%
-
+
Shutting down Pybitmessage... %1%
- Fermado de PyBitmessage... %1%
+ Fermado de PyBitmessage… %1%
-
+
Waiting for objects to be sent... %1%
- Atendado ĝis objektoj estos senditaj... %1%
+ Atendado ĝis objektoj estos senditaj… %1%
-
+
Saving settings... %1%
- Konservado de agordoj... %1%
+ Konservado de agordoj… %1%
-
+
Shutting down core... %1%
- Fermado de kerno... %1%
+ Fermado de kerno… %1%
-
+
Stopping notifications... %1%
- Haltigado de sciigoj... %1%
+ Haltigado de sciigoj… %1%
-
+
Shutdown imminent... %1%
- Fermado tuj... %1%
+ Fermado tuj… %1%
-
+
%n hour(s)
%n horo %n horoj
-
+
%n day(s)
%n tago %n tagoj
-
+
Shutting down PyBitmessage... %1%
- Fermado de PyBitmessage... %1%
+ Fermado de PyBitmessage… %1%
-
+
Sent
Senditaj
@@ -1182,7 +1211,7 @@ Are you sure you want to delete the channel?
Done generating address. Doing work necessary to broadcast it...
- Adreso kreita. Kalkulado de laborpruvo, kiu endas por elsendi ĝin...
+ Adreso kreita. Kalkulado de laborpruvo, kiu endas por elsendi ĝin…
@@ -1192,7 +1221,7 @@ Are you sure you want to delete the channel?
%1 is already in 'Your Identities'. Not adding it again.
- %1 jam estas en 'Viaj Identigoj'. Ĝi ne estos aldonita ree.
+ %1 jam estas en ‘Viaj Identigoj’. Ĝi ne estos aldonita ree.
@@ -1215,254 +1244,348 @@ Are you sure you want to delete the channel?
Atentu: Via disko aŭ subdisko estas plenplena. Bitmesaĝo fermiĝos.
-
+
Error! Could not find sender address (your address) in the keys.dat file.
Eraro! Ne povas trovi adreson de sendanto (vian adreson) en la dosiero keys.dat.
-
+
Doing work necessary to send broadcast...
- Kalkulado de laborpruvo, kiu endas por sendi elsendon...
+ Kalkulado de laborpruvo, kiu endas por sendi elsendon…
-
+
Broadcast sent on %1
Elsendo sendita je %1
-
+
Encryption key was requested earlier.
Peto pri ĉifroŝlosilo jam sendita.
-
+
Sending a request for the recipient's encryption key.
Sendado de peto pri ĉifroŝlosilo de ricevonto.
-
+
Looking up the receiver's public key
Serĉado de publika ĉifroŝlosilo de ricevonto
-
+
Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1
Eraro: celadreso estas portebla aparato kiu necesas, ke la celadreso estu enhavita en la mesaĝo, sed tio estas malpermesita ne viaj agordoj. %1
-
+
Doing work necessary to send message.
There is no required difficulty for version 2 addresses like this.
Kalkulado de laborpruvo, kiu endas por sendi mesaĝon.
Malfacilaĵo ne estas bezonata por adresoj versioj 2, kiel tiu ĉi adreso.
-
+
Doing work necessary to send message.
Receiver's required difficulty: %1 and %2
Kalkulado de laborpruvo, kiu endas por sendi mesaĝon.
Ricevonto postulas malfacilaĵon: %1 kaj %2
-
+
Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3
Eraro: la demandita laboro de la ricevonto (%1 kaj %2) estas pli malfacila ol vi pretas fari. %3
-
+
Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1
Eraro: Vi provis sendi mesaĝon al vi mem aŭ al kanalo, tamen via ĉifroŝlosilo ne estas trovebla en la dosiero keys.dat. Mesaĝo ne povis esti ĉifrita. %1
-
+
Doing work necessary to send message.
Kalkulado de laborpruvo, kiu endas por sendi mesaĝon.
-
+
Message sent. Waiting for acknowledgement. Sent on %1
Mesaĝo sendita. Atendado je konfirmo. Sendita je %1
-
+
Doing work necessary to request encryption key.
Kalkulado de laborpruvo, kiu endas por peti pri ĉifroŝlosilo.
-
+
Broadcasting the public key request. This program will auto-retry if they are offline.
Elsendado de peto pri publika ĉifroŝlosilo. La programo reprovos se ili estas eksterrete.
-
+
Sending public key request. Waiting for reply. Requested at %1
Sendado de peto pri publika ĉifroŝlosilo. Atendado je respondo. Petis je %1
-
+
UPnP port mapping established on port %1
UPnP pord-mapigo farita je pordo %1
-
+
UPnP port mapping removed
UPnP pord-mapigo forigita
-
+
Mark all messages as read
Marki ĉiujn mesaĝojn kiel legitajn
-
+
Are you sure you would like to mark all messages read?
Ĉu vi certe volas marki ĉiujn mesaĝojn kiel legitajn?
-
+
Doing work necessary to send broadcast.
Kalkulado de laborpruvo, kiu endas por sendi elsendon.
-
+
Proof of work pending
Laborpruvo haltigita
-
+
%n object(s) pending proof of work
Haltigis laborpruvon por %n objekto Haltigis laborpruvon por %n objektoj
-
+
%n object(s) waiting to be distributed
%n objekto atendas je sendato %n objektoj atendas je sendato
-
+
Wait until these tasks finish?
Ĉu atendi ĝis tiujn taskojn finos?
-
+
Problem communicating with proxy: %1. Please check your network settings.
- Eraro dum komunikado kun prokurilo: %1. Bonvolu kontroli viajn retajn agordojn.
+ Eraro dum komunikado kun retperanto: %1. Bonvolu kontroli viajn retajn agordojn.
-
+
SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings.
Eraro dum SOCKS5 aŭtentigado: %1. Bonvolu kontroli viajn SOCKS5-agordojn.
-
+
The time on your computer, %1, may be wrong. Please verify your settings.
La horloĝo de via komputilo, %1, eble eraras. Bonvolu kontroli viajn agordojn.
-
+
+ The name %1 was not found.
+ La nomo %1 ne trovita.
+
+
+
+ The namecoin query failed (%1)
+ La namecoin-peto fiaskis (%1)
+
+
+
+ The namecoin query failed.
+ La namecoin-peto fiaskis.
+
+
+
+ The name %1 has no valid JSON data.
+ La nomo %1 ne havas ĝustajn JSON-datumojn.
+
+
+
+ The name %1 has no associated Bitmessage address.
+ La nomo %1 ne estas atribuita kun bitmesaĝa adreso.
+
+
+
+ Success! Namecoind version %1 running.
+ Sukceso! Namecoind versio %1 funkcias.
+
+
+
+ Success! NMControll is up and running.
+ Sukceso! NMControl funkcias ĝuste.
+
+
+
+ Couldn't understand NMControl.
+ Ne povis kompreni NMControl.
+
+
+
Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.
Via(j) vidprocesoro(j) ne kalkulis senerare, malaktiviganta OpenCL. Bonvolu raporti tion al programistoj.
-
+
+ Set notification sound...
+ Agordi sciigan sonon…
+
+
+
+
+ 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
+
+
+Bonvenon al facila kaj sekura Bitmesaĝo
+* sendi mesaĝojn al aliaj homoj
+* sendi elsendajn mesaĝojn (kiel per Tvitero)
+* babili kun aliaj uloj en mesaĝ-kanaloj
+
+
+
not recommended for chans
malkonsilinda por kanaloj
-
+
+ Quiet Mode
+ Silenta reĝimo
+
+
+
Problems connecting? Try enabling UPnP in the Network Settings
Ĉu problemo kun konektado? Provu aktivigi UPnP en retaj agordoj.
-
+
+ You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?
+ Vi provas sendi retmesaĝon anstataŭ bitmesaĝ-mesaĝon. Tio ĉi postulas registri ĉe retpoŝta kluzo. Ĉu provi registri?
+
+
+
Error: Bitmessage addresses start with BM- Please check the recipient address %1
Eraro: bitmesaĝaj adresoj komenciĝas kun BM-. Bonvolu kontroli la adreson de ricevonto %1
-
+
Error: The recipient address %1 is not typed or copied correctly. Please check it.
Eraro: la adreso de ricevonto %1 estas malprave tajpita aŭ kopiita. Bonvolu kontroli ĝin.
-
+
Error: The recipient address %1 contains invalid characters. Please check it.
Eraro: la adreso de ricevonto %1 enhavas malpermesatajn simbolojn. Bonvolu kontroli ĝin.
-
+
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.
- Eraro: la versio de adreso de ricevonto %1 estas tro alta. Eble vi devas ĝisdatigi vian Bitmesaĝan programon aŭ via sagaca konato uzas alian programon.
+ Eraro: la versio de adreso de ricevonto %1 estas tro alta. Eble vi devas ĝisdatigi vian bitmesaĝan programon aŭ via sagaca konato uzas alian programon.
-
+
Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance.
Eraro: kelkaj datumoj koditaj en la adreso de ricevonto %1 estas tro mallongaj. Povus esti ke io en la programo de via konato malfunkcias.
-
+
Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance.
Eraro: kelkaj datumoj koditaj en la adreso de ricevonto %1 estas tro longaj. Povus esti ke io en la programo de via konato malfunkcias.
-
+
Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance.
Eraro: kelkaj datumoj koditaj en la adreso de ricevonto %1 estas misformitaj. Povus esti ke io en la programo de via konato malfunkcias.
-
+
Error: Something is wrong with the recipient address %1.
Eraro: io malĝustas kun la adreso de ricevonto %1.
-
+
+ Error: %1
+ Eraro: %1
+
+
+
+ From %1
+ De %1
+
+
+
Synchronisation pending
Samtempigado haltigita
-
+
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?
Bitmesaĝo ne estas samtempigita kun la reto, %n objekto elŝutendas. Se vi eliros nun, tio povas igi malfruiĝojn de liveradoj. Ĉu atendi ĝis la samtempigado finiĝos? Bitmesaĝo ne estas samtempigita kun la reto, %n objektoj elŝutendas. Se vi eliros nun, tio povas igi malfruiĝojn de liveradoj. Ĉu atendi ĝis la samtempigado finiĝos?
-
+
Not connected
Nekonektita
-
+
Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes?
Bitmesaĝo ne estas konektita al la reto. Se vi eliros nun, tio povas igi malfruiĝojn de liveradoj. Ĉu atendi ĝis ĝi konektos kaj la samtempigado finiĝos?
-
+
Waiting for network connection...
- Atendado je retkonekto...
+ Atendado je retkonekto…
-
+
Waiting for finishing synchronisation...
- Atendado ĝis samtempigado finiĝos...
+ Atendado ĝis samtempigado finiĝos…
+
+
+
+ You have already set a notification sound for this address book entry. Do you really want to overwrite it?
+ Vi jam agordis sciigan sonon por tiu ĉi adreso. Ĉu vi volas anstataŭigi ĝin?
+
+
+
+ Go online
+ Konekti
+
+
+
+ Go offline
+ Malkonekti
MessageView
-
+
Follow external link
Sekvi la eksteran ligilon
-
+
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?
La ligilo "%1" estos malfermita per foliumilo. Tio povas esti malsekura, ĝi povos malanonimigi vin aŭ elŝuti malicajn datumojn. Ĉu vi certas?
-
+
HTML detected, click here to display
HTML detektita, alklaku ĉi tie por montri
-
+
Click here to disable HTML
Alklaku ĉi tie por malaktivigi HTML
@@ -1470,14 +1593,14 @@ Ricevonto postulas malfacilaĵon: %1 kaj %2
MsgDecode
-
+
The message has an unknown encoding.
Perhaps you should upgrade Bitmessage.
La mesaĝo enhavas nekonatan kodoprezenton.
Eble vi devas ĝisdatigi Bitmesaĝon.
-
+
Unknown encoding
Nekonata kodoprezento
@@ -1485,178 +1608,183 @@ Eble vi devas ĝisdatigi Bitmesaĝon.
NewAddressDialog
-
+
Create new Address
Krei novan adreson
-
+
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.
The 'Random Number' option is selected by default but deterministic addresses have several pros and cons:
- Tie ĉi vi povas generi tiom da adresoj, kiom vi volas. Ververe kreado kaj forlasado de adresoj estas konsilinda. Vi povas krei adresojn uzante hazardajn nombrojn aŭ pasfrazon. Se vi uzos pasfrazon, la adreso estas nomita kiel 'antaŭkalkulebla' (determinisma) adreso.
-La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj havas kelkajn bonaĵojn kaj malbonaĵojn:
+ Tie ĉi vi povas generi tiom da adresoj, kiom vi volas. Ververe krei kaj forlasi adresojn estas konsilinda. Vi povas krei adresojn uzante hazardajn nombrojn aŭ pasfrazon. Se vi uzos pasfrazon, la adreso estas nomita kiel “antaŭkalkulebla” (determinisma) adreso.
+La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj havas kelkajn bonaĵojn kaj malbonaĵojn:
-
+
<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>
- <html><head/><body><p><span style=" font-weight:600;">Bonaĵoj:<br/></span>Vi poveblas rekrei viajn adresojn per iu ajn komputilo elkape.<br/>Vi ne devas klopodi fari sekurkopion de keys.dat dosiero tiel longe, dum vi memoras vian pasfrazon.<br/><span style=" font-weight:600;">Malbonaĵoj:<br/></span>Vi devas memori (aŭ konservi) vian pasfrazon se vi volas rekrei viajn ŝlosilojn kiam vi perdos ilin.<br/>Vi devas memori nombron de adresversio kaj de fluo kune kun vian pasfrazon.<br/>Se vi elektos malfortan pasfrazon kaj iu ajn Interrete eblos brutforti ĝin, li povos legi ĉiujn viajn mesaĝojn kaj sendi mesaĝojn kiel vi.</p></body></html>
+ <html><head/><body><p><span style=" font-weight:600;">Bonaĵoj:<br/></span>Vi poveblas rekrei viajn adresojn per iu ajn komputilo elkape.<br/>Vi ne devas klopodi fari sekurkopion de la dosiero keys.dat tiel longe, dum vi memoras vian pasfrazon.<br/><span style=" font-weight:600;">Malbonaĵoj:<br/></span>Vi devas memori (aŭ konservi) vian pasfrazon se vi volas rekrei viajn ŝlosilojn kiam vi perdos ilin.<br/>Vi devas memori nombron de adresversio kaj de fluo kune kun vian pasfrazon.<br/>Se vi elektos malfortan pasfrazon kaj iu ajn Interrete eblos brutforti ĝin, li povos legi ĉiujn viajn mesaĝojn kaj sendi mesaĝojn kiel vi.</p></body></html>
-
+
Use a random number generator to make an address
Uzi hazardnombran generilon por krei adreson
-
+
Use a passphrase to make addresses
Uzi pasfrazon por krei adreson
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
- Pasigi kelkajn minutojn aldone kompute por fari la adreso(j)n 1 aŭ 2 signoj pli mallongaj
+ Pasigi kelkajn minutojn aldone kompute por krei adreso(j)n mallongaj je 1 aŭ 2 signoj
-
+
Make deterministic addresses
Fari antaŭkalkuleblan adreson
-
+
Address version number: 4
Numero de adresversio: 4
-
+
In addition to your passphrase, you must remember these numbers:
Kune kun vian pasfrazon vi devas memori jenajn numerojn:
-
+
Passphrase
Pasfrazo
-
+
Number of addresses to make based on your passphrase:
Nombro da farotaj adresoj bazante sur via pasfrazo:
-
+
Stream number: 1
- Fluo numero: 1
+ Numero de fluo: 1
-
+
Retype passphrase
- Reenmeti pasfrazon
+ Pasfrazo ree
-
+
Randomly generate address
Hazardnombra adreso
-
+
Label (not shown to anyone except you)
Etikedo (ne montrata al iu ajn escepte de vi)
-
+
Use the most available stream
Uzi la plej disponeblan fluon
-
+
(best if this is the first of many addresses you will create)
- (plej bone se tiun ĉi estas la unuan de ĉiuj adresojn vi kreos)
+ (plej bone se ĝi estas la unua de ĉiuj adresojn vi kreos)
-
+
Use the same stream as an existing address
- Uzi saman fluon kiel ekzistan adreson
+ Uzi la saman fluon kiel ekzistan adreson
-
+
(saves you some bandwidth and processing power)
- (konservas iomete rettrafikon kaj komputopovon)
+ (konservas iomete da ret-trafiko kaj komput-povo)
NewSubscriptionDialog
-
+
Add new entry
Aldoni novan elementon
-
+
Label
Etikedo
-
+
Address
Adreso
-
+
Enter an address above.
- Enmetu adreson supre.
+ Entajpu adreson supre.
SpecialAddressBehaviorDialog
-
+
Special Address Behavior
Speciala sinteno de adreso
-
+
Behave as a normal address
Sintenadi kiel normala adreso
-
+
Behave as a pseudo-mailing-list address
Sintenadi kiel kvazaŭ-elsendlista adreso
-
+
Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public).
Mesaĝoj alvenintaj al kvazaŭ-dissendlisto estos aŭtomate dissenditaj al abonantoj (do ili estos publikaj).
-
+
Name of the pseudo-mailing-list:
- Nomo de la kvazaŭ-dissendlisto:
+ Nomo de kvazaŭ-dissendlisto:
+
+
+
+ This is a chan address. You cannot use it as a pseudo-mailing list.
+ Tio ĉi estas kanaladreso. Vi ne povas ĝin uzi kiel kvazaŭ-dissendolisto.
aboutDialog
-
+
About
Pri
-
+
PyBitmessage
- PyBitmessage
+
-
+
version ?
- versio ?
+
-
+
<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>
- <html><head/><body><p>Distribuata sub la permesilo "MIT/X11 software license"; vidu <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>
+ <html><head/><body><p>Distribuata laŭ la permesilo “MIT/X11 software license”; legu <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>
-
+
This is Beta software.
Tio ĉi estas beta-eldono.
@@ -1665,10 +1793,10 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
<html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
-
-
- <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
- <html><head/><body><p>Kopirajto © 2012-2016 Jonathan WARREN<br/>Kopirajto © 2013-2016 La programistoj de Bitmesaĝo</p></body></html>
+
+
+ <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 The Bitmessage Developers</p></body></html>
+ <html><head/><body><p>Kopirajto © 2012-2016 Jonathan WARREN<br/>Kopirajto © 2013-2017 La Programistoj de Bitmesaĝo</p></body></html>
@@ -1712,40 +1840,45 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
connectDialog
-
+
Bitmessage
Bitmesaĝo
-
+
Bitmessage won't connect to anyone until you let it.
Bitmesaĝo ne konektos antaŭ vi permesos al ĝi.
-
+
Connect now
Konekti nun
-
+
Let me configure special network settings first
- Lasu min unue fari specialajn retajn agordojn
+ Ebligi al mi unue alĝustigi specialajn agordojn de reto
+
+
+
+ Work offline
+ Funkcii eksterrete
helpDialog
-
+
Help
Helpo
-
+
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
-
+
As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki:
Ĉar Bitmesaĝo estas kunlabora projekto, vi povas trovi helpon enrete ĉe la vikio de Bitmesaĝo:
@@ -1753,30 +1886,35 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
iconGlossaryDialog
-
+
Icon Glossary
Piktograma Glosaro
-
+
You have no connections with other peers.
Vi havas neniujn konektojn al aliaj samtavolanoj.
-
+
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.
- Vi konektis almenaŭ al unu samtavolano uzante eliranta konekto, sed vi ankoraŭ ne ricevis enirantajn konetkojn. Via fajroŝirmilo (firewall) aŭ hejma enkursigilo (router) verŝajne estas agordita ne plusendi enirantajn TCP konektojn al via komputilo. Bitmesaĝo funkcios sufiĉe bone, sed helpus al la Bitmesaĝa reto se vi permesus enirantajn konektojn kaj tiel estus pli bone konektita nodo.
+ Vi konektis almenaŭ al unu samtavolano uzante elirantan konekton, sed vi ankoraŭ ne ricevis enirantajn konektojn. Via fajroŝirmilo (firewall) aŭ hejma enkursigilo (router) verŝajne estas agordita por ne plusendi enirantajn TCP-konektojn al via komputilo. Bitmesaĝo funkcios sufiĉe bone, sed helpus al la bitmesaĝa reto se vi permesus enirantajn konektojn kaj tiel estus pli bone konektita nodo.
You are using TCP port ?. (This can be changed in the settings).
- Vi uzas TCP-pordon ?. (Ĝi estas ŝanĝebla en la agordoj).
+
-
+
You do have connections with other peers and your firewall is correctly configured.
Vi havas konektojn al aliaj samtavolanoj kaj via fajroŝirmilo estas ĝuste agordita.
+
+
+ You are using TCP port %1. (This can be changed in the settings).
+ Vi uzas TCP-pordon %1 (tio ĉi estas ŝanĝebla en la agordoj).
+
networkstatus
@@ -1786,110 +1924,155 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
Ĉiuj konektoj:
-
+
Since startup:
Ekde starto:
-
+
Processed 0 person-to-person messages.
Pritraktis 0 inter-personajn mesaĝojn.
-
+
Processed 0 public keys.
Pritraktis 0 publikajn ŝlosilojn.
-
+
Processed 0 broadcasts.
Pritraktis 0 elsendojn.
-
+
Inventory lookups per second: 0
Petoj pri inventaro en sekundo: 0
-
+
Objects to be synced:
Samtempigotaj eroj:
-
+
Stream #
Fluo #
Connections
- Konektoj
+
-
+
Since startup on %1
Ekde lanĉo de la programo je %1
-
+
Down: %1/s Total: %2
Elŝuto: %1/s Sume: %2
-
+
Up: %1/s Total: %2
Alŝuto: %1/s Sume: %2
-
+
Total Connections: %1
Ĉiuj konektoj: %1
-
+
Inventory lookups per second: %1
Petoj pri inventaro en sekundo: %1
-
+
Up: 0 kB/s
Alŝuto: 0 kB/s
-
+
Down: 0 kB/s
Elŝuto: 0 kB/s
-
+
Network Status
- Reta Stato
+ Reta stato
-
+
byte(s)
bitoko bitokoj
-
+
Object(s) to be synced: %n
Objekto por samtempigi: %n Objektoj por samtempigi: %n
-
+
Processed %n person-to-person message(s).
Pritraktis %n inter-personan mesaĝon. Pritraktis %n inter-personajn mesaĝojn.
-
+
Processed %n broadcast message(s).
Pritraktis %n elsendon. Pritraktis %n elsendojn.
-
+
Processed %n public key(s).
Pritraktis %n publikan ŝlosilon. Pritraktis %n publikajn ŝlosilojn.
+
+
+ Peer
+ Samtavolano
+
+
+
+ IP address or hostname
+ IP-adreso aŭ gastiga nomo
+
+
+
+ Rating
+ Takso
+
+
+
+ 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
+ PyBitmessage registras frekvencon de sukcesaj konekt-provoj al aliaj nodoj. Takso varias de -1 ĝis 1 kaj influas probablon por elekti nodon estontece.
+
+
+
+ User agent
+ Klienta aplikaĵo
+
+
+
+ Peer's self-reported software
+ Anoncata klienta aplikaĵo
+
+
+
+ TLS
+ TLS
+
+
+
+ Connection encryption
+ Konekta ĉifrado
+
+
+
+ List of streams negotiated between you and the peer
+ Listo de interŝanĝataj fluoj inter vi kaj la samtavolano
+
newChanDialog
@@ -1945,7 +2128,7 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
- Chan passhphrase/name:
+ Chan passphrase/name:
Kanala pasfrazo/nomo:
@@ -1967,7 +2150,7 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
newchandialog
-
+
Successfully created / joined chan %1
Sukcese kreis / aniĝis al la kanalo %1
@@ -1985,72 +2168,80 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
proofofwork
-
+
C PoW module built successfully.
C PoW modulo konstruita sukcese.
-
+
Failed to build C PoW module. Please build it manually.
Malsukcesis konstrui C PoW modulon. Bonvolu konstrui ĝin permane.
-
+
C PoW module unavailable. Please build it.
C PoW modulo nedisponebla. Bonvolu konstrui ĝin.
+
+ qrcodeDialog
+
+
+ QR-code
+ QR-kodo
+
+
regenerateAddressesDialog
-
+
Regenerate Existing Addresses
Regeneri ekzistantajn adresojn
-
+
Regenerate existing addresses
Regeneri ekzistantajn adresojn
-
+
Passphrase
Pasfrazo
-
+
Number of addresses to make based on your passphrase:
- Kvanto de farotaj adresoj bazante sur via pasfrazo:
+ Nombro da farotaj adresoj bazante sur via pasfrazo:
-
+
Address version number:
Numero de adresversio:
-
+
Stream number:
- Fluo numero:
+ Numero de fluo:
-
+
1
1
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
- Pasigi kelkajn minutojn aldone kompute por krei la adreso(j)n 1 aŭ 2 signoj pli mallongaj
+ Pasigi kelkajn minutojn aldone kompute por krei adreso(j)n mallongaj je 1 aŭ 2 signoj
-
+
You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time.
- Vi devas marki (aŭ ne marki) tiun markobutono samkiel vi faris kiam vi generis vian adreson unuafoje.
+ Vi devas marki (aŭ ne marki) tiun ĉi mark-butonon samkiel vi faris kiam vi generis vian adresojn unuafoje.
-
+
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.
- Se vi antaŭe kreis antaŭkalkuleblajn (determinismajn) adresojn sed perdis ilin akcidente (ekz. en diska paneo), vi povas regeneri ilin ĉi tie. Se vi uzis la generilo de hazardnombroj por krei vian adreson tiu formularo ne taŭgos por vi.
+ Se vi antaŭe kreis antaŭkalkuleblajn (determinismajn) adresojn sed perdis ilin akcidente (ekz. en diska paneo), vi povas regeneri ilin tie ĉi. Se vi uzis la hazardnombran generilon por krei viajn adresojn, tiu ĉi formularo ne taŭgos por vi.
@@ -2164,7 +2355,7 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
Proxy server / Tor
- Prokurila (proxy) servilo / Tor
+ Retperanta (proxy) servilo / Tor
@@ -2199,7 +2390,7 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
Listen for incoming connections when using proxy
- Aŭskulti pri alvenaj konektoj kiam dum uzado de prokurilo
+ Aŭskulti pri alvenaj konektoj kiam dum uzado de retperanto
@@ -2229,7 +2420,7 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work.
- La 'Tuta malfacilaĵo' efikas sur la tuta kvalito da laboro kiu la sendonto devos fari. Duobligo de tiu valoro, duobligas la kvanton de laboro.
+ La 'Tuta malfacilaĵo' efikas sur la tuta kvalito da laboro, kiun la sendonto devos fari. Duobligo de tiu valoro, duobligas la kvanton de laboro.
@@ -2239,7 +2430,7 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
When someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1.
- Kiam iu ajn sendas al vi mesaĝon, lia komputilo devas unue fari iom da laboro. La malfacilaĵo de tiu laboro implicite estas 1. Vi povas pligrandigi tiun valoron por novaj adresoj, kiuj vi generos per ŝanĝo de ĉi-tiaj valoroj. Ĉiuj novaj adresoj kreotaj de vi bezonos por ke sendontoj akceptu pli altan malfacilaĵon. Estas unu escepto: se vi aldonos kolegon al vi adresaro, Bitmesaĝo aŭtomate sciigos lin kiam vi sendos mesaĝon, ke li bezonos fari nur minimuman kvaliton da laboro: malfacilaĵo 1.
+ Kiam iu ajn sendas al vi mesaĝon, lia komputilo devas unue fari iom da laboro. La malfacilaĵo de tiu laboro implicite estas 1. Vi povas pligrandigi tiun valoron por novaj adresoj, kiujn vi generos per ŝanĝo de ĉi-tiaj valoroj. Ĉiuj novaj adresoj kreotaj de vi bezonos por ke sendontoj akceptu pli altan malfacilaĵon. Estas unu escepto: se vi aldonos kolegon al vi adresaro, Bitmesaĝo aŭtomate sciigos lin kiam vi sendos mesaĝon, ke li bezonos fari nur minimuman kvaliton da laboro: malfacilaĵo 1.
@@ -2354,7 +2545,7 @@ La 'hazardnombra' adreso estas antaŭagordita, sed antaŭkalkuleblaj a
Maximum outbound connections: [0: none]
- Maksimume da eligaj konektoj: [0: none]
+ Maksimumo de eligaj konektoj: [0: senlima]
diff --git a/src/translations/bitmessage_fr.qm b/src/translations/bitmessage_fr.qm
index 1796355d..742e62d8 100644
Binary files a/src/translations/bitmessage_fr.qm and b/src/translations/bitmessage_fr.qm differ
diff --git a/src/translations/bitmessage_fr.ts b/src/translations/bitmessage_fr.ts
index 16d06f3d..3e64d657 100644
--- a/src/translations/bitmessage_fr.ts
+++ b/src/translations/bitmessage_fr.ts
@@ -58,12 +58,12 @@
EmailGatewayRegistrationDialog
-
+
Registration failed:
L’inscription a échoué :
-
+
The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:
L’adresse de courriel demandée n’est pas disponible, veuillez en essayer une nouvelle. Saisissez ci-dessous la nouvelle adresse de courriel désirée (incluant @mailchuck.com) :
@@ -83,7 +83,7 @@ Veuillez taper l’adresse de courriel désirée (incluant @mailchuck.com) :
Mailchuck
-
+
# You can use this to configure your email gateway account
# Uncomment the setting you want to use
# Here are the options:
@@ -173,122 +173,122 @@ Veuillez taper l’adresse de courriel désirée (incluant @mailchuck.com) :
MainWindow
-
+
Reply to sender
Répondre à l’expéditeur
-
+
Reply to channel
Répondre au canal
-
+
Add sender to your Address Book
Ajouter l’expéditeur au carnet d’adresses
-
+
Add sender to your Blacklist
Ajouter l’expéditeur à votre liste noire
-
+
Move to Trash
Envoyer à la Corbeille
-
+
Undelete
Restaurer
-
+
View HTML code as formatted text
Voir le code HTML comme du texte formaté
-
+
Save message as...
Enregistrer le message sous…
-
+
Mark Unread
Marquer comme non-lu
-
+
New
Nouvelle
-
+
Enable
Activer
-
+
Disable
Désactiver
-
+
Set avatar...
Configurer l’avatar
-
+
Copy address to clipboard
Copier l’adresse dans le presse-papier
-
+
Special address behavior...
Comportement spécial de l’adresse…
-
+
Email gateway
Passerelle de courriel
-
+
Delete
Effacer
-
+
Send message to this address
Envoyer un message à cette adresse
-
+
Subscribe to this address
S’abonner à cette adresse
-
+
Add New Address
Ajouter une nouvelle adresse
-
+
Copy destination address to clipboard
Copier l’adresse de destination dans le presse-papier
-
+
Force send
Forcer l’envoi
-
+
One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now?
Une de vos adresses, %1, est une vieille adresse de la version 1. Les adresses de la version 1 ne sont plus supportées. Nous pourrions la supprimer maintenant?
-
+
Waiting for their encryption key. Will request it again soon.
En attente de la clé de chiffrement. Une nouvelle requête sera bientôt lancée.
@@ -298,17 +298,17 @@ Veuillez taper l’adresse de courriel désirée (incluant @mailchuck.com) :
-
+
Queued.
En attente.
-
+
Message sent. Waiting for acknowledgement. Sent at %1
Message envoyé. En attente de l’accusé de réception. Envoyé %1
-
+
Message sent. Sent at %1
Message envoyé. Envoyé %1
@@ -318,47 +318,47 @@ Veuillez taper l’adresse de courriel désirée (incluant @mailchuck.com) :
-
+
Acknowledgement of the message received %1
Accusé de réception reçu %1
-
+
Broadcast queued.
Message de diffusion en attente.
-
+
Broadcast on %1
Message de diffusion du %1
-
+
Problem: The work demanded by the recipient is more difficult than you are willing to do. %1
Problème : Le travail demandé par le destinataire est plus difficile que ce que vous avez paramétré. %1
-
+
Problem: The recipient's encryption key is no good. Could not encrypt message. %1
Problème : la clé de chiffrement du destinataire n’est pas bonne. Il n’a pas été possible de chiffrer le message. %1
-
+
Forced difficulty override. Send should start soon.
Neutralisation forcée de la difficulté. L’envoi devrait bientôt commencer.
-
+
Unknown status: %1 %2
Statut inconnu : %1 %2
-
+
Not Connected
Déconnecté
-
+
Show Bitmessage
Afficher Bitmessage
@@ -368,12 +368,12 @@ Veuillez taper l’adresse de courriel désirée (incluant @mailchuck.com) :Envoyer
-
+
Subscribe
S’abonner
-
+
Channel
Canal
@@ -383,12 +383,12 @@ Veuillez taper l’adresse de courriel désirée (incluant @mailchuck.com) :Quitter
-
+
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.
Vous pouvez éditer vos clés en éditant le fichier keys.dat stocké dans le même répertoire que ce programme. Il est important de faire des sauvegardes de ce fichier.
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file.
@@ -396,54 +396,54 @@ It is important that you back up this file.
Il est important de faire des sauvegardes de ce fichier.
-
+
Open keys.dat?
Ouvrir keys.dat ?
-
+
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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
Vous pouvez éditer vos clés en éditant le fichier keys.dat stocké dans le même répertoire que ce programme. Il est important de faire des sauvegardes de ce fichier. Souhaitez-vous l’ouvrir maintenant ? (Assurez-vous de fermer Bitmessage avant d’effectuer des changements.)
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
Vous pouvez éditer vos clés en éditant le fichier keys.dat stocké dans le répertoire %1. Il est important de faire des sauvegardes de ce fichier. Souhaitez-vous l’ouvrir maintenant? (Assurez-vous de fermer Bitmessage avant d’effectuer des changements.)
-
+
Delete trash?
Supprimer la corbeille ?
-
+
Are you sure you want to delete all trashed messages?
Êtes-vous sûr de vouloir supprimer tous les messages dans la corbeille ?
-
+
bad passphrase
Mauvaise phrase secrète
-
+
You must type your passphrase. If you don't have one then this is not the form for you.
Vous devez taper votre phrase secrète. Si vous n’en avez pas, ce formulaire n’est pas pour vous.
-
+
Bad address version number
Mauvais numéro de version d’adresse
-
+
Your address version number must be a number: either 3 or 4.
Votre numéro de version d’adresse doit être un nombre : soit 3 soit 4.
-
+
Your address version number must be either 3 or 4.
Votre numéro de version d’adresse doit être soit 3 soit 4.
@@ -513,22 +513,22 @@ It is important that you back up this file. Would you like to open the file now?
-
+
Connection lost
Connexion perdue
-
+
Connected
Connecté
-
+
Message trashed
Message envoyé à la corbeille
-
+
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
@@ -537,17 +537,17 @@ It is important that you back up this file. Would you like to open the file now?
Le destinataire doit l’obtenir avant ce temps. Si votre client Bitmessage ne reçoit pas de confirmation de réception, il va le ré-envoyer automatiquement. Plus le Time-To-Live est long, plus grand est le travail que votre ordinateur doit effectuer pour envoyer le message. Un Time-To-Live de quatre ou cinq jours est souvent approprié.
-
+
Message too long
Message trop long
-
+
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.
Le message que vous essayez d’envoyer est trop long de %1 octets (le maximum est 261644 octets). Veuillez le réduire avant de l’envoyer.
-
+
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.
Erreur : votre compte n’a pas été inscrit à une passerelle de courrier électronique. Envoi de l’inscription maintenant en tant que %1, veuillez patienter tandis que l’inscription est en cours de traitement, avant de retenter l’envoi.
@@ -592,217 +592,217 @@ Le destinataire doit l’obtenir avant ce temps. Si votre client Bitmessage ne r
-
+
Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab.
Erreur : Vous devez spécifier une adresse d’expéditeur. Si vous n’en avez pas, rendez-vous dans l’onglet 'Vos identités'.
-
+
Address version number
Numéro de version de l’adresse
-
+
Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.
Concernant l’adresse %1, Bitmessage ne peut pas comprendre les numéros de version de %2. Essayez de mettre à jour Bitmessage vers la dernière version.
-
+
Stream number
Numéro de flux
-
+
Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version.
Concernant l’adresse %1, Bitmessage ne peut pas supporter les nombres de flux de %2. Essayez de mettre à jour Bitmessage vers la dernière version.
-
+
Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect.
Avertissement : Vous êtes actuellement déconnecté. Bitmessage fera le travail nécessaire pour envoyer le message mais il ne sera pas envoyé tant que vous ne vous connecterez pas.
-
+
Message queued.
Message mis en file d’attente.
-
+
Your 'To' field is empty.
Votre champ 'Vers' est vide.
-
+
Right click one or more entries in your address book and select 'Send message to this address'.
Cliquez droit sur une ou plusieurs entrées dans votre carnet d’adresses et sélectionnez 'Envoyer un message à ces adresses'.
-
+
Fetched address from namecoin identity.
Récupération avec succès de l’adresse de l’identité Namecoin.
-
+
New Message
Nouveau message
-
+
From
- De
+
-
+
Sending email gateway registration request
Envoi de la demande d’inscription de la passerelle de courriel
-
+
Address is valid.
L’adresse est valide.
-
+
The address you entered was invalid. Ignoring it.
L’adresse que vous avez entrée est invalide. Adresse ignorée.
-
+
Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.
Erreur : Vous ne pouvez pas ajouter une adresse déjà présente dans votre carnet d’adresses. Essayez de renommer l’adresse existante.
-
+
Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.
Erreur : vous ne pouvez pas ajouter la même adresse deux fois à vos abonnements. Peut-être que vous pouvez renommer celle qui existe si vous le souhaitez.
-
+
Restart
Redémarrer
-
+
You must restart Bitmessage for the port number change to take effect.
Vous devez redémarrer Bitmessage pour que le changement de port prenne effet.
-
+
Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any).
Bitmessage utilisera votre proxy dorénavant, mais vous pouvez redémarrer manuellement Bitmessage maintenant afin de fermer des connexions existantes (si il y en existe).
-
+
Number needed
Nombre requis
-
+
Your maximum download and upload rate must be numbers. Ignoring what you typed.
Vos taux maximum de téléchargement et de téléversement doivent être des nombres. Ce que vous avez tapé est ignoré.
-
+
Will not resend ever
Ne renverra jamais
-
+
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.
Notez que la limite de temps que vous avez entrée est plus courte que le temps d’attente respecté par Bitmessage avant le premier essai de renvoi, par conséquent votre message ne sera jamais renvoyé.
-
+
Sending email gateway unregistration request
Envoi de la demande de désinscription de la passerelle de courriel
-
+
Sending email gateway status request
Envoi à la passerelle de courriel d’une demande de statut
-
+
Passphrase mismatch
Phrases secrètes différentes
-
+
The passphrase you entered twice doesn't match. Try again.
Les phrases secrètes entrées sont différentes. Réessayez.
-
+
Choose a passphrase
Choisissez une phrase secrète
-
+
You really do need a passphrase.
Vous devez vraiment utiliser une phrase secrète.
-
+
Address is gone
L’adresse a disparu
-
+
Bitmessage cannot find your address %1. Perhaps you removed it?
Bitmessage ne peut pas trouver votre adresse %1. Peut-être l’avez-vous supprimée?
-
+
Address disabled
Adresse désactivée
-
+
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.
Erreur : L’adresse avec laquelle vous essayez de communiquer est désactivée. Vous devez d’abord l’activer dans l’onglet 'Vos identités' avant de l’utiliser.
-
+
Entry added to the Address Book. Edit the label to your liking.
Entrée ajoutée au carnet d’adresse. Éditez l’étiquette à votre convenance.
-
+
Entry added to the blacklist. Edit the label to your liking.
Entrée ajoutée à la liste noire. Éditez l’étiquette à votre convenance.
-
+
Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want.
Erreur : vous ne pouvez pas ajouter la même adresse deux fois à votre liste noire. Essayez de renommer celle qui existe si vous le souhaitez.
-
+
Moved items to trash.
Messages déplacés dans la corbeille.
-
+
Undeleted item.
Articles restaurés.
-
+
Save As...
Enregistrer sous…
-
+
Write error.
Erreur d’écriture.
-
+
No addresses selected.
Aucune adresse sélectionnée.
-
+
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.
Are you sure you want to delete the subscription?
@@ -811,7 +811,7 @@ Are you sure you want to delete the subscription?
Êtes-vous sur de vouloir supprimer cet abonnement ?
-
+
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.
Are you sure you want to delete the channel?
@@ -820,92 +820,92 @@ Are you sure you want to delete the channel?
Êtes-vous sûr de vouloir supprimer ce canal ?
-
+
Do you really want to remove this avatar?
Voulez-vous vraiment enlever cet avatar ?
-
+
You have already set an avatar for this address. Do you really want to overwrite it?
Vous avez déjà mis un avatar pour cette adresse. Voulez-vous vraiment l’écraser ?
-
+
Start-on-login not yet supported on your OS.
Le démarrage dès l’ouverture de session n’est pas encore supporté sur votre OS.
-
+
Minimize-to-tray not yet supported on your OS.
La minimisation en zone système n’est pas encore supportée sur votre OS.
-
+
Tray notifications not yet supported on your OS.
Les notifications en zone système ne sont pas encore supportées sur votre OS.
-
+
Testing...
Tester…
-
+
This is a chan address. You cannot use it as a pseudo-mailing list.
Ceci est une adresse de canal. Vous ne pouvez pas l’utiliser en tant que pseudo liste de diffusion.
-
+
The address should start with ''BM-''
L’adresse devrait commencer avec "BM-"
-
+
The address is not typed or copied correctly (the checksum failed).
L’adresse n’est pas correcte (la somme de contrôle a échoué).
-
+
The version number of this address is higher than this software can support. Please upgrade Bitmessage.
Le numéro de version de cette adresse est supérieur à celui que le programme peut supporter. Veuiller mettre Bitmessage à jour.
-
+
The address contains invalid characters.
L’adresse contient des caractères invalides.
-
+
Some data encoded in the address is too short.
Certaines données encodées dans l’adresse sont trop courtes.
-
+
Some data encoded in the address is too long.
Certaines données encodées dans l’adresse sont trop longues.
-
+
Some data encoded in the address is malformed.
Quelques données codées dans l’adresse sont mal formées.
-
+
Enter an address above.
Entrez ci-dessus une adresse.
-
+
Address is an old type. We cannot display its past broadcasts.
L’adresse est d’ancien type. Nous ne pouvons pas montrer ses messages de diffusion passés.
-
+
There are no recent broadcasts from this address to display.
Il n’y a aucun message de diffusion récent de cette adresse à afficher.
-
+
You are using TCP port %1. (This can be changed in the settings).
Vous utilisez le port TCP %1. (Ceci peut être changé dans les paramètres).
@@ -1105,57 +1105,57 @@ Are you sure you want to delete the channel?
Niveau de zoom %1%
-
+
Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want.
Erreur : vous ne pouvez pas ajouter la même adresse deux fois à votre liste. Vous pouvez peut-être, si vous le souhaitez, renommer celle qui existe déjà.
-
+
Add new entry
Ajouter une nouvelle entrée
-
+
Display the %1 recent broadcast(s) from this address.
Montre le(s) %1 plus récent(s) message(s) de diffusion issu(s) de cette adresse.
-
+
New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest
Une nouvelle version de PyBitmessage est disponible : %1. Veuillez la télécharger depuis https://github.com/Bitmessage/PyBitmessage/releases/latest
-
+
Waiting for PoW to finish... %1%
En attente de la fin de la PoW… %1%
-
+
Shutting down Pybitmessage... %1%
Pybitmessage en cours d’arrêt… %1%
-
+
Waiting for objects to be sent... %1%
En attente de l’envoi des objets… %1%
-
+
Saving settings... %1%
Enregistrement des paramètres… %1%
-
+
Shutting down core... %1%
Cœur en cours d’arrêt… %1%
-
+
Stopping notifications... %1%
Arrêt des notifications… %1%
-
+
Shutdown imminent... %1%
Arrêt imminent… %1%
@@ -1165,42 +1165,42 @@ Are you sure you want to delete the channel?
%n heure %n heures
-
+
%n day(s)
%n jour %n jours
-
+
Shutting down PyBitmessage... %1%
PyBitmessage en cours d’arrêt… %1%
-
+
Sent
Envoyé
-
+
Generating one new address
Production d’une nouvelle adresse
-
+
Done generating address. Doing work necessary to broadcast it...
La production de l’adresse a été effectuée. Travail en cours afin de l’émettre…
-
+
Generating %1 new addresses.
Production de %1 nouvelles adresses.
-
+
%1 is already in 'Your Identities'. Not adding it again.
%1 est déjà dans "Vos identités". Il ne sera pas ajouté de nouveau.
-
+
Done generating address
La production d’une adresse a été effectuée
@@ -1210,234 +1210,319 @@ Are you sure you want to delete the channel?
-
+
Disk full
Disque plein
-
+
Alert: Your disk or data storage volume is full. Bitmessage will now exit.
Alerte : votre disque ou le volume de stockage de données est plein. Bitmessage va maintenant se fermer.
-
+
Error! Could not find sender address (your address) in the keys.dat file.
Erreur ! Il n’a pas été possible de trouver l’adresse d’expéditeur (votre adresse) dans le fichier keys.dat.
-
+
Doing work necessary to send broadcast...
Travail en cours afin d’envoyer le message de diffusion…
-
+
Broadcast sent on %1
Message de diffusion envoyé %1
-
+
Encryption key was requested earlier.
La clé de chiffrement a été demandée plus tôt.
-
+
Sending a request for the recipient's encryption key.
Envoi d’une demande de la clé de chiffrement du destinataire.
-
+
Looking up the receiver's public key
Recherche de la clé publique du récepteur
-
+
Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1
Problème : la destination est un dispositif mobile qui nécessite que la destination soit incluse dans le message mais ceci n’est pas autorisé dans vos paramètres. %1
-
+
Doing work necessary to send message.
There is no required difficulty for version 2 addresses like this.
Travail en cours afin d’envoyer le message.
Il n’y a pas de difficulté requise pour les adresses version 2 comme celle-ci.
-
+
Doing work necessary to send message.
Receiver's required difficulty: %1 and %2
Travail en cours afin d’envoyer le message.
Difficulté requise du destinataire : %1 et %2
-
+
Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3
Problème : Le travail demandé par le destinataire (%1 and %2) est plus difficile que ce que vous avez paramétré. %3
-
+
Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1
Problème : Vous essayez d’envoyer un message à un canal ou à vous-même mais votre clef de chiffrement n’a pas été trouvée dans le fichier keys.dat. Le message ne peut pas être chiffré. %1
-
+
Doing work necessary to send message.
Travail en cours afin d’envoyer le message.
-
+
Message sent. Waiting for acknowledgement. Sent on %1
Message envoyé. En attente de l’accusé de réception. Envoyé %1
-
+
Doing work necessary to request encryption key.
Travail en cours afin d’obtenir la clé de chiffrement.
-
+
Broadcasting the public key request. This program will auto-retry if they are offline.
Diffusion de la demande de clef publique. Ce programme réessaiera automatiquement si ils sont déconnectés.
-
+
Sending public key request. Waiting for reply. Requested at %1
Envoi d’une demande de clef publique. En attente d’une réponse. Demandée à %1
-
+
UPnP port mapping established on port %1
Transfert de port UPnP établi sur le port %1
-
+
UPnP port mapping removed
Transfert de port UPnP retiré
-
+
Mark all messages as read
Marquer tous les messages comme lus
-
+
Are you sure you would like to mark all messages read?
Êtes-vous sûr(e) de vouloir marquer tous les messages comme lus ?
-
+
Doing work necessary to send broadcast.
Travail en cours afin d’envoyer la diffusion.
-
+
Proof of work pending
En attente de preuve de fonctionnement
-
+
%n object(s) pending proof of work
%n objet en attente de preuve de fonctionnement %n objet(s) en attente de preuve de fonctionnement
-
+
%n object(s) waiting to be distributed
%n objet en attente d'être distribué %n objet(s) en attente d'être distribués
-
+
Wait until these tasks finish?
Attendre jusqu'à ce que ces tâches se terminent ?
-
+
Problem communicating with proxy: %1. Please check your network settings.
Problème de communication avec le proxy : %1. Veuillez vérifier vos réglages réseau.
-
+
SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings.
Problème d’authentification SOCKS5 : %1. Veuillez vérifier vos réglages SOCKS5.
-
+
The time on your computer, %1, may be wrong. Please verify your settings.
L'heure sur votre ordinateur, %1, pourrait être faussse. Veuillez vérifier vos paramètres.
-
+
+ The name %1 was not found.
+ Le nom %1 n'a pas été trouvé.
+
+
+
+ The namecoin query failed (%1)
+ La requête Namecoin a échouée (%1)
+
+
+
+ The namecoin query failed.
+ La requête Namecoin a échouée.
+
+
+
+ The name %1 has no valid JSON data.
+ Le nom %1 n'a aucune donnée JSON valide.
+
+
+
+ The name %1 has no associated Bitmessage address.
+ Le nom %1 n'a aucune adresse Bitmessage d'associée.
+
+
+
+ Success! Namecoind version %1 running.
+ Succès ! Namecoind version %1 en cours d'exécution.
+
+
+
+ Success! NMControll is up and running.
+ Succès ! NMControll est debout et en cours d'exécution.
+
+
+
+ Couldn't understand NMControl.
+ Ne pouvait pas comprendre NMControl.
+
+
+
Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.
Votre GPU(s) n'a pas calculé correctement, mettant OpenCL hors service. Veuillez remonter ceci aux développeurs s'il vous plaît.
-
+
+ Set notification sound...
+ Mettre un son de notification ...
+
+
+
+
+ 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
+
+
+Bienvenue dans le facile et sécurisé Bitmessage
+* envoyer des messages à d'autres personnes
+* envoyer des messages par diffusion (broadcast) à la manière de Twitter ou
+* discuter dans des canaux (channels) avec d'autres personnes
+
+
+
+
+ not recommended for chans
+ pas recommandé pour les canaux
+
+
+
+ Problems connecting? Try enabling UPnP in the Network Settings
+ Des difficultés à se connecter ? Essayez de permettre UPnP dans les "Paramètres réseau"
+
+
+
+ You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?
+ Vous essayez d'envoyer un courrier électronique au lieu d'un bitmessage. Ceci exige votre inscription à une passerelle. Essayer de vous inscrire ?
+
+
+
Error: Bitmessage addresses start with BM- Please check the recipient address %1
Erreur : Les adresses Bitmessage commencent par BM- Veuillez vérifier l'adresse du destinataire %1
-
+
Error: The recipient address %1 is not typed or copied correctly. Please check it.
Erreur : L’adresse du destinataire %1 n’est pas correctement tapée ou recopiée. Veuillez la vérifier.
-
+
Error: The recipient address %1 contains invalid characters. Please check it.
Erreur : L’adresse du destinataire %1 contient des caractères invalides. Veuillez la vérifier.
-
+
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.
Erreur : la version de l’adresse destinataire %1 est trop élevée. Vous devez mettre à niveau votre logiciel Bitmessage ou alors celui de votre connaissance est plus intelligent.
-
+
Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance.
Erreur : quelques données codées dans l’adresse destinataire %1 sont trop courtes. Il pourrait y avoir un soucis avec le logiciel de votre connaissance.
-
+
Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance.
Erreur : quelques données codées dans l’adresse destinataire %1 sont trop longues. Il pourrait y avoir un soucis avec le logiciel de votre connaissance.
-
+
Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance.
Erreur : quelques données codées dans l’adresse destinataire %1 sont mal formées. Il pourrait y avoir un soucis avec le logiciel de votre connaissance.
-
+
Error: Something is wrong with the recipient address %1.
Erreur : quelque chose ne va pas avec l'adresse de destinataire %1.
-
+
+ From %1
+ De %1
+
+
+
Synchronisation pending
En attente de synchronisation
-
+
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?
Bitmessage ne s'est pas synchronisé avec le réseau, %n objet(s) à télécharger. Si vous quittez maintenant, cela pourrait causer des retards de livraison. Attendre jusqu'à ce que la synchronisation aboutisse ? Bitmessage ne s'est pas synchronisé avec le réseau, %n objet(s) à télécharger. Si vous quittez maintenant, cela pourrait causer des retards de livraison. Attendre jusqu'à ce que la synchronisation aboutisse ?
-
+
Not connected
Non connecté
-
+
Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes?
Bitmessage n'est pas connecté au réseau. Si vous quittez maintenant, cela pourrait causer des retards de livraison. Attendre jusqu'à ce qu'il soit connecté et que la synchronisation se termine ?
-
+
Waiting for network connection...
En attente de connexion réseau...
-
+
Waiting for finishing synchronisation...
En attente d'achèvement de la synchronisation...
+
+
+ You have already set a notification sound for this address book entry. Do you really want to overwrite it?
+ Vous avez déjà mis un son de notification pour cette adresse. Voulez-vous vraiment l’écraser ?
+
MessageView
@@ -1465,14 +1550,14 @@ Difficulté requise du destinataire : %1 et %2
MsgDecode
-
+
The message has an unknown encoding.
Perhaps you should upgrade Bitmessage.
Le message est codé de façon inconnue.
Peut-être que vous devriez mettre à niveau Bitmessage.
-
+
Unknown encoding
Encodage inconnu
@@ -1630,27 +1715,27 @@ The 'Random Number' option is selected by default but deterministic ad
aboutDialog
-
+
About
À propos
-
+
PyBitmessage
PyBitmessage
-
+
version ?
version ?
-
+
<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>
<html><head/><body><p>Distribué sous la licence logicielle MIT/X11; voir <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>
-
+
This is Beta software.
Version bêta.
@@ -1660,7 +1745,7 @@ The 'Random Number' option is selected by default but deterministic ad
-
+
<html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
<html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 Les développeurs de Bitmessage</p></body></html>
@@ -1693,12 +1778,12 @@ The 'Random Number' option is selected by default but deterministic ad
Adresse
-
+
Blacklist
Liste noire
-
+
Whitelist
Liste blanche
@@ -1780,77 +1865,77 @@ The 'Random Number' option is selected by default but deterministic ad
Total de connexions:
-
+
Since startup:
Depuis le démarrage :
-
+
Processed 0 person-to-person messages.
Traité 0 messages de personne à personne.
-
+
Processed 0 public keys.
Traité 0 clés publiques.
-
+
Processed 0 broadcasts.
Traité 0 message de diffusion.
-
+
Inventory lookups per second: 0
Consultations d’inventaire par seconde : 0
-
+
Objects to be synced:
Objets à synchroniser :
-
+
Stream #
Flux N°
Connections
- Connexions
+
-
+
Since startup on %1
Démarré depuis le %1
-
+
Down: %1/s Total: %2
Téléchargées : %1/s Total : %2
-
+
Up: %1/s Total: %2
Téléversées : %1/s Total : %2
-
+
Total Connections: %1
Total des connexions : %1
-
+
Inventory lookups per second: %1
Consultations d’inventaire par seconde : %1
-
+
Up: 0 kB/s
Téléversement : 0 kO/s
-
+
Down: 0 kB/s
Téléchargement : 0 kO/s
@@ -1860,30 +1945,75 @@ The 'Random Number' option is selected by default but deterministic ad
Statut du réseau
-
+
byte(s)
octet octets
-
+
Object(s) to be synced: %n
Objet à synchroniser : %n Objets à synchroniser : %n
-
+
Processed %n person-to-person message(s).
Traité %n message de personne à personne. Traité %n messages de personne à personne.
-
+
Processed %n broadcast message(s).
Traité %n message de diffusion. Traité %n messages de diffusion.
-
+
Processed %n public key(s).
Traité %n clé publique. Traité %n clés publiques.
+
+
+ Peer
+ Pair
+
+
+
+ IP address or hostname
+ Adresse IP ou nom d'hôte
+
+
+
+ Rating
+ Évaluation
+
+
+
+ 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
+ PyBitmessage suit à la trace le taux de réussites de connexions tentées vers les noeuds individuels. L'évaluation s'étend de -1 à 1 et affecte la probabilité de choisir ce noeud dans l'avenir
+
+
+
+ User agent
+ Agent utilisateur
+
+
+
+ Peer's self-reported software
+ Logiciel, auto-rapporté par le pair
+
+
+
+ TLS
+ TLS
+
+
+
+ Connection encryption
+
+
+
+
+ List of streams negotiated between you and the peer
+
+
newChanDialog
@@ -1966,12 +2096,12 @@ The 'Random Number' option is selected by default but deterministic ad
Le canal %1 a été rejoint ou créé avec succès.
-
+
Chan creation / joining failed
Échec lors de la création du canal ou de la tentative de le rejoindre
-
+
Chan creation / joining cancelled
Annulation de la création du canal ou de la tentative de le rejoindre
@@ -1979,21 +2109,29 @@ The 'Random Number' option is selected by default but deterministic ad
proofofwork
-
+
C PoW module built successfully.
Module PoW C construit avec succès.
-
+
Failed to build C PoW module. Please build it manually.
Échec à construire le module PoW C. Veuillez le construire manuellement.
-
+
C PoW module unavailable. Please build it.
Module PoW C non disponible. Veuillez le construire.
+
+ qrcodeDialog
+
+
+ QR-code
+ QR-code
+
+
regenerateAddressesDialog
@@ -2050,218 +2188,218 @@ The 'Random Number' option is selected by default but deterministic ad
settingsDialog
-
+
Settings
Paramètres
-
+
Start Bitmessage on user login
Démarrer Bitmessage à la connexion de l’utilisateur
-
+
Tray
Zone de notification
-
+
Start Bitmessage in the tray (don't show main window)
Démarrer Bitmessage dans la barre des tâches (ne pas montrer la fenêtre principale)
-
+
Minimize to tray
Minimiser dans la barre des tâches
-
+
Close to tray
Fermer vers la zone de notification
-
+
Show notification when message received
Montrer une notification lorsqu’un message est reçu
-
+
Run in Portable Mode
Lancer en Mode Portable
-
+
In Portable Mode, messages and config files are stored in the same directory as the program rather than the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive.
En Mode Portable, les messages et les fichiers de configuration sont stockés dans le même dossier que le programme plutôt que le dossier de l’application. Cela rend l’utilisation de Bitmessage plus facile depuis une clé USB.
-
+
Willingly include unencrypted destination address when sending to a mobile device
Inclure volontairement l’adresse de destination non chiffrée lors de l’envoi vers un dispositif mobile
-
+
Use Identicons
Utilise des Identicônes.
-
+
Reply below Quote
Réponse en dessous de la citation
-
+
Interface Language
Langue de l’interface
-
+
System Settings
system
Paramètres système
-
+
User Interface
Interface utilisateur
-
+
Listening port
Port d’écoute
-
+
Listen for connections on port:
Écouter les connexions sur le port :
-
+
UPnP:
UPnP :
-
+
Bandwidth limit
Limite de bande passante
-
+
Maximum download rate (kB/s): [0: unlimited]
Taux de téléchargement maximal (kO/s) : [0 : illimité]
-
+
Maximum upload rate (kB/s): [0: unlimited]
Taux de téléversement maximal (kO/s) : [0 : illimité]
-
+
Proxy server / Tor
Serveur proxy / Tor
-
+
Type:
Type :
-
+
Server hostname:
Nom du serveur:
-
+
Port:
Port :
-
+
Authentication
Authentification
-
+
Username:
Utilisateur :
-
+
Pass:
Mot de passe :
-
+
Listen for incoming connections when using proxy
Écoute les connexions entrantes lors de l’utilisation du proxy
-
+
none
aucun
-
+
SOCKS4a
SOCKS4a
-
+
SOCKS5
SOCKS5
-
+
Network Settings
Paramètres réseau
-
+
Total difficulty:
Difficulté totale :
-
+
The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work.
La 'difficulté totale' affecte le montant total de travail que l’envoyeur devra compléter. Doubler cette valeur double la charge de travail.
-
+
Small message difficulty:
Difficulté d’un message court :
-
+
When someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1.
Lorsque quelqu’un vous envoie un message, son ordinateur doit d’abord effectuer un travail. La difficulté de ce travail, par défaut, est de 1. Vous pouvez augmenter cette valeur pour les adresses que vous créez en changeant la valeur ici. Chaque nouvelle adresse que vous créez requerra à l’envoyeur de faire face à une difficulté supérieure. Il existe une exception : si vous ajoutez un ami ou une connaissance à votre carnet d’adresses, Bitmessage les notifiera automatiquement lors du prochain message que vous leur envoyez qu’ils ne doivent compléter que la charge de travail minimale : difficulté 1.
-
+
The 'Small message difficulty' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn't really affect large messages.
La 'difficulté d’un message court' affecte principalement la difficulté d’envoyer des messages courts. Doubler cette valeur rend la difficulté à envoyer un court message presque double, tandis qu’un message plus long ne sera pas réellement affecté.
-
+
Demanded difficulty
Difficulté exigée
-
+
Here you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable.
Vous pouvez préciser quelle charge de travail vous êtes prêt à effectuer afin d’envoyer un message à une personne. Placer cette valeur à 0 signifie que n’importe quelle valeur est acceptée.
-
+
Maximum acceptable total difficulty:
Difficulté maximale acceptée :
-
+
Maximum acceptable small message difficulty:
Difficulté maximale acceptée pour les messages courts :
-
+
Max acceptable difficulty
Difficulté maximale acceptée
@@ -2271,82 +2409,87 @@ The 'Random Number' option is selected by default but deterministic ad
-
+
<html><head/><body><p>Bitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to <span style=" font-style:italic;">test. </span></p><p>(Getting your own Bitmessage address into Namecoin is still rather difficult).</p><p>Bitmessage can use either namecoind directly or a running nmcontrol instance.</p></body></html>
<html><head/><body><p>Bitmessage peut utiliser Namecoin, un autre programme basé sur Bitcoin, pour avoir des adresses plus parlantes. Par exemple, plutôt que de donner à votre ami votre longue adresse Bitmessage, vous pouvez simplement lui dire d’envoyer un message à <span style=" font-style:italic;">test. </span></p><p>(Obtenir votre propre adresse Bitmessage au sein de Namecoin est encore assez difficile).</p><p>Bitmessage peut soit utiliser directement namecoind soit exécuter une instance de nmcontrol.</p></body></html>
-
+
Host:
Hôte :
-
+
Password:
Mot de passe :
-
+
Test
Test
-
+
Connect to:
Connexion à :
-
+
Namecoind
Namecoind
-
+
NMControl
NMControl
-
+
Namecoin integration
Intégration avec Namecoin
-
+
<html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html>
<html><head/><body><p>Par défaut, si vous envoyez un message à quelqu’un et que cette personne est hors connexion pendant plus de deux jours, Bitmessage enverra le message de nouveau après des deux jours supplémentaires. Ceci sera continué avec reculement (backoff) exponentiel pour toujours; les messages seront réenvoyés après 5, 10, 20 jours etc. jusqu’à ce que le récepteur accuse leur réception. Ici vous pouvez changer ce comportement en faisant en sorte que Bitmessage renonce après un certain nombre de jours ou de mois.</p> <p>Si vous souhaitez obtenir le comportement par défaut alors laissez vides ces champs de saisie. </p></body></html>
-
+
Give up after
Abandonner après
-
+
and
et
-
+
days
jours
-
+
months.
mois.
-
+
Resends Expire
Expiration des renvois automatiques
-
+
Hide connection notifications
Cacher les notifications de connexion
-
+
+ Maximum outbound connections: [0: none]
+ Connexions sortantes maximum: [0: aucune]
+
+
+
Hardware GPU acceleration (OpenCL):
Accélération matérielle GPU (OpenCL) :
diff --git a/src/translations/bitmessage_ja.qm b/src/translations/bitmessage_ja.qm
index 2f28764b..3756d6d3 100644
Binary files a/src/translations/bitmessage_ja.qm and b/src/translations/bitmessage_ja.qm differ
diff --git a/src/translations/bitmessage_ja.ts b/src/translations/bitmessage_ja.ts
index c000c254..90e49dc2 100644
--- a/src/translations/bitmessage_ja.ts
+++ b/src/translations/bitmessage_ja.ts
@@ -58,12 +58,12 @@
EmailGatewayRegistrationDialog
-
+
Registration failed:
登録に失敗しました:
-
+
The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:
リクエストしたメールアドレスは利用できません。新しいメールアドレスをお試しください。 新しい希望メールアドレス (@mailchuck.com を含む) を次のように記入してください:
@@ -168,52 +168,52 @@ Please type the desired email address (including @mailchuck.com) below:
MainWindow
-
+
Reply to sender
送信元に返信
-
+
Reply to channel
チャンネルに返信
-
+
Add sender to your Address Book
送信元をアドレス帳に追加
-
+
Add sender to your Blacklist
送信元をブラックリストに追加
-
+
Move to Trash
ゴミ箱へ移動
-
+
Undelete
削除を元に戻す
-
+
View HTML code as formatted text
HTMLコードを整形したテキストで表示
-
+
Save message as...
形式を選択してメッセージを保存
-
+
Mark Unread
未読にする
-
+
New
新規
@@ -238,12 +238,12 @@ Please type the desired email address (including @mailchuck.com) below:
アドレスをコピー
-
+
Special address behavior...
アドレスの特別な動作
-
+
Email gateway
メールゲートウェイ
@@ -253,37 +253,37 @@ Please type the desired email address (including @mailchuck.com) below:
削除
-
+
Send message to this address
このアドレスへ送信
-
+
Subscribe to this address
このアドレスを購読
-
+
Add New Address
アドレスを追加
-
+
Copy destination address to clipboard
宛先アドレスをコピー
-
+
Force send
強制的に送信
-
+
One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now?
%1は古いバージョン1のアドレスです。バージョン1のアドレスはサポートが終了しています。すぐに削除しますか?
-
+
Waiting for their encryption key. Will request it again soon.
暗号鍵を待っています。 すぐにもう一度リクエストします。
@@ -293,17 +293,17 @@ Please type the desired email address (including @mailchuck.com) below:
-
+
Queued.
キューに入りました。
-
+
Message sent. Waiting for acknowledgement. Sent at %1
メッセージを送信しました。 確認応答を待っています。 %1 で送信されました
-
+
Message sent. Sent at %1
メッセージは送信されました。送信先: %1
@@ -313,47 +313,47 @@ Please type the desired email address (including @mailchuck.com) below:
-
+
Acknowledgement of the message received %1
メッセージの確認を受け取りました %1
-
+
Broadcast queued.
配信がキューに入りました。
-
+
Broadcast on %1
配信: %1
-
+
Problem: The work demanded by the recipient is more difficult than you are willing to do. %1
問題: 受信者が要求している処理は現在あなたが設定しているよりも高い難易度です。 %1
-
+
Problem: The recipient's encryption key is no good. Could not encrypt message. %1
問題: 受信者の暗号鍵は正当でない物です。メッセージを暗号化できません。 %1
-
+
Forced difficulty override. Send should start soon.
難易度を強制上書きしました。まもなく送信されます。
-
+
Unknown status: %1 %2
不明なステータス: %1 %2
-
+
Not Connected
未接続
-
+
Show Bitmessage
Bitmessageを表示
@@ -363,12 +363,12 @@ Please type the desired email address (including @mailchuck.com) below:
送る
-
+
Subscribe
購読
-
+
Channel
チャンネル
@@ -378,66 +378,66 @@ Please type the desired email address (including @mailchuck.com) below:
終了
-
+
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.
プログラムを同じディレクトリに保存されているkeys.datファイルを編集することで鍵を管理できます。ファイルをバックアップしておくことも重要です。
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file.
%1に保存されているkeys.datファイルを編集することで鍵を管理できます。ファイルをバックアップしておくことも重要です。
-
+
Open keys.dat?
keys.datを開きますか?
-
+
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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
プログラムを同じディレクトリに保存されているkeys.datファイルを編集することで鍵を管理できます。ファイルをバックアップしておくことも重要です。すぐにファイルを開きますか?(必ず編集する前にBitmessageを終了してください)
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
%1に保存されているkeys.datファイルを編集することで鍵を管理できます。ファイルをバックアップしておくことも重要です。すぐにファイルを開きますか?(必ず編集する前にBitmessageを終了してください)
-
+
Delete trash?
ゴミ箱を空にしますか?
-
+
Are you sure you want to delete all trashed messages?
ゴミ箱内のメッセージを全て削除してもよろしいですか?
-
+
bad passphrase
不正なパスフレーズ
-
+
You must type your passphrase. If you don't have one then this is not the form for you.
パスフレーズを入力してください。パスフレーズがない場合は入力する必要はありません。
-
+
Bad address version number
不正なアドレスのバージョン番号
-
+
Your address version number must be a number: either 3 or 4.
アドレスのバージョン番号は数字にする必要があります: 3 または 4。
-
+
Your address version number must be either 3 or 4.
アドレスのバージョン番号は、3 または 4 のどちらかにする必要があります。
@@ -507,22 +507,22 @@ It is important that you back up this file. Would you like to open the file now?
-
+
Connection lost
接続が切断されました
-
+
Connected
接続済み
-
+
Message trashed
メッセージが削除されました
-
+
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
@@ -533,17 +533,17 @@ It is important that you back up this file. Would you like to open the file now?
コンピュータがメッセージを送信するために必要な処理が増えます。 多くの場合 4〜5 日のTTL(Time-To-Live)が適切です。
-
+
Message too long
メッセージが長すぎます
-
+
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.
送信しようとしているメッセージが %1 バイト長すぎます。 (最大は261644バイトです)。 送信する前に短くしてください。
-
+
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.
エラー: アカウントがメールゲートウェイに登録されていません。 今 %1 として登録を送信しています。送信を再試行する前に、登録が処理されるまでお待ちください。
@@ -588,67 +588,67 @@ It is important that you back up this file. Would you like to open the file now?
-
+
Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab.
エラー: 送信元アドレスを指定してください。まだ作成していない場合には「アドレス一覧」のタブを開いてください。
-
+
Address version number
アドレスのバージョン番号
-
+
Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.
アドレス %1 に接続。%2 のバージョン番号は処理できません。Bitmessageを最新のバージョンへアップデートしてください。
-
+
Stream number
ストリーム番号
-
+
Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version.
アドレス %1 に接続。%2 のストリーム番号は処理できません。Bitmessageを最新のバージョンへアップデートしてください。
-
+
Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect.
警告: 接続されていません。Bitmessageはメッセージの処理を行いますが、ネットワークに接続するまで送信はされません。
-
+
Message queued.
メッセージがキューに入りました。
-
+
Your 'To' field is empty.
宛先が指定されていません。
-
+
Right click one or more entries in your address book and select 'Send message to this address'.
アドレス帳から一つ、または複数のアドレスを右クリックして「このアドレスへ送信」を選んでください。
-
+
Fetched address from namecoin identity.
namecoin IDからアドレスを取得。
-
+
New Message
新規メッセージ
-
+
From
- 送信元
+
-
+
Sending email gateway registration request
メールゲートウェイの登録リクエストを送信しています
@@ -663,142 +663,142 @@ It is important that you back up this file. Would you like to open the file now?
入力されたアドレスは不正です。無視されました。
-
+
Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.
エラー: 同じアドレスを複数アドレス帳に追加する事はできません。既存の項目をリネームしてください。
-
+
Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.
エラー: 購読に、同じアドレスを2回追加することはできません。 必要に応じて、既存の名前を変更してください。
-
+
Restart
再開
-
+
You must restart Bitmessage for the port number change to take effect.
ポート番号の変更を有効にするにはBitmessageを再起動してください。
-
+
Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any).
プロキシの設定を有効にするには手動でBitmessageを再起動してください。既に接続がある場合は切断されます。
-
+
Number needed
数字が必要です
-
+
Your maximum download and upload rate must be numbers. Ignoring what you typed.
最大ダウンロード数とアップロード数は数字にする必要があります。 入力されたものを無視します。
-
+
Will not resend ever
今後再送信されません
-
+
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.
入力した時間制限は、Bitmessageが最初の再送試行を待つ時間よりも短いため、メッセージは再送信されないことにご注意ください。
-
+
Sending email gateway unregistration request
メールゲートウェイの登録抹消リクエストを送信しています
-
+
Sending email gateway status request
メールゲートウェイの状態リクエストを送信しています
-
+
Passphrase mismatch
パスフレーズが一致しません
-
+
The passphrase you entered twice doesn't match. Try again.
再度入力されたパスフレーズが一致しません。再入力してください。
-
+
Choose a passphrase
パスフレーズを選択してください
-
+
You really do need a passphrase.
パスフレーズが必要です。
-
+
Address is gone
アドレスが無効になりました
-
+
Bitmessage cannot find your address %1. Perhaps you removed it?
アドレス %1 が見つかりません。既に削除していませんか?
-
+
Address disabled
アドレスが無効になりました
-
+
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.
エラー: 送信しようとしたアドレスは無効になっています。使用する前に「アドレス一覧」で有効にしてください。
-
+
Entry added to the Address Book. Edit the label to your liking.
アドレス帳に項目が追加されました。ラベルは自由に編集できます。
-
+
Entry added to the blacklist. Edit the label to your liking.
ブラックリストに項目が追加されました。ラベルは自由に編集できます。
-
+
Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want.
エラー: ブラックリストに同じアドレスを2回追加することはできません。 必要に応じて既存の名前を変更してみてください。
-
+
Moved items to trash.
アイテムをゴミ箱へ移動。
-
+
Undeleted item.
アイテムの削除を元に戻します。
-
+
Save As...
形式を選択して保存
-
+
Write error.
書き込みエラー。
-
+
No addresses selected.
アドレスが未選択です。
-
+
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.
Are you sure you want to delete the subscription?
@@ -807,7 +807,7 @@ Are you sure you want to delete the subscription?
購読を削除してもよろしいですか?
-
+
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.
Are you sure you want to delete the channel?
@@ -816,92 +816,92 @@ Are you sure you want to delete the channel?
チャンネルを削除してもよろしいですか?
-
+
Do you really want to remove this avatar?
このアバターを削除してもよろしいですか?
-
+
You have already set an avatar for this address. Do you really want to overwrite it?
すでにこのアドレスのアバターを設定しています。 上書きしてもよろしいですか?
-
+
Start-on-login not yet supported on your OS.
ログイン時に開始は、まだお使いのOSでサポートされていません。
-
+
Minimize-to-tray not yet supported on your OS.
トレイに最小化は、まだお使いのOSでサポートされていません。
-
+
Tray notifications not yet supported on your OS.
トレイ通知は、まだお使いのOSでサポートされていません。
-
+
Testing...
テスト中
-
+
This is a chan address. You cannot use it as a pseudo-mailing list.
chanアドレスは仮想メーリングリストのアドレスには使用できません。
-
+
The address should start with ''BM-''
アドレスは「BM-」から始まります
-
+
The address is not typed or copied correctly (the checksum failed).
このアドレスは正しく入力、またはコピーされていません。(チェックサムが一致しません)。
-
+
The version number of this address is higher than this software can support. Please upgrade Bitmessage.
このアドレスのバージョン番号はこのプログラムのサポート範囲外です。Bitmessageをアップデートしてください。
-
+
The address contains invalid characters.
入力されたアドレスは不正な文字を含んでいます。
-
+
Some data encoded in the address is too short.
このアドレスでエンコードされたデータが短すぎます。
-
+
Some data encoded in the address is too long.
このアドレスでエンコードされたデータが長過ぎます。
-
+
Some data encoded in the address is malformed.
このアドレスでエンコードされた一部のデータが不正です。
-
+
Enter an address above.
上にアドレスを入力してください。
-
+
Address is an old type. We cannot display its past broadcasts.
アドレスが古い形式です。 過去の配信は表示できません。
-
+
There are no recent broadcasts from this address to display.
このアドレスから表示する最近の配信はありません。
-
+
You are using TCP port %1. (This can be changed in the settings).
使用中のポート %1 (設定で変更できます)。
@@ -1111,47 +1111,47 @@ Are you sure you want to delete the channel?
新しい項目を追加
-
+
Display the %1 recent broadcast(s) from this address.
このアドレスから%1の最新の配信を表示します。
-
+
New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest
新しいバージョンの PyBitmessage が利用可能です: %1。 https://github.com/Bitmessage/PyBitmessage/releases/latest からダウンロードしてください
-
+
Waiting for PoW to finish... %1%
PoW(証明)が完了するのを待っています... %1%
-
+
Shutting down Pybitmessage... %1%
Pybitmessageをシャットダウンしています... %1%
-
+
Waiting for objects to be sent... %1%
オブジェクトの送信待ち... %1%
-
+
Saving settings... %1%
設定を保存しています... %1%
-
+
Shutting down core... %1%
コアをシャットダウンしています... %1%
-
+
Stopping notifications... %1%
通知を停止しています... %1%
-
+
Shutdown imminent... %1%
すぐにシャットダウンします... %1%
@@ -1161,17 +1161,17 @@ Are you sure you want to delete the channel?
%n 時間
-
+
%n day(s)
%n 日
-
+
Shutting down PyBitmessage... %1%
PyBitmessageをシャットダウンしています... %1%
-
+
Sent
送信済
@@ -1216,76 +1216,76 @@ Are you sure you want to delete the channel?
アラート: ディスクまたはデータストレージのボリュームがいっぱいです。 Bitmessageが終了します。
-
+
Error! Could not find sender address (your address) in the keys.dat file.
エラー! keys.datファイルで送信元アドレス (あなたのアドレス) を見つけることができませんでした。
-
+
Doing work necessary to send broadcast...
配信に必要な処理を行っています...
-
+
Broadcast sent on %1
配信が送信されました %1
-
+
Encryption key was requested earlier.
暗号鍵は以前にリクエストされました。
-
+
Sending a request for the recipient's encryption key.
受信者の暗号鍵のリクエストを送信します。
-
+
Looking up the receiver's public key
受信者の公開鍵を探しています
-
+
Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1
問題: メッセージに含まれた宛先のリクエストはモバイルデバイスですが、設定では許可されていません。 %1
-
+
Doing work necessary to send message.
There is no required difficulty for version 2 addresses like this.
メッセージの送信に必要な処理を行っています。
このようなバージョン2のアドレスには、必要な難易度はありません。
-
+
Doing work necessary to send message.
Receiver's required difficulty: %1 and %2
メッセージの送信に必要な処理を行っています。
受信者の必要な難易度: %1 および %2
-
+
Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3
問題: 受信者が要求している処理 (%1 および %2) は、現在あなたが設定しているよりも高い難易度です。 %3
-
+
Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1
問題: あなた自身またはチャンネルにメッセージを送信しようとしていますが、暗号鍵がkeys.datファイルに見つかりませんでした。 メッセージを暗号化できませんでした。 %1
-
+
Doing work necessary to send message.
メッセージの送信に必要な処理を行っています。
-
+
Message sent. Waiting for acknowledgement. Sent on %1
メッセージを送信しました。 確認応答を待っています。 %1 で送信しました
-
+
Doing work necessary to request encryption key.
暗号鍵のリクエストに必要な処理を行っています。
@@ -1300,150 +1300,235 @@ Receiver's required difficulty: %1 and %2
公開鍵のリクエストを送信しています。 返信を待っています。 %1 でリクエストしました
-
+
UPnP port mapping established on port %1
ポート%1でUPnPポートマッピングが確立しました
-
+
UPnP port mapping removed
UPnPポートマッピングを削除しました
-
+
Mark all messages as read
すべてのメッセージを既読にする
-
+
Are you sure you would like to mark all messages read?
すべてのメッセージを既読にしてもよろしいですか?
-
+
Doing work necessary to send broadcast.
配信に必要な処理を行っています。
-
+
Proof of work pending
PoW(証明)を待っています
-
+
%n object(s) pending proof of work
%n オブジェクトが証明待ち (PoW)
-
+
%n object(s) waiting to be distributed
%n オブジェクトが配布待ち
-
+
Wait until these tasks finish?
これらのタスクが完了するまで待ちますか?
-
+
Problem communicating with proxy: %1. Please check your network settings.
プロキシとの通信に問題があります: %1。 ネットワーク設定を確認してください。
-
+
SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings.
SOCKS5認証に問題があります: %1。 SOCKS5の設定を確認してください。
-
+
The time on your computer, %1, may be wrong. Please verify your settings.
お使いのコンピュータの時間 %1 は間違っている可能性があります。 設定を確認してください。
-
+
+ The name %1 was not found.
+ 名前 %1 が見つかりませんでした。
+
+
+
+ The namecoin query failed (%1)
+ namecoin のクエリに失敗しました (%1)
+
+
+
+ The namecoin query failed.
+ namecoin のクエリに失敗しました。
+
+
+
+ The name %1 has no valid JSON data.
+ 名前 %1 は有効な JSON データがありません。
+
+
+
+ The name %1 has no associated Bitmessage address.
+ 名前 %1 は関連付けられた Bitmessage アドレスがありません。
+
+
+
+ Success! Namecoind version %1 running.
+ 成功! Namecoind バージョン %1 が実行中。
+
+
+
+ Success! NMControll is up and running.
+ 成功! NMControll が開始して実行中です。
+
+
+
+ Couldn't understand NMControl.
+ NMControl を理解できませんでした。
+
+
+
Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.
GPUが正しく求められないため、OpenCLが無効になりました。 開発者に報告してください。
-
+
+ Set notification sound...
+ 通知音を設定...
+
+
+
+
+ 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
+
+
+簡単で安全な Bitmessage へようこそ
+* 他の人にメッセージを送ります
+* Twitter のようなブロードキャストメッセージを送信します
+* 他の人と一緒にチャン(ネル)で議論します
+
+
+
+
not recommended for chans
チャンネルにはお勧めしません
-
+
+ Quiet Mode
+ マナーモード
+
+
+
Problems connecting? Try enabling UPnP in the Network Settings
接続に問題がありますか? ネットワーク設定でUPnPを有効にしてみてください
-
+
+ You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?
+ Bitmessage の代わりにメールを送信しようとしています。 これは、ゲートウェイに登録する必要があります。 登録しますか?
+
+
+
Error: Bitmessage addresses start with BM- Please check the recipient address %1
エラー: BitmessageのアドレスはBM-で始まります。 受信者のアドレス %1 を確認してください
-
+
Error: The recipient address %1 is not typed or copied correctly. Please check it.
エラー: 受信者のアドレス %1 は正しく入力、またはコピーされていません。確認して下さい。
-
+
Error: The recipient address %1 contains invalid characters. Please check it.
エラー: 受信者のアドレス %1 は不正な文字を含んでいます。確認して下さい。
-
+
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.
エラー: 受信者アドレスのバージョン %1 は高すぎます。 Bitmessageソフトウェアをアップグレードする必要があるか、連絡先が賢明になっているかのいずれかです。
-
+
Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance.
エラー: アドレス %1 でエンコードされたデータが短すぎます。連絡先のソフトウェアが何かしら誤っている可能性があります。
-
+
Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance.
エラー: 受信者のアドレス %1 でエンコードされたデータが短すぎます。連絡先のソフトウェアが何かしら誤っている可能性があります。
-
+
Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance.
エラー: 受信者のアドレス %1 でエンコードされたデータの一部が不正です。連絡先のソフトウェアが何かしら誤っている可能性があります。
-
+
Error: Something is wrong with the recipient address %1.
エラー: 受信者のアドレス %1 には何かしら誤りがあります。
-
+
+ Error: %1
+
+
+
+
+ From %1
+ 送信元 %1
+
+
+
Synchronisation pending
同期を保留しています
-
+
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?
Bitmessageはネットワークと同期していません。%n のオブジェクトをダウンロードする必要があります。 今、終了すると、配送が遅れることがあります。 同期が完了するまで待ちますか?
-
+
Not connected
未接続
-
+
Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes?
Bitmessageはネットワークに接続していません。 今、終了すると、配送が遅れることがあります。 接続して、同期が完了するまで待ちますか?
-
+
Waiting for network connection...
ネットワーク接続を待っています...
-
+
Waiting for finishing synchronisation...
同期の完了を待っています...
+
+
+ You have already set a notification sound for this address book entry. Do you really want to overwrite it?
+ すでにこのアドレス帳エントリの通知音を設定しています。 上書きしてもよろしいですか?
+
MessageView
@@ -1471,14 +1556,14 @@ Receiver's required difficulty: %1 and %2
MsgDecode
-
+
The message has an unknown encoding.
Perhaps you should upgrade Bitmessage.
メッセージのエンコードが不明です。
Bitmessageをアップグレードする必要があるかもしれません。
-
+
Unknown encoding
不明なエンコード
@@ -1786,77 +1871,77 @@ The 'Random Number' option is selected by default but deterministic ad
接続数:
-
+
Since startup:
起動日時:
-
+
Processed 0 person-to-person messages.
0 通の1対1のメッセージを処理しました。
-
+
Processed 0 public keys.
0 件の公開鍵を処理しました。
-
+
Processed 0 broadcasts.
0 件の配信を処理しました。
-
+
Inventory lookups per second: 0
毎秒のインベントリ検索: 0
-
+
Objects to be synced:
同期する必要のあるオブジェクト:
-
+
Stream #
ストリーム #
Connections
- 接続
+
-
+
Since startup on %1
起動日時 %1
-
+
Down: %1/s Total: %2
ダウン: %1/秒 合計: %2
-
+
Up: %1/s Total: %2
アップ: %1/秒 合計: %2
-
+
Total Connections: %1
接続数: %1
-
+
Inventory lookups per second: %1
毎秒のインベントリ検索: %1
-
+
Up: 0 kB/s
アップ: 0 kB/秒
-
+
Down: 0 kB/s
ダウン: 0 kB/秒
@@ -1866,30 +1951,75 @@ The 'Random Number' option is selected by default but deterministic ad
ネットワークの状態
-
+
byte(s)
バイト
-
+
Object(s) to be synced: %n
同期する必要のあるオブジェクト: %n
-
+
Processed %n person-to-person message(s).
%n 通の1対1のメッセージを処理しました。
-
+
Processed %n broadcast message(s).
%n 件の配信を処理しました。
-
+
Processed %n public key(s).
%n 件の公開鍵を処理しました。
+
+
+ Peer
+ ピア
+
+
+
+ IP address or hostname
+ IP アドレスまたはホスト名
+
+
+
+ Rating
+ 評価
+
+
+
+ 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
+ PyBitmessage は、個々のノードへの接続試行の成功率を追跡します。 率は -1 から 1 の範囲で、将来ノードを選択する可能性に影響します
+
+
+
+ User agent
+ ユーザーエージェント
+
+
+
+ Peer's self-reported software
+ ピアの自己報告ソフトウェア
+
+
+
+ TLS
+ TLS
+
+
+
+ Connection encryption
+ 接続暗号化
+
+
+
+ List of streams negotiated between you and the peer
+ あなたとピアの間でネゴシエーションしたストリームのリスト
+
newChanDialog
@@ -1945,8 +2075,8 @@ The 'Random Number' option is selected by default but deterministic ad
- Chan passhphrase/name:
- チャンネルのパスフレーズ/名前:
+ Chan passphrase/name:
+
@@ -1967,17 +2097,17 @@ The 'Random Number' option is selected by default but deterministic ad
newchandialog
-
+
Successfully created / joined chan %1
チャンネル %1 を正常に作成 / 参加しました
-
+
Chan creation / joining failed
チャンネルの作成 / 参加に失敗しました
-
+
Chan creation / joining cancelled
チャンネルの作成 / 参加をキャンセルしました
@@ -1985,21 +2115,29 @@ The 'Random Number' option is selected by default but deterministic ad
proofofwork
-
+
C PoW module built successfully.
C PoW モジュールのビルドに成功しました。
-
+
Failed to build C PoW module. Please build it manually.
C PoW モジュールのビルドに失敗しました。手動でビルドしてください。
-
+
C PoW module unavailable. Please build it.
C PoW モジュールが利用できません。ビルドしてください。
+
+ qrcodeDialog
+
+
+ QR-code
+ QR コード
+
+
regenerateAddressesDialog
diff --git a/src/translations/bitmessage_pl.qm b/src/translations/bitmessage_pl.qm
index 16f9595f..3b9a0dcc 100644
Binary files a/src/translations/bitmessage_pl.qm and b/src/translations/bitmessage_pl.qm differ
diff --git a/src/translations/bitmessage_pl.ts b/src/translations/bitmessage_pl.ts
index 0875084d..c97cb4bb 100644
--- a/src/translations/bitmessage_pl.ts
+++ b/src/translations/bitmessage_pl.ts
@@ -2,17 +2,17 @@
AddAddressDialog
-
+
Add new entry
- Dodaj adres
+ Dodaj nowy wpis
-
+
Label
- Nazwa
+ Etykieta
-
+
Address
Adres
@@ -20,70 +20,99 @@
EmailGatewayDialog
-
+
Email gateway
Przekaźnik e-mail
-
+
Register on email gateway
Zarejestruj u przekaźnika e-mail
-
+
Account status at email gateway
Status konta u przekaźnika e-mail
-
+
Change account settings at email gateway
Zmień ustawienia konta u przekaźnika e-mail
-
+
Unregister from email gateway
Wyrejestruj od przekaźnika e-mail
-
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
Przekaźnik e-mail umożliwia komunikację z użytkownikami poczty elektronicznej. Obecnie usługa ta oferowana jest tylko przez Mailchuck (@mailchuck.com).
-
+
Desired email address (including @mailchuck.com):
Wybrany adres e-mail (razem a końcówką @mailchuck.com):
+
+
+ @mailchuck.com
+ @mailchuck.com
+
+
+
+ Registration failed:
+ Rejestracja nie powiodła się:
+
+
+
+ The requested email address is not available, please try a new one.
+ Wybrany adres e-mail nie jest dostępny, proszę spróbować inny.
+
+
+
+ Sending email gateway registration request
+ Wysyłanie zapytania o rejestrację na bramce poczty
+
+
+
+ Sending email gateway unregistration request
+ Wysyłanie zapytania o wyrejestrowanie z bramki poczty
+
+
+
+ Sending email gateway status request
+ Wysyłanie zapytania o stan bramki poczty
+
EmailGatewayRegistrationDialog
-
+
Registration failed:
- Rejestracja nie powiodła się:
+
-
+
The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:
- Wybrany adres e-mail nie jest dostępny, proszę spróbować inny. Wpisz adres poniżej (razem z końcówką @mailchuck.com):
+
Email gateway registration
- Rejestracja u przekaźnika e-mail
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
Please type the desired email address (including @mailchuck.com) below:
- Przekaźnik e-mail umożliwia komunikację z użytkownikami e-maili. Obecnie usługa ta oferowana jest tylko przez bramkę Mailchuck (@mailchuck.com).
-Wprowadź wybrany adres e-mail (razem z końcówką @mailchuck.com) poniżej:
+
Mailchuck
-
+
# You can use this to configure your email gateway account
# Uncomment the setting you want to use
# Here are the options:
@@ -170,52 +199,52 @@ Wprowadź wybrany adres e-mail (razem z końcówką @mailchuck.com) poniżej:
MainWindow
-
+
Reply to sender
Odpowiedz do nadawcy
-
+
Reply to channel
Odpowiedz do kanału
-
+
Add sender to your Address Book
Dodaj nadawcę do Książki Adresowej
-
+
Add sender to your Blacklist
Dodaj nadawcę do Listy Blokowanych
-
+
Move to Trash
Przenieś do kosza
-
+
Undelete
Przywróć
-
+
View HTML code as formatted text
Wyświetl kod HTML w postaci sformatowanej
-
+
Save message as...
- Zapisz wiadomość jako...
+ Zapisz wiadomość jako…
-
+
Mark Unread
Oznacz jako nieprzeczytane
-
+
New
Nowe
@@ -232,7 +261,7 @@ Wprowadź wybrany adres e-mail (razem z końcówką @mailchuck.com) poniżej:
Set avatar...
- Ustaw awatar...
+ Ustaw awatar…
@@ -240,12 +269,12 @@ Wprowadź wybrany adres e-mail (razem z końcówką @mailchuck.com) poniżej:Kopiuj adres do schowka
-
+
Special address behavior...
- Specjalne zachowanie adresu...
+ Specjalne zachowanie adresu…
-
+
Email gateway
Przekaźnik e-mail
@@ -255,37 +284,37 @@ Wprowadź wybrany adres e-mail (razem z końcówką @mailchuck.com) poniżej:Usuń
-
+
Send message to this address
Wyślij wiadomość pod ten adres
-
+
Subscribe to this address
Subskrybuj ten adres
-
+
Add New Address
Dodaj nowy adres
-
+
Copy destination address to clipboard
Kopiuj adres odbiorcy do schowka
-
+
Force send
Wymuś wysłanie
-
+
One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now?
Jeden z adresów, %1, jest starym adresem wersji 1. Adresy tej wersji nie są już wspierane. Usunąć go?
-
+
Waiting for their encryption key. Will request it again soon.
Oczekiwanie na klucz szyfrujący odbiorcy. Niedługo nastąpi ponowne wysłanie o niego prośby.
@@ -295,17 +324,17 @@ Wprowadź wybrany adres e-mail (razem z końcówką @mailchuck.com) poniżej:
-
+
Queued.
W kolejce do wysłania.
-
+
Message sent. Waiting for acknowledgement. Sent at %1
Wiadomość wysłana. Oczekiwanie na potwierdzenie odbioru. Wysłano o %1
-
+
Message sent. Sent at %1
Wiadomość wysłana. Wysłano o %1
@@ -315,77 +344,77 @@ Wprowadź wybrany adres e-mail (razem z końcówką @mailchuck.com) poniżej:
-
+
Acknowledgement of the message received %1
Otrzymano potwierdzenie odbioru wiadomości %1
-
+
Broadcast queued.
Przekaz w kolejce do wysłania.
-
+
Broadcast on %1
Wysłana o %1
-
+
Problem: The work demanded by the recipient is more difficult than you are willing to do. %1
Problem: dowód pracy wymagany przez odbiorcę jest trudniejszy niż zaakceptowany przez Ciebie. %1
-
+
Problem: The recipient's encryption key is no good. Could not encrypt message. %1
Problem: klucz szyfrujący odbiorcy jest nieprawidłowy. Nie można zaszyfrować wiadomości. %1
-
+
Forced difficulty override. Send should start soon.
Wymuszono ominięcie trudności. Wysłanie zostanie wkrótce rozpoczęte.
-
+
Unknown status: %1 %2
Nieznany status: %1 %2
-
+
Not Connected
Brak połączenia
-
+
Show Bitmessage
Pokaż Bitmessage
-
+
Send
Wyślij
-
+
Subscribe
Subskrybuj
-
+
Channel
Kanał
-
+
Quit
Zamknij
-
+
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.
Możesz zarządzać swoimi kluczami edytując plik keys.dat znajdujący się w tym samym katalogu co program. Zaleca się zrobienie kopii zapasowej tego pliku.
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file.
@@ -394,17 +423,17 @@ It is important that you back up this file.
Zaleca się zrobienie kopii zapasowej tego pliku.
-
+
Open keys.dat?
Otworzyć plik keys.dat?
-
+
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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
Możesz zarządzać swoimi kluczami edytując plik keys.dat znajdujący się w tym samym katalogu co program. Zaleca się zrobienie kopii zapasowej tego pliku. Czy chcesz otworzyć ten plik teraz? (Zamknij Bitmessage, przed wprowadzeniem jakichkolwiek zmian.)
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
@@ -413,37 +442,37 @@ It is important that you back up this file. Would you like to open the file now?
Zaleca się zrobienie kopii zapasowej tego pliku. Czy chcesz otworzyć ten plik teraz? (Zamknij Bitmessage przed wprowadzeniem jakichkolwiek zmian.)
-
+
Delete trash?
Opróżnić kosz?
-
+
Are you sure you want to delete all trashed messages?
Czy na pewno usunąć wszystkie wiadomości z kosza?
-
+
bad passphrase
nieprawidłowe hasło
-
+
You must type your passphrase. If you don't have one then this is not the form for you.
Musisz wpisać swoje hasło. Jeżeli go nie posiadasz, to ten formularz nie jest dla Ciebie.
-
+
Bad address version number
Nieprawidłowy numer wersji adresu
-
+
Your address version number must be a number: either 3 or 4.
Twój numer wersji adresu powinien wynosić: 3 lub 4.
-
+
Your address version number must be either 3 or 4.
Twój numer wersji adresu powinien wynosić: 3 lub 4.
@@ -513,22 +542,22 @@ Zaleca się zrobienie kopii zapasowej tego pliku. Czy chcesz otworzyć ten plik
-
+
Connection lost
Połączenie utracone
-
+
Connected
Połączono
-
+
Message trashed
Wiadomość usunięta
-
+
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
@@ -539,17 +568,17 @@ Im dłuższy TTL, tym więcej pracy będzie musiał wykonac komputer wysyłając
Zwykle 4-5 dniowy TTL jest odpowiedni.
-
+
Message too long
Wiadomość zbyt długa
-
+
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.
Wiadomość jest za długa o %1 bajtów (maksymalna długość wynosi 261644 bajty). Przed wysłaniem należy ją skrócić.
-
+
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.
Błąd: Twoje konto nie było zarejestrowane w bramce poczty. Rejestrowanie jako %1, proszę poczekać na zakończenie procesu przed ponowną próbą wysłania wiadomości.
@@ -594,69 +623,69 @@ Zwykle 4-5 dniowy TTL jest odpowiedni.
-
+
Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab.
Błąd: musisz wybrać adres wysyłania. Jeżeli go nie posiadasz, przejdź do zakładki 'Twoje tożsamości'.
-
+
Address version number
Numer wersji adresu
-
+
Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.
Odnośnie adresu %1, Bitmessage nie potrafi odczytać wersji adresu %2. Może uaktualnij Bitmessage do najnowszej wersji.
-
+
Stream number
Numer strumienia
-
+
Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version.
Odnośnie adresu %1, Bitmessage nie potrafi operować na strumieniu adresu %2. Może uaktualnij Bitmessage do najnowszej wersji.
-
+
Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect.
Uwaga: nie jesteś obecnie połączony. Bitmessage wykona niezbędną pracę do wysłania wiadomości, ale nie wyśle jej póki się nie połączysz.
-
+
Message queued.
W kolejce do wysłania
-
+
Your 'To' field is empty.
Pole 'Do' jest puste
-
+
Right click one or more entries in your address book and select 'Send message to this address'.
Użyj prawego przycisku myszy na adresie z książki adresowej i wybierz opcję "Wyślij wiadomość do tego adresu".
-
+
Fetched address from namecoin identity.
Pobrano adres z identyfikatora Namecoin.
-
+
New Message
Nowa wiadomość
-
+
From
- Od
+
-
+
Sending email gateway registration request
- Wysyłanie zapytania o rejestrację na bramce poczty
+
@@ -669,142 +698,142 @@ Zwykle 4-5 dniowy TTL jest odpowiedni.
Wprowadzono niewłaściwy adres, który został zignorowany.
-
+
Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.
Błąd: Adres znajduje się już w książce adresowej.
-
+
Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.
Błąd: Adres znajduje się już na liście subskrybcji.
-
+
Restart
Uruchom ponownie
-
+
You must restart Bitmessage for the port number change to take effect.
Musisz zrestartować Bitmessage, aby zmiana numeru portu weszła w życie.
-
+
Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any).
Bitmessage będzie of teraz korzystał z serwera proxy, ale możesz ręcznie zrestartować Bitmessage, aby zamknąć obecne połączenia (jeżeli występują).
-
+
Number needed
Wymagany numer
-
+
Your maximum download and upload rate must be numbers. Ignoring what you typed.
Maksymalne prędkości wysyłania i pobierania powinny być liczbami. Zignorowano zmiany.
-
+
Will not resend ever
Nigdy nie wysyłaj ponownie
-
+
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.
Zauważ, że wpisany limit czasu wynosi mniej niż czas, który Bitmessage czeka przed pierwszą ponowną próbą wysłania wiadomości, więc Twoje wiadomości nie zostaną nigdy wysłane ponownie.
-
+
Sending email gateway unregistration request
- Wysyłanie zapytania o wyrejestrowanie z bramki poczty
+
-
+
Sending email gateway status request
- Wysyłanie zapytania o stan bramki poczty
+
-
+
Passphrase mismatch
Hasła różnią się
-
+
The passphrase you entered twice doesn't match. Try again.
Hasła, które wpisałeś nie pasują. Spróbuj ponownie.
-
+
Choose a passphrase
Wpisz hasło
-
+
You really do need a passphrase.
Naprawdę musisz wpisać hasło.
-
+
Address is gone
Adres zniknął
-
+
Bitmessage cannot find your address %1. Perhaps you removed it?
Bitmessage nie może odnaleźć Twojego adresu %1. Może go usunąłeś?
-
+
Address disabled
Adres nieaktywny
-
+
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.
Błąd: adres, z którego próbowałeś wysłać wiadomość jest nieaktywny. Włącz go w zakładce 'Twoje tożsamości' zanim go użyjesz.
-
+
Entry added to the Address Book. Edit the label to your liking.
- Dodano wpis do książki adresowej. Można teraz zmienić jego nazwę.
+
-
+
Entry added to the blacklist. Edit the label to your liking.
Dodano wpis do listy blokowanych. Można teraz zmienić jego nazwę.
-
+
Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want.
Błąd: Adres znajduje się już na liście blokowanych.
-
+
Moved items to trash.
Przeniesiono wiadomości do kosza.
-
+
Undeleted item.
- Przywróć wiadomość.
+ Przywrócono wiadomość.
-
+
Save As...
- Zapisz jako...
+ Zapisz jako…
-
+
Write error.
Błąd zapisu.
-
+
No addresses selected.
Nie wybrano adresu.
-
+
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.
Are you sure you want to delete the subscription?
@@ -813,7 +842,7 @@ Are you sure you want to delete the subscription?
Czy na pewno chcesz usunąć tę subskrypcję?
-
+
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.
Are you sure you want to delete the channel?
@@ -822,277 +851,277 @@ Are you sure you want to delete the channel?
Czy na pewno chcesz usunąć ten kanał?
-
+
Do you really want to remove this avatar?
Czy na pewno chcesz usunąć ten awatar?
-
+
You have already set an avatar for this address. Do you really want to overwrite it?
Już ustawiłeś awatar dla tego adresu. Czy na pewno chcesz go nadpisać?
-
+
Start-on-login not yet supported on your OS.
Start po zalogowaniu jeszcze nie jest wspierany pod Twoim systemem.
-
+
Minimize-to-tray not yet supported on your OS.
Minimalizacja do zasobnika nie jest jeszcze wspierana pod Twoim systemem.
-
+
Tray notifications not yet supported on your OS.
Powiadomienia w zasobniku nie są jeszcze wspierane pod Twoim systemem.
-
+
Testing...
- Testowanie...
+ Testowanie…
-
+
This is a chan address. You cannot use it as a pseudo-mailing list.
- To jest adres kanału. Nie możesz go użyć jako pseudo-listy-dyskusyjnej.
+
-
+
The address should start with ''BM-''
- Adres powinien zaczynać sie od "BM-"
+ Adres powinien zaczynać się od „BM-”
-
+
The address is not typed or copied correctly (the checksum failed).
Adres nie został skopiowany lub przepisany poprawnie (błąd sumy kontrolnej).
-
+
The version number of this address is higher than this software can support. Please upgrade Bitmessage.
Numer wersji tego adresu jest wyższy niż ten program może obsłużyć. Proszę zaktualizować Bitmessage.
-
+
The address contains invalid characters.
Adres zawiera nieprawidłowe znaki.
-
+
Some data encoded in the address is too short.
Niektóre dane zakodowane w adresie są za krótkie.
-
+
Some data encoded in the address is too long.
Niektóre dane zakodowane w adresie są za długie.
-
+
Some data encoded in the address is malformed.
Niektóre dane zakodowane w adresie są uszkodzone.
-
+
Enter an address above.
- Wprowadź adres powyżej.
+
-
+
Address is an old type. We cannot display its past broadcasts.
- Adres starego typu
+ Adres starego typu. Nie można wyświetlić jego wiadomości subskrypcji.
-
+
There are no recent broadcasts from this address to display.
- Brak niedawnych wiadomości przekazów do wyświetlenia.
+ Brak niedawnych wiadomości subskrypcji do wyświetlenia.
-
+
You are using TCP port %1. (This can be changed in the settings).
- Btimessage używa portu TCP %1. (Można go zmienić w ustawieniach).
+
-
+
Bitmessage
Bitmessage
-
+
Identities
Tożsamości
-
+
New Identity
Nowa tożsamość
-
+
Search
Szukaj
-
+
All
Wszystkie
-
+
To
Do
-
+
From
Od
-
+
Subject
Temat
-
+
Message
Wiadomość
-
+
Received
Odebrana
-
+
Messages
Wiadomości
-
+
Address book
Książka adresowa
-
+
Address
Adres
-
+
Add Contact
Dodaj kontakt
-
+
Fetch Namecoin ID
Pobierz Namecoin ID
-
+
Subject:
Temat:
-
+
From:
Od:
-
+
To:
Do:
-
+
Send ordinary Message
Wyślij zwykłą wiadomość
-
+
Send Message to your Subscribers
Wyślij wiadomość broadcast
-
+
TTL:
Czas życia:
-
+
Subscriptions
Subskrypcje
-
+
Add new Subscription
Dodaj subskrypcję
-
+
Chans
Kanały
-
+
Add Chan
Dodaj kanał
-
+
File
Plik
-
+
Settings
Ustawienia
-
+
Help
Pomoc
-
+
Import keys
Importuj klucze
-
+
Manage keys
Zarządzaj kluczami
-
+
Ctrl+Q
Ctrl+Q
-
+
F1
F1
-
+
Contact support
Kontakt z twórcami
-
+
About
O programie
-
+
Regenerate deterministic addresses
Odtwórz adres deterministyczny
-
+
Delete all trashed messages
Usuń wiadomości z kosza
-
+
Join / Create chan
Dołącz / Utwórz kanał
@@ -1117,67 +1146,67 @@ Czy na pewno chcesz usunąć ten kanał?
Dodaj nowy wpis
-
+
Display the %1 recent broadcast(s) from this address.
- Pokaż %1 ostatnich wiadomości przekazów z tego adresu.
+ Wyświetl %1 ostatnich wiadomości subskrypcji z tego adresu.
-
+
New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest
Nowa wersja Bitmessage jest dostępna: %1. Pobierz ją z https://github.com/Bitmessage/PyBitmessage/releases/latest
-
+
Waiting for PoW to finish... %1%
- Oczekiwanie na wykonanie dowodu pracy... %1%
+ Oczekiwanie na wykonanie dowodu pracy… %1%
-
+
Shutting down Pybitmessage... %1%
- Zamykanie PyBitmessage... %1%
+ Zamykanie PyBitmessage… %1%
-
+
Waiting for objects to be sent... %1%
- Oczekiwanie na wysłanie obiektów... %1%
+ Oczekiwanie na wysłanie obiektów… %1%
-
+
Saving settings... %1%
- Zapisywanie ustawień... %1%
+ Zapisywanie ustawień… %1%
-
+
Shutting down core... %1%
- Zamykanie rdzenia programu... %1%
+ Zamykanie rdzenia programu… %1%
-
+
Stopping notifications... %1%
- Zatrzymywanie powiadomień... %1%
+ Zatrzymywanie powiadomień… %1%
-
+
Shutdown imminent... %1%
- Zaraz zamknę... %1%
+ Zaraz zamknę… %1%
-
+
%n hour(s)
%n godzina %n godziny %n godzin %n godzin
-
+
%n day(s)
%n dzień %n dni %n dni %n dni
-
+
Shutting down PyBitmessage... %1%
- Zamykanie PyBitmessage... %1%
+ Zamykanie PyBitmessage… %1%
-
+
Sent
Wysłane
@@ -1189,7 +1218,7 @@ Czy na pewno chcesz usunąć ten kanał?
Done generating address. Doing work necessary to broadcast it...
- Adresy wygenerowany. Wykonywanie dowodu pracy niezbędnego na jego rozesłanie...
+ Adresy wygenerowany. Wykonywanie dowodu pracy niezbędnego na jego rozesłanie…
@@ -1222,254 +1251,348 @@ Czy na pewno chcesz usunąć ten kanał?
Uwaga: Twój dysk lub partycja jest pełny. Bitmessage zamknie się.
-
+
Error! Could not find sender address (your address) in the keys.dat file.
Błąd! Nie można odnaleźć adresu nadawcy (Twojego adresu) w pliku keys.dat.
-
+
Doing work necessary to send broadcast...
- Wykonywanie dowodu pracy niezbędnego do wysłania przekazu...
+ Wykonywanie dowodu pracy niezbędnego do wysłania przekazu…
-
+
Broadcast sent on %1
- Przekaz wysłane o %1
+ Wysłano: %1
-
+
Encryption key was requested earlier.
Prośba o klucz szyfrujący została już wysłana.
-
+
Sending a request for the recipient's encryption key.
Wysyłanie zapytania o klucz szyfrujący odbiorcy.
-
+
Looking up the receiver's public key
Wyszukiwanie klucza publicznego odbiorcy
-
+
Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1
Problem: adres docelowy jest urządzeniem przenośnym, które wymaga, aby adres docelowy był zawarty w wiadomości, ale jest to zabronione w Twoich ustawieniach. %1
-
+
Doing work necessary to send message.
There is no required difficulty for version 2 addresses like this.
Wykonywanie dowodu pracy niezbędnego do wysłania wiadomości.
Nie ma wymaganej trudności dla adresów w wersji 2, takich jak ten adres.
-
+
Doing work necessary to send message.
Receiver's required difficulty: %1 and %2
Wykonywanie dowodu pracy niezbędnego do wysłania wiadomości.
Odbiorca wymaga trudności: %1 i %2
-
+
Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3
Problem: dowód pracy wymagany przez odbiorcę (%1 i %2) jest trudniejszy niż chciałbyś wykonać. %3
-
+
Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1
Problem: próbujesz wysłać wiadomość do siebie lub na kanał, ale Twój klucz szyfrujący nie został znaleziony w pliku keys.dat. Nie można zaszyfrować wiadomości. %1
-
+
Doing work necessary to send message.
Wykonywanie pracy potrzebnej do wysłania wiadomości.
-
+
Message sent. Waiting for acknowledgement. Sent on %1
Wiadomość wysłana. Oczekiwanie na potwierdzenie odbioru. Wysłano o %1
-
+
Doing work necessary to request encryption key.
Wykonywanie pracy niezbędnej do prośby o klucz szyfrujący.
-
+
Broadcasting the public key request. This program will auto-retry if they are offline.
Rozsyłanie prośby o klucz publiczny. Program spróbuje ponownie, jeżeli jest on niepołączony.
-
+
Sending public key request. Waiting for reply. Requested at %1
Wysyłanie prośby o klucz publiczny. Oczekiwanie na odpowiedź. Zapytano o %1
-
+
UPnP port mapping established on port %1
Mapowanie portów UPnP wykonano na porcie %1
-
+
UPnP port mapping removed
Usunięto mapowanie portów UPnP
-
+
Mark all messages as read
Oznacz wszystkie jako przeczytane
-
+
Are you sure you would like to mark all messages read?
Czy na pewno chcesz oznaczyć wszystkie wiadomości jako przeczytane?
-
+
Doing work necessary to send broadcast.
Wykonywanie dowodu pracy niezbędnego do wysłania przekazu.
-
+
Proof of work pending
Dowód pracy zawieszony
-
+
%n object(s) pending proof of work
Zawieszony dowód pracy %n obiektu Zawieszony dowód pracy %n obiektów Zawieszony dowód pracy %n obiektów Zawieszony dowód pracy %n obiektów
-
+
%n object(s) waiting to be distributed
%n obiekt oczekuje na wysłanie %n obiektów oczekuje na wysłanie %n obiektów oczekuje na wysłanie %n obiektów oczekuje na wysłanie
-
+
Wait until these tasks finish?
Czy poczekać aż te zadania zostaną zakończone?
-
+
Problem communicating with proxy: %1. Please check your network settings.
Błąd podczas komunikacji z proxy: %1. Proszę sprawdź swoje ustawienia sieci.
-
+
SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings.
Problem z autoryzacją SOCKS5: %1. Proszę sprawdź swoje ustawienia SOCKS5.
-
+
The time on your computer, %1, may be wrong. Please verify your settings.
Czas na Twoim komputerze, %1, może być błędny. Proszę sprawdź swoje ustawienia.
-
+
+ The name %1 was not found.
+ Ksywka %1 nie została znaleziona.
+
+
+
+ The namecoin query failed (%1)
+ Zapytanie namecoin nie powiodło się (%1)
+
+
+
+ The namecoin query failed.
+ Zapytanie namecoin nie powiodło się.
+
+
+
+ The name %1 has no valid JSON data.
+ Ksywka %1 nie zawiera prawidłowych danych JSON.
+
+
+
+ The name %1 has no associated Bitmessage address.
+ Ksywka %1 nie ma powiązanego adresu Bitmessage.
+
+
+
+ Success! Namecoind version %1 running.
+ Namecoind wersja %1 działa poprawnie!
+
+
+
+ Success! NMControll is up and running.
+ NMControl działa poprawnie!
+
+
+
+ Couldn't understand NMControl.
+ Nie można zrozumieć NMControl.
+
+
+
Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.
Twoje procesory graficzne nie obliczyły poprawnie, wyłączam OpenCL. Prosimy zaraportować przypadek twórcom programu.
-
+
+ Set notification sound...
+ Ustaw dźwięk powiadomień…
+
+
+
+
+ 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
+
+
+Witamy w przyjaznym i bezpiecznym Bitmessage
+* wysyłaj wiadomości do innych użytkowników
+* wysyłaj wiadomości subskrypcji (jak na Twitterze)
+* dyskutuj na kanałach (chany) z innymi ludźmi
+
+
+
not recommended for chans
niezalecany dla kanałów
-
+
+ Quiet Mode
+ Tryb cichy
+
+
+
Problems connecting? Try enabling UPnP in the Network Settings
Problem z połączeniem? Spróbuj włączyć UPnP w ustawieniach sieci.
-
+
+ You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?
+ Próbujesz wysłać e-mail zamiast wiadomość bitmessage. To wymaga zarejestrowania się na bramce. Czy zarejestrować?
+
+
+
Error: Bitmessage addresses start with BM- Please check the recipient address %1
Błąd: adresy Bitmessage zaczynają się od BM-. Proszę sprawdzić adres odbiorcy %1.
-
+
Error: The recipient address %1 is not typed or copied correctly. Please check it.
Błąd: adres odbiorcy %1 nie został skopiowany lub przepisany poprawnie. Proszę go sprawdzić.
-
+
Error: The recipient address %1 contains invalid characters. Please check it.
Błąd: adres odbiorcy %1 zawiera nieprawidłowe znaki. Proszę go sprawdzić.
-
+
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.
Błąd: wersja adresu odbiorcy %1 jest za wysoka. Musisz albo zaktualizować Twoje oprogramowanie Bitmessage, albo twój znajomy Cię trolluje.
-
+
Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance.
Błąd: niektóre dane zakodowane w adresie odbiorcy %1 są zbyt krótkie. Być może coś nie działa należycie w programie Twojego znajomego.
-
+
Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance.
Błąd: niektóre dane zakodowane w adresie odbiorcy %1 są zbyt długie. Być może coś nie działa należycie w programie Twojego znajomego.
-
+
Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance.
Błąd: niektóre dane zakodowane w adresie odbiorcy %1 są uszkodzone. Być może coś nie działa należycie w programie Twojego znajomego.
-
+
Error: Something is wrong with the recipient address %1.
Błąd: coś jest nie tak z adresem odbiorcy %1.
-
+
+ Error: %1
+ Błąd: %1
+
+
+
+ From %1
+ Od %1
+
+
+
Synchronisation pending
Synchronizacja zawieszona
-
+
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?
Bitmessage nie zsynchronizował się z siecią, %n obiekt oczekuje na pobranie. Jeżeli zamkniesz go teraz, może to spowodować opóźnienia dostarczeń. Czy poczekać na zakończenie synchronizacji? Bitmessage nie zsynchronizował się z siecią, %n obiekty oczekują na pobranie. Jeżeli zamkniesz go teraz, może to spowodować opóźnienia dostarczeń. Czy poczekać na zakończenie synchronizacji? Bitmessage nie zsynchronizował się z siecią, %n obiektów oczekuje na pobranie. Jeżeli zamkniesz go teraz, może to spowodować opóźnienia dostarczeń. Czy poczekać na zakończenie synchronizacji? Bitmessage nie zsynchronizował się z siecią, %n obiektów oczekuje na pobranie. Jeżeli zamkniesz go teraz, może to spowodować opóźnienia dostarczeń. Czy poczekać na zakończenie synchronizacji?
-
+
Not connected
Niepołączony
-
+
Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes?
Bitmessage nie połączył się z siecią. Jeżeli zamkniesz go teraz, może to spowodować opóźnienia dostarczeń. Czy poczekać na połączenie i zakończenie synchronizacji?
-
+
Waiting for network connection...
- Oczekiwanie na połączenie sieciowe...
+ Oczekiwanie na połączenie sieciowe…
-
+
Waiting for finishing synchronisation...
- Oczekiwanie na zakończenie synchronizacji...
+ Oczekiwanie na zakończenie synchronizacji…
+
+
+
+ You have already set a notification sound for this address book entry. Do you really want to overwrite it?
+ Już ustawiłeś dźwięk powiadomienia dla tego kontaktu. Czy chcesz go zastąpić?
+
+
+
+ Go online
+ Połącz
+
+
+
+ Go offline
+ Rozłącz
MessageView
-
+
Follow external link
Otwórz zewnętrzne łącze
-
+
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?
Odnośnik "%1" zostanie otwarty w przeglądarce. Może to spowodować zagrożenie bezpieczeństwa, może on ujawnić Twoją anonimowość lub pobrać złośliwe dane. Czy jesteś pewien?
-
+
HTML detected, click here to display
Wykryto HTML, kliknij tu, aby go wyświetlić
-
+
Click here to disable HTML
Kliknij tutaj aby wyłączyć HTML
@@ -1477,14 +1600,14 @@ Odbiorca wymaga trudności: %1 i %2
MsgDecode
-
+
The message has an unknown encoding.
Perhaps you should upgrade Bitmessage.
Wiadomość zawiera nierozpoznane kodowanie.
Prawdopodobnie powinieneś zaktualizować Bitmessage.
-
+
Unknown encoding
Nierozpoznane kodowanie
@@ -1492,178 +1615,183 @@ Prawdopodobnie powinieneś zaktualizować Bitmessage.
NewAddressDialog
-
+
Create new Address
Generuj nowy adres
-
+
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.
The 'Random Number' option is selected by default but deterministic addresses have several pros and cons:
- Tutaj możesz utworzyć tyle adresów ile tylko potrzebujesz. W istocie, tworzenie nowych i porzucanie adresów jest zalecane. Możesz wygenerować adres używając albo losowych liczb albo hasła. Jeżeli użyjesz hasła, adres taki jest nazywany adresem 'deterministycznym'.
-Generowanie adresów 'losowych' jest wybrane domyślnie, jednak deterministyczne adresy mają swoje wady i zalety:
+ Tutaj możesz utworzyć tyle adresów, ile tylko potrzebujesz. W istocie, tworzenie nowych i porzucanie adresów jest zalecane. Możesz wygenerować adres używając albo losowych liczb albo hasła. Jeżeli użyjesz hasła, adres taki jest nazywany adresem „deterministycznym”.
+Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministyczne adresy mają swoje wady i zalety:
-
+
<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>
- <html><head/><body><p><span style=" font-weight:600;">Zalety:<br/></span>Możesz wygenerować swój adres na każdym komputerze 'z głowy'.<br/>Nie musisz się martwić o tworzenie kopii zapasowej pliku keys.dat tak długo jak pamiętasz hasło.</br><span style=" font-weight:600;">Wady:<br/></span>Musisz zapamiętać (będź zapisać) swoje hasło, jeżeli chcesz odzyskać swój adres po utracie kluczy.</br>Musisz zapamiętać numer wersji adresu i numer strumienia razem z hasłem.</br>Jeżeli użyjesz słabego hasła, ktoś z Internetu może je odgadnąć i przeczytać wszystkie Twoje wiadomości i wysyłać wiadomości jako Ty.</p></body></html>
+ <html><head/><body><p><span style=" font-weight:600;">Zalety:<br/></span>Możesz wygenerować swój adres na każdym komputerze z głowy.<br/>Nie musisz się martwić o tworzenie kopii zapasowej pliku keys.dat tak długo jak pamiętasz hasło.</br><span style=" font-weight:600;">Wady:<br/></span>Musisz zapamiętać (bądź zapisać) swoje hasło, jeżeli chcesz odzyskać swój adres po utracie kluczy.</br>Musisz zapamiętać numer wersji adresu i numer strumienia razem z hasłem.</br>Jeżeli użyjesz słabego hasła, ktoś z Internetu może je odgadnąć i przeczytać wszystkie Twoje wiadomości i wysyłać wiadomości jako Ty.</p></body></html>
-
+
Use a random number generator to make an address
Użyj generatora liczb losowych do utworzenia adresu
-
+
Use a passphrase to make addresses
Użyj hasła do utworzenia adresu
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
Dołóż kilka minut dodatkowych obliczeń aby wygenerować adres(y) krótsze o 1 lub 2 znaki
-
+
Make deterministic addresses
Utwórz adres deterministyczny
-
+
Address version number: 4
Numer wersji adresu: 4
-
+
In addition to your passphrase, you must remember these numbers:
Razem ze swoim hasłem musisz zapamiętać te liczby:
-
+
Passphrase
Hasło
-
+
Number of addresses to make based on your passphrase:
Liczba adresów do wygenerowanie na podstawie hasła:
-
+
Stream number: 1
Numer strumienia: 1
-
+
Retype passphrase
Hasło ponownie
-
+
Randomly generate address
Adres losowy
-
+
Label (not shown to anyone except you)
Etykieta (nie wyświetlana komukolwiek oprócz Ciebie)
-
+
Use the most available stream
Użyj najbardziej dostępnego strumienia
-
+
(best if this is the first of many addresses you will create)
(zalecane, jeżeli jest to pierwszy z adresów który chcesz utworzyć)
-
+
Use the same stream as an existing address
Użyj tego samego strumienia co istniejący adres
-
+
(saves you some bandwidth and processing power)
- (oszczędza trochę transferu i procesora)
+ (oszczędza trochę transferu i mocy procesora)
NewSubscriptionDialog
-
+
Add new entry
- Dodaj subskrypcję
+ Dodaj nowy wpis
-
+
Label
Etykieta
-
+
Address
Adres
-
+
Enter an address above.
- Wpisz adres powyżej.
+ Wprowadź adres powyżej.
SpecialAddressBehaviorDialog
-
+
Special Address Behavior
Specjalne zachowanie adresu
-
+
Behave as a normal address
- Zachowuj się jako normalny adres
+ Zachowuj się jak normalny adres
-
+
Behave as a pseudo-mailing-list address
- Zachowuj się jako pseudo-lista-dyskusyjna
+ Zachowuj się jak pseudo-lista-dyskusyjna
-
+
Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public).
Wiadomości wysłane na pseudo-listę-dyskusyjną zostaną automatycznie rozesłane do abonentów (i w ten sposób będą publiczne).
-
+
Name of the pseudo-mailing-list:
Nazwa pseudo-listy-dyskusyjnej:
+
+
+ This is a chan address. You cannot use it as a pseudo-mailing list.
+ To jest adres kanału. Nie możesz go użyć jako pseudo-listy-dyskusyjnej.
+
aboutDialog
-
+
About
O programie
-
+
PyBitmessage
- PyBitmessage
+
-
+
version ?
- wersja ?
+
-
+
<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>
<html><head/><body><p>Rozpowszechniane na licencji MIT/X11 software license; zobacz <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>
-
+
This is Beta software.
To jest wersja Beta.
@@ -1672,10 +1800,10 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
<html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
-
-
- <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
- <html><head/><body><p>Prawa autorskie © 2012-2016 Jonathan Warren<br/>Prawa autorskie © 2013-2016 Programiści Bitmessage</p></body></html>
+
+
+ <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 The Bitmessage Developers</p></body></html>
+ <html><head/><body><p>Prawa autorskie © 2012-2016 Jonathan Warren<br/>Prawa autorskie © 2013-2017 Programiści Bitmessage</p></body></html>
@@ -1683,7 +1811,7 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
Use a Blacklist (Allow all incoming messages except those on the Blacklist)
- Użyj czarnej listy (zezwala na wszystkie przychodzące z wyjątkiem od tych na czarnej liście)
+ Użyj czarnej listy (zezwala na wszystkie przychodzące wiadomości, z wyjątkiem tych na czarnej liście)
@@ -1719,40 +1847,45 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
connectDialog
-
+
Bitmessage
Bitmessage
-
+
Bitmessage won't connect to anyone until you let it.
Bitmessage nie połączy się, zanim mu na to nie pozwolisz.
-
+
Connect now
Połącz teraz
-
+
Let me configure special network settings first
Pozwól mi najpierw ustawić specjalne opcje konfiguracji sieci
+
+
+ Work offline
+ Działaj bez sieci
+
helpDialog
-
+
Help
Pomoc
-
+
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
-
+
As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki:
Ponieważ Bitmessage jest tworzone przez społeczność, możesz uzyskać pomoc w sieci na wiki Bitmessage:
@@ -1760,30 +1893,35 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
iconGlossaryDialog
-
+
Icon Glossary
Opis ikon
-
+
You have no connections with other peers.
Nie masz żadnych połączeń z innymi użytkownikami.
-
+
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.
Masz co najmniej jedno połączenie wychodzące z innymi użytkownikami, ale nie masz jeszcze żadnych połączeń przychodzących. Twoja zapora sieciowa lub domowy ruter prawdopodobnie nie są poprawnie skonfigurowane aby przekazywać połączenia przychodzące TCP na Twój komputer. Bitmessage będzie działał dobrze, ale byłoby fajnie, gdybyś pomógł sieci Bitmessage i zezwolił na połączenia przychodzące.
You are using TCP port ?. (This can be changed in the settings).
- Używasz portu TCP ?. (Możesz go zmienić w ustawieniach.)
+
-
+
You do have connections with other peers and your firewall is correctly configured.
Masz połączenia z innymi użytkownikami i twoja zapora sieciowa jest skonfigurowana poprawnie.
+
+
+ You are using TCP port %1. (This can be changed in the settings).
+ Btimessage używa portu TCP %1. (Można go zmienić w ustawieniach).
+
networkstatus
@@ -1793,110 +1931,155 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
Wszystkich połączeń:
-
+
Since startup:
Od startu:
-
+
Processed 0 person-to-person messages.
Przetworzono 0 wiadomości zwykłych.
-
+
Processed 0 public keys.
Przetworzono 0 kluczy publicznych.
-
+
Processed 0 broadcasts.
- Przetworzono 0 wiadomości przekazów.
+ Przetworzono 0 wiadomości subskrypcji.
-
+
Inventory lookups per second: 0
Zapytań o elementy na sekundę: 0
-
+
Objects to be synced:
Obiektów do zsynchronizowania:
-
+
Stream #
Strumień #
Connections
- Połączeń
+
-
+
Since startup on %1
Od startu programu o %1
-
+
Down: %1/s Total: %2
Pobieranie: %1/s W całości: %2
-
+
Up: %1/s Total: %2
Wysyłanie: %1/s W całości: %2
-
+
Total Connections: %1
Wszystkich połączeń: %1
-
+
Inventory lookups per second: %1
Zapytań o elementy na sekundę: %1
-
+
Up: 0 kB/s
Wysyłanie: 0 kB/s
-
+
Down: 0 kB/s
Pobieranie: 0 kB/s
-
+
Network Status
Stan sieci
-
+
byte(s)
bajt bajtów bajtów bajtów
-
+
Object(s) to be synced: %n
Jeden obiekt do zsynchronizowania Obieków do zsynchronizowania: %n Obieków do zsynchronizowania: %n Obieków do zsynchronizowania: %n
-
+
Processed %n person-to-person message(s).
Przetworzono %n wiadomość zwykłą. Przetworzono %n wiadomości zwykłych. Przetworzono %n wiadomości zwykłych. Przetworzono %n wiadomości zwykłych.
-
+
Processed %n broadcast message(s).
- Przetworzono %n wiadomość przekazów. Przetworzono %n wiadomości przekazów. Przetworzono %n wiadomości przekazów. Przetworzono %n wiadomości przekazów.
+ Przetworzono %n wiadomość subskrypcji. Przetworzono %n wiadomości subskrypcji. Przetworzono %n wiadomości subskrypcji. Przetworzono %n wiadomości subskrypcji.
-
+
Processed %n public key(s).
Przetworzono %n klucz publiczny. Przetworzono %n kluczy publicznych. Przetworzono %n kluczy publicznych. Przetworzono %n kluczy publicznych.
+
+
+ Peer
+ Użytkownik
+
+
+
+ IP address or hostname
+ IP lub nazwa hosta
+
+
+
+ Rating
+ Ocena
+
+
+
+ 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
+ PyBitmessage rejestruje pomyślność prób połączeń z indywidualnymi węzłami. Ocena przyjmuje wartości od -1 do 1 i ma wpływ na prawdopodobieństwo wybrania węzła w przyszłości.
+
+
+
+ User agent
+ Klient
+
+
+
+ Peer's self-reported software
+ Ogłaszana aplikacja kliencka
+
+
+
+ TLS
+ TLS
+
+
+
+ Connection encryption
+ Szyfrowanie połączenia
+
+
+
+ List of streams negotiated between you and the peer
+ Lista strumieni negocjowanych pomiędzy Tobą i użytkownikiem
+
newChanDialog
@@ -1952,8 +2135,8 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
- Chan passhphrase/name:
- Wpisz hasło/nazwę:
+ Chan passphrase/name:
+ Nazwa/hasło kanału:
@@ -1974,7 +2157,7 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
newchandialog
-
+
Successfully created / joined chan %1
Pomyślnie utworzono / dołączono do kanału %1
@@ -1992,70 +2175,78 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
proofofwork
-
+
C PoW module built successfully.
Moduł C PoW zbudowany poprawnie.
-
+
Failed to build C PoW module. Please build it manually.
Nie można zbudować modułu C PoW. Prosimy zbudować go ręcznie.
-
+
C PoW module unavailable. Please build it.
Moduł C PoW niedostępny. Prosimy zbudować go.
+
+ qrcodeDialog
+
+
+ QR-code
+ Kod QR
+
+
regenerateAddressesDialog
-
+
Regenerate Existing Addresses
Odtwórz istniejące adresy
-
+
Regenerate existing addresses
Odtwórz istniejące adresy
-
+
Passphrase
Hasło
-
+
Number of addresses to make based on your passphrase:
Liczba adresów do wygenerowanie na podstawie hasła:
-
+
Address version number:
Numer wersji adresu:
-
+
Stream number:
Numer strumienia:
-
+
1
1
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
Dołóż kilka minut dodatkowych obliczeń aby wygenerować adres(y) krótsze o 1 lub 2 znaki
-
+
You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time.
Musisz to zaznaczyć (albo nie zaznaczyć) jeżeli zaznaczyłeś (bądź nie zaznaczyłeś) to podczas tworzenia adresu po raz pierwszy.
-
+
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.
Jeżeli poprzednio wygenerowałeś deterministyczne adresy, ale je straciłeś przez przypadek (jak np. awaria dysku), możesz je tutaj odtworzyć. Jeżeli użyłeś generatora liczb losowych do utworzenia adresu, wtedy ten formularz jest dla Ciebie bezużyteczny.
@@ -2115,7 +2306,7 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
Use Identicons
- Użyj 'Identiconów'
+ Użyj graficznych awatarów
@@ -2331,7 +2522,7 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
Give up after
- Nie wysyłaj ponownie po
+ Poddaj się po
@@ -2361,7 +2552,7 @@ Generowanie adresów 'losowych' jest wybrane domyślnie, jednak determ
Maximum outbound connections: [0: none]
- Maksymalnych połączeń wychodzących: [0: none]
+ Maksymalnych połączeń wychodzących: [0: brak]
diff --git a/src/translations/bitmessage_ru.qm b/src/translations/bitmessage_ru.qm
index d14f75ae..53d58f01 100644
Binary files a/src/translations/bitmessage_ru.qm and b/src/translations/bitmessage_ru.qm differ
diff --git a/src/translations/bitmessage_ru.ts b/src/translations/bitmessage_ru.ts
index 15b2506a..ffb2eac9 100644
--- a/src/translations/bitmessage_ru.ts
+++ b/src/translations/bitmessage_ru.ts
@@ -2,17 +2,17 @@
AddAddressDialog
-
+
Add new entry
Добавить новую запись
-
+
Label
Имя
-
+
Address
Адрес
@@ -20,70 +20,99 @@
EmailGatewayDialog
-
+
Email gateway
Email-шлюз
-
+
Register on email gateway
Зарегистрироваться на Email-шлюзе
-
+
Account status at email gateway
Статус аккаунта Email-шлюза
-
+
Change account settings at email gateway
Изменить настройки аккаунта Email-шлюза
-
+
Unregister from email gateway
Отменить регистрацию на Email-шлюзе
-
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
- Email-шлюз позволяет вам обмениваться сообщениями с пользователями обычной электронной почты. В настоящий момент доступен только 1 шлюз Mailchuck (@mailchuck.com).
+ Email-шлюз позволяет вам обмениваться сообщениями с пользователями обычной электронной почты. В настоящий момент доступен только шлюз Mailchuck (@mailchuck.com).
-
+
Desired email address (including @mailchuck.com):
- Введите желаемый email-адрес (включая @mailchuck.com)
+ Желаемый email-адрес (включая @mailchuck.com)
+
+
+
+ @mailchuck.com
+ @mailchuck.com
+
+
+
+ Registration failed:
+ Регистрация не удалась:
+
+
+
+ The requested email address is not available, please try a new one.
+ Запрашиваемый адрес email недоступен, попробуйте ввести другой.
+
+
+
+ Sending email gateway registration request
+ Отправка запроса на регистрацию на Email-шлюзе
+
+
+
+ Sending email gateway unregistration request
+ Отправка запроса на отмену регистрации на Email-шлюзе
+
+
+
+ Sending email gateway status request
+ Отправка запроса статуса аккаунта на Email-шлюзе
EmailGatewayRegistrationDialog
-
+
Registration failed:
- Регистрация не удалась:
+
-
+
The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:
- Запрашиваемый адрес email недоступен, попробуйте ввести другой. Введите желаемый адрес (включая @mailchuck.com) ниже:
+
Email gateway registration
- Регистрация на Email-шлюзе
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
Please type the desired email address (including @mailchuck.com) below:
- Email-шлюз позволяет вам обмениваться сообщениями с пользователями обычной электронной почты. В настоящий момент доступен только 1 шлюз Mailchuck (@mailchuck.com).
-Пожалуйста, введите желаемый адрес email (включая @mailchuck.com) ниже:
+
Mailchuck
-
+
# You can use this to configure your email gateway account
# Uncomment the setting you want to use
# Here are the options:
@@ -168,52 +197,52 @@ Please type the desired email address (including @mailchuck.com) below:
MainWindow
-
+
Reply to sender
Ответить отправителю
-
+
Reply to channel
Ответить в канал
-
+
Add sender to your Address Book
Добавить отправителя в адресную книгу
-
+
Add sender to your Blacklist
Добавить отправителя в чёрный список
-
+
Move to Trash
Поместить в корзину
-
+
Undelete
Отменить удаление
-
+
View HTML code as formatted text
Просмотреть HTML код как отформатированный текст
-
+
Save message as...
Сохранить сообщение как ...
-
+
Mark Unread
Отметить как непрочитанное
-
+
New
Новый адрес
@@ -238,12 +267,12 @@ Please type the desired email address (including @mailchuck.com) below:
Скопировать адрес в буфер обмена
-
+
Special address behavior...
Особое поведение адресов...
-
+
Email gateway
Email-шлюз
@@ -253,37 +282,37 @@ Please type the desired email address (including @mailchuck.com) below:
Удалить
-
+
Send message to this address
Отправить сообщение на этот адрес
-
+
Subscribe to this address
Подписаться на рассылку с этого адреса
-
+
Add New Address
Добавить новый адрес
-
+
Copy destination address to clipboard
Скопировать адрес отправки в буфер обмена
-
+
Force send
Форсировать отправку
-
+
One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now?
Один из Ваших адресов, %1, является устаревшим адресом версии 1. Адреса версии 1 больше не поддерживаются. Хотите ли Вы удалить его сейчас?
-
+
Waiting for their encryption key. Will request it again soon.
Ожидаем ключ шифрования от Вашего собеседника. Запрос будет повторен через некоторое время.
@@ -293,17 +322,17 @@ Please type the desired email address (including @mailchuck.com) below:
-
+
Queued.
В очереди.
-
+
Message sent. Waiting for acknowledgement. Sent at %1
Сообщение отправлено. Ожидаем подтверждения. Отправлено в %1
-
+
Message sent. Sent at %1
Сообщение отправлено в %1
@@ -313,139 +342,139 @@ Please type the desired email address (including @mailchuck.com) below:
-
+
Acknowledgement of the message received %1
- Сообщение доставлено в %1
+ Доставлено в %1
-
+
Broadcast queued.
Рассылка ожидает очереди.
-
+
Broadcast on %1
Рассылка на %1
-
+
Problem: The work demanded by the recipient is more difficult than you are willing to do. %1
Проблема: Ваш получатель требует более сложных вычислений, чем максимум, указанный в Ваших настройках. %1
-
+
Problem: The recipient's encryption key is no good. Could not encrypt message. %1
Проблема: ключ получателя неправильный. Невозможно зашифровать сообщение. %1
-
+
Forced difficulty override. Send should start soon.
Форсирована смена сложности. Отправляем через некоторое время.
-
+
Unknown status: %1 %2
Неизвестный статус: %1 %2
-
+
Not Connected
Не соединено
-
+
Show Bitmessage
Показать Bitmessage
-
+
Send
Отправить
-
+
Subscribe
Подписки
-
+
Channel
Канал
-
+
Quit
Выйти
-
+
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.
- Вы можете управлять Вашими ключами, отредактировав файл keys.dat, находящийся в той же папке, что и эта программа.
+ Вы можете управлять Вашими ключами, редактируя файл keys.dat, находящийся в той же папке, что и эта программа.
Создайте резервную копию этого файла перед тем как будете его редактировать.
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file.
- Вы можете управлять Вашими ключами, отредактировав файл keys.dat, находящийся в
+ Вы можете управлять Вашими ключами, редактируя файл keys.dat, находящийся в
%1
Создайте резервную копию этого файла перед тем как будете его редактировать.
-
+
Open keys.dat?
Открыть файл keys.dat?
-
+
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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
- Вы можете управлять Вашими ключами, отредактировав файл keys.dat, находящийся в той же папке, что и эта программа.
+ Вы можете управлять Вашими ключами, редактируя файл keys.dat, находящийся в той же папке, что и эта программа.
Создайте резервную копию этого файла перед тем как будете его редактировать. Хотели бы Вы открыть этот файл сейчас?
(пожалуйста, закройте Bitmessage до того как Вы внесёте в этот файл какие-либо изменения.)
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
- Вы можете управлять Вашими ключами, отредактировав файл keys.dat, находящийся в
+ Вы можете управлять Вашими ключами, редактируя файл keys.dat, находящийся в
%1
Создайте резервную копию этого файла перед тем как будете его редактировать. Хотели бы Вы открыть этот файл сейчас?
(пожалуйста, закройте Bitmessage до того как Вы внесёте в этот файл какие-либо изменения.)
-
+
Delete trash?
Очистить корзину?
-
+
Are you sure you want to delete all trashed messages?
Вы уверены что хотите очистить корзину?
-
+
bad passphrase
Неподходящая секретная фраза
-
+
You must type your passphrase. If you don't have one then this is not the form for you.
Вы должны ввести секретную фразу. Если Вы не хотите этого делать, то Вы выбрали неправильную опцию.
-
+
Bad address version number
- Неверный адрес номера версии
+ Неверный номер версии адреса
-
+
Your address version number must be a number: either 3 or 4.
Адрес номера версии должен быть числом: либо 3, либо 4.
-
+
Your address version number must be either 3 or 4.
Адрес номера версии должен быть либо 3, либо 4.
@@ -515,22 +544,22 @@ It is important that you back up this file. Would you like to open the file now?
-
+
Connection lost
Соединение потеряно
-
+
Connected
Соединено
-
+
Message trashed
Сообщение удалено
-
+
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
@@ -541,17 +570,17 @@ It is important that you back up this file. Would you like to open the file now?
сообщение. Часто разумным вариантом будет установка TTL на 4 или 5 дней.
-
+
Message too long
Сообщение слишком длинное
-
+
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.
- Сообщение, которое вы пытаетесь отправить, длиннее максимально допустимого на %1 байт. (Максимально допустимое значение 261644 байт). Пожалуйста, сократите сообщение перед отправкой.
+ Сообщение, которое вы пытаетесь отправить, длиннее максимально допустимого на %1 байт. (Максимально допустимое значение 261644 байта). Пожалуйста, сократите сообщение перед отправкой.
-
+
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.
Ошибка: ваш аккаунт не зарегистрирован на Email-шлюзе. Отправка регистрации %1, пожалуйста, подождите пока процесс регистрации не завершится, прежде чем попытаться отправить сообщение заново.
@@ -596,69 +625,69 @@ It is important that you back up this file. Would you like to open the file now?
-
+
Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab.
- Вы должны указать адрес в поле "От кого". Вы можете найти Ваш адрес во вкладе "Ваши Адреса".
+ Вы должны указать адрес в поле "От кого". Вы можете найти Ваш адрес во вкладке "Ваши Адреса".
-
+
Address version number
Версия адреса
-
+
Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.
По поводу адреса %1: Bitmessage не поддерживаем адреса версии %2. Возможно, Вам нужно обновить клиент Bitmessage.
-
+
Stream number
Номер потока
-
+
Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version.
По поводу адреса %1: Bitmessage не поддерживаем стрим номер %2. Возможно, Вам нужно обновить клиент Bitmessage.
-
+
Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect.
Внимание: Вы не подключены к сети. Bitmessage выполнит работу, требуемую для отправки сообщения, но не отправит его до тех пор, пока Вы не подключитесь.
-
+
Message queued.
Сообщение в очереди.
-
+
Your 'To' field is empty.
Вы не заполнили поле 'Кому'.
-
+
Right click one or more entries in your address book and select 'Send message to this address'.
Нажмите правую кнопку мыши на каком-либо адресе и выберите "Отправить сообщение на этот адрес".
-
+
Fetched address from namecoin identity.
Получить адрес через Namecoin.
-
+
New Message
Новое сообщение
-
+
From
- От
+
-
+
Sending email gateway registration request
- Отправка запроса на регистрацию на Email-шлюзе
+
@@ -671,142 +700,142 @@ It is important that you back up this file. Would you like to open the file now?
Вы ввели неправильный адрес. Это адрес проигнорирован.
-
+
Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.
Ошибка: Вы не можете добавлять один и тот же адрес в Адресную Книгу несколько раз. Попробуйте переименовать существующий адрес.
-
+
Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.
Ошибка: вы не можете добавить один и тот же адрес в ваши подписки дважды. Пожалуйста, переименуйте имеющийся адрес, если хотите.
-
+
Restart
Перезапустить
-
+
You must restart Bitmessage for the port number change to take effect.
Вы должны перезапустить Bitmessage, чтобы смена номера порта имела эффект.
-
+
Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any).
Bitmessage будет использовать Ваш прокси, начиная прямо сейчас. Тем не менее Вам имеет смысл перезапустить Bitmessage, чтобы закрыть уже существующие соединения.
-
+
Number needed
Требуется число
-
+
Your maximum download and upload rate must be numbers. Ignoring what you typed.
Скорости загрузки и выгрузки должны быть числами. Игнорирую то, что вы набрали.
-
+
Will not resend ever
Не пересылать никогда
-
+
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.
Обратите внимание, что лимит времени, который вы ввели, меньше чем время, которое Bitmessage ждет перед первой попыткой переотправки сообщения, поэтому ваши сообщения никогда не будут переотправлены.
-
+
Sending email gateway unregistration request
- Отправка запроса на отмену регистрации на Email-шлюзе
+
-
+
Sending email gateway status request
- Отправка запроса статуса аккаунта на Email-шлюзе
+
-
+
Passphrase mismatch
Секретная фраза не подходит
-
+
The passphrase you entered twice doesn't match. Try again.
Вы ввели две разные секретные фразы. Пожалуйста, повторите заново.
-
+
Choose a passphrase
Придумайте секретную фразу
-
+
You really do need a passphrase.
Вы действительно должны ввести секретную фразу.
-
+
Address is gone
Адрес утерян
-
+
Bitmessage cannot find your address %1. Perhaps you removed it?
Bitmessage не может найти Ваш адрес %1. Возможно Вы удалили его?
-
+
Address disabled
Адрес выключен
-
+
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.
Ошибка: адрес, с которого Вы пытаетесь отправить, выключен. Вам нужно будет включить этот адрес во вкладке "Ваши адреса".
-
+
Entry added to the Address Book. Edit the label to your liking.
- Запись добавлена в Адресную Книгу. Вы можете её отредактировать.
+
-
+
Entry added to the blacklist. Edit the label to your liking.
Запись добавлена в чёрный список. Измените название по своему вкусу.
-
+
Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want.
Ошибка: вы не можете добавить один и тот же адрес в чёрный список дважды. Попробуйте переименовать существующий адрес.
-
+
Moved items to trash.
Удалено в корзину.
-
+
Undeleted item.
- Отменить удаление записи
+ Элемент восстановлен.
-
+
Save As...
Сохранить как ...
-
+
Write error.
Ошибка записи.
-
+
No addresses selected.
Вы не выбрали адрес.
-
+
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.
Are you sure you want to delete the subscription?
@@ -815,7 +844,7 @@ Are you sure you want to delete the subscription?
Вы уверены, что хотите отменить подписку?
-
+
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.
Are you sure you want to delete the channel?
@@ -824,282 +853,282 @@ Are you sure you want to delete the channel?
Вы уверены, что хотите удалить канал?
-
+
Do you really want to remove this avatar?
Вы уверены, что хотите удалить этот аватар?
-
+
You have already set an avatar for this address. Do you really want to overwrite it?
У вас уже есть аватар для этого адреса. Вы уверены, что хотите перезаписать аватар?
-
+
Start-on-login not yet supported on your OS.
Запуск программы при входе в систему ещё не поддерживается в вашей операционной системе.
-
+
Minimize-to-tray not yet supported on your OS.
Сворачивание в трей ещё не поддерживается в вашей операционной системе.
-
+
Tray notifications not yet supported on your OS.
Уведомления в трее ещё не поддерживаеются в вашей операционной системе.
-
+
Testing...
Проверяем...
-
+
This is a chan address. You cannot use it as a pseudo-mailing list.
- Это адрес chan-а. Вы не можете его использовать как адрес рассылки.
+
-
+
The address should start with ''BM-''
Адрес должен начинаться с "BM-"
-
+
The address is not typed or copied correctly (the checksum failed).
Адрес введен или скопирован неверно (контрольная сумма не сходится).
-
+
The version number of this address is higher than this software can support. Please upgrade Bitmessage.
- Версия этого адреса более поздняя, чем Ваша программа. Пожалуйста, обновите программу Bitmessage.
+ Версия этого адреса более поздняя, чем те, что поддерживает программа. Пожалуйста, обновите Bitmessage.
-
+
The address contains invalid characters.
Адрес содержит запрещённые символы.
-
+
Some data encoded in the address is too short.
Данные, закодированные в адресе, слишком короткие.
-
+
Some data encoded in the address is too long.
Данные, закодированные в адресе, слишком длинные.
-
+
Some data encoded in the address is malformed.
Данные, закодированные в адресе, имеют неверный формат.
-
+
Enter an address above.
- Введите адрес выше.
+
-
+
Address is an old type. We cannot display its past broadcasts.
Адрес старого типа. Мы не можем отобразить его прошлые рассылки.
-
+
There are no recent broadcasts from this address to display.
Нет недавних рассылок с этого адреса для отображения.
-
+
You are using TCP port %1. (This can be changed in the settings).
- Вы используете TCP порт %1. (Его можно поменять в настройках).
+
-
+
Bitmessage
Bitmessage
-
+
Identities
Адреса
-
+
New Identity
Создать новый адрес
-
+
Search
Поиск
-
+
All
По всем полям
-
+
To
Кому
-
+
From
От кого
-
+
Subject
Тема
-
+
Message
Текст сообщения
-
+
Received
Получено
-
+
Messages
Сообщения
-
+
Address book
Адресная книга
-
+
Address
Адрес
-
+
Add Contact
Добавить контакт
-
+
Fetch Namecoin ID
Получить Namecoin ID
-
+
Subject:
Тема:
-
+
From:
От:
-
+
To:
Кому:
-
+
Send ordinary Message
- Отправить обыкновенное сообщение
+ Отправить обычное сообщение
-
+
Send Message to your Subscribers
Отправить сообщение для ваших подписчиков
-
+
TTL:
TTL:
-
+
Subscriptions
Подписки
-
+
Add new Subscription
Добавить новую подписку
-
+
Chans
Чаны
-
+
Add Chan
Добавить чан
-
+
File
Файл
-
+
Settings
Настройки
-
+
Help
Помощь
-
+
Import keys
Импортировать ключи
-
+
Manage keys
Управлять ключами
-
+
Ctrl+Q
Ctrl+Q
-
+
F1
F1
-
+
Contact support
Связаться с поддержкой
-
+
About
О программе
-
+
Regenerate deterministic addresses
Сгенерировать заново все адреса
-
+
Delete all trashed messages
Стереть все сообщения из корзины
-
+
Join / Create chan
Подключить или создать чан
-
+
All accounts
Все аккаунты
@@ -1119,69 +1148,69 @@ Are you sure you want to delete the channel?
Добавить новую запись
-
+
Display the %1 recent broadcast(s) from this address.
- Показать %1 прошлых рассылок с этого адреса.
+
-
+
New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest
Доступна новая версия PyBitmessage: %1. Загрузите её: https://github.com/Bitmessage/PyBitmessage/releases/latest
-
+
Waiting for PoW to finish... %1%
Ожидание окончания PoW... %1%
-
+
Shutting down Pybitmessage... %1%
Завершение PyBitmessage... %1%
-
+
Waiting for objects to be sent... %1%
Ожидание отправки объектов... %1%
-
+
Saving settings... %1%
Сохранение настроек... %1%
-
+
Shutting down core... %1%
Завершение работы ядра... %1%
-
+
Stopping notifications... %1%
Остановка сервиса уведомлений... %1%
-
+
Shutdown imminent... %1%
Завершение вот-вот произойдет... %1%
-
+
%n hour(s)
%n час %n часа %n часов %n час(а/ов)
-
+
%n day(s)
%n день %n дня %n дней %n дней
-
+
Shutting down PyBitmessage... %1%
Завершение PyBitmessage... %1%
-
+
Sent
- Отправленные
+ Отправлено
@@ -1214,259 +1243,393 @@ Are you sure you want to delete the channel?
-
+
Disk full
Диск переполнен
-
+
Alert: Your disk or data storage volume is full. Bitmessage will now exit.
Внимание: свободное место на диске закончилось. Bitmessage завершит свою работу.
-
+
Error! Could not find sender address (your address) in the keys.dat file.
Ошибка: невозможно найти адрес отправителя (ваш адрес) в файле ключей keys.dat
-
+
Doing work necessary to send broadcast...
Выполнение работы, требуемой для рассылки...
-
+
Broadcast sent on %1
Рассылка отправлена на %1
-
+
Encryption key was requested earlier.
Ключ шифрования запрошен ранее.
-
+
Sending a request for the recipient's encryption key.
Отправка запроса ключа шифрования получателя.
-
+
Looking up the receiver's public key
Поиск открытого ключа получателя
-
+
Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1
Проблема: адресат является мобильным устройством, которое требует, чтобы адрес назначения был включен в сообщение, однако, это запрещено в ваших настройках. %1
-
+
Doing work necessary to send message.
There is no required difficulty for version 2 addresses like this.
Выполнение работы, требуемой для отправки сообщения.
Для адреса версии 2 (как этот), не требуется указание сложности.
-
+
Doing work necessary to send message.
Receiver's required difficulty: %1 and %2
Выполнение работы, требуемой для отправки сообщения.
Получатель запросил сложность: %1 и %2
-
+
Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3
Проблема: сложность, затребованная получателем (%1 и %2) гораздо больше, чем вы готовы сделать. %3
-
+
Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1
Проблема: вы пытаетесь отправить сообщение самому себе или в чан, но ваш ключ шифрования не найден в файле ключей keys.dat. Невозможно зашифровать сообщение. %1
-
+
Doing work necessary to send message.
Выполнение работы, требуемой для отправки сообщения.
-
+
Message sent. Waiting for acknowledgement. Sent on %1
- Сообщение отправлено. Ожидаем подтверждения. Отправлено на %1
+ Отправлено. Ожидаем подтверждения. Отправлено в %1
-
+
Doing work necessary to request encryption key.
Выполнение работы, требуемой для запроса ключа шифрования.
-
+
Broadcasting the public key request. This program will auto-retry if they are offline.
Рассылка запросов открытого ключа шифрования. Программа будет повторять попытки, если они оффлайн.
-
+
Sending public key request. Waiting for reply. Requested at %1
Отправка запроса открытого ключа шифрования. Ожидание ответа. Запрошено в %1
-
+
UPnP port mapping established on port %1
Распределение портов UPnP завершилось выделением порта %1
-
+
UPnP port mapping removed
Распределение портов UPnP отменено
-
+
Mark all messages as read
- Отметить все сообщения прочтенные
+ Отметить все сообщения как прочтенные
-
+
Are you sure you would like to mark all messages read?
Вы уверены, что хотите отметить все сообщения как прочтенные?
-
+
Doing work necessary to send broadcast.
Выполнение работы, требуемой для отправки рассылки.
-
+
Proof of work pending
Ожидается доказательство работы
-
+
%n object(s) pending proof of work
- %n объект в ожидании подтверждения работы %n объекта в ожидании подтверждения работы %n объектов в ожидании подтверждения работы %n объектов в ожидании подтверждения работы
+ %n объект в ожидании доказательства работы %n объекта в ожидании доказательства работы %n объектов в ожидании доказательства работы %n объектов в ожидании доказательства работы
-
+
%n object(s) waiting to be distributed
%n объект ожидает раздачи %n объекта ожидают раздачи %n объектов ожидают раздачи %n объектов ожидают раздачи
-
+
Wait until these tasks finish?
Подождать завершения этих задач?
-
+
Problem communicating with proxy: %1. Please check your network settings.
Проблема коммуникации с прокси: %1. Пожалуйста, проверьте ваши сетевые настройки.
-
+
SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings.
Проблема аутентификации SOCKS5: %1. Пожалуйста, проверьте настройки SOCKS5.
-
+
The time on your computer, %1, may be wrong. Please verify your settings.
Время на компьютере, %1, возможно неправильное. Пожалуйста, проверьте ваши настройки.
-
+
+ The name %1 was not found.
+ Имя %1 не найдено.
+
+
+
+ The namecoin query failed (%1)
+ Запрос к namecoin не удался (%1).
+
+
+
+ The namecoin query failed.
+ Запрос к namecoin не удался.
+
+
+
+ The name %1 has no valid JSON data.
+ Имя %1 не содержит корректных данных JSON.
+
+
+
+ The name %1 has no associated Bitmessage address.
+ Имя %1 не имеет связанного адреса Bitmessage.
+
+
+
+ Success! Namecoind version %1 running.
+ Успех! Namecoind версии %1 работает.
+
+
+
+ Success! NMControll is up and running.
+ Успех! NMControl запущен и работает.
+
+
+
+ Couldn't understand NMControl.
+ Не удалось разобрать ответ NMControl.
+
+
+
Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.
Ваша видеокарта вычислила неправильно, отключаем OpenCL. Пожалуйста, сообщите разработчикам.
-
+
+ Set notification sound...
+ Установить звук уведомления...
+
+
+
+
+ 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
+
+
+Добро пожаловать в простой и безопасный Bitmessage
+* отправляйте сообщения другим людям
+* вещайте, как в twitter или
+* участвуйте в обсуждениях в чанах
+
+
+
not recommended for chans
не рекомендовано для чанов
-
+
+ Quiet Mode
+ Тихий режим
+
+
+
+ Problems connecting? Try enabling UPnP in the Network Settings
+ Проблемы подключения? Попробуйте включить UPnP в сетевых настройках.
+
+
+
+ You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?
+ Вы пытаетесь отправить email вместо bitmessage. Для этого нужно зарегистрироваться на шлюзе. Попробовать зарегистрироваться?
+
+
+
Error: Bitmessage addresses start with BM- Please check the recipient address %1
Ошибка: адреса Bitmessage начинаются с "BM-". Пожалуйста, проверьте адрес получателя %1.
-
+
Error: The recipient address %1 is not typed or copied correctly. Please check it.
Ошибка: адрес получателя %1 набран или скопирован неправильно. Пожалуйста, проверьте его.
-
+
Error: The recipient address %1 contains invalid characters. Please check it.
Ошибка: адрес получателя %1 содержит недопустимые символы. Пожалуйста, проверьте его.
-
+
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.
Ошибка: версия адреса получателя %1 слишком высокая. Либо вам нужно обновить программу Bitmessage, либо ваш знакомый - умник.
-
+
Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance.
Ошибка: часть данных, закодированных в адресе получателя %1 слишком короткая. Видимо, что-то не так с программой, используемой вашим знакомым.
-
+
Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance.
Ошибка: часть данных, закодированных в адресе получателя %1 слишком длинная. Видимо, что-то не так с программой, используемой вашим знакомым.
-
+
Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance.
Ошибка: часть данных, закодированных в адресе получателя %1 сформирована неправильно. Видимо, что-то не так с программой, используемой вашим знакомым.
-
+
Error: Something is wrong with the recipient address %1.
Ошибка: что-то не так с адресом получателя %1.
-
+
+ Error: %1
+ Ошибка: %1
+
+
+
+ From %1
+ От %1
+
+
+
Synchronisation pending
Ожидается синхронизация
-
+
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?
Bitmessage не синхронизирован с сетью, незагруженных объектов: %n. Выход сейчас может привести к задержкам доставки. Подождать завершения синхронизации? Bitmessage не синхронизирован с сетью, незагруженных объектов: %n. Выход сейчас может привести к задержкам доставки. Подождать завершения синхронизации? Bitmessage не синхронизирован с сетью, незагруженных объектов: %n. Выход сейчас может привести к задержкам доставки. Подождать завершения синхронизации? Bitmessage не синхронизирован с сетью, незагруженных объектов: %n. Выход сейчас может привести к задержкам доставки. Подождать завершения синхронизации?
-
+
Not connected
Не подключено
-
+
Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes?
Bitmessage не подключен к сети. Выход сейчас может привести к задержкам доставки. Подождать подключения и завершения синхронизации?
-
+
Waiting for network connection...
Ожидание сетевого подключения...
-
+
Waiting for finishing synchronisation...
Ожидание окончания синхронизации...
+
+
+ You have already set a notification sound for this address book entry. Do you really want to overwrite it?
+ У вас уже есть звук уведомления для этого адресата. Вы уверены, что хотите перезаписать звук уведомления?
+
+
+
+ Error occurred: could not load message from disk.
+ Произошла ошибка: не удалось загрузить сообщение с диска.
+
+
+
+ Display the %n recent broadcast(s) from this address.
+
+
+
+
+ Go online
+ Подключиться к сети
+
+
+
+ Go offline
+ Отключиться от сети
+
+
+
+ Clear
+ Очистить
+
+
+
+ inbox
+ входящие
+
+
+
+ new
+ новые
+
+
+
+ sent
+ отправленные
+
+
+
+ trash
+
+
MessageView
-
+
Follow external link
Перейти по внешней ссылке
-
+
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?
Ссылка "%1" откроется в браузере. Это может быть угрозой безопасности, например деанонимизировать вас или привести к скачиванию вредоносных данных. Вы уверены?
-
+
HTML detected, click here to display
Обнаружен HTML, нажмите здесь чтоб отобразить
-
+
Click here to disable HTML
Нажмите здесь для отключения
@@ -1474,14 +1637,14 @@ Receiver's required difficulty: %1 and %2
MsgDecode
-
+
The message has an unknown encoding.
Perhaps you should upgrade Bitmessage.
Сообщение в неизвестной кодировке.
Возможно, вам следует обновить Bitmessage.
-
+
Unknown encoding
Неизвестная кодировка
@@ -1489,98 +1652,98 @@ Perhaps you should upgrade Bitmessage.
NewAddressDialog
-
+
Create new Address
Создать новый адрес
-
+
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.
The 'Random Number' option is selected by default but deterministic addresses have several pros and cons:
Здесь Вы сможете сгенерировать столько адресов сколько хотите. На самом деле, создание и выкидывание адресов даже поощряется. Вы можете сгенерировать адреса используя либо генератор случайных чисел, либо придумав секретную фразу. Если Вы используете секретную фразу, то адреса будут называться "детерминистическими". Генератор случайных чисел выбран по умолчанию, однако детерминистические адреса имеют следующие плюсы и минусы по сравнению с ними:
-
+
<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>
<html><head/><body><p><span style=" font-weight:600;">Плюсы:<br/></span>Вы сможете восстановить адрес по памяти на любом компьютере<br/>Вам не нужно беспокоиться о сохранении файла keys.dat, если Вы запомнили секретную фразу<br/><span style=" font-weight:600;">Минусы:<br/></span>Вы должны запомнить (или записать) секретную фразу, если Вы хотите когда-либо восстановить Ваш адрес на другом компьютере <br/>Вы должны также запомнить версию адреса и номер потока вместе с секретной фразой<br/>Если Вы выберите слишком короткую секретную фразу, кто-нибудь в интернете сможет подобрать ключ и, как следствие, читать и отправлять от Вашего имени сообщения.</p></body></html>
-
+
Use a random number generator to make an address
Использовать генератор случайных чисел для создания адреса
-
+
Use a passphrase to make addresses
Использовать секретную фразу для создания адресов
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
Потратить несколько лишних минут, чтобы сделать адрес(а) короче на 1 или 2 символа
-
+
Make deterministic addresses
- Создать детерминистический адрес
+ Создать детерминистические адреса
-
+
Address version number: 4
Номер версии адреса: 4
-
+
In addition to your passphrase, you must remember these numbers:
В дополнение к секретной фразе, Вам необходимо запомнить эти числа:
-
+
Passphrase
- Придумайте секретную фразу
+ Секретная фраза
-
+
Number of addresses to make based on your passphrase:
- Кол-во адресов, которые Вы хотите получить из секретной фразы:
+ Количество адресов, которые нужно создать из секретной фразы:
-
+
Stream number: 1
Номер потока: 1
-
+
Retype passphrase
Повторите секретную фразу
-
+
Randomly generate address
Сгенерировать случайный адрес
-
+
Label (not shown to anyone except you)
Имя (не показывается никому кроме Вас)
-
+
Use the most available stream
Использовать наиболее доступный поток
-
+
(best if this is the first of many addresses you will create)
(выберите этот вариант если это лишь первый из многих адресов, которые Вы планируете создать)
-
+
Use the same stream as an existing address
Использовать тот же поток, что и указанный существующий адрес
-
+
(saves you some bandwidth and processing power)
(немного сэкономит Вам пропускную способность сети и вычислительную мощь)
@@ -1588,22 +1751,22 @@ The 'Random Number' option is selected by default but deterministic ad
NewSubscriptionDialog
-
+
Add new entry
Добавить новую запись
-
+
Label
Имя
-
+
Address
Адрес
-
+
Enter an address above.
Введите адрес выше.
@@ -1611,55 +1774,60 @@ The 'Random Number' option is selected by default but deterministic ad
SpecialAddressBehaviorDialog
-
+
Special Address Behavior
Особое поведение адреса
-
+
Behave as a normal address
Вести себя как обычный адрес
-
+
Behave as a pseudo-mailing-list address
Вести себя как адрес псевдо-рассылки
-
+
Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public).
- Почта, полученная на адрес псевдо-рассылки, будет автоматически разослана всем подписчикам (и поэтому будет доступна общей публике).
+ Почта, полученная на адрес псевдо-рассылки, будет автоматически разослана всем подписчикам (и поэтому будет публичной).
-
+
Name of the pseudo-mailing-list:
Имя псевдо-рассылки:
+
+
+ This is a chan address. You cannot use it as a pseudo-mailing list.
+ Это адрес чана. Вы не можете его использовать как адрес рассылки.
+
aboutDialog
-
+
About
О программе
-
+
PyBitmessage
- PyBitmessage
+
-
+
version ?
- версия ?
+
-
+
<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>
<html><head/><body><p>Программа распространяется в соответствии с лицензией MIT/X11; см. <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>
-
+
This is Beta software.
Это бета версия программы.
@@ -1668,10 +1836,10 @@ The 'Random Number' option is selected by default but deterministic ad
<html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
-
-
- <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
- <html><head/><body><p>Авторское право: © 2012-2016 Джонатан Уоррен<br/>Авторское право: © 2013-2016 Разработчики Bitmessage</p></body></html>
+
+
+ <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 The Bitmessage Developers</p></body></html>
+ <html><head/><body><p>Авторское право: © 2012-2016 Джонатан Уоррен<br/>Авторское право: © 2013-2017 Разработчики Bitmessage</p></body></html>
@@ -1679,7 +1847,7 @@ The 'Random Number' option is selected by default but deterministic ad
Use a Blacklist (Allow all incoming messages except those on the Blacklist)
- Использовать чёрный список (Разрешить все входящие сообщения, кроме указанных в чёрном списке)
+ Использовать чёрный список (разрешить все входящие сообщения, кроме указанных в чёрном списке)
@@ -1715,40 +1883,45 @@ The 'Random Number' option is selected by default but deterministic ad
connectDialog
-
+
Bitmessage
Bitmessage
-
+
Bitmessage won't connect to anyone until you let it.
Bitmessage не будет соединяться ни с кем, пока Вы это не разрешите.
-
+
Connect now
Соединиться прямо сейчас
-
+
Let me configure special network settings first
Я хочу сперва настроить сетевые настройки
+
+
+ Work offline
+ Работать без соединения с сетью
+
helpDialog
-
+
Help
Помощь
-
+
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
-
+
As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki:
Bitmessage - общественный проект. Вы можете найти подсказки и советы на Wiki-страничке Bitmessage:
@@ -1756,30 +1929,35 @@ The 'Random Number' option is selected by default but deterministic ad
iconGlossaryDialog
-
+
Icon Glossary
Описание значков
-
+
You have no connections with other peers.
Нет соединения с другими участниками сети.
-
+
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.
- На текущий момент Вы установили по-крайней мере одно исходящее соединение, но пока ни одного входящего. Ваш файрвол или маршрутизатор скорее всего не настроен на переброс входящих TCP соединений к Вашему компьютеру. Bitmessage будет прекрасно работать и без этого, но Вы могли бы помочь сети если бы разрешили и входящие соединения тоже. Это помогло бы Вам стать более важным узлом сети.
+ Вы установили по-крайней мере одно исходящее соединение, но пока ни одного входящего. Ваш файрвол или маршрутизатор скорее всего не настроен на переброс входящих TCP соединений к Вашему компьютеру. Bitmessage будет прекрасно работать и без этого, но Вы могли бы помочь сети если бы разрешили и входящие соединения тоже. Это помогло бы Вам стать более важным узлом сети.
You are using TCP port ?. (This can be changed in the settings).
- Вы используете TCP порт ?. (Его можно поменять в настройках).
+
-
+
You do have connections with other peers and your firewall is correctly configured.
Вы установили соединение с другими участниками сети и ваш файрвол настроен правильно.
+
+
+ You are using TCP port %1. (This can be changed in the settings).
+ Вы используете TCP порт %1. (Его можно поменять в настройках).
+
networkstatus
@@ -1789,109 +1967,154 @@ The 'Random Number' option is selected by default but deterministic ad
Всего соединений:
-
+
Since startup:
С начала работы:
-
+
Processed 0 person-to-person messages.
Обработано 0 сообщений.
-
+
Processed 0 public keys.
Обработано 0 открытых ключей.
-
+
Processed 0 broadcasts.
Обработано 0 рассылок.
-
+
Inventory lookups per second: 0
Поисков в каталоге в секунду: 0
-
+
Objects to be synced:
Несинхронизированные объекты:
-
+
Stream #
№ потока
Connections
- Соединений
+
-
+
Since startup on %1
- С начала работы в %1
+ С начала работы, %1
-
+
Down: %1/s Total: %2
Загрузка: %1/s Всего: %2
-
+
Up: %1/s Total: %2
Отправка: %1/s Всего: %2
-
+
Total Connections: %1
Всего соединений: %1
-
+
Inventory lookups per second: %1
Поисков в каталоге в секунду: %1
-
+
Up: 0 kB/s
Отправка: 0 кБ/с
-
+
Down: 0 kB/s
Загрузка: 0 кБ/с
-
+
Network Status
Состояние сети
-
+
byte(s)
байт байт байт байт
-
+
Object(s) to be synced: %n
Несинхронизированные объекты: %n Несинхронизированные объекты: %n Несинхронизированные объекты: %n Несинхронизированные объекты: %n
-
+
Processed %n person-to-person message(s).
- Обработано %n сообщение. Обработано %n сообщений. Обработано %n сообщений. Обработано %n сообщений.
+ Обработано %n сообщение. Обработано %n сообщения. Обработано %n сообщений. Обработано %n сообщений.
-
+
Processed %n broadcast message(s).
- Обработана %n рассылка. Обработано %n рассылок. Обработано %n рассылок. Обработано %n рассылок.
+ Обработана %n рассылка. Обработано %n рассылки. Обработано %n рассылок. Обработано %n рассылок.
-
+
Processed %n public key(s).
- Обработан %n открытый ключ. Обработано %n открытых ключей. Обработано %n открытых ключей. Обработано %n открытых ключей.
+ Обработан %n открытый ключ. Обработано %n открытых ключа. Обработано %n открытых ключей. Обработано %n открытых ключей.
+
+
+
+ Peer
+ Узел
+
+
+
+ IP address or hostname
+ Адрес IP или имя узла
+
+
+
+ Rating
+ Рейтинг
+
+
+
+ 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
+ PyBitmessage отслеживает шанс успеха попыток подключения к отдельным узлам. Рейтинг варьируется в диапазоне от -1 до 1 и влияет на вероятность выбора узла в будущем.
+
+
+
+ User agent
+ Название приложения
+
+
+
+ Peer's self-reported software
+ Название ПО, как сообщает узел
+
+
+
+ TLS
+ TLS
+
+
+
+ Connection encryption
+ Шифрование соединения
+
+
+
+ List of streams negotiated between you and the peer
+ Перечень потоков, согласованных с конкретным узлом
@@ -1948,7 +2171,7 @@ The 'Random Number' option is selected by default but deterministic ad
- Chan passhphrase/name:
+ Chan passphrase/name:
Пароль/имя чана:
@@ -1970,7 +2193,7 @@ The 'Random Number' option is selected by default but deterministic ad
newchandialog
-
+
Successfully created / joined chan %1
Успешно создан / подключен чан %1
@@ -1988,72 +2211,80 @@ The 'Random Number' option is selected by default but deterministic ad
proofofwork
-
+
C PoW module built successfully.
Модуль C для PoW успешно собран.
-
+
Failed to build C PoW module. Please build it manually.
Не удалось собрать модуль C для PoW. Пожалуйста, соберите его вручную.
-
+
C PoW module unavailable. Please build it.
Модуль C для PoW недоступен. Пожалуйста, соберите его.
+
+ qrcodeDialog
+
+
+ QR-code
+ QR-код
+
+
regenerateAddressesDialog
-
+
Regenerate Existing Addresses
Сгенерировать заново существующие адреса
-
+
Regenerate existing addresses
Сгенерировать заново существующие адреса
-
+
Passphrase
Секретная фраза
-
+
Number of addresses to make based on your passphrase:
- Кол-во адресов, которые Вы хотите получить из Вашей секретной фразы:
+ Количество адресов, которые нужно создать из секретной фразы:
-
+
Address version number:
Номер версии адреса:
-
+
Stream number:
Номер потока:
-
+
1
1
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
Потратить несколько лишних минут, чтобы сделать адрес(а) короче на 1 или 2 символа
-
+
You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time.
Вы должны отметить эту галочку (или не отмечать) точно так же, как Вы сделали (или не сделали) в самый первый раз, когда создавали Ваши адреса.
-
+
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.
- Если Вы ранее создавали детерминистические адреса, но случайно потеряли их, Вы можете их восстановить здесь. Если же Вы использовали генератор случайных чисел чтобы создать Ваши адреса, то Вы не сможете их здесь восстановить.
+ Если Вы ранее создали детерминистические адреса, но случайно потеряли их, Вы можете их восстановить здесь. Если же Вы использовали генератор случайных чисел чтобы создать Ваши адреса, то Вы не сможете их здесь восстановить.
@@ -2322,12 +2553,12 @@ The 'Random Number' option is selected by default but deterministic ad
<html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html>
- <html><head/><body><p>По умолчанию, когда вы отправляете сообщение кому-либо, и адресат находится оффлайн несколько дней, ваш Bitmessage перепосылает сообщение. Это будет продолжаться с увеличивающимся по экспоненте интервалом; сообщение будет переотправляться, например, через 5, 10, 20 дней, пока адресат их запрашивает. Здесь вы можете поменять поведение по умолчанию, заставив Bitmessage отказываться от переотправки по прошествии указанного количества дней или месяцев.</p><p>Оставите поля пустыми, чтобы вернуться к поведению по умолчанию.</p></body></html>
+ <html><head/><body><p>По умолчанию, когда вы отправляете сообщение кому-либо, и адресат находится оффлайн несколько дней, ваш Bitmessage перепосылает сообщение. Это будет продолжаться с увеличивающимся по экспоненте интервалом; сообщение будет переотправляться, например, через 5, 10, 20 дней, пока адресат их запрашивает. Здесь вы можете изменить это поведение, заставив Bitmessage прекращать переотправку по прошествии указанного количества дней или месяцев.</p><p>Оставьте поля пустыми, чтобы вернуться к поведению по умолчанию.</p></body></html>
Give up after
- Отказ от переотправки через
+ Прекратить через
diff --git a/src/translations/bitmessage_sk.qm b/src/translations/bitmessage_sk.qm
index 89a52107..26c2a24d 100644
Binary files a/src/translations/bitmessage_sk.qm and b/src/translations/bitmessage_sk.qm differ
diff --git a/src/translations/bitmessage_sk.ts b/src/translations/bitmessage_sk.ts
index de2519dc..8c3b0209 100644
--- a/src/translations/bitmessage_sk.ts
+++ b/src/translations/bitmessage_sk.ts
@@ -2,17 +2,17 @@
AddAddressDialog
-
+
Add new entry
Pridať nový záznam
-
+
Label
Označenie
-
+
Address
Adresa
@@ -20,70 +20,99 @@
EmailGatewayDialog
-
+
Email gateway
E-mailová brána
-
+
Register on email gateway
Registrácia na e-mailovej bráne
-
+
Account status at email gateway
Stav účtu na e-mailovej bráne
-
+
Change account settings at email gateway
Zmena nastavení účtu na e-mailovej bráne
-
+
Unregister from email gateway
Zrušiť registráciu na e-mailovej bráne
-
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
E-mailové brány umožňujú komunikovať s užívateľmi e-mailu. Momentálne je k dispozícii iba e-mailová brána Mailchuck (@mailchuck.com).
-
+
Desired email address (including @mailchuck.com):
Požadovaná e-mailová adresa (vrátane @ mailchuck.com):
+
+
+ @mailchuck.com
+ @mailchuck.com
+
+
+
+ Registration failed:
+ Registrácia zlyhala:
+
+
+
+ The requested email address is not available, please try a new one.
+ Požadovaná e-mailová adresa nie je k dispozícii, skúste znova.
+
+
+
+ Sending email gateway registration request
+ Odosielam žiadosť o registráciu na e-mailovej bráne
+
+
+
+ Sending email gateway unregistration request
+ Odosielam žiadosť o odhlásenie z e-mailovej brány
+
+
+
+ Sending email gateway status request
+ Odosielam žiadosť o stav e-mailovej brány
+
EmailGatewayRegistrationDialog
-
+
Registration failed:
- Registrácia zlyhala:
+
-
+
The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:
- Požadovaná e-mailová adresa nie je k dispozícii, skúste znova. Vyplňte novú požadovanú e-mailovú adresu (vrátane @mailchuck.com) nižšie:
+
Email gateway registration
- Registrácia na e-mailovej bráne
+
Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available.
Please type the desired email address (including @mailchuck.com) below:
- E-mailové brány umožňujú komunikovať s užívateľmi e-mailu. Momentálne je k dispozícii iba e-mailová brána Mailchuck (@mailchuck.com).
-Vyplňte požadovanú e-mailovú adresu (vrátane @mailchuck.com) nižšie:
+
Mailchuck
-
+
# You can use this to configure your email gateway account
# Uncomment the setting you want to use
# Here are the options:
@@ -171,52 +200,52 @@ Vyplňte požadovanú e-mailovú adresu (vrátane @mailchuck.com) nižšie:
MainWindow
-
+
Reply to sender
Odpovedať odosielateľovi
-
+
Reply to channel
Odpoveď na kanál
-
+
Add sender to your Address Book
Pridať odosielateľa do adresára
-
+
Add sender to your Blacklist
Pridať odosielateľa do svojho zoznamu zakázaných
-
+
Move to Trash
Presunúť do koša
-
+
Undelete
Obnoviť
-
+
View HTML code as formatted text
Zobraziť HTML kód ako formátovaný text
-
+
Save message as...
Uložiť správu ako...
-
+
Mark Unread
Označiť ako neprečítané
-
+
New
Nová
@@ -241,12 +270,12 @@ Vyplňte požadovanú e-mailovú adresu (vrátane @mailchuck.com) nižšie:Kopírovať adresu do clipboardu
-
+
Special address behavior...
Zvláštne správanie adresy...
-
+
Email gateway
E-mailová brána
@@ -256,37 +285,37 @@ Vyplňte požadovanú e-mailovú adresu (vrátane @mailchuck.com) nižšie:Zmazať
-
+
Send message to this address
Poslať správu na túto adresu
-
+
Subscribe to this address
Prihlásiť sa k odberu tejto adresy
-
+
Add New Address
Pridať novú adresu
-
+
Copy destination address to clipboard
Kopírovať cieľovú adresu do clipboardu
-
+
Force send
Vynútiť odoslanie
-
+
One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now?
Jedna z vašich adries, %1, je stará verzia adresy, 1. Verzie adresy 1 už nie sú podporované. Odstrániť ju teraz?
-
+
Waiting for their encryption key. Will request it again soon.
Čakanie na šifrovací kľúč príjemcu. Čoskoro bude vyžiadaný znova.
@@ -296,17 +325,17 @@ Vyplňte požadovanú e-mailovú adresu (vrátane @mailchuck.com) nižšie:
-
+
Queued.
Vo fronte.
-
+
Message sent. Waiting for acknowledgement. Sent at %1
Správa odoslaná. Čakanie na potvrdenie. Odoslaná %1
-
+
Message sent. Sent at %1
Správa odoslaná. Odoslaná %1
@@ -316,77 +345,77 @@ Vyplňte požadovanú e-mailovú adresu (vrátane @mailchuck.com) nižšie:
-
+
Acknowledgement of the message received %1
Potvrdenie prijatia správy %1
-
+
Broadcast queued.
Rozoslanie vo fronte.
-
+
Broadcast on %1
Rozoslané 1%
-
+
Problem: The work demanded by the recipient is more difficult than you are willing to do. %1
Problém: práca požadovná príjemcom je oveľa ťažšia, než je povolené v nastaveniach. %1
-
+
Problem: The recipient's encryption key is no good. Could not encrypt message. %1
Problém: šifrovací kľúč príjemcu je nesprávny. Nie je možné zašifrovať správu. %1
-
+
Forced difficulty override. Send should start soon.
Obmedzenie obtiažnosti práce zrušené. Odosielanie by malo čoskoro začať.
-
+
Unknown status: %1 %2
Neznámy stav: %1 %2
-
+
Not Connected
Nepripojený
-
+
Show Bitmessage
Ukázať Bitmessage
-
+
Send
Odoslať
-
+
Subscribe
Prihlásiť sa k odberu
-
+
Channel
Kanál
-
+
Quit
Ukončiť
-
+
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.
Kľúče môžete spravovať úpravou súboru keys.dat, ktorý je uložený v rovnakom adresári ako tento program. Tento súbor je dôležité zálohovať.
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file.
@@ -395,17 +424,17 @@ It is important that you back up this file.
Tento súbor je dôležité zálohovať.
-
+
Open keys.dat?
Otvoriť keys.dat?
-
+
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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
Kľúče môžete spravovať úpravou súboru keys.dat, ktorý je uložený v rovnakom adresári ako tento program. Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Nezabudnite zatvoriť Bitmessage pred vykonaním akýchkoľvek zmien.)
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
@@ -414,37 +443,37 @@ It is important that you back up this file. Would you like to open the file now?
Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Nezabudnite zatvoriť Bitmessage pred vykonaním akýchkoľvek zmien.)
-
+
Delete trash?
Vyprázdniť kôš?
-
+
Are you sure you want to delete all trashed messages?
Ste si istí, že chcete všetky správy z koša odstrániť?
-
+
bad passphrase
zlé heslo
-
+
You must type your passphrase. If you don't have one then this is not the form for you.
Je nutné zadať prístupové heslo. Ak heslo nemáte, tento formulár nie je pre Vás.
-
+
Bad address version number
Nesprávne číslo verzie adresy
-
+
Your address version number must be a number: either 3 or 4.
Číslo verzie adresy musí byť číslo: buď 3 alebo 4.
-
+
Your address version number must be either 3 or 4.
Vaše číslo verzie adresy musí byť buď 3 alebo 4.
@@ -514,22 +543,22 @@ Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Ne
-
+
Connection lost
Spojenie bolo stratené
-
+
Connected
Spojený
-
+
Message trashed
Správa odstránenia
-
+
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
@@ -537,17 +566,17 @@ Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Ne
TTL (doba životnosti) je čas, počas ktorého bude sieť udržiavať správu. Príjemca musí správu prijať počas tejto životnosti. Keď odosielateľov Bitmessage nedostane po vypršaní životnosti potvrdenie o prijatí, automaticky správu odošle znova. Čím vyššia doba životnosti, tým viac práce musí počítač odosielateľa vykonat na odoslanie správy. Zvyčajne je vhodná doba životnosti okolo štyroch-piatich dní.
-
+
Message too long
Správa je príliš dlhá
-
+
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.
Správa, ktorú skúšate poslať, má %1 bajtov naviac. (Maximum je 261 644 bajtov). Prosím pred odoslaním skrátiť.
-
+
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.
Chyba: Váš účet nebol registrovaný na e-mailovej bráne. Skúšam registrovať ako %1, prosím počkajte na spracovanie registrácie pred opakovaným odoslaním správy.
@@ -592,7 +621,7 @@ Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Ne
-
+
Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab.
Chyba: musíte zadať adresu "Od". Ak žiadnu nemáte, prejdite na kartu "Vaše identity".
@@ -622,39 +651,39 @@ Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Ne
Upozornenie: momentálne nie ste pripojení. Bitmessage vykoná prácu potrebnú na odoslanie správy, ale odoslať ju môže, až keď budete pripojení.
-
+
Message queued.
Správa vo fronte.
-
+
Your 'To' field is empty.
Pole "Komu" je prázdne.
-
+
Right click one or more entries in your address book and select 'Send message to this address'.
Vybertie jednu alebo viacero položiek v adresári, pravým tlačidlom myši zvoľte "Odoslať správu na túto adresu".
-
+
Fetched address from namecoin identity.
Prebratá adresa z namecoin-ovej identity.
-
+
New Message
Nová správa
-
+
From
- Od
+
-
+
Sending email gateway registration request
- Odosielam požiadavku o registráciu na e-mailovej bráne
+
@@ -667,107 +696,107 @@ Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Ne
Zadaná adresa bola neplatná a bude ignorovaná.
-
+
Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.
Chyba: tú istú adresu nemožno pridať do adresára dvakrát. Ak chcete, môžete skúsiť premenovať existujúcu menovku.
-
+
Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.
Chyba: nemožno pridať rovnakú adresu k odberu dvakrát. Keď chcete, môžete premenovať existujúci záznam.
-
+
Restart
Reštart
-
+
You must restart Bitmessage for the port number change to take effect.
Aby sa zmena čísla portu prejavila, musíte reštartovať Bitmessage.
-
+
Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any).
Bitmessage bude odteraz používať proxy, ale ak chcete ukončiť existujúce spojenia, musíte Bitmessage manuálne reštartovať.
-
+
Number needed
Číslo potrebné
-
+
Your maximum download and upload rate must be numbers. Ignoring what you typed.
Maxímálna rýchlosť príjmu a odoslania musí byť uvedená v číslach. Ignorujem zadané údaje.
-
+
Will not resend ever
Nikdy opätovne neodosielať
-
+
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.
Upozornenie: časový limit, ktorý ste zadali, je menší ako čas, ktorý Bitmessage čaká na prvý pokus o opätovné zaslanie, a preto vaše správy nebudú nikdy opätovne odoslané.
-
+
Sending email gateway unregistration request
- Odosielam žiadosť o odhlásenie z e-mailovej brány
+
-
+
Sending email gateway status request
- Odosielam požiadavku o stave e-mailovej brány
+
-
+
Passphrase mismatch
Nezhoda hesla
-
+
The passphrase you entered twice doesn't match. Try again.
Zadané heslá sa rôznia. Skúste znova.
-
+
Choose a passphrase
Vyberte heslo
-
+
You really do need a passphrase.
Heslo je skutočne potrebné.
-
+
Address is gone
Adresa zmizla
-
+
Bitmessage cannot find your address %1. Perhaps you removed it?
Bitmessage nemôže nájsť vašu adresu %1. Možno ste ju odstránili?
-
+
Address disabled
Adresa deaktivovaná
-
+
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.
Chyba: adresa, z ktorej sa pokúšate odoslať, je neaktívna. Pred použitím ju musíte aktivovať v karte "Vaše identity".
-
+
Entry added to the Address Book. Edit the label to your liking.
- Záznam pridaný do adresára. Upravte označenie podľa vašich predstáv.
+
-
+
Entry added to the blacklist. Edit the label to your liking.
Záznam pridaný na zoznam zakázaných. Upravte označenie podľa vašich predstáv.
@@ -777,32 +806,32 @@ Tento súbor je dôležité zálohovať. Chcete tento súbor teraz otvoriť? (Ne
Chyba: tú istú adresu nemožno pridať na zoznam zakázaných dvakrát. Ak chcete, môžete skúsiť premenovať existujúce označenie.
-
+
Moved items to trash.
Položky presunuté do koša.
-
+
Undeleted item.
Položka obnovená.
-
+
Save As...
Uložiť ako...
-
+
Write error.
Chyba pri zapisovaní.
-
+
No addresses selected.
Nevybraná žiadna adresa.
-
+
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.
Are you sure you want to delete the subscription?
@@ -811,7 +840,7 @@ Are you sure you want to delete the subscription?
Ste si istý, že chcete odber odstrániť?
-
+
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.
Are you sure you want to delete the channel?
@@ -820,282 +849,282 @@ Are you sure you want to delete the channel?
Ste si istý, že chcete kanál odstrániť?
-
+
Do you really want to remove this avatar?
Naozaj chcete odstrániť tento avatar?
-
+
You have already set an avatar for this address. Do you really want to overwrite it?
Pre túto adresu ste už ste nastavili avatar. Naozaj ho chcete ho zmeniť?
-
+
Start-on-login not yet supported on your OS.
Spustenie pri prihlásení zatiaľ pre váš operačný systém nie je podporované.
-
+
Minimize-to-tray not yet supported on your OS.
Minimalizovanie do panelu úloh zatiaľ pre váš operačný systém nie je podporované.
-
+
Tray notifications not yet supported on your OS.
Oblasť oznámení zatiaľ pre váš operačný systém nie je podporovaná.
-
+
Testing...
Testujem...
-
+
This is a chan address. You cannot use it as a pseudo-mailing list.
- Toto je adresa kanálu. Nie je možné ju používať ako pseudo poštový zoznam.
+
-
+
The address should start with ''BM-''
Adresa by mala začínať ''BM-''
-
+
The address is not typed or copied correctly (the checksum failed).
Nesprávne zadaná alebo skopírovaná adresa (kontrolný súčet zlyhal).
-
+
The version number of this address is higher than this software can support. Please upgrade Bitmessage.
Číslo verzie tejto adresy je vyššie ako tento softvér podporuje. Prosím inovujte Bitmessage.
-
+
The address contains invalid characters.
Adresa obsahuje neplatné znaky.
-
+
Some data encoded in the address is too short.
Niektoré dáta zakódované v adrese sú príliš krátke.
-
+
Some data encoded in the address is too long.
Niektoré dáta zakódované v adrese sú príliš dlhé.
-
+
Some data encoded in the address is malformed.
Niektoré dáta zakódované v adrese sú poškodené.
-
+
Enter an address above.
- Zadajte adresu vyššie.
+
-
+
Address is an old type. We cannot display its past broadcasts.
Starý typ adresy. Nie je možné zobraziť jej predchádzajúce hromadné správy.
-
+
There are no recent broadcasts from this address to display.
Neboli nájdené žiadne nedávne hromadé správy z tejto adresy.
-
+
You are using TCP port %1. (This can be changed in the settings).
- Používate port TCP %1. (Možno zmeniť v nastaveniach).
+
-
+
Bitmessage
Bitmessage
-
+
Identities
Identity
-
+
New Identity
Nová identita
-
+
Search
Hľadaj
-
+
All
Všetky
-
+
To
Príjemca
-
+
From
Odosielateľ
-
+
Subject
Predmet
-
+
Message
Správa
-
+
Received
Prijaté
-
+
Messages
Správy
-
+
Address book
Adresár
-
+
Address
Adresa
-
+
Add Contact
Pridať kontakt
-
+
Fetch Namecoin ID
Získať identifikátor namecoin-u
-
+
Subject:
Predmet:
-
+
From:
Odosielateľ:
-
+
To:
Príjemca:
-
+
Send ordinary Message
Poslať obyčajnú správu
-
+
Send Message to your Subscribers
Poslať správu vašim odberateľom
-
+
TTL:
Doba životnosti:
-
+
Subscriptions
Odbery
-
+
Add new Subscription
Pridať nový odber
-
+
Chans
Kanály
-
+
Add Chan
Pridať kanál
-
+
File
Súbor
-
+
Settings
Nastavenia
-
+
Help
Pomoc
-
+
Import keys
Importovať kľúče
-
+
Manage keys
Spravovať kľúče
-
+
Ctrl+Q
Ctrl+Q
-
+
F1
F1
-
+
Contact support
Kontaktovať používateľskú podporu
-
+
About
O
-
+
Regenerate deterministic addresses
Znova vytvoriť deterministické adresy
-
+
Delete all trashed messages
Odstrániť všetky správy z koša
-
+
Join / Create chan
Pripojiť / vytvoriť kanál
-
+
All accounts
Všetky účty
@@ -1115,37 +1144,37 @@ Ste si istý, že chcete kanál odstrániť?
Pridať nový záznam
-
+
Display the %1 recent broadcast(s) from this address.
- Zobraziť posledných %1 hromadných správ z tejto adresy.
+
-
+
New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest
K dispozícii je nová verzia PyBitmessage: %1. Môžete ju stiahnuť na https://github.com/Bitmessage/PyBitmessage/releases/latest
-
+
Waiting for PoW to finish... %1%
Čakám na ukončenie práce... %1%
-
+
Shutting down Pybitmessage... %1%
Ukončujem PyBitmessage... %1%
-
+
Waiting for objects to be sent... %1%
Čakám na odoslanie objektov... %1%
-
+
Saving settings... %1%
Ukladám nastavenia... %1%
-
+
Shutting down core... %1%
Ukončujem jadro... %1%
@@ -1155,27 +1184,27 @@ Ste si istý, že chcete kanál odstrániť?
Zastavujem oznámenia... %1%
-
+
Shutdown imminent... %1%
Posledná fáza ukončenia... %1%
-
+
%n hour(s)
%n hodina %n hodiny %n hodín
-
+
%n day(s)
%n deň %n dni %n dní
-
+
Shutting down PyBitmessage... %1%
Ukončujem PyBitmessage... %1%
-
+
Sent
Odoslané
@@ -1220,254 +1249,384 @@ Ste si istý, že chcete kanál odstrániť?
Upozornenie: Váš disk alebo priestor na ukladanie dát je plný. Bitmessage bude teraz ukončený.
-
+
Error! Could not find sender address (your address) in the keys.dat file.
Chyba! Nemožno nájsť adresu odosielateľa (vašu adresu) v súbore keys.dat.
-
+
Doing work necessary to send broadcast...
Vykonávam prácu potrebnú na rozoslanie...
-
+
Broadcast sent on %1
Rozoslané %1
-
+
Encryption key was requested earlier.
Šifrovací klúč bol vyžiadaný.
-
+
Sending a request for the recipient's encryption key.
Odosielam požiadavku na kľúč príjemcu.
-
+
Looking up the receiver's public key
Hľadám príjemcov verejný kľúč
-
+
Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1
Problém: adresa príjemcu je na mobilnom zariadení a požaduje, aby správy obsahovali nezašifrovanú adresu príjemcu. Vaše nastavenia však túto možnost nemajú povolenú. %1
-
+
Doing work necessary to send message.
There is no required difficulty for version 2 addresses like this.
Vykonávam prácu potrebnú na odoslanie správy.
Adresy verzie dva, ako táto, nepožadujú obtiažnosť.
-
+
Doing work necessary to send message.
Receiver's required difficulty: %1 and %2
Vykonávam prácu potrebnú na odoslanie správy.
Priímcova požadovaná obtiažnosť: %1 a %2
-
+
Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3
Problém: Práca požadovná príjemcom (%1 a %2) je obtiažnejšia, ako máte povolené. %3
-
+
Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1
Problém: skúšate odslať správu sami sebe, ale nemôžem nájsť šifrovací kľúč v súbore keys.dat. Nemožno správu zašifrovať: %1
-
+
Doing work necessary to send message.
Vykonávam prácu potrebnú na odoslanie...
-
+
Message sent. Waiting for acknowledgement. Sent on %1
Správa odoslaná. Čakanie na potvrdenie. Odoslaná %1
-
+
Doing work necessary to request encryption key.
Vykonávam prácu potrebnú na vyžiadanie šifrovacieho kľúča.
-
+
Broadcasting the public key request. This program will auto-retry if they are offline.
Rozosielam požiadavku na verejný kľúč. Ak nebude príjemca spojený zo sieťou, budem skúšať znova.
-
+
Sending public key request. Waiting for reply. Requested at %1
Odosielam požiadavku na verejný kľúč. Čakám na odpoveď. Vyžiadaný %1
-
+
UPnP port mapping established on port %1
Mapovanie portov UPnP vytvorené na porte %1
-
+
UPnP port mapping removed
Mapovanie portov UPnP zrušené
-
+
Mark all messages as read
Označiť všetky správy ako prečítané
-
+
Are you sure you would like to mark all messages read?
Ste si istý, že chcete označiť všetky správy ako prečítané?
-
+
Doing work necessary to send broadcast.
Vykonávam prácu potrebnú na rozoslanie.
-
+
Proof of work pending
Vykonávaná práca
-
+
%n object(s) pending proof of work
%n objekt čaká na vykonanie práce %n objekty čakajú na vykonanie práce %n objektov čaká na vykonanie práce
-
+
%n object(s) waiting to be distributed
%n objekt čaká na rozoslanie %n objekty čakajú na rozoslanie %n objektov čaká na rozoslanie
-
+
Wait until these tasks finish?
Počkať, kým tieto úlohy skončia?
-
+
Problem communicating with proxy: %1. Please check your network settings.
Problém komunikácie s proxy: %1. Prosím skontrolujte nastavenia siete.
-
+
SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings.
Problém autentikácie SOCKS5: %1. Prosím skontrolujte nastavenia SOCKS5.
-
+
The time on your computer, %1, may be wrong. Please verify your settings.
Čas na vašom počítači, %1, možno nie je správny. Prosím, skontrolujete nastavenia.
-
+
+ The name %1 was not found.
+ Meno % nenájdené.
+
+
+
+ The namecoin query failed (%1)
+ Dotaz prostredníctvom namecoinu zlyhal (%1)
+
+
+
+ The namecoin query failed.
+ Dotaz prostredníctvom namecoinu zlyhal.
+
+
+
+ The name %1 has no valid JSON data.
+ Meno %1 neobsahuje planté JSON dáta.
+
+
+
+ The name %1 has no associated Bitmessage address.
+ Meno %1 nemá priradenú žiadnu adresu Bitmessage.
+
+
+
+ Success! Namecoind version %1 running.
+ Úspech! Namecoind verzia %1 spustený.
+
+
+
+ Success! NMControll is up and running.
+ Úspech! NMControl spustený.
+
+
+
+ Couldn't understand NMControl.
+ Nie je rozumieť NMControl-u.
+
+
+
Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.
Vaša grafická karta vykonala nesprávny výpočet, deaktivujem OpenCL. Prosím, pošlite správu vývojárom.
-
+
+ Set notification sound...
+ Nastaviť zvuk oznámenia...
+
+
+
+
+ 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
+
+
+Vitajte v jednoduchom a bezpečnom Bitmessage
+* posielajte správy druhým ľuďom
+* posielajte hromadné správy ako na twitteri alebo
+* diskutuje s druhými v kanáloch
+
+
+
+
not recommended for chans
nie je odporúčaná pre kanály
-
+
+ Quiet Mode
+ Tichý režim
+
+
+
Problems connecting? Try enabling UPnP in the Network Settings
Problémy so spojením? Skúste zapnúť UPnP v Nastaveniach siete
-
+
+ You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?
+ Pokúšate sa odoslať e-mail namiesto bitmessage. To si vyžaduje registráciu na bráne. Pokúsiť sa o registráciu?
+
+
+
Error: Bitmessage addresses start with BM- Please check the recipient address %1
Chyba: Bitmessage adresy začínajú s BM- Prosím skontrolujte adresu príjemcu %1
-
+
Error: The recipient address %1 is not typed or copied correctly. Please check it.
Chyba: adresa príjemcu %1 nie je na správne napísaná alebo skopírovaná. Prosím skontrolujte ju.
-
+
Error: The recipient address %1 contains invalid characters. Please check it.
Chyba: adresa príjemcu %1 obsahuje neplatné znaky. Prosím skontrolujte ju.
-
+
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.
Chyba: verzia adresy príjemcu %1 je príliš veľká. Buď musíte aktualizovať program Bitmessage alebo váš známy s vami žartuje.
-
+
Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance.
Chyba: niektoré údaje zakódované v adrese príjemcu %1 sú príliš krátke. Softér vášho známeho možno nefunguje správne.
-
+
Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance.
Chyba: niektoré údaje zakódované v adrese príjemcu %1 sú príliš dlhé. Softvér vášho známeho možno nefunguje správne.
-
+
Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance.
Chyba: niektoré údaje zakódované v adrese príjemcu %1 sú poškodené. Softvér vášho známeho možno nefunguje správne.
-
+
Error: Something is wrong with the recipient address %1.
Chyba: niečo s adresou príjemcu %1 je nie je v poriadku.
-
+
+ Error: %1
+ Chyba: %1
+
+
+
+ From %1
+ Od %1
+
+
+
Synchronisation pending
Prebieha synchronizácia
-
+
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?
Bitmessage sa nezosynchronizoval so sieťou, treba prevzať ešte %n objekt. Keď program ukončíte teraz, môže dôjsť k spozdeniu doručenia. Počkať, kým sa synchronizácia ukončí? Bitmessage sa nezosynchronizoval so sieťou, treba prevzať ešte %n objekty. Keď program ukončíte teraz, môže dôjsť k spozdeniu doručenia. Počkať, kým sa synchronizácia ukončí? Bitmessage sa nezosynchronizoval so sieťou, treba prevzať ešte %n objektov. Keď program ukončíte teraz, môže dôjsť k spozdeniu doručenia. Počkať, kým sa synchronizácia ukončí?
-
+
Not connected
Nepripojený
-
+
Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes?
Bitmessage nie je pripojený do siete. Keď program ukončíte teraz, môže dôjsť k spozdeniu doručenia. Počkať, kým dôjde k spojeniu a prebehne synchronizácia?
-
+
Waiting for network connection...
Čakám na pripojenie do siete...
-
+
Waiting for finishing synchronisation...
Čakám na ukončenie synchronizácie...
+
+
+ You have already set a notification sound for this address book entry. Do you really want to overwrite it?
+ Pre túto adresu ste už ste nastavili zvuk oznámenia. Naozaj ho chcete ho zmeniť?
+
+
+
+ Error occurred: could not load message from disk.
+ Chyba pri načítaní správy.
+
+
+
+ Display the %n recent broadcast(s) from this address.
+ Zobraziť poslednú %1 hromadnú správu z tejto adresy. Zobraziť posledné %1 hromadné správy z tejto adresy. Zobraziť posledných %1 hromadných správ z tejto adresy.
+
+
+
+ Go online
+ Pripojiť k sieti
+
+
+
+ Go offline
+ Odpojiť od siete
+
+
+
+ Clear
+ Vymazať
+
+
+
+ inbox
+ Doručená pošta
+
+
+
+ new
+ Nová doručená pošta
+
+
+
+ sent
+
+
+
+
+ trash
+
+
MessageView
-
+
Follow external link
Nasledovať externý odkaz
-
+
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?
Odkaz "%1" bude otvorený v prehliadači. Tento úkon môže predstavovať bezpečnostné riziko a Vás deanonymizovať, alebo vykonať škodlivú činnost. Ste si istý?
-
+
HTML detected, click here to display
Nájdené HTML, kliknite sem ak chcete zobraziť
-
+
Click here to disable HTML
Kliknite sem na vypnutie HTML
@@ -1475,14 +1634,14 @@ Priímcova požadovaná obtiažnosť: %1 a %2
MsgDecode
-
+
The message has an unknown encoding.
Perhaps you should upgrade Bitmessage.
Správa má neznáme kódovanie.
Možno by ste mali inovovať Bitmessage.
-
+
Unknown encoding
Neznáme kódovanie
@@ -1490,99 +1649,99 @@ Možno by ste mali inovovať Bitmessage.
NewAddressDialog
-
+
Create new Address
Vytvoriť novú adresu
-
+
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.
The 'Random Number' option is selected by default but deterministic addresses have several pros and cons:
Tu si môžete vygenerovať toľko adries, koľko chcete. Vytváranie a opúšťanie adries je vrelo odporúčané. Adresy môžete generovať buď pomocou náhodných čísel alebo pomocou hesla. Ak používate heslo, takáto adresa sa nazýva "deterministická".
Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adresy majú niekoľko výhod a nevýhod:
-
+
<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>
<html><head/><body><p> <span style=" font-weight:600;">Pros: <br/></span>Svoje adresy môžete znovu vytvoriť na ľubovoľnom počítači z pamäte.<br/>Dokým si pamätáte heslo, nemusíte sa starať o zálohovanie keys.dat<br/> <span style=" font-weight:600;"Nevýhody: <br/></span>Ak chcete znovu vytvoriť kľúče ak ich stratíte, musíte si pamätať (alebo niekam zapísať) heslo. <br/> Zároveň si musíte si zapamätať aj číslo verzie adresy a číslo toku.<br/>Ak si zvolíte slabé prístupové heslo a niekto na internete ho uhádne, napr. hrubou silou, môže čítať vaše správy a odosielať ich za vás.</p></body></html>
-
+
Use a random number generator to make an address
Vytvoriť novú adresu pomocou generátora náhodných čísel
-
+
Use a passphrase to make addresses
Vytvoriť novú adresu pomocou hesla
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
Stráviť niekoľko minút výpočtového času navyše, aby adresa(y) bola o 1 alebo 2 znakov kratšia
-
+
Make deterministic addresses
Vytvoriť deterministické adresy
-
+
Address version number: 4
Číslo verzie adresy: 4
-
+
In addition to your passphrase, you must remember these numbers:
Okrem svojho hesla si musíte zapamätať tiejto údaje:
-
+
Passphrase
Heslo
-
+
Number of addresses to make based on your passphrase:
Počet adries, ktoré majú byť na základe vášho hesla vytvorené:
-
+
Stream number: 1
Číslo prúdu: 1
-
+
Retype passphrase
Opakovať heslo
-
+
Randomly generate address
Adresu generovať náhodne
-
+
Label (not shown to anyone except you)
Označenie (zobrazené len vám a nikomu inému)
-
+
Use the most available stream
Použiť najviac prístupný prúd
-
+
(best if this is the first of many addresses you will create)
(najlepšie, ak ide o prvú z mnohých vytvorených adries)
-
+
Use the same stream as an existing address
Použiť ten istý prúd ako existujúca adresa
-
+
(saves you some bandwidth and processing power)
(ušetrí nejaké množstvo prenesených dát a výpočtový výkon)
@@ -1590,22 +1749,22 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr
NewSubscriptionDialog
-
+
Add new entry
Pridať nový záznam
-
+
Label
Označenie
-
+
Address
Adresa
-
+
Enter an address above.
Zadajte adresu vyššie.
@@ -1613,55 +1772,60 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr
SpecialAddressBehaviorDialog
-
+
Special Address Behavior
Zvláštne správanie adresy
-
+
Behave as a normal address
Správanie ako normálna adresa
-
+
Behave as a pseudo-mailing-list address
Správanie ako pseudo poštový zoznam
-
+
Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public).
Správy prijaté na adresu pseudo poštového zoznamu budú automaticky rozoslaná odberateľom (a teda budú zverejnené).
-
+
Name of the pseudo-mailing-list:
Meno pseudo poštového zoznamu:
+
+
+ This is a chan address. You cannot use it as a pseudo-mailing list.
+ Toto je adresa kanálu. Nie je možné ju používať ako pseudo poštový zoznam.
+
aboutDialog
-
+
About
O
-
+
PyBitmessage
- PyBitmessage
+
-
+
version ?
- verzia ?
+
-
+
<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>
<html><head/><body><p>Šírený pod licenciou na softvér MIT / X11; pozri <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>
-
+
This is Beta software.
Toto je beta softvér.
@@ -1670,10 +1834,10 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr
<html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
-
-
- <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
- <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 Vývojári Bitmessage</p></body></html>
+
+
+ <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 The Bitmessage Developers</p></body></html>
+ <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 Vývojári Bitmessage</p></body></html>
@@ -1717,40 +1881,45 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr
connectDialog
-
+
Bitmessage
Bitmessage
-
+
Bitmessage won't connect to anyone until you let it.
Bitmessage sa s nikým nespojí, dokým to nepovolíte.
-
+
Connect now
Spojiť teraz
-
+
Let me configure special network settings first
Najprv upraviť zvláštne sieťové nastavenia
+
+
+ Work offline
+ Zostať odpojený
+
helpDialog
-
+
Help
Pomoc
-
+
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
<a href="https://bitmessage.org/wiki/PyBitmessage_Help">https://bitmessage.org/wiki/PyBitmessage_Help</a>
-
+
As Bitmessage is a collaborative project, help can be found online in the Bitmessage Wiki:
Keďže Bitmessage je projekt založený na spolupráci, pomoc možno nájsť na internete v Bitmessage Wiki:
@@ -1758,30 +1927,35 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr
iconGlossaryDialog
-
+
Icon Glossary
Legenda ikon
-
+
You have no connections with other peers.
Nemáte žiadne spojenia s partnerskými uzlami.
-
+
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.
Vykonali ste aspoň jedno vychádzajúce spojenie do siete, ale ešte ste nenaviazali žiadne prichádzajúce spojenia. Váš firewall alebo domáci router pravdepodobne nie je nakonfigurovaný tak, aby presmeroval prichádzajúce TCP spojenia k vášmu počítaču. Bitmessage bude fungovať v pohode, keby ste však mali fungujúce prichádzajúce spojenia, pomôžete sieti Bitmessage a váš uzol bude lepšie pripojený.
You are using TCP port ?. (This can be changed in the settings).
- Používate port TCP ?. (Možno zmeniť v nastaveniach).
+
-
+
You do have connections with other peers and your firewall is correctly configured.
Máte spojenia s partnerskými uzlami a vaša brána firewall je nastavená správne.
+
+
+ You are using TCP port %1. (This can be changed in the settings).
+ Používate port TCP %1. (Možno zmeniť v nastaveniach).
+
networkstatus
@@ -1791,110 +1965,155 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr
Spojení spolu:
-
+
Since startup:
Od spustenia:
-
+
Processed 0 person-to-person messages.
Spracovaných 0 bežných správ.
-
+
Processed 0 public keys.
Spracovaných 0 verejných kľúčov.
-
+
Processed 0 broadcasts.
Spracovaných 0 hromadných správ.
-
+
Inventory lookups per second: 0
Vyhľadaní v inventári za sekundu: 0
-
+
Objects to be synced:
Zostáva synchronizovať objektov:
-
+
Stream #
Prúd #
Connections
- Spojenia
+
-
+
Since startup on %1
Od spustenia %1
-
+
Down: %1/s Total: %2
Prijatých: %1/s Spolu: %2
-
+
Up: %1/s Total: %2
Odoslaných: %1/s Spolu: %2
-
+
Total Connections: %1
Spojení spolu: %1
-
+
Inventory lookups per second: %1
Vyhľadaní v inventári za sekundu: %1
-
+
Up: 0 kB/s
Odoslaných: 0 kB/s
-
+
Down: 0 kB/s
Prijatých: 0 kB/s
-
+
Network Status
Stav siete
-
+
byte(s)
bajt bajty bajtov
-
+
Object(s) to be synced: %n
Zostáva synchronizovať %n objekt Zostáva synchronizovať %n objekty Zostáva synchronizovať %n objektov
-
+
Processed %n person-to-person message(s).
Spracovaná %n bežná správa. Spracované %n bežné správy. Spracovaných %n bežných správ.
-
+
Processed %n broadcast message(s).
Spracovaná %n hromadná správa. Spracované %n hromadné správy. Spracovaných %n hromadných správ.
-
+
Processed %n public key(s).
Spracovaný %n verejný kľúč. Spracované %n verejné kľúče. Spracovaných %n verejných kľúčov.
+
+
+ Peer
+ Partnerský uzol
+
+
+
+ IP address or hostname
+ IP adresa alebo názov hostiteľa
+
+
+
+ Rating
+ Hodnotenie
+
+
+
+ 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
+ PyBitmessage sleduje úspešnosť pokusov o pripojenie k jednotlivým uzlom. Hodnotenie sa pohybuje od -1 do 1 a ovplyvňuje pravdepodobnosť výberu uzla v budúcnosti
+
+
+
+ User agent
+ Užívateľský agent
+
+
+
+ Peer's self-reported software
+ Software hlásený partnerským uzlom
+
+
+
+ TLS
+ TLS
+
+
+
+ Connection encryption
+ Šifrovanie pripojenia
+
+
+
+ List of streams negotiated between you and the peer
+ Zoznam prúdov dohodnutých medzi vami a partnerom
+
newChanDialog
@@ -1950,7 +2169,7 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr
- Chan passhphrase/name:
+ Chan passphrase/name:
Názov kanálu:
@@ -1972,7 +2191,7 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr
newchandialog
-
+
Successfully created / joined chan %1
Kanál %1 úspešne vytvorený/pripojený
@@ -1990,70 +2209,78 @@ Predvoľba je pomocou generátora náhodných čísiel, ale deterministické adr
proofofwork
-
+
C PoW module built successfully.
C PoW modul úspešne zostavený.
-
+
Failed to build C PoW module. Please build it manually.
Zostavenie C PoW modulu zlyhalo. Prosím, zostavte ho manuálne.
-
+
C PoW module unavailable. Please build it.
C PoW modul nie je dostupný. Prosím, zostavte ho.
+
+ qrcodeDialog
+
+
+ QR-code
+ QR kód
+
+
regenerateAddressesDialog
-
+
Regenerate Existing Addresses
Znova vytvoriť existujúce adresy
-
+
Regenerate existing addresses
Znova vytvoriť existujúce adresy
-
+
Passphrase
Heslo
-
+
Number of addresses to make based on your passphrase:
Počet adries, ktoré majú byť na základe vášho hesla vytvorené:
-
+
Address version number:
Číslo verzie adresy:
-
+
Stream number:
Číslo prúdu:
-
+
1
1
-
+
Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
Stráviť niekoľko minút výpočtového času navyše, aby adresa(y) bola o 1 alebo 2 znakov kratšia
-
+
You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time.
Je nutné začiarknuť (alebo nezačiarknuť) toto políčko tak isto, ako keď ste vytvárali svoje adresy prvýkrát.
-
+
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.
Ak ste v minulosti používali deterministické adresy, ale stratili ich kvôli nehode (ako je napráklad zlyhanie pevného disku), môžete ich vytvoriť znova. Ak ste na vytvorenie adries použili generátor náhodných čísel, potom je vám tento formulár zbytočný.
diff --git a/src/translations/bitmessage_zh_cn.qm b/src/translations/bitmessage_zh_cn.qm
index 14625aef..cc37b146 100644
Binary files a/src/translations/bitmessage_zh_cn.qm and b/src/translations/bitmessage_zh_cn.qm differ
diff --git a/src/translations/bitmessage_zh_cn.ts b/src/translations/bitmessage_zh_cn.ts
index 323bf123..58e16cb8 100644
--- a/src/translations/bitmessage_zh_cn.ts
+++ b/src/translations/bitmessage_zh_cn.ts
@@ -58,12 +58,12 @@
EmailGatewayRegistrationDialog
-
+
Registration failed:
注册失败:
-
+
The requested email address is not available, please try a new one. Fill out the new desired email address (including @mailchuck.com) below:
要求的电子邮件地址不详,请尝试一个新的。填写新的所需电子邮件地址(包括 @mailchuck.com)如下:
@@ -82,7 +82,7 @@ Please type the desired email address (including @mailchuck.com) below:
Mailchuck
-
+
# You can use this to configure your email gateway account
# Uncomment the setting you want to use
# Here are the options:
@@ -166,122 +166,122 @@ Please type the desired email address (including @mailchuck.com) below:
MainWindow
-
+
Reply to sender
回复发件人
-
+
Reply to channel
回复通道
-
+
Add sender to your Address Book
将发送者添加到您的通讯簿
-
+
Add sender to your Blacklist
将发件人添加到您的黑名单
-
+
Move to Trash
移入回收站
-
+
Undelete
取消删除
-
+
View HTML code as formatted text
作为HTML查看
-
+
Save message as...
将消息保存为...
-
+
Mark Unread
标记为未读
-
+
New
新建
-
+
Enable
启用
-
+
Disable
禁用
-
+
Set avatar...
设置头像...
-
+
Copy address to clipboard
将地址复制到剪贴板
-
+
Special address behavior...
特别的地址行为...
-
+
Email gateway
电子邮件网关
-
+
Delete
删除
-
+
Send message to this address
发送消息到这个地址
-
+
Subscribe to this address
订阅到这个地址
-
+
Add New Address
创建新地址
-
+
Copy destination address to clipboard
复制目标地址到剪贴板
-
+
Force send
强制发送
-
+
One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. May we delete it now?
您的地址中的一个, %1,是一个过时的版本1地址. 版本1地址已经不再受到支持了. 我们可以将它删除掉么?
-
+
Waiting for their encryption key. Will request it again soon.
正在等待他们的加密密钥,我们会在稍后再次请求。
@@ -291,17 +291,17 @@ Please type the desired email address (including @mailchuck.com) below:
-
+
Queued.
已经添加到队列。
-
+
Message sent. Waiting for acknowledgement. Sent at %1
消息已经发送. 正在等待回执. 发送于 %1
-
+
Message sent. Sent at %1
消息已经发送. 发送于 %1
@@ -311,47 +311,47 @@ Please type the desired email address (including @mailchuck.com) below:
-
+
Acknowledgement of the message received %1
消息的回执已经收到于 %1
-
+
Broadcast queued.
广播已经添加到队列中。
-
+
Broadcast on %1
已经广播于 %1
-
+
Problem: The work demanded by the recipient is more difficult than you are willing to do. %1
错误: 收件人要求的做工量大于我们的最大接受做工量。 %1
-
+
Problem: The recipient's encryption key is no good. Could not encrypt message. %1
错误: 收件人的加密密钥是无效的。不能加密消息。 %1
-
+
Forced difficulty override. Send should start soon.
已经忽略最大做工量限制。发送很快就会开始。
-
+
Unknown status: %1 %2
未知状态: %1 %2
-
+
Not Connected
未连接
-
+
Show Bitmessage
显示比特信
@@ -361,12 +361,12 @@ Please type the desired email address (including @mailchuck.com) below:
发送
-
+
Subscribe
订阅
-
+
Channel
频道
@@ -376,66 +376,66 @@ Please type the desired email address (including @mailchuck.com) below:
退出
-
+
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.
您可以通过编辑和程序储存在同一个目录的 keys.dat 来编辑密钥。备份这个文件十分重要。
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file.
您可以通过编辑储存在 %1 的 keys.dat 来编辑密钥。备份这个文件十分重要。
-
+
Open keys.dat?
打开 keys.dat ?
-
+
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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
您可以通过编辑和程序储存在同一个目录的 keys.dat 来编辑密钥。备份这个文件十分重要。您现在想打开这个文件么?(请在进行任何修改前关闭比特信)
-
+
You may manage your keys by editing the keys.dat file stored in
%1
It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)
您可以通过编辑储存在 %1 的 keys.dat 来编辑密钥。备份这个文件十分重要。您现在想打开这个文件么?(请在进行任何修改前关闭比特信)
-
+
Delete trash?
清空回收站?
-
+
Are you sure you want to delete all trashed messages?
您确定要删除全部被回收的消息么?
-
+
bad passphrase
错误的密钥
-
+
You must type your passphrase. If you don't have one then this is not the form for you.
您必须输入您的密钥。如果您没有的话,这个表单不适用于您。
-
+
Bad address version number
地址的版本号无效
-
+
Your address version number must be a number: either 3 or 4.
您的地址的版本号必须是一个数字: 3 或 4.
-
+
Your address version number must be either 3 or 4.
您的地址的版本号必须是 3 或 4.
@@ -505,22 +505,22 @@ It is important that you back up this file. Would you like to open the file now?
-
+
Connection lost
连接已丢失
-
+
Connected
已经连接
-
+
Message trashed
消息已经移入回收站
-
+
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
@@ -529,17 +529,17 @@ It is important that you back up this file. Would you like to open the file now?
收件人必须在此期间得到它. 如果您的Bitmessage客户沒有听到确认, 它会自动重新发送信息. Time-To-Live的时间越长, 您的电脑必须要做更多工作來发送信息. 四天或五天的 Time-To-Time, 经常是合适的.
-
+
Message too long
信息太长
-
+
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.
你正在尝试发送的信息已超过%1个字节太长, (最大为261644个字节). 发送前请剪下来。
-
+
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.
错误: 您的帐户没有在电子邮件网关注册。现在发送注册为%1, 注册正在处理请稍候重试发送.
@@ -584,217 +584,217 @@ It is important that you back up this file. Would you like to open the file now?
-
+
Error: You must specify a From address. If you don't have one, go to the 'Your Identities' tab.
错误: 您必须指出一个表单地址, 如果您没有,请到“您的身份”标签页。
-
+
Address version number
地址版本号
-
+
Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.
地址 %1 的地址版本号 %2 无法被比特信理解。也许你应该升级你的比特信到最新版本。
-
+
Stream number
节点流序号
-
+
Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version.
地址 %1 的节点流序号 %2 无法被比特信理解。也许你应该升级你的比特信到最新版本。
-
+
Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won't send until you connect.
警告: 您尚未连接。 比特信将做足够的功来发送消息,但是消息不会被发出直到您连接。
-
+
Message queued.
信息排队。
-
+
Your 'To' field is empty.
“收件人"是空的。
-
+
Right click one or more entries in your address book and select 'Send message to this address'.
在您的地址本的一个条目上右击,之后选择”发送消息到这个地址“。
-
+
Fetched address from namecoin identity.
已经自namecoin接收了地址。
-
+
New Message
新消息
-
+
From
- 来自
+
-
+
Sending email gateway registration request
发送电子邮件网关注册请求
-
+
Address is valid.
地址有效。
-
+
The address you entered was invalid. Ignoring it.
您输入的地址是无效的,将被忽略。
-
+
Error: You cannot add the same address to your address book twice. Try renaming the existing one if you want.
错误:您无法将一个地址添加到您的地址本两次,请尝试重命名已经存在的那个。
-
+
Error: You cannot add the same address to your subscriptions twice. Perhaps rename the existing one if you want.
错误: 您不能在同一地址添加到您的订阅两次. 也许您可重命名现有之一.
-
+
Restart
重启
-
+
You must restart Bitmessage for the port number change to take effect.
您必须重启以便使比特信对于使用的端口的改变生效。
-
+
Bitmessage will use your proxy from now on but you may want to manually restart Bitmessage now to close existing connections (if any).
比特信将会从现在开始使用代理,但是您可能想手动重启比特信以便使之前的连接关闭(如果有的话)。
-
+
Number needed
需求数字
-
+
Your maximum download and upload rate must be numbers. Ignoring what you typed.
您最大的下载和上传速率必须是数字. 忽略您键入的内容.
-
+
Will not resend ever
不尝试再次发送
-
+
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.
请注意,您所输入的时间限制小于比特信的最小重试时间,因此您将永远不会重发消息。
-
+
Sending email gateway unregistration request
发送电子邮件网关注销请求
-
+
Sending email gateway status request
发送电子邮件网关状态请求
-
+
Passphrase mismatch
密钥不匹配
-
+
The passphrase you entered twice doesn't match. Try again.
您两次输入的密码并不匹配,请再试一次。
-
+
Choose a passphrase
选择一个密钥
-
+
You really do need a passphrase.
您真的需要一个密码。
-
+
Address is gone
已经失去了地址
-
+
Bitmessage cannot find your address %1. Perhaps you removed it?
比特信无法找到你的地址 %1。 也许你已经把它删掉了?
-
+
Address disabled
地址已经禁用
-
+
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.
错误: 您想以一个您已经禁用的地址发出消息。在使用之前您需要在“您的身份”处再次启用。
-
+
Entry added to the Address Book. Edit the label to your liking.
条目已经添加到地址本。您可以去修改您的标签。
-
+
Entry added to the blacklist. Edit the label to your liking.
条目添加到黑名单. 根据自己的喜好编辑标签.
-
+
Error: You cannot add the same address to your blacklist twice. Try renaming the existing one if you want.
错误: 您不能在同一地址添加到您的黑名单两次. 也许您可重命名现有之一.
-
+
Moved items to trash.
已经移动项目到回收站。
-
+
Undeleted item.
未删除的项目。
-
+
Save As...
另存为...
-
+
Write error.
写入失败。
-
+
No addresses selected.
没有选择地址。
-
+
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.
Are you sure you want to delete the subscription?
@@ -803,7 +803,7 @@ Are you sure you want to delete the subscription?
你确定要删除订阅?
-
+
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.
Are you sure you want to delete the channel?
@@ -812,92 +812,92 @@ Are you sure you want to delete the channel?
你确定要删除频道?
-
+
Do you really want to remove this avatar?
您真的想移除这个头像么?
-
+
You have already set an avatar for this address. Do you really want to overwrite it?
您已经为这个地址设置了头像了。您真的想移除么?
-
+
Start-on-login not yet supported on your OS.
登录时启动尚未支持您在使用的操作系统。
-
+
Minimize-to-tray not yet supported on your OS.
最小化到托盘尚未支持您的操作系统。
-
+
Tray notifications not yet supported on your OS.
托盘提醒尚未支持您所使用的操作系统。
-
+
Testing...
正在测试...
-
+
This is a chan address. You cannot use it as a pseudo-mailing list.
这是一个频道地址,您无法把它作为伪邮件列表。
-
+
The address should start with ''BM-''
地址应该以"BM-"开始
-
+
The address is not typed or copied correctly (the checksum failed).
地址没有被正确的键入或复制(校验码校验失败)。
-
+
The version number of this address is higher than this software can support. Please upgrade Bitmessage.
这个地址的版本号大于此软件的最大支持。 请升级比特信。
-
+
The address contains invalid characters.
这个地址中包含无效字符。
-
+
Some data encoded in the address is too short.
在这个地址中编码的部分信息过少。
-
+
Some data encoded in the address is too long.
在这个地址中编码的部分信息过长。
-
+
Some data encoded in the address is malformed.
在地址编码的某些数据格式不正确.
-
+
Enter an address above.
请在上方键入地址。
-
+
Address is an old type. We cannot display its past broadcasts.
地址没有近期的广播。我们无法显示之间的广播。
-
+
There are no recent broadcasts from this address to display.
没有可以显示的近期广播。
-
+
You are using TCP port %1. (This can be changed in the settings).
您正在使用TCP端口 %1 。(可以在设置中修改)。
@@ -1097,57 +1097,57 @@ Are you sure you want to delete the channel?
缩放级别%1%
-
+
Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want.
错误: 您不能在同一地址添加到列表中两次. 也许您可重命名现有之一.
-
+
Add new entry
添加新条目
-
+
Display the %1 recent broadcast(s) from this address.
显示从这个地址%1的最近广播
-
+
New version of PyBitmessage is available: %1. Download it from https://github.com/Bitmessage/PyBitmessage/releases/latest
PyBitmessage的新版本可用: %1. 从https://github.com/Bitmessage/PyBitmessage/releases/latest下载
-
+
Waiting for PoW to finish... %1%
等待PoW完成...%1%
-
+
Shutting down Pybitmessage... %1%
关闭Pybitmessage ...%1%
-
+
Waiting for objects to be sent... %1%
等待要发送对象...%1%
-
+
Saving settings... %1%
保存设置...%1%
-
+
Shutting down core... %1%
关闭核心...%1%
-
+
Stopping notifications... %1%
停止通知...%1%
-
+
Shutdown imminent... %1%
关闭即将来临...%1%
@@ -1157,42 +1157,42 @@ Are you sure you want to delete the channel?
%n 小时
-
+
%n day(s)
%n 天
-
+
Shutting down PyBitmessage... %1%
关闭PyBitmessage...%1%
-
+
Sent
发送
-
+
Generating one new address
生成一个新的地址
-
+
Done generating address. Doing work necessary to broadcast it...
完成生成地址. 做必要的工作, 以播放它...
-
+
Generating %1 new addresses.
生成%1个新地址.
-
+
%1 is already in 'Your Identities'. Not adding it again.
%1已经在'您的身份'. 不必重新添加.
-
+
Done generating address
完成生成地址
@@ -1202,229 +1202,328 @@ Are you sure you want to delete the channel?
-
+
Disk full
磁盘已满
-
+
Alert: Your disk or data storage volume is full. Bitmessage will now exit.
警告: 您的磁盘或数据存储量已满. 比特信将立即退出.
-
+
Error! Could not find sender address (your address) in the keys.dat file.
错误! 找不到在keys.dat 件发件人的地址 ( 您的地址).
-
+
Doing work necessary to send broadcast...
做必要的工作, 以发送广播...
-
+
Broadcast sent on %1
广播发送%1
-
+
Encryption key was requested earlier.
加密密钥已请求.
-
+
Sending a request for the recipient's encryption key.
发送收件人的加密密钥的请求.
-
+
Looking up the receiver's public key
展望接收方的公钥
-
+
Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1
问题: 目标是移动电话设备所请求的目的地包括在消息中, 但是这是在你的设置禁止. %1
-
+
Doing work necessary to send message.
There is no required difficulty for version 2 addresses like this.
做必要的工作, 以发送信息.
这样第2版的地址没有难度.
-
+
Doing work necessary to send message.
Receiver's required difficulty: %1 and %2
做必要的工作, 以发送短信.
接收者的要求难度: %1与%2
-
+
Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3
问题: 由接收者(%1%2)要求的工作量比您愿意做的工作量來得更困难. %3
-
+
Problem: You are trying to send a message to yourself or a chan but your encryption key could not be found in the keys.dat file. Could not encrypt message. %1
问题: 您正在尝试将信息发送给自己或频道, 但您的加密密钥无法在keys.dat文件中找到. 无法加密信息. %1
-
+
Doing work necessary to send message.
做必要的工作, 以发送信息.
-
+
Message sent. Waiting for acknowledgement. Sent on %1
信息发送. 等待确认. 已发送%1
-
+
Doing work necessary to request encryption key.
做必要的工作以要求加密密钥.
-
+
Broadcasting the public key request. This program will auto-retry if they are offline.
广播公钥请求. 这个程序将自动重试, 如果他们处于离线状态.
-
+
Sending public key request. Waiting for reply. Requested at %1
发送公钥的请求. 等待回复. 请求在%1
-
+
UPnP port mapping established on port %1
UPnP端口映射建立在端口%1
-
+
UPnP port mapping removed
UPnP端口映射被删除
-
+
Mark all messages as read
标记全部信息为已读
-
+
Are you sure you would like to mark all messages read?
确定将所有信息标记为已读吗?
-
+
Doing work necessary to send broadcast.
持续进行必要的工作,以发送广播。
-
+
Proof of work pending
待传输内容的校验
-
+
%n object(s) pending proof of work
%n 待传输内容校验任务
-
+
%n object(s) waiting to be distributed
%n 任务等待分配
-
+
Wait until these tasks finish?
等待所有任务执行完?
-
+
Problem communicating with proxy: %1. Please check your network settings.
与代理通信故障率:%1。请检查你的网络连接。
-
+
SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings.
SOCK5认证错误:%1。请检查你的SOCK5设置。
-
+
The time on your computer, %1, may be wrong. Please verify your settings.
你电脑上时间有误:%1。请检查你的设置。
-
+
+ The name %1 was not found.
+ 名字%1未找到。
+
+
+
+ The namecoin query failed (%1)
+ 域名币查询失败(%1)
+
+
+
+ The namecoin query failed.
+ 域名币查询失败。
+
+
+
+ The name %1 has no valid JSON data.
+ 名字%1没有有效地JSON数据。
+
+
+
+ The name %1 has no associated Bitmessage address.
+ 名字%1没有关联比特信地址。
+
+
+
+ Success! Namecoind version %1 running.
+ 成功!域名币系统%1运行中。
+
+
+
+ Success! NMControll is up and running.
+ 成功!域名币控制上线运行!
+
+
+
+ Couldn't understand NMControl.
+ 不能理解 NMControl。
+
+
+
+ Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.
+ 你的GPU(s)不能够正确计算,关闭OpenGL。请报告给开发者。
+
+
+
+ Set notification sound...
+ 设置通知提示音...
+
+
+
+
+ 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
+
+
+欢迎使用简便安全的比特信
+*发送信息给其他人
+*像推特那样发送广播信息
+*在频道(s)里和其他人讨论
+
+
+
+ not recommended for chans
+ 频道内不建议的内容
+
+
+
+ Quiet Mode
+ 静默模式
+
+
+
+ Problems connecting? Try enabling UPnP in the Network Settings
+ 连接问题?请尝试在网络设置里打开UPnP
+
+
+
+ You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?
+ 您将要尝试经由 Bitmessage 发送一封电子邮件。该操作需要在一个网关上注册。尝试注册?
+
+
+
Error: Bitmessage addresses start with BM- Please check the recipient address %1
错误:Bitmessage地址是以BM-开头的,请检查收信地址%1.
-
+
Error: The recipient address %1 is not typed or copied correctly. Please check it.
错误:收信地址%1未填写或复制错误。请检查。
-
+
Error: The recipient address %1 contains invalid characters. Please check it.
错误:收信地址%1还有非法字符。请检查。
-
+
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.
错误:收信地址%1版本太高。要么你需要更新你的软件,要么对方需要降级 。
-
+
Error: Some data encoded in the recipient address %1 is too short. There might be something wrong with the software of your acquaintance.
错误:收信地址%1编码数据太短。可能对方使用的软件有问题。
-
+
Error: Some data encoded in the recipient address %1 is too long. There might be something wrong with the software of your acquaintance.
错误:
-
+
Error: Some data encoded in the recipient address %1 is malformed. There might be something wrong with the software of your acquaintance.
错误:收信地址%1编码数据太长。可能对方使用的软件有问题。
-
+
Error: Something is wrong with the recipient address %1.
错误:收信地址%1有问题。
-
+
+ Error: %1
+ 错误:%1
+
+
+
+ From %1
+ 来自 %1
+
+
+
Synchronisation pending
待同步
-
+
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?
Bitmessage还没有与网络同步,%n 件任务需要下载。如果你现在退出软件,可能会造成传输延时。是否等同步完成?
-
+
Not connected
未连接成功。
-
+
Bitmessage isn't connected to the network. If you quit now, it may cause delivery delays. Wait until connected and the synchronisation finishes?
Bitmessage未连接到网络。如果现在退出软件,可能会造成传输延时。是否等待同步完成?
-
+
Waiting for network connection...
等待网络连接……
-
+
Waiting for finishing synchronisation...
等待同步完成……
+
+
+ You have already set a notification sound for this address book entry. Do you really want to overwrite it?
+
+
MessageView
@@ -1452,14 +1551,14 @@ Receiver's required difficulty: %1 and %2
MsgDecode
-
+
The message has an unknown encoding.
Perhaps you should upgrade Bitmessage.
这些消息使用了未知编码方式。
你可能需要更新Bitmessage软件。
-
+
Unknown encoding
未知编码
@@ -1617,27 +1716,27 @@ The 'Random Number' option is selected by default but deterministic ad
aboutDialog
-
+
About
关于
-
+
PyBitmessage
PyBitmessage
-
+
version ?
版本 ?
-
+
<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>
<html><head/><body><p>以 MIT/X11 软件授权发布; 详情参见 <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>
-
+
This is Beta software.
本软件处于Beta阶段。
@@ -1647,7 +1746,7 @@ The 'Random Number' option is selected by default but deterministic ad
-
+
<html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2016 The Bitmessage Developers</p></body></html>
<html><head/><body><p>版权:2012-2016 Jonathan Warren<br/>版权: 2013-2016 The Bitmessage Developers</p></body></html>
@@ -1680,12 +1779,12 @@ The 'Random Number' option is selected by default but deterministic ad
地址
-
+
Blacklist
黑名单
-
+
Whitelist
白名单
@@ -1767,77 +1866,77 @@ The 'Random Number' option is selected by default but deterministic ad
总连接:
-
+
Since startup:
自启动:
-
+
Processed 0 person-to-person messages.
处理0人对人的信息.
-
+
Processed 0 public keys.
处理0公钥。
-
+
Processed 0 broadcasts.
处理0广播.
-
+
Inventory lookups per second: 0
每秒库存查询: 0
-
+
Objects to be synced:
对象 已同步:
-
+
Stream #
数据流 #
Connections
- 连接
+
-
+
Since startup on %1
自从%1启动
-
+
Down: %1/s Total: %2
下: %1/秒 总计: %2
-
+
Up: %1/s Total: %2
上: %1/秒 总计: %2
-
+
Total Connections: %1
总的连接数: %1
-
+
Inventory lookups per second: %1
每秒库存查询: %1
-
+
Up: 0 kB/s
上载: 0 kB /秒
-
+
Down: 0 kB/s
下载: 0 kB /秒
@@ -1847,30 +1946,75 @@ The 'Random Number' option is selected by default but deterministic ad
网络状态
-
+
byte(s)
字节
-
+
Object(s) to be synced: %n
要同步的对象: %n
-
+
Processed %n person-to-person message(s).
处理%n人对人的信息.
-
+
Processed %n broadcast message(s).
处理%n广播信息.
-
+
Processed %n public key(s).
处理%n公钥.
+
+
+ Peer
+ 节点
+
+
+
+ IP address or hostname
+ IP地址或主机名
+
+
+
+ Rating
+ 评分
+
+
+
+ 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
+ PyBitmessage 跟踪连接尝试到各个节点的成功率。评分范围从 -1 到 1 ,这会影响到未来选择节点的可能性。
+
+
+
+ User agent
+ 用户代理
+
+
+
+ Peer's self-reported software
+ 节点自我报告的软件
+
+
+
+ TLS
+ TLS
+
+
+
+ Connection encryption
+ 连接加密
+
+
+
+ List of streams negotiated between you and the peer
+ 您和节点之间已协商的流列表
+
newChanDialog
@@ -1926,8 +2070,8 @@ The 'Random Number' option is selected by default but deterministic ad
- Chan passhphrase/name:
- 频道命名:
+ Chan passphrase/name:
+
@@ -1948,21 +2092,47 @@ The 'Random Number' option is selected by default but deterministic ad
newchandialog
-
+
Successfully created / joined chan %1
成功创建或加入频道%1
-
+
Chan creation / joining failed
频道创建或加入失败
-
+
Chan creation / joining cancelled
频道创建或加入已取消
+
+ proofofwork
+
+
+ C PoW module built successfully.
+ C PoW模块编译成功。
+
+
+
+ Failed to build C PoW module. Please build it manually.
+ 无法编译C PoW模块。请手动编译。
+
+
+
+ C PoW module unavailable. Please build it.
+ C PoW模块不可用。请编译它。
+
+
+
+ qrcodeDialog
+
+
+ QR-code
+ 二维码
+
+
regenerateAddressesDialog
@@ -2019,218 +2189,218 @@ The 'Random Number' option is selected by default but deterministic ad
settingsDialog
-
+
Settings
设置
-
+
Start Bitmessage on user login
在用户登录时启动比特信
-
+
Tray
任务栏
-
+
Start Bitmessage in the tray (don't show main window)
启动比特信到托盘 (不要显示主窗口)
-
+
Minimize to tray
最小化到托盘
-
+
Close to tray
关闭任务栏
-
+
Show notification when message received
在收到消息时提示
-
+
Run in Portable Mode
以便携方式运行
-
+
In Portable Mode, messages and config files are stored in the same directory as the program rather than the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive.
在便携模式下, 消息和配置文件和程序保存在同一个目录而不是通常的程序数据文件夹。 这使在U盘中允许比特信很方便。
-
+
Willingly include unencrypted destination address when sending to a mobile device
愿意在发送到手机时使用不加密的目标地址
-
+
Use Identicons
用户身份
-
+
Reply below Quote
回复 引述如下
-
+
Interface Language
界面语言
-
+
System Settings
system
系统设置
-
+
User Interface
用户界面
-
+
Listening port
监听端口
-
+
Listen for connections on port:
监听连接于端口:
-
+
UPnP:
UPnP:
-
+
Bandwidth limit
带宽限制
-
+
Maximum download rate (kB/s): [0: unlimited]
最大下载速率(kB/秒): [0: 无限制]
-
+
Maximum upload rate (kB/s): [0: unlimited]
最大上传速度 (kB/秒): [0: 无限制]
-
+
Proxy server / Tor
代理服务器 / Tor
-
+
Type:
类型:
-
+
Server hostname:
服务器主机名:
-
+
Port:
端口:
-
+
Authentication
认证
-
+
Username:
用户名:
-
+
Pass:
密码:
-
+
Listen for incoming connections when using proxy
在使用代理时仍然监听入站连接
-
+
none
无
-
+
SOCKS4a
SOCKS4a
-
+
SOCKS5
SOCKS5
-
+
Network Settings
网络设置
-
+
Total difficulty:
总难度:
-
+
The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work.
“总难度”影响发送者所需要的做工总数。当这个值翻倍时,做工的总数也翻倍。
-
+
Small message difficulty:
小消息难度:
-
+
When someone sends you a message, their computer must first complete some work. The difficulty of this work, by default, is 1. You may raise this default for new addresses you create by changing the values here. Any new addresses you create will require senders to meet the higher difficulty. There is one exception: if you add a friend or acquaintance to your address book, Bitmessage will automatically notify them when you next send a message that they need only complete the minimum amount of work: difficulty 1.
当一个人向您发送消息的时候, 他们的电脑必须先做工。这个难度的默认值是1,您可以在创建新的地址前提高这个值。任何新创建的地址都会要求更高的做工量。这里有一个例外,当您将您的朋友添加到地址本的时候,比特信将自动提示他们,当他们下一次向您发送的时候,他们需要的做功量将总是1.
-
+
The 'Small message difficulty' mostly only affects the difficulty of sending small messages. Doubling this value makes it almost twice as difficult to send a small message but doesn't really affect large messages.
“小消息困难度”几乎仅影响发送消息。当这个值翻倍时,发小消息时做工的总数也翻倍,但是并不影响大的消息。
-
+
Demanded difficulty
要求的难度
-
+
Here you may set the maximum amount of work you are willing to do to send a message to another person. Setting these values to 0 means that any value is acceptable.
你可以在这里设置您所愿意接受的发送消息的最大难度。0代表接受任何难度。
-
+
Maximum acceptable total difficulty:
最大接受难度:
-
+
Maximum acceptable small message difficulty:
最大接受的小消息难度:
-
+
Max acceptable difficulty
最大可接受难度
@@ -2240,82 +2410,87 @@ The 'Random Number' option is selected by default but deterministic ad
-
+
<html><head/><body><p>Bitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to <span style=" font-style:italic;">test. </span></p><p>(Getting your own Bitmessage address into Namecoin is still rather difficult).</p><p>Bitmessage can use either namecoind directly or a running nmcontrol instance.</p></body></html>
<html><head/><body><p>比特信可以利用基于比特币的Namecoin让地址更加友好。比如除了告诉您的朋友您的长长的比特信地址,您还可以告诉他们发消息给 <span style=" font-style:italic;">test. </span></p><p>把您的地址放入Namecoin还是相当的难的.</p><p>比特信可以不但直接连接到namecoin守护程序或者连接到运行中的nmcontrol实例.</p></body></html>
-
+
Host:
主机名:
-
+
Password:
密码:
-
+
Test
测试
-
+
Connect to:
连接到:
-
+
Namecoind
Namecoind
-
+
NMControl
NMControl
-
+
Namecoin integration
Namecoin整合
-
+
<html><head/><body><p>By default, if you send a message to someone and he is offline for more than two days, Bitmessage will send the message again after an additional two days. This will be continued with exponential backoff forever; messages will be resent after 5, 10, 20 days ect. until the receiver acknowledges them. Here you may change that behavior by having Bitmessage give up after a certain number of days or months.</p><p>Leave these input fields blank for the default behavior. </p></body></html>
<html><head/><body><p>您发给他们的消息默认会在网络上保存两天,之后比特信会再重发一次. 重发时间会随指数上升; 消息会在5, 10, 20... 天后重发并以此类推. 直到收到收件人的回执. 你可以在这里改变这一行为,让比特信在尝试一段时间后放弃.</p><p>留空意味着默认行为. </p></body></html>
-
+
Give up after
在
-
+
and
和
-
+
days
天
-
+
months.
月后放弃。
-
+
Resends Expire
重发超时
-
+
Hide connection notifications
隐藏连接通知
-
+
+ Maximum outbound connections: [0: none]
+ 最大外部连接:[0: 无]
+
+
+
Hardware GPU acceleration (OpenCL):
硬件GPU加速(OpenCL):
diff --git a/src/upnp.py b/src/upnp.py
index 6c245345..ad72560c 100644
--- a/src/upnp.py
+++ b/src/upnp.py
@@ -7,6 +7,7 @@ from struct import unpack, pack
import threading
import time
from bmconfigparser import BMConfigParser
+from network.connectionpool import BMConnectionPool
from helper_threading import *
import queues
import shared
@@ -179,7 +180,6 @@ class uPnPThread(threading.Thread, StoppableThread):
def __init__ (self):
threading.Thread.__init__(self, name="uPnPThread")
- self.localPort = BMConfigParser().getint('bitmessagesettings', 'port')
try:
self.extPort = BMConfigParser().getint('bitmessagesettings', 'extport')
except:
@@ -187,7 +187,7 @@ class uPnPThread(threading.Thread, StoppableThread):
self.localIP = self.getLocalIP()
self.routers = []
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- self.sock.bind((self.localIP, 10000))
+ self.sock.bind((self.localIP, 0))
self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
self.sock.settimeout(5)
self.sendSleep = 60
@@ -199,6 +199,17 @@ class uPnPThread(threading.Thread, StoppableThread):
logger.debug("Starting UPnP thread")
logger.debug("Local IP: %s", self.localIP)
lastSent = 0
+
+ # wait until asyncore binds so that we know the listening port
+ bound = False
+ while state.shutdown == 0 and not self._stopped and not bound:
+ for s in BMConnectionPool().listeningSockets.values():
+ if s.is_bound():
+ bound = True
+ if not bound:
+ time.sleep(1)
+
+ self.localPort = BMConfigParser().getint('bitmessagesettings', 'port')
while state.shutdown == 0 and BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp'):
if time.time() - lastSent > self.sendSleep and len(self.routers) == 0:
try:
diff --git a/src/version.py b/src/version.py
index bd06ca9b..076b8c56 100644
--- a/src/version.py
+++ b/src/version.py
@@ -1 +1,2 @@
-softwareVersion = '0.6.2'
+softwareName = 'PyBitmessage'
+softwareVersion = '0.6.3.2'