diff --git a/.buildbot/android/Dockerfile b/.buildbot/android/Dockerfile deleted file mode 100755 index 2d722834..00000000 --- a/.buildbot/android/Dockerfile +++ /dev/null @@ -1,102 +0,0 @@ -# A container for buildbot - -FROM ubuntu:focal AS android - -ENV DEBIAN_FRONTEND=noninteractive -ENV ANDROID_HOME="/opt/android" - -RUN apt-get update -qq > /dev/null \ - && apt-get -y install -qq --no-install-recommends locales \ - && locale-gen en_US.UTF-8 -ENV LANG="en_US.UTF-8" \ - LANGUAGE="en_US.UTF-8" \ - LC_ALL="en_US.UTF-8" - -# install system/build dependencies -RUN apt-get -y update -qq \ - && apt-get -y install -qq --no-install-recommends \ - curl autoconf automake build-essential cmake git nano libtool \ - libltdl-dev libffi-dev libssl-dev \ - patch pkg-config python-is-python3 python3-dev python3-pip unzip zip - -RUN apt-get -y install -qq --no-install-recommends openjdk-17-jdk \ - && apt-get -y autoremove - -RUN pip install pip install buildozer cython virtualenv - - -ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk" -ENV ANDROID_NDK_VERSION="25b" -ENV ANDROID_NDK_HOME_V="${ANDROID_NDK_HOME}-r${ANDROID_NDK_VERSION}" - -# get the latest version from https://developer.android.com/ndk/downloads/index.html -ENV ANDROID_NDK_ARCHIVE="android-ndk-r${ANDROID_NDK_VERSION}-linux.zip" -ENV ANDROID_NDK_DL_URL="https://dl.google.com/android/repository/${ANDROID_NDK_ARCHIVE}" -# download and install Android NDK -RUN curl "${ANDROID_NDK_DL_URL}" --output "${ANDROID_NDK_ARCHIVE}" \ - && mkdir -p "${ANDROID_NDK_HOME_V}" \ - && unzip -q "${ANDROID_NDK_ARCHIVE}" -d "${ANDROID_HOME}" \ - && ln -sfn "${ANDROID_NDK_HOME_V}" "${ANDROID_NDK_HOME}" \ - && rm -rf "${ANDROID_NDK_ARCHIVE}" - -ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk" - -# get the latest version from https://developer.android.com/studio/index.html -ENV ANDROID_SDK_TOOLS_VERSION="11076708" -ENV ANDROID_SDK_BUILD_TOOLS_VERSION="34.0.0" -ENV ANDROID_SDK_CMDLINE_TOOLS_VERSION="12.0" -ENV ANDROID_SDK_TOOLS_ARCHIVE="commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip" -ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}" -ENV ANDROID_CMDLINE_TOOLS_DIR="${ANDROID_SDK_HOME}/cmdline-tools/${ANDROID_SDK_CMDLINE_TOOLS_VERSION}" -ENV ANDROID_SDK_MANAGER="${ANDROID_CMDLINE_TOOLS_DIR}/bin/sdkmanager --sdk_root=${ANDROID_SDK_HOME}" - -# download and install Android SDK -RUN curl "${ANDROID_SDK_TOOLS_DL_URL}" --output "${ANDROID_SDK_TOOLS_ARCHIVE}" \ - && mkdir -p "${ANDROID_SDK_HOME}/cmdline-tools" \ - && unzip -q "${ANDROID_SDK_TOOLS_ARCHIVE}" \ - -d "${ANDROID_SDK_HOME}/cmdline-tools" \ - && mv "${ANDROID_SDK_HOME}/cmdline-tools/cmdline-tools" \ - ${ANDROID_CMDLINE_TOOLS_DIR} \ - && ln -sfn ${ANDROID_CMDLINE_TOOLS_DIR} "${ANDROID_SDK_HOME}/tools" \ - && rm -rf "${ANDROID_SDK_TOOLS_ARCHIVE}" - -# update Android SDK, install Android API, Build Tools... -RUN mkdir -p "${ANDROID_SDK_HOME}/.android/" \ - && echo '### User Sources for Android SDK Manager' \ - > "${ANDROID_SDK_HOME}/.android/repositories.cfg" - -# accept Android licenses (JDK necessary!) -RUN yes | ${ANDROID_SDK_MANAGER} --licenses > /dev/null - -# download platforms, API, build tools -RUN ${ANDROID_SDK_MANAGER} "platforms;android-30" > /dev/null \ - && ${ANDROID_SDK_MANAGER} "platforms;android-28" > /dev/null \ - && ${ANDROID_SDK_MANAGER} "platform-tools" > /dev/null \ - && ${ANDROID_SDK_MANAGER} "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" \ - > /dev/null \ - && ${ANDROID_SDK_MANAGER} "extras;android;m2repository" > /dev/null \ - && chmod +x "${ANDROID_CMDLINE_TOOLS_DIR}/bin/avdmanager" - -# download ANT -ENV APACHE_ANT_VERSION="1.9.4" -ENV APACHE_ANT_ARCHIVE="apache-ant-${APACHE_ANT_VERSION}-bin.tar.gz" -ENV APACHE_ANT_DL_URL="https://archive.apache.org/dist/ant/binaries/${APACHE_ANT_ARCHIVE}" -ENV APACHE_ANT_HOME="${ANDROID_HOME}/apache-ant" -ENV APACHE_ANT_HOME_V="${APACHE_ANT_HOME}-${APACHE_ANT_VERSION}" - -RUN curl "${APACHE_ANT_DL_URL}" --output "${APACHE_ANT_ARCHIVE}" \ - && tar -xf "${APACHE_ANT_ARCHIVE}" -C "${ANDROID_HOME}" \ - && ln -sfn "${APACHE_ANT_HOME_V}" "${APACHE_ANT_HOME}" \ - && rm -rf "${APACHE_ANT_ARCHIVE}" - - -RUN useradd -m -U builder && mkdir /android - -WORKDIR /android - -RUN chown -R builder.builder /android "${ANDROID_SDK_HOME}" \ - && chmod -R go+w "${ANDROID_SDK_HOME}" - -USER builder - -ADD . . diff --git a/.buildbot/android/build.sh b/.buildbot/android/build.sh deleted file mode 100755 index 10176ef3..00000000 --- a/.buildbot/android/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -export LC_ALL=en_US.UTF-8 -export LANG=en_US.UTF-8 -pushd packages/android -buildozer android debug || exit $? -popd - -mkdir -p ../out -cp packages/android/bin/*.apk ../out diff --git a/.buildbot/android/test.sh b/.buildbot/android/test.sh deleted file mode 100755 index b61fac85..00000000 --- a/.buildbot/android/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -unzip -p packages/android/bin/*.apk assets/private.tar \ - | tar --list -z > package.list -cat package.list -cat package.list | grep '\.sql$' || exit 1 diff --git a/.buildbot/appimage/Dockerfile b/.buildbot/appimage/Dockerfile deleted file mode 100644 index 6c2b87d5..00000000 --- a/.buildbot/appimage/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM ubuntu:bionic - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - ca-certificates software-properties-common \ - build-essential libcap-dev libssl-dev \ - python-all-dev python-setuptools wget \ - git gtk-update-icon-cache \ - binutils-multiarch crossbuild-essential-armhf crossbuild-essential-arm64 - -RUN dpkg --add-architecture armhf -RUN dpkg --add-architecture arm64 - -RUN sed -iE "s|deb |deb [arch=amd64] |g" /etc/apt/sources.list \ - && echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" >> /etc/apt/sources.list \ - && echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" >> /etc/apt/sources.list - -RUN apt-get update | true - -RUN apt-get install -yq libssl-dev:armhf libssl-dev:arm64 - -RUN wget -qO appimage-builder-x86_64.AppImage \ - https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage - -ADD . . diff --git a/.buildbot/appimage/build.sh b/.buildbot/appimage/build.sh deleted file mode 100755 index c592c1e8..00000000 --- a/.buildbot/appimage/build.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -export APPIMAGE_EXTRACT_AND_RUN=1 -BUILDER=appimage-builder-x86_64.AppImage -RECIPE=packages/AppImage/AppImageBuilder.yml - -export APP_VERSION=$(git describe --tags | cut -d- -f1,3 | tr -d v) - -function set_sourceline { - if [ ${ARCH} == amd64 ]; then - export SOURCELINE="deb http://archive.ubuntu.com/ubuntu/ bionic main universe" - else - export SOURCELINE="deb [arch=${ARCH}] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" - fi -} - -[ -f ${BUILDER} ] || wget -qO ${BUILDER} \ - https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage \ - && chmod +x ${BUILDER} - - -export ARCH=amd64 -export APPIMAGE_ARCH=x86_64 -export RUNTIME=${APPIMAGE_ARCH} -set_sourceline - -./${BUILDER} --recipe ${RECIPE} || exit 1 - -export ARCH=armhf -export APPIMAGE_ARCH=${ARCH} -export RUNTIME=gnueabihf -export CC=arm-linux-gnueabihf-gcc -export CXX=${CC} -set_sourceline - -./${BUILDER} --recipe ${RECIPE} || exit 1 - -export ARCH=arm64 -export APPIMAGE_ARCH=aarch64 -export RUNTIME=${APPIMAGE_ARCH} -export CC=aarch64-linux-gnu-gcc -export CXX=${CC} -set_sourceline - -./${BUILDER} --recipe ${RECIPE} - -mkdir -p ../out -sha256sum PyBitmessage*.AppImage > ../out/SHA256SUMS -cp PyBitmessage*.AppImage ../out diff --git a/.buildbot/appimage/test.sh b/.buildbot/appimage/test.sh deleted file mode 100755 index 871fc83a..00000000 --- a/.buildbot/appimage/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -export APPIMAGE_EXTRACT_AND_RUN=1 - -chmod +x PyBitmessage-*-x86_64.AppImage -./PyBitmessage-*-x86_64.AppImage -t diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile deleted file mode 100644 index 10b1e569..00000000 --- a/.buildbot/kivy/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -# A container for buildbot -FROM ubuntu:focal AS kivy - -ENV DEBIAN_FRONTEND=noninteractive - -ENV SKIPCACHE=2022-08-29 - -RUN apt-get update - -RUN apt-get install -yq \ - build-essential libcap-dev libssl-dev \ - libmtdev-dev libpq-dev \ - python3-dev python3-pip python3-virtualenv \ - xvfb ffmpeg xclip xsel - -RUN ln -sf /usr/bin/python3 /usr/bin/python - -RUN pip3 install --upgrade setuptools pip diff --git a/.buildbot/kivy/build.sh b/.buildbot/kivy/build.sh deleted file mode 100755 index 87aae8f7..00000000 --- a/.buildbot/kivy/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -pip3 install -r kivy-requirements.txt - -export INSTALL_TESTS=True - -pip3 install . diff --git a/.buildbot/kivy/test.sh b/.buildbot/kivy/test.sh deleted file mode 100755 index 3231f250..00000000 --- a/.buildbot/kivy/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -export INSTALL_TESTS=True - -xvfb-run --server-args="-screen 0, 720x1280x24" python3 tests-kivy.py diff --git a/.buildbot/snap/Dockerfile b/.buildbot/snap/Dockerfile deleted file mode 100644 index 7fde093d..00000000 --- a/.buildbot/snap/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM ubuntu:bionic - -ENV SKIPCACHE=2022-07-17 - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends snapcraft diff --git a/.buildbot/snap/build.sh b/.buildbot/snap/build.sh deleted file mode 100755 index 5d65a745..00000000 --- a/.buildbot/snap/build.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -pushd packages && snapcraft || exit 1 - -popd -mkdir -p ../out -mv packages/pybitmessage*.snap ../out -cd ../out -sha256sum pybitmessage*.snap > SHA256SUMS diff --git a/.buildbot/tox-bionic/Dockerfile b/.buildbot/tox-bionic/Dockerfile deleted file mode 100644 index 5cc36b7f..00000000 --- a/.buildbot/tox-bionic/Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM ubuntu:bionic - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools \ - python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ - python-msgpack python-qt4 language-pack-en qt5dxcb-plugin tor xvfb - -RUN apt-get install -yq sudo - -RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers - -RUN python3.8 -m pip install setuptools wheel -RUN python3.8 -m pip install --upgrade pip tox virtualenv - -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 diff --git a/.buildbot/tox-bionic/build.sh b/.buildbot/tox-bionic/build.sh deleted file mode 100755 index 87f670ce..00000000 --- a/.buildbot/tox-bionic/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -sudo service tor start diff --git a/.buildbot/tox-bionic/test.sh b/.buildbot/tox-bionic/test.sh deleted file mode 100755 index b280953a..00000000 --- a/.buildbot/tox-bionic/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -tox -e lint-basic || exit 1 -tox diff --git a/.buildbot/tox-focal/Dockerfile b/.buildbot/tox-focal/Dockerfile deleted file mode 100644 index c2ba0ffc..00000000 --- a/.buildbot/tox-focal/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM ubuntu:focal - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools \ - python3-dev python3-pip python3.9 python3.9-dev python3.9-venv \ - language-pack-en qt5dxcb-plugin tor xvfb - -RUN python3.9 -m pip install --upgrade pip tox virtualenv - -ADD . . diff --git a/.buildbot/tox-focal/test.sh b/.buildbot/tox-focal/test.sh deleted file mode 120000 index a9f8525c..00000000 --- a/.buildbot/tox-focal/test.sh +++ /dev/null @@ -1 +0,0 @@ -../tox-bionic/test.sh \ No newline at end of file diff --git a/.buildbot/tox-jammy/Dockerfile b/.buildbot/tox-jammy/Dockerfile deleted file mode 100644 index 8ca63aa0..00000000 --- a/.buildbot/tox-jammy/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM ubuntu:jammy - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-is-python3 python-setuptools \ - python3-dev python3-pip language-pack-en qt5dxcb-plugin tor xvfb - -RUN pip install tox diff --git a/.buildbot/tox-jammy/test.sh b/.buildbot/tox-jammy/test.sh deleted file mode 100755 index 3de38252..00000000 --- a/.buildbot/tox-jammy/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -tox -e lint-basic # || exit 1 -tox -e py310 diff --git a/.buildbot/winebuild/Dockerfile b/.buildbot/winebuild/Dockerfile deleted file mode 100644 index 9b687f8f..00000000 --- a/.buildbot/winebuild/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM ubuntu:bionic - -ENV DEBIAN_FRONTEND=noninteractive - -RUN dpkg --add-architecture i386 - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools xvfb \ - mingw-w64 wine-stable winetricks wine32 wine64 - -ADD . . diff --git a/.buildbot/winebuild/build.sh b/.buildbot/winebuild/build.sh deleted file mode 100755 index fdf5bedc..00000000 --- a/.buildbot/winebuild/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -xvfb-run -a buildscripts/winbuild.sh || exit 1 - -mkdir -p ../out -mv packages/pyinstaller/dist/Bitmessage*.exe ../out -cd ../out -sha256sum Bitmessage*.exe > SHA256SUMS diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 38a67283..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -FROM ubuntu:latest - -ARG USERNAME=user -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -RUN apt-get update -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y \ - curl \ - flake8 \ - gh \ - git \ - gnupg2 \ - jq \ - libcap-dev \ - libssl-dev \ - pylint \ - python-setuptools \ - python2.7 \ - python2.7-dev \ - python3 \ - python3-dev \ - python3-flake8 \ - python3-pip \ - python3-pycodestyle \ - software-properties-common \ - sudo \ - zsh - -RUN apt-add-repository ppa:deadsnakes/ppa - -RUN pip install 'tox<4' 'virtualenv<20.22.0' - -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - && chsh -s /usr/bin/zsh user \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME - -USER $USERNAME -WORKDIR /home/$USERNAME diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 91a470aa..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Codespaces Python3", - "extensions": [ - "cschleiden.vscode-github-actions", - "eamodio.gitlens", - "github.vscode-pull-request-github", - "ms-azuretools.vscode-docker", - "ms-python.flake8", - "ms-python.pylint", - "ms-python.python", - "ms-vsliveshare.vsliveshare", - "nwgh.bandit", - "the-compiler.python-tox", - "vscode-icons-team.vscode-icons", - "visualstudioexptteam.vscodeintellicode" - ], - "dockerFile": "Dockerfile", - "postCreateCommand": "pip3 install -r requirements.txt", - "updateContentCommand": "python2.7 setup.py install --user", - "remoteEnv": { - "PATH": "${containerEnv:PATH}:/home/user/.local/bin" - }, - "settings": { - "flake8.args": ["--config=setup.cfg"], - "pylint.args": ["--rcfile=setup.cfg"], - "terminal.integrated.shell.linux": "/usr/bin/zsh", - "terminal.integrated.defaultProfile.linux": "zsh", - "terminal.integrated.fontFamily": "'SourceCodePro+Powerline+Awesome Regular'", - "terminal.integrated.fontSize": 14, - "files.exclude": { - "**/CODE_OF_CONDUCT.md": true, - "**/LICENSE": true - } - } -} diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 77d72c64..00000000 --- a/.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -bin -build -dist -__pycache__ -.buildozer -.tox -mprofile_* diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 776a13c1..00000000 --- a/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Pickle files (for testing) should always have UNIX line endings. -# Windows issue like here https://stackoverflow.com/questions/556269 -knownnodes.dat text eol=lf diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index fb735a84..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,30 +0,0 @@ -## Repository contributions to the PyBitmessage project - -### Code - -- Try to refer to github issue tracker or other permanent sources of discussion about the issue. -- It is clear from the diff *what* you have done, it may be less clear *why* you have done it so explain why this change is necessary rather than what it does. - -### Documentation - -Use `tox -e py27-doc` to build a local copy of the documentation. - -### Tests - -- If there has been a change to the code, there's a good possibility there should be a corresponding change to the tests -- To run tests locally use `tox` or `./run-tests-in-docker.sh` - -## 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). - -### Gitiquette - -- Make the pull request against the ["v0.6" branch](https://github.com/Bitmessage/PyBitmessage/tree/v0.6) -- PGP-sign the commits included in the pull request -- Use references to tickets, e.g. `addresses #123` or `fixes #234` in your commit messages -- Try to use a good editor that removes trailing whitespace, highlights potential python issues and uses unix line endings -- 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. - diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 415583a9..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -# Basic dependabot.yml for kivymd -version: 2 -updates: - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "daily" diff --git a/.gitignore b/.gitignore index fc331499..a11f6fad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,15 @@ **pyc +**dat **.DS_Store src/build src/dist -src/.eggs src/.project src/.pydevproject src/.settings/ src/**/.dll src/**/*.o src/**/*.so -src/**/a.out build/lib.* build/temp.* -bin dist *.egg-info -docs/_*/* -docs/autodoc/ -build -pyan/ -**.coverage -coverage.xml -**htmlcov* -**coverage.json -.buildozer -.tox diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index 136ef6e9..00000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 2 - -build: - os: ubuntu-20.04 - tools: - python: "2.7" - -python: - install: - - requirements: docs/requirements.txt - - method: pip - path: . diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9ecb65ba..00000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: python -cache: pip -dist: bionic -python: - - "2.7_with_system_site_packages" - - "3.7" -addons: - apt: - packages: - - build-essential - - libcap-dev - - python-qt4 - - python-pyqt5 - - tor - - xvfb -install: - - pip install -r requirements.txt - - python setup.py install - - export PYTHONWARNINGS=all -script: - - python checkdeps.py - - python src/bitmessagemain.py -t - - python -bm tests diff --git a/COPYING b/COPYING index 279cef2a..4fe0f2b0 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ Copyright (c) 2012-2016 Jonathan Warren -Copyright (c) 2012-2022 The Bitmessage Developers +Copyright (c) 2013-2016 The Bitmessage Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b409d27a..00000000 --- a/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# A container for PyBitmessage daemon - -FROM ubuntu:bionic - -RUN apt-get update - -# Install dependencies -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - build-essential libcap-dev libssl-dev \ - python-all-dev python-msgpack python-pip python-setuptools - -EXPOSE 8444 8442 - -ENV HOME /home/bitmessage -ENV BITMESSAGE_HOME ${HOME} - -WORKDIR ${HOME} -ADD . ${HOME} -COPY packages/docker/launcher.sh /usr/bin/ - -# Install -RUN pip2 install jsonrpclib . - -# Cleanup -RUN rm -rf /var/lib/apt/lists/* -RUN rm -rf ${HOME} - -# Create a user -RUN useradd -r bitmessage && chown -R bitmessage ${HOME} - -USER bitmessage - -# Generate default config -RUN pybitmessage -t - -ENTRYPOINT ["launcher.sh"] -CMD ["-d"] diff --git a/INSTALL.md b/INSTALL.md index 7942a957..e66915fd 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,86 +1,103 @@ # PyBitmessage Installation Instructions -- Binary (64bit, no separate installation of dependencies required) - - Windows: https://download.bitmessage.org/snapshots/ - - Linux AppImages: https://artifacts.bitmessage.at/appimage/ - - Linux snaps: https://artifacts.bitmessage.at/snap/ - - Mac (not up to date): https://github.com/Bitmessage/PyBitmessage/releases/tag/v0.6.1 -- Source - `git clone git://github.com/Bitmessage/PyBitmessage.git` -## Helper Script for building from source -Go to the directory with PyBitmessage source code and run: -``` -python checkdeps.py -``` -If there are missing dependencies, it will explain you what is missing -and for many Unix-like systems also what you have to do to resolve it. You need -to repeat calling the script until you get nothing mandatory missing. How you -then run setuptools depends on whether you want to install it to -user's directory or system. +For an up-to-date version of these instructions, please visit the +[Bitmessage Wiki](https://bitmessage.org/wiki/Compiling_instructions). -### If checkdeps fails, then verify manually which dependencies are missing from below +PyBitmessage can be run either straight from source or from an installed +package. + +## Dependencies Before running PyBitmessage, make sure you have all the necessary dependencies installed on your system. -These dependencies may not be available on a recent OS and PyBitmessage may not -build on such systems. Here's a list of dependencies needed for PyBitmessage -based on operating system +Here's a list of dependencies needed for PyBitmessage +- python2.7 +- python2-qt4 (python-qt4 on Debian/Ubuntu) +- openssl +- (Fedora & Redhat only) openssl-compat-bitcoin-libs -For Debian-based (Ubuntu, Raspbian, PiBang, others) +## 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. + +Under Linux/Uni* just do "make run" and PyMessage will be started from source +code without changing to the right path by yourself. + +#### Updating +To update PyBitmessage from source (Linux/OS X), you can do these easy steps: ``` -python2.7 openssl libssl-dev python-msgpack python-qt4 python-six +cd PyBitmessage/src/ +git fetch --all +git reset --hard origin/master +python bitmessagemain.py ``` -For Arch Linux +Voilà! Bitmessage is updated! + +#### Linux +To run PyBitmessage from the command-line, you must download the source, then +run `src/bitmessagemain.py`. ``` -python2 openssl python2-pyqt4 python-six -``` -For Fedora -``` -python python-qt4 openssl-compat-bitcoin-libs python-six -``` -For Red Hat Enterprise Linux (RHEL) -``` -python python-qt4 openssl-compat-bitcoin-libs python-six -``` -For GNU Guix -``` -python2-msgpack python2-pyqt@4.11.4 python2-sip openssl python-six +git clone git://github.com/Bitmessage/PyBitmessage.git +cd PyBitmessage/ && python src/bitmessagemain.py ``` -## setuptools -This is now the recommended and in most cases the easiest way for -installing PyBitmessage. +That's it! *Honestly*! -There are 2 options for installing with setuptools: root and user. +#### Windows +On Windows you can download an executable for Bitmessage +[here](https://bitmessage.org/download/windows/Bitmessage.exe). -### as root: +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 +First off, install Homebrew. ``` -python setup.py install -pybitmessage +ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ``` -### as user: +Now, install the required dependencies ``` -python setup.py install --user -~/.local/bin/pybitmessage +brew install git python pyqt ``` -## pip venv (daemon): -Create virtualenv with Python 2.x version +Download and run PyBitmessage: ``` -virtualenv -p python2 env +git clone git://github.com/Bitmessage/PyBitmessage.git +cd PyBitmessage && python src/bitmessagemain.py ``` -Activate env +## 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 +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. + +Next, edit the generate.sh script to your liking. + +Now, run the appropriate script for the type of package you'd like to make ``` -source env/bin/activate +arch.sh - create a package for Arch Linux +debian.sh - create a package for Debian/Ubuntu +ebuild.sh - create a package for Gentoo +osx.sh - create a package for OS X +puppy.sh - create a package for Puppy Linux +rpm.sh - create a RPM package +slack.sh - create a package for Slackware ``` -Build & run pybitmessage -``` -pip install . -pybitmessage -d -``` +#### 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. -## Alternative way to run PyBitmessage, without setuptools (this isn't recommended) -run `./start.sh`. +Please note that some versions of OS X don't work. + +#### Windows +## TODO: Create Windows package creation instructions diff --git a/LICENSE b/LICENSE index fd772201..d2afc3c4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) Copyright (c) 2012-2016 Jonathan Warren -Copyright (c) 2012-2022 The Bitmessage Developers +Copyright (c) 2013-2016 The Bitmessage Developers 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 @@ -19,76 +19,3 @@ 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. - -===== qidenticon.py identicon python implementation with QPixmap output by sendiulo - -qidenticon.py is Licensed under FreeBSD License. -(http://www.freebsd.org/copyright/freebsd-license.html) - -Copyright 2013 "Sendiulo". All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -===== based on identicon.py identicon python implementation. by Shin Adachi - -identicon.py is Licensed under FreeBSD License. -(http://www.freebsd.org/copyright/freebsd-license.html) - -Copyright 1994-2009 Shin Adachi. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -===== based on asyncore_pollchoose.py asyncore_pollchoose python implementation. by 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. - -===== based on namecoin.py namecoin.py python implementation by Daniel Kraft - -Copyright (C) 2013 by Daniel Kraft - -This file is part of the Bitmessage project. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in index 15a6bf81..2d29971b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,3 @@ include COPYING include README.md -include requirements.txt recursive-include desktop * -recursive-include packages/apparmor * 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 06c97c01..0ea6144b 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,27 @@ PyBitmessage ============ -Bitmessage is a P2P communication protocol used to send encrypted messages to +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. BM aims to hide metadata from passive eavesdroppers -like those ongoing warrantless wiretapping programs. Hence the sender and receiver -of Bitmessages stay anonymous. +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. Development ---------- Bitmessage is a collaborative project. You are welcome to submit pull requests although if you plan to put a non-trivial amount of work into coding new -features, it is recommended that you first describe your ideas in the -separate issue. +features, it is recommended that you first solicit feedback on the DevTalk +pseudo-mailing list: +BM-2D9QKN4teYRvoq2fyzpiftPh9WP9qggtzh -Feel welcome to join chan "bitmessage", BM-2cWy7cvHoq3f1rYMerRJp8PT653jjSuEdY References ---------- * [Project Website](https://bitmessage.org) -* [Protocol Specification](https://pybitmessage.rtfd.io/en/v0.6/protocol.html) +* [Protocol Specification](https://bitmessage.org/wiki/Protocol_specification) * [Whitepaper](https://bitmessage.org/bitmessage.pdf) * [Installation](https://bitmessage.org/wiki/Compiling_instructions) -* [Discuss on Reddit](https://www.reddit.com/r/bitmessage) -* [Chat on Gitter](https://gitter.im/Bitmessage/PyBitmessage) - diff --git a/buildscripts/README.md b/build/README.md similarity index 100% rename from buildscripts/README.md rename to build/README.md diff --git a/build/changelang.sh b/build/changelang.sh new file mode 100755 index 00000000..915c5dea --- /dev/null +++ b/build/changelang.sh @@ -0,0 +1,16 @@ +export LANG=de_DE.UTF-8 +export LANGUAGE=de_DE +export LC_CTYPE="de_DE.UTF-8" +export LC_NUMERIC=de_DE.UTF-8 +export LC_TIME=de_DE.UTF-8 +export LC_COLLATE="de_DE.UTF-8" +export LC_MONETARY=de_DE.UTF-8 +export LC_MESSAGES="de_DE.UTF-8" +export LC_PAPER=de_DE.UTF-8 +export LC_NAME=de_DE.UTF-8 +export LC_ADDRESS=de_DE.UTF-8 +export LC_TELEPHONE=de_DE.UTF-8 +export LC_MEASUREMENT=de_DE.UTF-8 +export LC_IDENTIFICATION=de_DE.UTF-8 +export LC_ALL= +python2.7 src/bitmessagemain.py diff --git a/build/compiletest.py b/build/compiletest.py new file mode 100755 index 00000000..fdbf7db1 --- /dev/null +++ b/build/compiletest.py @@ -0,0 +1,23 @@ +#!/usr/bin/python2.7 + +import ctypes +import fnmatch +import os +import sys +import traceback + +matches = [] +for root, dirnames, filenames in os.walk('src'): + for filename in fnmatch.filter(filenames, '*.py'): + matches.append(os.path.join(root, filename)) + +for filename in matches: + source = open(filename, 'r').read() + '\n' + try: + compile(source, filename, 'exec') + except Exception as e: + if 'win' in sys.platform: + ctypes.windll.user32.MessageBoxA(0, traceback.format_exc(), "Exception in " + filename, 1) + else: + print "Exception in %s: %s" % (filename, traceback.format_exc()) + sys.exit(1) diff --git a/build/mergepullrequest.sh b/build/mergepullrequest.sh new file mode 100755 index 00000000..35e87566 --- /dev/null +++ b/build/mergepullrequest.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ -z "$1" ]; then + echo "You must specify pull request number" + exit +fi + +git pull +git checkout v0.6 +git fetch origin pull/"$1"/head:"$1" +git merge --ff-only "$1" diff --git a/buildscripts/osx.sh b/build/osx.sh similarity index 100% rename from buildscripts/osx.sh rename to build/osx.sh diff --git a/buildscripts/updatetranslations.sh b/build/updatetranslations.sh similarity index 100% rename from buildscripts/updatetranslations.sh rename to build/updatetranslations.sh diff --git a/buildscripts/androiddev.sh b/buildscripts/androiddev.sh deleted file mode 100755 index 1634d4c0..00000000 --- a/buildscripts/androiddev.sh +++ /dev/null @@ -1,112 +0,0 @@ -#!/bin/sh - -ANDROID_HOME="/opt/android" -get_python_version=3 - -# INSTALL ANDROID PACKAGES -install_android_pkg () -{ - BUILDOZER_VERSION=1.2.0 - CYTHON_VERSION=0.29.15 - pip3 install buildozer==$BUILDOZER_VERSION - pip3 install --upgrade cython==$CYTHON_VERSION -} - -# SYSTEM DEPENDENCIES -system_dependencies () -{ - apt -y update -qq - apt -y install --no-install-recommends python3-pip pip3 python3 virtualenv python3-setuptools python3-wheel git wget unzip sudo patch bzip2 lzma - apt -y autoremove -} - -# build dependencies -# https://buildozer.readthedocs.io/en/latest/installation.html#android-on-ubuntu-16-04-64bit -build_dependencies () -{ - dpkg --add-architecture i386 - apt -y update -qq - apt -y install -qq --no-install-recommends build-essential ccache git python3 python3-dev libncurses5:i386 libstdc++6:i386 libgtk2.0-0:i386 libpangox-1.0-0:i386 libpangoxft-1.0-0:i386 libidn11:i386 zip zlib1g-dev zlib1g:i386 - apt -y autoremove - apt -y clean -} - -# RECIPES DEPENDENCIES -specific_recipes_dependencies () -{ - dpkg --add-architecture i386 - apt -y update -qq - apt -y install -qq --no-install-recommends libffi-dev autoconf automake cmake gettext libltdl-dev libtool pkg-config - apt -y autoremove - apt -y clean -} - -# INSTALL NDK -install_ndk() -{ - ANDROID_NDK_VERSION=23b - ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk" - ANDROID_NDK_HOME_V="${ANDROID_NDK_HOME}-r${ANDROID_NDK_VERSION}" - # get the latest version from https://developer.android.com/ndk/downloads/index.html - ANDROID_NDK_ARCHIVE="android-ndk-r${ANDROID_NDK_VERSION}-linux.zip" - ANDROID_NDK_DL_URL="https://dl.google.com/android/repository/${ANDROID_NDK_ARCHIVE}" - wget -nc ${ANDROID_NDK_DL_URL} - mkdir --parents "${ANDROID_NDK_HOME_V}" - unzip -q "${ANDROID_NDK_ARCHIVE}" -d "${ANDROID_HOME}" - ln -sfn "${ANDROID_NDK_HOME_V}" "${ANDROID_NDK_HOME}" - rm -rf "${ANDROID_NDK_ARCHIVE}" -} - -# INSTALL SDK -install_sdk() -{ - ANDROID_SDK_BUILD_TOOLS_VERSION="29.0.2" - ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk" - # get the latest version from https://developer.android.com/studio/index.html - ANDROID_SDK_TOOLS_VERSION="4333796" - ANDROID_SDK_TOOLS_ARCHIVE="sdk-tools-linux-${ANDROID_SDK_TOOLS_VERSION}.zip" - ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}" - wget -nc ${ANDROID_SDK_TOOLS_DL_URL} - mkdir --parents "${ANDROID_SDK_HOME}" - unzip -q "${ANDROID_SDK_TOOLS_ARCHIVE}" -d "${ANDROID_SDK_HOME}" - rm -rf "${ANDROID_SDK_TOOLS_ARCHIVE}" - # update Android SDK, install Android API, Build Tools... - mkdir --parents "${ANDROID_SDK_HOME}/.android/" - echo '### Sources for Android SDK Manager' > "${ANDROID_SDK_HOME}/.android/repositories.cfg" - # accept Android licenses (JDK necessary!) - apt -y update -qq - apt -y install -qq --no-install-recommends openjdk-11-jdk - apt -y autoremove - yes | "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null - # download platforms, API, build tools - "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-24" > /dev/null - "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "platforms;android-28" > /dev/null - "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" > /dev/null - "${ANDROID_SDK_HOME}/tools/bin/sdkmanager" "extras;android;m2repository" > /dev/null - find /opt/android/android-sdk -type f -perm /0111 -print0|xargs -0 chmod a+x - chown -R buildbot.buildbot /opt/android/android-sdk - chmod +x "${ANDROID_SDK_HOME}/tools/bin/avdmanager" -} - -# INSTALL APACHE-ANT -install_ant() -{ - APACHE_ANT_VERSION="1.10.12" - - APACHE_ANT_ARCHIVE="apache-ant-${APACHE_ANT_VERSION}-bin.tar.gz" - APACHE_ANT_DL_URL="http://archive.apache.org/dist/ant/binaries/${APACHE_ANT_ARCHIVE}" - APACHE_ANT_HOME="${ANDROID_HOME}/apache-ant" - APACHE_ANT_HOME_V="${APACHE_ANT_HOME}-${APACHE_ANT_VERSION}" - wget -nc ${APACHE_ANT_DL_URL} - tar -xf "${APACHE_ANT_ARCHIVE}" -C "${ANDROID_HOME}" - ln -sfn "${APACHE_ANT_HOME_V}" "${APACHE_ANT_HOME}" - rm -rf "${APACHE_ANT_ARCHIVE}" -} - -system_dependencies -build_dependencies -specific_recipes_dependencies -install_android_pkg -install_ndk -install_sdk -install_ant \ No newline at end of file diff --git a/buildscripts/appimage.sh b/buildscripts/appimage.sh deleted file mode 100755 index a5691783..00000000 --- a/buildscripts/appimage.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -# Cleanup -rm -rf PyBitmessage -export VERSION=$(python setup.py --version) - -[ -f "pkg2appimage" ] || wget -O "pkg2appimage" https://github.com/AppImage/pkg2appimage/releases/download/continuous/pkg2appimage-1807-x86_64.AppImage -chmod a+x pkg2appimage - -echo "Building AppImage" - -if grep docker /proc/1/cgroup; then - export APPIMAGE_EXTRACT_AND_RUN=1 - mkdir PyBitmessage - wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O PyBitmessage/appimagetool \ - && chmod +x PyBitmessage/appimagetool -fi - -./pkg2appimage packages/AppImage/PyBitmessage.yml - -./pkg2appimage --appimage-extract - -. ./squashfs-root/usr/share/pkg2appimage/functions.sh - -GLIBC=$(glibc_needed) - -VERSION_EXPANDED=${VERSION}.glibc${GLIBC}-${SYSTEM_ARCH} - -if [ -f "out/PyBitmessage-${VERSION_EXPANDED}.AppImage" ]; then - echo "Build Successful"; - echo "Run out/PyBitmessage-${VERSION_EXPANDED}.AppImage"; - out/PyBitmessage-${VERSION_EXPANDED}.AppImage -t -else - echo "Build Failed"; - exit 1 -fi diff --git a/buildscripts/update_translation_source.sh b/buildscripts/update_translation_source.sh deleted file mode 100644 index 205767cb..00000000 --- a/buildscripts/update_translation_source.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -xgettext -Lpython --output=src/translations/messages.pot \ -src/bitmessagekivy/mpybit.py src/bitmessagekivy/main.kv \ -src/bitmessagekivy/baseclass/*.py src/bitmessagekivy/kv/*.kv diff --git a/buildscripts/winbuild.sh b/buildscripts/winbuild.sh deleted file mode 100755 index fab0b3e0..00000000 --- a/buildscripts/winbuild.sh +++ /dev/null @@ -1,204 +0,0 @@ -#!/bin/bash - -# INIT -MACHINE_TYPE=$(uname -m) -BASE_DIR=$(pwd) -PYTHON_VERSION=2.7.17 -PYQT_VERSION=4-4.11.4-gpl-Py2.7-Qt4.8.7 -OPENSSL_VERSION=1_0_2t -SRCPATH=~/Downloads - -#Functions -function download_sources_32 { - if [ ! -d ${SRCPATH} ]; then - mkdir -p ${SRCPATH} - fi - wget -P ${SRCPATH} -c -nc --content-disposition \ - https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}.msi \ - https://web.archive.org/web/20210420044701/https://download.microsoft.com/download/1/1/1/1116b75a-9ec3-481a-a3c8-1777b5381140/vcredist_x86.exe \ - https://github.com/Bitmessage/ThirdPartyLibraries/blob/master/PyQt${PYQT_VERSION}-x32.exe?raw=true \ - https://github.com/Bitmessage/ThirdPartyLibraries/blob/master/Win32OpenSSL-${OPENSSL_VERSION}.exe?raw=true \ - https://github.com/Bitmessage/ThirdPartyLibraries/blob/master/pyopencl-2015.1-cp27-none-win32.whl?raw=true -} - -function download_sources_64 { - if [ ! -d ${SRCPATH} ]; then - mkdir -p ${SRCPATH} - fi - wget -P ${SRCPATH} -c -nc --content-disposition \ - http://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}.amd64.msi \ - https://download.microsoft.com/download/d/2/4/d242c3fb-da5a-4542-ad66-f9661d0a8d19/vcredist_x64.exe \ - https://github.com/Bitmessage/ThirdPartyLibraries/blob/master/PyQt${PYQT_VERSION}-x64.exe?raw=true \ - https://github.com/Bitmessage/ThirdPartyLibraries/blob/master/Win64OpenSSL-${OPENSSL_VERSION}.exe?raw=true \ - https://github.com/Bitmessage/ThirdPartyLibraries/blob/master/pyopencl-2015.1-cp27-none-win_amd64.whl?raw=true -} - -function download_sources { - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - download_sources_64 - else - download_sources_32 - fi -} - -function install_wine { - echo "Setting up wine" - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - export WINEPREFIX=${HOME}/.wine64 WINEARCH=win64 - else - export WINEPREFIX=${HOME}/.wine32 WINEARCH=win32 - fi - rm -rf "${WINEPREFIX}" - rm -rf packages/pyinstaller/{build,dist} -} - -function install_python(){ - cd ${SRCPATH} || exit 1 - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - echo "Installing Python ${PYTHON_VERSION} 64b" - wine msiexec -i python-${PYTHON_VERSION}.amd64.msi /q /norestart - echo "Installing vcredist for 64 bit" - wine vcredist_x64.exe /q /norestart - else - echo "Installing Python ${PYTHON_VERSION} 32b" - wine msiexec -i python-${PYTHON_VERSION}.msi /q /norestart - # MSVCR 2008 required for Windows XP - cd ${SRCPATH} || exit 1 - echo "Installing vc_redist (2008) for 32 bit " - wine vcredist_x86.exe /Q - fi - echo "Installing pytools 2020.2" - # last version compatible with python 2 - wine python -m pip install pytools==2020.2 - echo "Upgrading pip" - wine python -m pip install --upgrade pip -} - -function install_pyqt(){ - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - echo "Installing PyQt-${PYQT_VERSION} 64b" - wine PyQt${PYQT_VERSION}-x64.exe /S /WX - else - echo "Installing PyQt-${PYQT_VERSION} 32b" - wine PyQt${PYQT_VERSION}-x32.exe /S /WX - fi -} - -function install_openssl(){ - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - echo "Installing OpenSSL ${OPENSSL_VERSION} 64b" - wine Win64OpenSSL-${OPENSSL_VERSION}.exe /q /norestart /silent /verysilent /sp- /suppressmsgboxes - else - echo "Installing OpenSSL ${OPENSSL_VERSION} 32b" - wine Win32OpenSSL-${OPENSSL_VERSION}.exe /q /norestart /silent /verysilent /sp- /suppressmsgboxes - fi -} - -function install_pyinstaller() -{ - cd "${BASE_DIR}" || exit 1 - echo "Installing PyInstaller" - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - # 3.6 is the last version to support python 2.7 - # but the resulting executable cannot run in wine - # see https://github.com/pyinstaller/pyinstaller/issues/4628 - wine python -m pip install -I pyinstaller==3.5 - else - # 3.2.1 is the last version to work on XP - # see https://github.com/pyinstaller/pyinstaller/issues/2931 - wine python -m pip install -I pyinstaller==3.2.1 - fi -} - -function install_pip_depends() -{ - cd "${BASE_DIR}" || exit 1 - echo "Installing pip depends" - wine python -m pip install msgpack-python .[json] .[qrcode] .[tor] .[xml] - python setup.py egg_info -} - -function install_pyopencl() -{ - cd "${SRCPATH}" || exit 1 - echo "Installing PyOpenCL" - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - wine python -m pip install pyopencl-2015.1-cp27-none-win_amd64.whl - else - wine python -m pip install pyopencl-2015.1-cp27-none-win32.whl - fi - sed -Ei 's/_DEFAULT_INCLUDE_OPTIONS = .*/_DEFAULT_INCLUDE_OPTIONS = [] /' \ - "$WINEPREFIX/drive_c/Python27/Lib/site-packages/pyopencl/__init__.py" -} - -function build_dll(){ - cd "${BASE_DIR}" || exit 1 - cd src/bitmsghash || exit 1 - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - echo "Create dll" - x86_64-w64-mingw32-g++ -D_WIN32 -Wall -O3 -march=x86-64 \ - "-I$HOME/.wine64/drive_c/OpenSSL-Win64/include" \ - -I/usr/x86_64-w64-mingw32/include \ - "-L$HOME/.wine64/drive_c/OpenSSL-Win64/lib" \ - -c bitmsghash.cpp - x86_64-w64-mingw32-g++ -static-libgcc -shared bitmsghash.o \ - -D_WIN32 -O3 -march=x86-64 \ - "-I$HOME/.wine64/drive_c/OpenSSL-Win64/include" \ - "-L$HOME/.wine64/drive_c/OpenSSL-Win64" \ - -L/usr/lib/x86_64-linux-gnu/wine \ - -fPIC -shared -lcrypt32 -leay32 -lwsock32 \ - -o bitmsghash64.dll -Wl,--out-implib,bitmsghash.a - else - echo "Create dll" - i686-w64-mingw32-g++ -D_WIN32 -Wall -m32 -O3 -march=i686 \ - "-I$HOME/.wine32/drive_c/OpenSSL-Win32/include" \ - -I/usr/i686-w64-mingw32/include \ - "-L$HOME/.wine32/drive_c/OpenSSL-Win32/lib" \ - -c bitmsghash.cpp - i686-w64-mingw32-g++ -static-libgcc -shared bitmsghash.o \ - -D_WIN32 -O3 -march=i686 \ - "-I$HOME/.wine32/drive_c/OpenSSL-Win32/include" \ - "-L$HOME/.wine32/drive_c/OpenSSL-Win32/lib/MinGW" \ - -fPIC -shared -lcrypt32 -leay32 -lwsock32 \ - -o bitmsghash32.dll -Wl,--out-implib,bitmsghash.a - fi -} - -function build_exe(){ - cd "${BASE_DIR}" || exit 1 - cd packages/pyinstaller || exit 1 - wine pyinstaller bitmessagemain.spec -} - -function dryrun_exe(){ - cd "${BASE_DIR}" || exit 1 - local VERSION=$(python setup.py --version) - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - EXE=Bitmessage_x64_$VERSION.exe - else - EXE=Bitmessage_x86_$VERSION.exe - fi - wine packages/pyinstaller/dist/$EXE -t -} - -# prepare on ubuntu -# dpkg --add-architecture i386 -# apt update -# apt -y install wget wine-stable wine-development winetricks mingw-w64 wine32 wine64 xvfb - - -download_sources -if [ "$1" == "--download-only" ]; then - exit -fi - -install_wine -install_python -install_pyqt -install_openssl -install_pyopencl -install_pip_depends -install_pyinstaller -build_dll -build_exe -dryrun_exe diff --git a/checkdeps.py b/checkdeps.py old mode 100755 new mode 100644 index 0a28a6d2..05f00944 --- a/checkdeps.py +++ b/checkdeps.py @@ -1,33 +1,87 @@ -#!/usr/bin/env python -""" -Check dependencies and give recommendations about how to satisfy them +"""Check dependendies and give recommendations about how to satisfy them""" -Limitations: - - * Does not detect whether packages are already installed. Solving this requires writing more of a configuration - management system. Or we could switch to an existing one. - * Not fully PEP508 compliant. Not slightly. It makes bold assumptions about the simplicity of the contents of - EXTRAS_REQUIRE. This is fine because most developers do, too. -""" - -import os -import sys 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 - # another import from setuptools is in setup.py - from setup import EXTRAS_REQUIRE except ImportError: HAVE_SETUPTOOLS = False - EXTRAS_REQUIRE = {} - from importlib import import_module +import os +import sys -from src.depends import detectOS, PACKAGES, PACKAGE_MANAGER +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", @@ -37,24 +91,46 @@ COMPILING = { "optional": False, } -# OS-specific dependencies for optional components listed in EXTRAS_REQUIRE -EXTRAS_REQUIRE_DEPS = { - # The values from setup.EXTRAS_REQUIRE - 'python_prctl': { - # The packages needed for this requirement, by OS - "OpenBSD": [""], - "FreeBSD": [""], - "Debian": ["libcap-dev python-prctl"], - "Ubuntu": ["libcap-dev python-prctl"], - "Ubuntu 12": ["libcap-dev python-prctl"], - "Ubuntu 20": [""], - "openSUSE": [""], - "Fedora": ["prctl"], - "Guix": [""], - "Gentoo": ["dev-python/python-prctl"], - }, -} +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 = [] @@ -68,21 +144,18 @@ def detectPrereqs(missing=True): available.append(module) return available - def prereqToPackages(): if not detectPrereqs(): return - print("%s %s" % ( + print "%s %s" % ( PACKAGE_MANAGER[detectOS()], " ".join( - PACKAGES[x][detectOS()] for x in detectPrereqs()))) - + 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])) - + print "%s %s" % ( + PACKAGE_MANAGER[detectOS.result], COMPILING[detectOS.result]) def testCompiler(): if not HAVE_SETUPTOOLS: @@ -109,70 +182,34 @@ def testCompiler(): 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) + 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.") + print "Building the bitmsghash module failed.\n" \ + "You may be missing a C++ compiler and/or the OpenSSL headers." if prereqs: - mandatory = [x for x in prereqs if not PACKAGES[x].get("optional")] - optional = [x for x in prereqs if PACKAGES[x].get("optional")] + 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)) + print "Missing mandatory dependencies: %s" % (" ".join(mandatory)) if optional: - print("Missing optional dependencies: %s" % " ".join(optional)) + print "Missing optional dependencies: %s" % (" ".join(optional)) for package in optional: - print(PACKAGES[package].get('description')) + print PACKAGES[package].get('description') -# Install the system dependencies of optional extras_require components -OPSYS = detectOS() -CMD = PACKAGE_MANAGER[OPSYS] if OPSYS in PACKAGE_MANAGER else 'UNKNOWN_INSTALLER' -for lhs, rhs in EXTRAS_REQUIRE.items(): - if OPSYS is None: - break - if rhs and any([ - EXTRAS_REQUIRE_DEPS[x][OPSYS] - for x in rhs - if x in EXTRAS_REQUIRE_DEPS - ]): - try: - import_module(lhs) - except Exception as e: - rhs_cmd = ''.join([ - CMD, - ' ', - ' '.join([ - ''. join([ - xx for xx in EXTRAS_REQUIRE_DEPS[x][OPSYS] - ]) - for x in rhs - if x in EXTRAS_REQUIRE_DEPS - ]), - ]) - print( - "Optional dependency `pip install .[{}]` would require `{}`" - " to be run as root".format(lhs, rhs_cmd)) - -if detectOS.result == "Ubuntu 20": - print( - "Qt interface isn't supported in %s" % detectOS.result) - -if (not compiler or prereqs) and OPSYS in PACKAGE_MANAGER: - print("You can install the missing dependencies by running, as root:") +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() - if prereqs and mandatory: - sys.exit(1) else: - print("All the dependencies satisfied, you can install PyBitmessage") + print "All the dependencies satisfied, you can install PyBitmessage" diff --git a/configure b/configure new file mode 100755 index 00000000..0519ecba --- /dev/null +++ b/configure @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/desktop/pybitmessage.desktop b/desktop/pybitmessage.desktop index c30276e4..05970440 100644 --- a/desktop/pybitmessage.desktop +++ b/desktop/pybitmessage.desktop @@ -6,4 +6,4 @@ Comment=Send encrypted messages Exec=pybitmessage %F Icon=pybitmessage Terminal=false -Categories=Office;Email;Network; +Categories=Office;Email; diff --git a/dev/bloomfiltertest.py b/dev/bloomfiltertest.py index 19c93c76..539d00f3 100644 --- a/dev/bloomfiltertest.py +++ b/dev/bloomfiltertest.py @@ -1,16 +1,10 @@ -""" -dev/bloomfiltertest.py -====================== - -""" - +from math import ceil +from os import stat, getenv, path +from pybloom import BloomFilter as BloomFilter1 +from pybloomfilter import BloomFilter as BloomFilter2 import sqlite3 -from os import getenv, path from time import time -from pybloom import BloomFilter as BloomFilter1 # pylint: disable=import-error -from pybloomfilter import BloomFilter as BloomFilter2 # pylint: disable=import-error - # Ubuntu: apt-get install python-pybloomfiltermmap conn = sqlite3.connect(path.join(getenv("HOME"), '.config/PyBitmessage/messages.dat')) @@ -47,20 +41,20 @@ for row in cur.fetchall(): except IndexError: pass -# f = open("/home/shurdeek/tmp/bloom.dat", "wb") -# sb1.tofile(f) -# f.close() +#f = open("/home/shurdeek/tmp/bloom.dat", "wb") +#sb1.tofile(f) +#f.close() -print("Item count: %i" % (itemcount)) -print("Raw length: %i" % (rawlen)) -print("Bloom filter 1 length: %i, reduction to: %.2f%%" % +print "Item count: %i" % (itemcount) +print "Raw length: %i" % (rawlen) +print "Bloom filter 1 length: %i, reduction to: %.2f%%" % \ (bf1.bitarray.buffer_info()[1], - 100.0 * bf1.bitarray.buffer_info()[1] / rawlen)) -print("Bloom filter 1 capacity: %i and error rate: %.3f%%" % (bf1.capacity, 100.0 * bf1.error_rate)) -print("Bloom filter 1 took %.2fs" % (bf1time)) -print("Bloom filter 2 length: %i, reduction to: %.3f%%" % + 100.0 * bf1.bitarray.buffer_info()[1] / rawlen) +print "Bloom filter 1 capacity: %i and error rate: %.3f%%" % (bf1.capacity, 100.0 * bf1.error_rate) +print "Bloom filter 1 took %.2fs" % (bf1time) +print "Bloom filter 2 length: %i, reduction to: %.3f%%" % \ (bf2.num_bits / 8, - 100.0 * bf2.num_bits / 8 / rawlen)) -print("Bloom filter 2 capacity: %i and error rate: %.3f%%" % (bf2.capacity, 100.0 * bf2.error_rate)) -print("Bloom filter 2 took %.2fs" % (bf2time)) + 100.0 * bf2.num_bits / 8 / rawlen) +print "Bloom filter 2 capacity: %i and error rate: %.3f%%" % (bf2.capacity, 100.0 * bf2.error_rate) +print "Bloom filter 2 took %.2fs" % (bf2time) diff --git a/dev/msgtest.py b/dev/msgtest.py new file mode 100644 index 00000000..d5a8be8e --- /dev/null +++ b/dev/msgtest.py @@ -0,0 +1,27 @@ +import importlib +from os import listdir, path +from pprint import pprint +import sys +import traceback + +data = {"": "message", "subject": "subject", "body": "body"} +#data = {"": "vote", "msgid": "msgid"} +#data = {"fsck": 1} + +import messagetypes + +if __name__ == '__main__': + try: + msgType = data[""] + except KeyError: + print "Message type missing" + sys.exit(1) + else: + print "Message type: %s" % (msgType) + msgObj = messagetypes.constructObject(data) + if msgObj is None: + sys.exit(1) + try: + msgObj.process() + except: + pprint(sys.exc_info()) diff --git a/dev/powinterrupttest.py b/dev/powinterrupttest.py index bfb55d78..cc4c2197 100644 --- a/dev/powinterrupttest.py +++ b/dev/powinterrupttest.py @@ -11,7 +11,7 @@ shutdown = 0 def signal_handler(signal, frame): global shutdown - print("Got signal %i in %s/%s" % (signal, current_process().name, current_thread().name)) + 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 != "PyBitmessage": @@ -20,21 +20,21 @@ def signal_handler(signal, frame): def _doCPoW(target, initialHash): - # global shutdown +# global shutdown h = initialHash m = target out_h = ctypes.pointer(ctypes.create_string_buffer(h, 64)) out_m = ctypes.c_ulonglong(m) - print("C PoW start") + print "C PoW start" for c in range(0, 200000): - print("Iter: %i" % (c)) + print "Iter: %i" % (c) nonce = bmpow(out_h, out_m) if shutdown: break trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) if shutdown != 0: raise StopIteration("Interrupted") - print("C PoW done") + print "C PoW done" return [trialValue, nonce] diff --git a/dev/ssltest.py b/dev/ssltest.py index 7268b65f..4ddca8ca 100644 --- a/dev/ssltest.py +++ b/dev/ssltest.py @@ -8,15 +8,14 @@ import traceback HOST = "127.0.0.1" PORT = 8912 - def sslProtocolVersion(): # sslProtocolVersion - if sys.version_info >= (2, 7, 13): + if sys.version_info >= (2,7,13): # this means TLSv1 or higher # in the future change to # ssl.PROTOCOL_TLS1.2 return ssl.PROTOCOL_TLS - elif sys.version_info >= (2, 7, 9): + elif sys.version_info >= (2,7,9): # this means any SSL/TLS. SSLv2 and 3 are excluded with an option after context is created return ssl.PROTOCOL_SSLv23 else: @@ -24,19 +23,16 @@ def sslProtocolVersion(): # "TLSv1.2" in < 2.7.9 return ssl.PROTOCOL_TLSv1 - def sslProtocolCiphers(): if ssl.OPENSSL_VERSION_NUMBER >= 0x10100000: return "AECDH-AES256-SHA@SECLEVEL=0" else: return "AECDH-AES256-SHA" - def connect(): sock = socket.create_connection((HOST, PORT)) return sock - def listen(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -44,51 +40,45 @@ def listen(): sock.listen(0) return sock - def sslHandshake(sock, server=False): - if sys.version_info >= (2, 7, 9): + if sys.version_info >= (2,7,9): context = ssl.SSLContext(sslProtocolVersion()) context.set_ciphers(sslProtocolCiphers()) context.set_ecdh_curve("secp256k1") context.check_hostname = False context.verify_mode = ssl.CERT_NONE - context.options = ssl.OP_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3\ - | ssl.OP_SINGLE_ECDH_USE | ssl.OP_CIPHER_SERVER_PREFERENCE - sslSock = context.wrap_socket(sock, server_side=server, 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 + sslSock = context.wrap_socket(sock, server_side = server, do_handshake_on_connect=False) else: - sslSock = ssl.wrap_socket(sock, keyfile=os.path.join('src', 'sslkeys', 'key.pem'), - certfile=os.path.join('src', 'sslkeys', 'cert.pem'), - server_side=server, ssl_version=sslProtocolVersion(), - do_handshake_on_connect=False, ciphers='AECDH-AES256-SHA') + sslSock = ssl.wrap_socket(sock, keyfile = os.path.join('src', 'sslkeys', 'key.pem'), certfile = os.path.join('src', 'sslkeys', 'cert.pem'), server_side = server, ssl_version=sslProtocolVersion(), do_handshake_on_connect=False, ciphers='AECDH-AES256-SHA') while True: try: sslSock.do_handshake() break except ssl.SSLWantReadError: - print("Waiting for SSL socket handhake read") + print "Waiting for SSL socket handhake read" select.select([sslSock], [], [], 10) except ssl.SSLWantWriteError: - print("Waiting for SSL socket handhake write") + print "Waiting for SSL socket handhake write" select.select([], [sslSock], [], 10) except Exception: - print("SSL socket handhake failed, shutting down connection") + print "SSL socket handhake failed, shutting down connection" traceback.print_exc() return - print("Success!") + print "Success!" return sslSock - if __name__ == "__main__": if len(sys.argv) != 2: - print("Usage: ssltest.py client|server") + print "Usage: ssltest.py client|server" sys.exit(0) elif sys.argv[1] == "server": serversock = listen() while True: - print("Waiting for connection") + print "Waiting for connection" sock, addr = serversock.accept() - print("Got connection from %s:%i" % (addr[0], addr[1])) + print "Got connection from %s:%i" % (addr[0], addr[1]) sslSock = sslHandshake(sock, True) if sslSock: sslSock.shutdown(socket.SHUT_RDWR) @@ -100,5 +90,5 @@ if __name__ == "__main__": sslSock.shutdown(socket.SHUT_RDWR) sslSock.close() else: - print("Usage: ssltest.py client|server") + print "Usage: ssltest.py client|server" sys.exit(0) diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 3e91a7ea..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = PyBitmessage -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/_static/custom.css b/docs/_static/custom.css deleted file mode 100644 index e0ba75c1..00000000 --- a/docs/_static/custom.css +++ /dev/null @@ -1,18 +0,0 @@ -/* Hide "On GitHub" section from versions menu */ -li.wy-breadcrumbs-aside > a.fa { - display: none; -} - -/* Override table width restrictions */ -/* @media screen and (min-width: 700px) { */ - -.wy-table-responsive table td { - /* !important prevents the common CSS stylesheets from overriding - this as on RTD they are loaded after this stylesheet */ - white-space: normal !important; -} - -.wy-table-responsive { - overflow: visible !important; -} -/* } */ diff --git a/docs/address.rst b/docs/address.rst deleted file mode 100644 index eec4bd2c..00000000 --- a/docs/address.rst +++ /dev/null @@ -1,106 +0,0 @@ -Address -======= - -Bitmessage adresses are Base58 encoded public key hashes. An address looks like -``BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash``. All Addresses start with ``BM-``, -however clients should accept addresses without the prefix. PyBitmessage does -this. The reason behind this idea is the fact, that when double clicking on an -address for copy and paste, the prefix is usually not selected due to the dash -being a common separator. - -Public Key usage ----------------- - -Addresses may look complicated but they fulfill the purpose of verifying the -sender. A Message claiming to be from a specific address can simply be checked by -decoding a special field in the data packet with the public key, that represents -the address. If the decryption succeeds, the message is from the address it -claims to be. - -Length ------- - -Without the ``BM-`` prefix, an address is usually 32-34 chars long. Since an -address is a hash it can be calculated by the client in a way, that the first -bytes are zero (``\0``) and bitmessage strips these. This causes the client to do -much more work to be lucky and find such an address. This is an optional checkbox -in address generation dialog. - -Versions --------- - - * v1 addresses used a single RSA key pair - * v2 addresses use 2 ECC key pairs - * v3 addresses extends v2 addresses to allow specifying the proof of work - requirements. The pubkey object is signed to mitigate against - forgery/tampering. - * v4 addresses protect against harvesting addresses from getpubkey and pubkey - objects - -Address Types -------------- - -There are two address types the user can generate in PyBitmessage. The resulting -addresses have no difference, but the method how they are created differs. - -Random Address -^^^^^^^^^^^^^^ - -Random addresses are generated from a randomly chosen number. The resulting -address cannot be regenerated without knowledge of the number and therefore the -keys.dat should be backed up. Generating random addresses takes slightly longer -due to the POW required for the public key broadcast. - -Usage -""""" - - * Generate unique addresses - * Generate one time addresses. - - -Deterministic Address -^^^^^^^^^^^^^^^^^^^^^ - -For this type of Address a passphrase is required, that is used to seed the -random generator. Using the same passphrase creates the same addresses. -Using deterministic addresses should be done with caution, using a word from a -dictionary or a common number can lead to others generating the same address and -thus being able to receive messages not intended for them. Generating a -deterministic address will not publish the public key. The key is sent in case -somebody requests it. This saves :doc:`pow` time, when generating a bunch of -addresses. - -Usage -""""" - - * Create the same address on multiple systems without the need of copying - keys.dat or an Address Block. - * create a Channel. (Use the *Join/create chan* option in the file menu instead) - * Being able to restore the address in case of address database corruption or - deletation. - -Address generation ------------------- - - 1. Create a private and a public key for encryption and signing (resulting in - 4 keys) - 2. Merge the public part of the signing key and the encryption key together. - (encoded in uncompressed X9.62 format) (A) - 3. Take the SHA512 hash of A. (B) - 4. Take the RIPEMD160 of B. (C) - 5. Repeat step 1-4 until you have a result that starts with a zero - (Or two zeros, if you want a short address). (D) - 6. Remove the zeros at the beginning of D. (E) - 7. Put the stream number (as a var_int) in front of E. (F) - 8. Put the address version (as a var_int) in front of F. (G) - 9. Take a double SHA512 (hash of a hash) of G and use the first four bytes as a - checksum, that you append to the end. (H) - 10. base58 encode H. (J) - 11. Put "BM-" in front J. (K) - -K is your full address - - .. note:: Bitmessage's base58 encoding uses the following sequence - (the same as Bitcoin's): - "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz". - Many existing libraries for base58 do not use this ordering. diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index b0cfef7b..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,276 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Configuration file for the Sphinx documentation builder. - -For a full list of options see the documentation: -http://www.sphinx-doc.org/en/master/config -""" - -import os -import sys - -sys.path.insert(0, os.path.abspath('../src')) - -from importlib import import_module - -import version # noqa:E402 - - -# -- Project information ----------------------------------------------------- - -project = u'PyBitmessage' -copyright = u'2019-2022, The Bitmessage Team' # pylint: disable=redefined-builtin -author = u'The Bitmessage Team' - -# The short X.Y version -version = unicode(version.softwareVersion) - -# The full version, including alpha/beta/rc tags -release = version - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', # FIXME: unused - 'sphinx.ext.imgmath', # legacy unused - 'sphinx.ext.intersphinx', - 'sphinx.ext.linkcode', - 'sphinx.ext.napoleon', - 'sphinx.ext.todo', - 'sphinxcontrib.apidoc', - 'm2r', -] - -default_role = 'obj' - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -source_suffix = ['.rst', '.md'] - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -# language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build'] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# Don't prepend every class or function name with full module path -add_module_names = False - -# A list of ignored prefixes for module index sorting. -modindex_common_prefix = ['pybitmessage.'] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -html_css_files = [ - 'custom.css', -] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - -html_show_sourcelink = False - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'PyBitmessagedoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'PyBitmessage.tex', u'PyBitmessage Documentation', - u'The Bitmessage Team', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pybitmessage', u'PyBitmessage Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'PyBitmessage', u'PyBitmessage Documentation', - author, 'PyBitmessage', 'One line description of project.', - 'Miscellaneous'), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - - -# -- Extension configuration ------------------------------------------------- - -autodoc_mock_imports = [ - 'debug', - 'pybitmessage.bitmessagekivy', - 'pybitmessage.bitmessageqt.foldertree', - 'pybitmessage.helper_startup', - 'pybitmessage.mockbm', - 'pybitmessage.network.httpd', - 'pybitmessage.network.https', - 'ctypes', - 'dialog', - 'gi', - 'kivy', - 'logging', - 'msgpack', - 'numpy', - 'pkg_resources', - 'pycanberra', - 'pyopencl', - 'PyQt4', - 'PyQt5', - 'qrcode', - 'stem', - 'xdg', -] -autodoc_member_order = 'bysource' - -# Apidoc settings -apidoc_module_dir = '../pybitmessage' -apidoc_output_dir = 'autodoc' -apidoc_excluded_paths = [ - 'bitmessagekivy', 'build_osx.py', - 'bitmessageqt/addressvalidator.py', 'bitmessageqt/foldertree.py', - 'bitmessageqt/migrationwizard.py', 'bitmessageqt/newaddresswizard.py', - 'helper_startup.py', - 'kivymd', 'mockbm', 'main.py', 'navigationdrawer', 'network/http*', - 'src', 'tests', 'version.py' -] -apidoc_module_first = True -apidoc_separate_modules = True -apidoc_toc_file = False -apidoc_extra_args = ['-a'] - -# Napoleon settings -napoleon_google_docstring = True - - -# linkcode function -def linkcode_resolve(domain, info): - """This generates source URL's for sphinx.ext.linkcode""" - if domain != 'py' or not info['module']: - return - try: - home = os.path.abspath(import_module('pybitmessage').__path__[0]) - mod = import_module(info['module']).__file__ - except ImportError: - return - repo = 'https://github.com/Bitmessage/PyBitmessage/blob/v0.6/src%s' - path = mod.replace(home, '') - if path != mod: - # put the link only for top level definitions - if len(info['fullname'].split('.')) > 1: - return - if path.endswith('.pyc'): - path = path[:-1] - return repo % path - - -# -- Options for intersphinx extension --------------------------------------- - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/2.7/': None} - -# -- Options for todo extension ---------------------------------------------- - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True diff --git a/docs/encrypted_payload.rst b/docs/encrypted_payload.rst deleted file mode 100644 index 346d370d..00000000 --- a/docs/encrypted_payload.rst +++ /dev/null @@ -1,19 +0,0 @@ -+------------+-------------+-----------+--------------------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+============================================+ -| 16 | IV | uchar[] | Initialization Vector used for AES-256-CBC | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | Curve type | uint16_t | Elliptic Curve type 0x02CA (714) | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | X length | uint16_t | Length of X component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| X length | X | uchar[] | X component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | Y length | uint16_t | Length of Y component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| Y length | Y | uchar[] | Y component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| ? | encrypted | uchar[] | Cipher text | -+------------+-------------+-----------+--------------------------------------------+ -| 32 | MAC | uchar[] | HMACSHA256 Message Authentication Code | -+------------+-------------+-----------+--------------------------------------------+ diff --git a/docs/encryption.rst b/docs/encryption.rst deleted file mode 100644 index 61c7fb3e..00000000 --- a/docs/encryption.rst +++ /dev/null @@ -1,257 +0,0 @@ -Encryption -========== - -Bitmessage uses the Elliptic Curve Integrated Encryption Scheme -`(ECIES) `_ -to encrypt the payload of the Message and Broadcast objects. - -The scheme uses Elliptic Curve Diffie-Hellman -`(ECDH) `_ to generate a shared secret used -to generate the encryption parameters for Advanced Encryption Standard with -256bit key and Cipher-Block Chaining -`(AES-256-CBC) `_. -The encrypted data will be padded to a 16 byte boundary in accordance to -`PKCS7 `_. This -means that the data is padded with N bytes of value N. - -The Key Derivation Function -`(KDF) `_ used to -generate the key material for AES is -`SHA512 `_. The Message Authentication -Code (MAC) scheme used is `HMACSHA256 `_. - -Format ------- - -(See also: :doc:`protocol`) - -.. include:: encrypted_payload.rst - -In order to reconstitute a usable (65 byte) public key (starting with 0x04), -the X and Y components need to be expanded by prepending them with 0x00 bytes -until the individual component lengths are 32 bytes. - -Encryption ----------- - - 1. The destination public key is called K. - 2. Generate 16 random bytes using a secure random number generator. - Call them IV. - 3. Generate a new random EC key pair with private key called r and public key - called R. - 4. Do an EC point multiply with public key K and private key r. This gives you - public key P. - 5. Use the X component of public key P and calculate the SHA512 hash H. - 6. The first 32 bytes of H are called key_e and the last 32 bytes are called - key_m. - 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. [#f1]_ - 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, - key_e as encryption key and the padded input text as payload. Call the - output cipher text. - 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and - IV + R [#f2]_ + cipher text as data. Call the output MAC. - -The resulting data is: IV + R + cipher text + MAC - -Decryption ----------- - - 1. The private key used to decrypt is called k. - 2. Do an EC point multiply with private key k and public key R. This gives you - public key P. - 3. Use the X component of public key P and calculate the SHA512 hash H. - 4. The first 32 bytes of H are called key_e and the last 32 bytes are called - key_m. - 5. Calculate MAC' with HMACSHA256, using key_m as salt and - IV + R + cipher text as data. - 6. Compare MAC with MAC'. If not equal, decryption will fail. - 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization - vector, key_e as decryption key and the cipher text as payload. The output - is the padded input text. - -.. highlight:: nasm - -Partial Example ---------------- - -.. list-table:: Public key K: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 04 - 09 d4 e5 c0 ab 3d 25 fe - 04 8c 64 c9 da 1a 24 2c - 7f 19 41 7e 95 17 cd 26 - 69 50 d7 2c 75 57 13 58 - 5c 61 78 e9 7f e0 92 fc - 89 7c 9a 1f 17 20 d5 77 - 0a e8 ea ad 2f a8 fc bd - 08 e9 32 4a 5d de 18 57 - - Public key, 0x04 prefix, then 32 bytes X and 32 bytes Y. - - -.. list-table:: Initialization Vector IV: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - bd db 7c 28 29 b0 80 38 - 75 30 84 a2 f3 99 16 81 - - 16 bytes generated with a secure random number generator. - -.. list-table:: Randomly generated key pair with private key r and public key R: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 5b e6 fa cd 94 1b 76 e9 - d3 ea d0 30 29 fb db 6b - 6e 08 09 29 3f 7f b1 97 - d0 c5 1f 84 e9 6b 8b a4 - - Private key r - * - - - :: - - 02 ca 00 20 - 02 93 21 3d cf 13 88 b6 - 1c 2a e5 cf 80 fe e6 ff - ff c0 49 a2 f9 fe 73 65 - fe 38 67 81 3c a8 12 92 - 00 20 - df 94 68 6c 6a fb 56 5a - c6 14 9b 15 3d 61 b3 b2 - 87 ee 2c 7f 99 7c 14 23 - 87 96 c1 2b 43 a3 86 5a - - Public key R - -.. list-table:: Derived public key P (point multiply r with K): - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 04 - 0d b8 e3 ad 8c 0c d7 3f - a2 b3 46 71 b7 b2 47 72 - 9b 10 11 41 57 9d 19 9e - 0d c0 bd 02 4e ae fd 89 - ca c8 f5 28 dc 90 b6 68 - 11 ab ac 51 7d 74 97 be - 52 92 93 12 29 be 0b 74 - 3e 05 03 f4 43 c3 d2 96 - - Public key P - * - - - :: - - 0d b8 e3 ad 8c 0c d7 3f - a2 b3 46 71 b7 b2 47 72 - 9b 10 11 41 57 9d 19 9e - 0d c0 bd 02 4e ae fd 89 - - X component of public key P - -.. list-table:: SHA512 of public key P X component (H): - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 17 05 43 82 82 67 86 71 - 05 26 3d 48 28 ef ff 82 - d9 d5 9c bf 08 74 3b 69 - 6b cc 5d 69 fa 18 97 b4 - - First 32 bytes of H called key_e - * - - - :: - - f8 3f 1e 9c c5 d6 b8 44 - 8d 39 dc 6a 9d 5f 5b 7f - 46 0e 4a 78 e9 28 6e e8 - d9 1c e1 66 0a 53 ea cd - - Last 32 bytes of H called key_m - -.. list-table:: Padded input: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 54 68 65 20 71 75 69 63 - 6b 20 62 72 6f 77 6e 20 - 66 6f 78 20 6a 75 6d 70 - 73 20 6f 76 65 72 20 74 - 68 65 20 6c 61 7a 79 20 - 64 6f 67 2e 04 04 04 04 - - The quick brown fox jumps over the lazy dog.0x04,0x04,0x04,0x04 - -.. list-table:: Cipher text: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 64 20 3d 5b 24 68 8e 25 - 47 bb a3 45 fa 13 9a 5a - 1d 96 22 20 d4 d4 8a 0c - f3 b1 57 2c 0d 95 b6 16 - 43 a6 f9 a0 d7 5a f7 ea - cc 1b d9 57 14 7b f7 23 - - 3 blocks of 16 bytes of encrypted data. - -.. list-table:: MAC: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - f2 52 6d 61 b4 85 1f b2 - 34 09 86 38 26 fd 20 61 - 65 ed c0 21 36 8c 79 46 - 57 1c ea d6 90 46 e6 19 - - 32 bytes hash - - -.. rubric:: Footnotes - -.. [#f1] The pyelliptic implementation used in PyBitmessage takes unpadded data, - see :obj:`.pyelliptic.Cipher.ciphering`. -.. [#f2] The pyelliptic encodes the pubkey with curve and length, - see :obj:`.pyelliptic.ECC.get_pubkey` diff --git a/docs/extended_encoding.rst b/docs/extended_encoding.rst deleted file mode 100644 index 25539ad4..00000000 --- a/docs/extended_encoding.rst +++ /dev/null @@ -1,55 +0,0 @@ -Extended encoding -================= - -Extended encoding is an attempt to create a standard for transmitting structured -data. The goals are flexibility, wide platform support and extensibility. It is -currently available in the v0.6 branch and can be enabled by holding "Shift" -while clicking on Send. It is planned that v5 addresses will have to support -this. It's a work in progress, the basic plain text message works but don't -expect anthing else at this time. - -The data structure is in msgpack, then compressed with zlib. The top level is -a key/value store, and the "" key (empty string) contains the value of the type -of object, which can then have its individual format and standards. - -Text fields are encoded using UTF-8. - -Types ------ - -You can find the implementations in the ``src/messagetypes`` directory of -PyBitmessage. Each type has its own file which includes one class, and they are -dynamically loaded on startup. It's planned that this will also contain -initialisation, rendering and so on, so that developers can simply add a new -object type by adding a single file in the messagetypes directory and not have -to change any other part of the code. - -message -^^^^^^^ - -The replacement for the old messages. Mandatory keys are ``body`` and -``subject``, others are currently not implemented and not mandatory. Proposed -other keys: - -``parents``: - array of msgids referring to messages that logically precede it in a - conversation. Allows to create a threaded conversation view - -``files``: - array of files (which is a key/value pair): - - ``name``: - file name, mandatory - ``data``: - the binary data of the file - ``type``: - MIME content type - ``disposition``: - MIME content disposition, possible values are "inline" and "attachment" - -vote -^^^^ - -Dummy code available in the repository. Supposed to serve voting in a chan -(thumbs up/down) for decentralised moderation. Does not actually do anything at -the moment and specification can change. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 6edb0313..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,30 +0,0 @@ -.. mdinclude:: ../README.md - :end-line: 20 - -Protocol documentation ----------------------- -.. toctree:: - :maxdepth: 2 - - protocol - address - encryption - pow - -Code documentation ------------------- -.. toctree:: - :maxdepth: 3 - - autodoc/pybitmessage - - -Indices and tables ------------------- - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - -.. mdinclude:: ../README.md - :start-line: 21 diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 2548d34e..00000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=PyBitmessage - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/pow.rst b/docs/pow.rst deleted file mode 100644 index 3786b075..00000000 --- a/docs/pow.rst +++ /dev/null @@ -1,77 +0,0 @@ -Proof of work -============= - -This page describes Bitmessage's Proof of work ("POW") mechanism as it exists in -Protocol Version 3. In this document, hash() means SHA512(). SHA512 was chosen -as it is widely supported and so that Bitcoin POW hardware cannot trivially be -used for Bitmessage POWs. The author acknowledges that they are essentially the -same algorithm with a different key size. - -Both ``averageProofOfWorkNonceTrialsPerByte`` and ``payloadLengthExtraBytes`` -are set by the owner of a Bitmessage address. The default and minimum for each -is 1000. (This is the same as difficulty 1. If the difficulty is 2, then this -value is 2000). The purpose of ``payloadLengthExtraBytes`` is to add some extra -weight to small messages. - -Do a POW --------- - -Let us use a ``msg`` message as an example:: - - payload = embeddedTime + encodedObjectVersion + encodedStreamNumber + encrypted - -``payloadLength`` - the length of payload, in bytes, + 8 - (to account for the nonce which we will append later) -``TTL`` - the number of seconds in between now and the object expiresTime. - -.. include:: pow_formula.rst - -:: - - initialHash = hash(payload) - -start with ``trialValue = 99999999999999999999`` - -also start with ``nonce = 0`` where nonce is 8 bytes in length and can be -hashed as if it is a string. - -:: - - while trialValue > target: - nonce = nonce + 1 - resultHash = hash(hash( nonce || initialHash )) - trialValue = the first 8 bytes of resultHash, converted to an integer - -When this loop finishes, you will have your 8 byte nonce value which you can -prepend onto the front of the payload. The message is then ready to send. - -Check a POW ------------ - -Let us assume that ``payload`` contains the payload for a msg message (the nonce -down through the encrypted message data). - -``nonce`` - the first 8 bytes of payload -``dataToCheck`` - the ninth byte of payload on down (thus it is everything except the nonce) - -:: - - initialHash = hash(dataToCheck) - - resultHash = hash(hash( nonce || initialHash )) - -``POWValue`` - the first eight bytes of resultHash converted to an integer -``TTL`` - the number of seconds in between now and the object ``expiresTime``. - -.. include:: pow_formula.rst - -If ``POWValue`` is less than or equal to ``target``, then the POW check passes. - - - diff --git a/docs/pow_formula.rst b/docs/pow_formula.rst deleted file mode 100644 index 16c3f174..00000000 --- a/docs/pow_formula.rst +++ /dev/null @@ -1,7 +0,0 @@ - -.. math:: - - target = \frac{2^{64}}{{\displaystyle - nonceTrialsPerByte (payloadLength + payloadLengthExtraBytes + \frac{ - TTL (payloadLength + payloadLengthExtraBytes)}{2^{16}}) - }} diff --git a/docs/protocol.rst b/docs/protocol.rst deleted file mode 100644 index 17a13dd9..00000000 --- a/docs/protocol.rst +++ /dev/null @@ -1,997 +0,0 @@ -Protocol specification -====================== - -.. warning:: All objects sent on the network should support protocol v3 - starting on Sun, 16 Nov 2014 22:00:00 GMT. - -.. toctree:: - :maxdepth: 2 - -Common standards ----------------- - -Hashes -^^^^^^ - -Most of the time `SHA-512 `_ hashes are -used, however `RIPEMD-160 `_ is also used -when creating an address. - -A double-round of SHA-512 is used for the Proof Of Work. Example of -double-SHA-512 encoding of string "hello": - -.. highlight:: nasm - -:: - - hello - 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round of sha-512) - 0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200(second round of sha-512) - -For Bitmessage addresses (RIPEMD-160) this would give: - -:: - - hello - 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round is sha-512) - 79a324faeebcbf9849f310545ed531556882487e (with ripemd-160) - - -Common structures ------------------ - -All integers are encoded in big endian. (This is different from Bitcoin). - -.. list-table:: Message structure - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - magic - - uint32_t - - Magic value indicating message origin network, and used to seek to next - message when stream state is unknown - * - 12 - - command - - char[12] - - ASCII string identifying the packet content, NULL padded (non-NULL - padding results in packet rejected) - * - 4 - - length - - uint32_t - - Length of payload in number of bytes. Because of other restrictions, - there is no reason why this length would ever be larger than 1600003 - bytes. Some clients include a sanity-check to avoid processing messages - which are larger than this. - * - 4 - - checksum - - uint32_t - - First 4 bytes of sha512(payload) - * - ? - - message_payload - - uchar[] - - The actual data, a :ref:`message ` or an object_. - Not to be confused with objectPayload. - -Known magic values: - -+-------------+-------------------+ -| Magic value | Sent over wire as | -+=============+===================+ -| 0xE9BEB4D9 | E9 BE B4 D9 | -+-------------+-------------------+ - -.. _varint: - -Variable length integer -^^^^^^^^^^^^^^^^^^^^^^^ - -Integer can be encoded depending on the represented value to save space. -Variable length integers always precede an array/vector of a type of data that -may vary in length. Varints **must** use the minimum possible number of bytes to -encode a value. For example, the value 6 can be encoded with one byte therefore -a varint that uses three bytes to encode the value 6 is malformed and the -decoding task must be aborted. - -+---------------+----------------+------------------------------------------+ -| Value | Storage length | Format | -+===============+================+==========================================+ -| < 0xfd | 1 | uint8_t | -+---------------+----------------+------------------------------------------+ -| <= 0xffff | 3 | 0xfd followed by the integer as uint16_t | -+---------------+----------------+------------------------------------------+ -| <= 0xffffffff | 5 | 0xfe followed by the integer as uint32_t | -+---------------+----------------+------------------------------------------+ -| - | 9 | 0xff followed by the integer as uint64_t | -+---------------+----------------+------------------------------------------+ - -Variable length string -^^^^^^^^^^^^^^^^^^^^^^ - -Variable length string can be stored using a variable length integer followed by -the string itself. - -+------------+-------------+------------+----------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+============+==================================+ -| 1+ | length | |var_int| | Length of the string | -+------------+-------------+------------+----------------------------------+ -| ? | string | char[] | The string itself (can be empty) | -+------------+-------------+------------+----------------------------------+ - -Variable length list of integers -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -n integers can be stored using n+1 :ref:`variable length integers ` -where the first var_int equals n. - -+------------+-------------+-----------+----------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+============================+ -| 1+ | count | |var_int| | Number of var_ints below | -+------------+-------------+-----------+----------------------------+ -| 1+ | | var_int | The first value stored | -+------------+-------------+-----------+----------------------------+ -| 1+ | | var_int | The second value stored... | -+------------+-------------+-----------+----------------------------+ -| 1+ | | var_int | etc... | -+------------+-------------+-----------+----------------------------+ - -.. |var_int| replace:: :ref:`var_int ` - -Network address -^^^^^^^^^^^^^^^ - -When a network address is needed somewhere, this structure is used. Network -addresses are not prefixed with a timestamp or stream in the version_ message. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 8 - - time - - uint64 - - the Time. - * - 4 - - stream - - uint32 - - Stream number for this node - * - 8 - - services - - uint64_t - - same service(s) listed in version_ - * - 16 - - IPv6/4 - - char[16] - - IPv6 address. IPv4 addresses are written into the message as a 16 byte - `IPv4-mapped IPv6 address `_ - (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of - the IPv4 address). - * - 2 - - port - - uint16_t - - port number - -Inventory Vectors -^^^^^^^^^^^^^^^^^ - -Inventory vectors are used for notifying other nodes about objects they have or -data which is being requested. Two rounds of SHA-512 are used, resulting in a -64 byte hash. Only the first 32 bytes are used; the later 32 bytes are ignored. - -Inventory vectors consist of the following data format: - -+------------+-------------+-----------+--------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+====================+ -| 32 | hash | char[32] | Hash of the object | -+------------+-------------+-----------+--------------------+ - -Encrypted payload -^^^^^^^^^^^^^^^^^ - -Bitmessage uses `ECIES `_ to encrypt its messages. For more information see :doc:`encryption` - -.. include:: encrypted_payload.rst - -Unencrypted Message Data -^^^^^^^^^^^^^^^^^^^^^^^^ - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 1+ - - msg_version - - var_int - - Message format version. **This field is not included after the - protocol v3 upgrade period**. - * - 1+ - - address_version - - var_int - - Sender's address version number. This is needed in order to calculate - the sender's address to show in the UI, and also to allow for forwards - compatible changes to the public-key data included below. - * - 1+ - - stream - - var_int - - Sender's stream number - * - 4 - - behavior bitfield - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node with this pubkey included in this msg message (the sender's - pubkey). - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. **This field is new and is - only included when the address_version >= 3**. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. **This field is new and is only included when the - address_version >= 3**. - * - 20 - - destination ripe - - uchar[] - - The ripe hash of the public key of the receiver of the message - * - 1+ - - encoding - - var_int - - :ref:`Message Encoding ` type - * - 1+ - - message_length - - var_int - - Message Length - * - message_length - - message - - uchar[] - - The message. - * - 1+ - - ack_length - - var_int - - Length of the acknowledgement data - * - ack_length - - ack_data - - uchar[] - - The acknowledgement data to be transmitted. This takes the form of a - Bitmessage protocol message, like another msg message. The POW therein - must already be completed. - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The ECDSA signature which covers the object header starting with the - time, appended with the data described in this table down to the - ack_data. - -.. _msg-encodings: - -Message Encodings -""""""""""""""""" - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Value - - Name - - Description - * - 0 - - IGNORE - - Any data with this number may be ignored. The sending node might simply - be sharing its public key with you. - * - 1 - - TRIVIAL - - UTF-8. No 'Subject' or 'Body' sections. Useful for simple strings - of data, like URIs or magnet links. - * - 2 - - SIMPLE - - UTF-8. Uses 'Subject' and 'Body' sections. No MIME is used. - :: - messageToTransmit = 'Subject:' + subject + '\n' + 'Body:' + message - * - 3 - - EXTENDED - - See :doc:`extended_encoding` - -Further values for the message encodings can be decided upon by the community. -Any MIME or MIME-like encoding format, should they be used, should make use of -Bitmessage's 8-bit bytes. - -.. _behavior-bitfield: - -Pubkey bitfield features -"""""""""""""""""""""""" - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Bit - - Name - - Description - * - 0 - - undefined - - The most significant bit at the beginning of the structure. Undefined - * - 1 - - undefined - - The next most significant bit. Undefined - * - ... - - ... - - ... - * - 27 - - onion_router - - (**Proposal**) Node can be used to onion-route messages. In theory any - node can onion route, but since it requires more resources, they may have - the functionality disabled. This field will be used to indicate that the - node is willing to do this. - * - 28 - - forward_secrecy - - (**Proposal**) Receiving node supports a forward secrecy encryption - extension. The exact design is pending. - * - 29 - - chat - - (**Proposal**) Address if for chatting rather than messaging. - * - 30 - - include_destination - - (**Proposal**) Receiving node expects that the RIPE hash encoded in their - address preceedes the encrypted message data of msg messages bound for - them. - - .. note:: since hardly anyone implements this, this will be redesigned as - `simple recipient verification `_ - * - 31 - - does_ack - - If true, the receiving node does send acknowledgements (rather than - dropping them). - -.. _msg-types: - -Message types -------------- - -Undefined messages received on the wire must be ignored. - -version -^^^^^^^ - -When a node creates an outgoing connection, it will immediately advertise its -version. The remote node will respond with its version. No futher communication -is possible until both peers have exchanged their version. - -.. list-table:: Payload - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - version - - int32_t - - Identifies protocol version being used by the node. Should equal 3. - Nodes should disconnect if the remote node's version is lower but - continue with the connection if it is higher. - * - 8 - - services - - uint64_t - - bitfield of features to be enabled for this connection - * - 8 - - timestamp - - int64_t - - standard UNIX timestamp in seconds - * - 26 - - addr_recv - - net_addr - - The network address of the node receiving this message (not including the - time or stream number) - * - 26 - - addr_from - - net_addr - - The network address of the node emitting this message (not including the - time or stream number and the ip itself is ignored by the receiver) - * - 8 - - nonce - - uint64_t - - Random nonce used to detect connections to self. - * - 1+ - - user_agent - - var_str - - :doc:`useragent` (0x00 if string is 0 bytes long). Sending nodes must not - include a user_agent longer than 5000 bytes. - * - 1+ - - stream_numbers - - var_int_list - - The stream numbers that the emitting node is interested in. Sending nodes - must not include more than 160000 stream numbers. - -A "verack" packet shall be sent if the version packet was accepted. Once you -have sent and received a verack messages with the remote node, send an addr -message advertising up to 1000 peers of which you are aware, and one or more -inv messages advertising all of the valid objects of which you are aware. - -.. list-table:: The following services are currently assigned - :header-rows: 1 - :widths: auto - - * - Value - - Name - - Description - * - 1 - - NODE_NETWORK - - This is a normal network node. - * - 2 - - NODE_SSL - - This node supports SSL/TLS in the current connect (python < 2.7.9 only - supports a SSL client, so in that case it would only have this on when - the connection is a client). - * - 3 - - NODE_POW - - (**Proposal**) This node may do PoW on behalf of some its peers (PoW - offloading/delegating), but it doesn't have to. Clients may have to meet - additional requirements (e.g. TLS authentication) - * - 4 - - NODE_DANDELION - - Node supports `dandelion `_ - -verack -^^^^^^ - -The *verack* message is sent in reply to *version*. This message consists of -only a :ref:`message header ` with the command string -"verack". The TCP timeout starts out at 20 seconds; after verack messages are -exchanged, the timeout is raised to 10 minutes. - -If both sides announce that they support SSL, they **must** perform an SSL -handshake immediately after they both send and receive verack. During this SSL -handshake, the TCP client acts as an SSL client, and the TCP server acts as an -SSL server. The current implementation (v0.5.4 or later) requires the -AECDH-AES256-SHA cipher over TLSv1 protocol, and prefers the secp256k1 curve -(but other curves may be accepted, depending on the version of python and -OpenSSL used). - -addr -^^^^ - -Provide information on known nodes of the network. Non-advertised nodes should -be forgotten after typically 3 hours - -Payload: - -+------------+-------------+-----------+---------------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+=======================================+ -| 1+ | count | |var_int| | Number of address entries (max: 1000) | -+------------+-------------+-----------+---------------------------------------+ -| 38 | addr_list | net_addr | Address of other nodes on the network.| -+------------+-------------+-----------+---------------------------------------+ - -inv -^^^ - -Allows a node to advertise its knowledge of one or more objects. Payload -(maximum payload length: 50000 items): - -+------------+-------------+------------+-----------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+============+=============================+ -| ? | count | |var_int| | Number of inventory entries | -+------------+-------------+------------+-----------------------------+ -| 32x? | inventory | inv_vect[] | Inventory vectors | -+------------+-------------+------------+-----------------------------+ - -getdata -^^^^^^^ - -getdata is used in response to an inv message to retrieve the content of a -specific object after filtering known elements. - -Payload (maximum payload length: 50000 entries): - -+------------+-------------+------------+-----------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+============+=============================+ -| ? | count | |var_int| | Number of inventory entries | -+------------+-------------+------------+-----------------------------+ -| 32x? | inventory | inv_vect[] | Inventory vectors | -+------------+-------------+------------+-----------------------------+ - -error -^^^^^ -.. note:: New in version 3 - -This message may be silently ignored (and therefor handled like any other -"unknown" message). - -The message is intended to inform the other node about protocol errors and -can be used for debugging and improving code. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 1+ - - fatal - - |var_int| - - This qualifies the error. If set to 0, than its just a "warning". - You can expect, everything still worked fine. If set to 1, than - it's an error, so you may expect, something was going wrong - (e.g. an object got lost). If set to 2, it's a fatal error. The node - will drop the line for that error and maybe ban you for some time. - * - 1+ - - ban time - - var_int - - If the error is fatal, you can specify the ban time in seconds, here. - You inform the other node, that you will not accept further connections - for this number of seconds. For non fatal errors this field has - no meaning and should be zero. - * - 1+ - - inventory vector - - var_str - - If the error is related to an object, this Variable length string - contains the inventory vector of that object. If the error is not - related to an object, this string is empty. - * - 1+ - - error text - - var_str - - A human readable string in English, which describes the error. - - -object -^^^^^^ - -An object is a message which is shared throughout a stream. It is the only -message which propagates; all others are only between two nodes. Objects have a -type, like 'msg', or 'broadcast'. To be a valid object, the -:doc:`pow` must be done. The maximum allowable length of an object -(not to be confused with the ``objectPayload``) is |2^18| bytes. - -.. |2^18| replace:: 2\ :sup:`18`\ - -.. list-table:: Message structure - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 8 - - nonce - - uint64_t - - Random nonce used for the :doc:`pow` - * - 8 - - expiresTime - - uint64_t - - The "end of life" time of this object (be aware, in version 2 of the - protocol this was the generation time). Objects shall be shared with - peers until its end-of-life time has been reached. The node should store - the inventory vector of that object for some extra period of time to - avoid reloading it from another node with a small time delay. The time - may be no further than 28 days + 3 hours in the future. - * - 4 - - objectType - - uint32_t - - Four values are currently defined: 0-"getpubkey", 1-"pubkey", 2-"msg", - 3-"broadcast". All other values are reserved. Nodes should relay objects - even if they use an undefined object type. - * - 1+ - - version - - var_int - - The object's version. Note that msg objects won't contain a version - until Sun, 16 Nov 2014 22:00:00 GMT. - * - 1+ - - stream number - - var_int - - The stream number in which this object may propagate - * - ? - - objectPayload - - uchar[] - - This field varies depending on the object type; see below. - - -Unsupported messages -^^^^^^^^^^^^^^^^^^^^ - -If a node receives an unknown message it **must** silently ignore it. This is -for further extensions of the protocol with other messages. Nodes that don't -understand such a new message type shall be able to work correct with the -message types they understand. - -Maybe some version 2 nodes did already implement it that way, but in version 3 -it is **part of the protocol specification**, that a node **must** -silently ignore unsupported messages. - - -Object types ------------- - -Here are the payloads for various object types. - -getpubkey -^^^^^^^^^ - -When a node has the hash of a public key (from an address) but not the public -key itself, it must send out a request for the public key. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 20 - - ripe - - uchar[] - - The ripemd hash of the public key. This field is only included when the - address version is <= 3. - * - 32 - - tag - - uchar[] - - The tag derived from the address version, stream number, and ripe. This - field is only included when the address version is >= 4. - -pubkey -^^^^^^ - -A version 2 pubkey. This is still in use and supported by current clients but -*new* v2 addresses are not generated by clients. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node receiving the message. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04 ) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04 ) - -.. list-table:: A version 3 pubkey - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node receiving the message. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04 ) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04 ) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The ECDSA signature which, as of protocol v3, covers the object - header starting with the time, appended with the data described in this - table down to the extra_bytes. - -.. list-table:: A version 4 pubkey - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 32 - - tag - - uchar[] - - The tag, made up of bytes 32-64 of the double hash of the address data - (see example python code below) - * - ? - - encrypted - - uchar[] - - Encrypted pubkey data. - -When version 4 pubkeys are created, most of the data in the pubkey is encrypted. -This is done in such a way that only someone who has the Bitmessage address -which corresponds to a pubkey can decrypt and use that pubkey. This prevents -people from gathering pubkeys sent around the network and using the data from -them to create messages to be used in spam or in flooding attacks. - -In order to encrypt the pubkey data, a double SHA-512 hash is calculated from -the address version number, stream number, and ripe hash of the Bitmessage -address that the pubkey corresponds to. The first 32 bytes of this hash are used -to create a public and private key pair with which to encrypt and decrypt the -pubkey data, using the same algorithm as message encryption -(see :doc:`encryption`). The remaining 32 bytes of this hash are added to the -unencrypted part of the pubkey and used as a tag, as above. This allows nodes to -determine which pubkey to decrypt when they wish to send a message. - -In PyBitmessage, the double hash of the address data is calculated using the -python code below: - -.. code-block:: python - - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + hash - ).digest()).digest() - - -.. list-table:: Encrypted data in version 4 pubkeys: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node receiving the message. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04 ) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04 ) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The ECDSA signature which covers everything from the object header - starting with the time, then appended with the decrypted data down to - the extra_bytes. This was changed in protocol v3. - -msg -^^^ - -Used for person-to-person messages. Note that msg objects won't contain a -version in the object header until Sun, 16 Nov 2014 22:00:00 GMT. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - ? - - encrypted - - uchar[] - - Encrypted data. See `Encrypted payload`_. - See also `Unencrypted Message Data`_ - -broadcast -^^^^^^^^^ - -Users who are subscribed to the sending address will see the message appear in -their inbox. Broadcasts are version 4 or 5. - -Pubkey objects and v5 broadcast objects are encrypted the same way: The data -encoded in the sender's Bitmessage address is hashed twice. The first 32 bytes -of the resulting hash constitutes the "private" encryption key and the last -32 bytes constitute a **tag** so that anyone listening can easily decide if -this particular message is interesting. The sender calculates the public key -from the private key and then encrypts the object with this public key. Thus -anyone who knows the Bitmessage address of the sender of a broadcast or pubkey -object can decrypt it. - -The version of broadcast objects was previously 2 or 3 but was changed to 4 or -5 for protocol v3. Having a broadcast version of 5 indicates that a tag is used -which, in turn, is used when the sender's address version is >=4. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 32 - - tag - - uchar[] - - The tag. This field is new and only included when the broadcast version - is >= 5. Changed in protocol v3 - * - ? - - encrypted - - uchar[] - - Encrypted broadcast data. The keys are derived as described in the - paragraph above. See Encrypted payload for details about the encryption - algorithm itself. - -Unencrypted data format: - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 1+ - - broadcast version - - var_int - - The version number of this broadcast protocol message which is equal - to 2 or 3. This is included here so that it can be signed. This is - no longer included in protocol v3 - * - 1+ - - address version - - var_int - - The sender's address version - * - 1+ - - stream number - - var_int - - The sender's stream number - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the owner of this pubkey. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. This field is new and is - only included when the address_version >= 3. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. This field is new and is only included when the - address_version >= 3. - * - 1+ - - encoding - - var_int - - The encoding type of the message - * - 1+ - - messageLength - - var_int - - The message length in bytes - * - messageLength - - message - - uchar[] - - The message - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The signature which did cover the unencrypted data from the broadcast - version down through the message. In protocol v3, it covers the - unencrypted object header starting with the time, all appended with - the decrypted data. - -.. |behavior_bitfield| replace:: :ref:`behavior bitfield ` diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index f8b4b17c..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -mistune<=0.8.4 -m2r<=0.2.1 -sphinx_rtd_theme -sphinxcontrib-apidoc -docutils<=0.17.1 diff --git a/docs/useragent.rst b/docs/useragent.rst deleted file mode 100644 index 3523a274..00000000 --- a/docs/useragent.rst +++ /dev/null @@ -1,53 +0,0 @@ -User Agent -========== - -Bitmessage user agents are a modified browser user agent with more structure -to aid parsers and provide some coherence. The user agent strings are arranged -in a stack with the most underlying software listed first. - -Basic format:: - - /Name:Version/Name:Version/.../ - -Example:: - - /PyBitmessage:0.2.2/Corporate Mail System:0.8/ - /Surdo:5.64/surdo-qt:0.4/ - -The version numbers are not defined to any strict format, although this guide -recommends: - - * Version numbers in the form of Major.Minor.Revision (2.6.41) - * Repository builds using a date in the format of YYYYMMDD (20110128) - -For git repository builds, implementations are free to use the git commitish. -However the issue lies in that it is not immediately obvious without the -repository which version preceeds another. For this reason, we lightly -recommend dates in the format specified above, although this is by no means -a requirement. - -Optional ``-r1``, ``-r2``, ... can be appended to user agent version numbers. -This is another light recommendation, but not a requirement. Implementations -are free to specify version numbers in whatever format needed insofar as it -does not include ``(``, ``)``, ``:`` or ``/`` to interfere with the user agent -syntax. - -An optional comments field after the version number is also allowed. Comments -should be delimited by parenthesis ``(...)``. The contents of comments is -entirely implementation defined although this document recommends the use of -semi-colons ``;`` as a delimiter between pieces of information. - -Example:: - - /cBitmessage:0.2(iPad; U; CPU OS 3_2_1)/AndroidBuild:0.8/ - -Reserved symbols are therefore: ``/ : ( )`` - -They should not be misused beyond what is specified in this section. - -``/`` - separates the code-stack -``:`` - specifies the implementation version of the particular stack -``( and )`` - delimits a comment which optionally separates data using ``;`` diff --git a/kivy-requirements.txt b/kivy-requirements.txt deleted file mode 100644 index 185a3ae7..00000000 --- a/kivy-requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -kivy-garden.qrcode -kivymd==1.0.2 -kivy==2.1.0 -opencv-python -pyzbar -git+https://github.com/tito/telenium@9b54ff1#egg=telenium -Pillow==9.4.0 -jaraco.collections==3.8.0 -jaraco.classes==3.2.3 -pytz==2022.7.1 -pydantic==1.10.6 diff --git a/packages/AppImage/AppImageBuilder.yml b/packages/AppImage/AppImageBuilder.yml deleted file mode 100644 index 2c5890d2..00000000 --- a/packages/AppImage/AppImageBuilder.yml +++ /dev/null @@ -1,81 +0,0 @@ -version: 1 -script: - # Remove any previous build - - rm -rf AppDir | true - - python setup.py install --prefix=/usr --root=AppDir - -AppDir: - path: ./AppDir - - app_info: - id: pybitmessage - name: PyBitmessage - icon: pybitmessage - version: !ENV ${APP_VERSION} - # Set the python executable as entry point - exec: usr/bin/python - # Set the application main script path as argument. - # Use '$@' to forward CLI parameters - exec_args: "$APPDIR/usr/bin/pybitmessage $@" - - after_runtime: - - sed -i "s|GTK_.*||g" AppDir/AppRun.env - - cp packages/AppImage/qt.conf AppDir/usr/bin/ - - apt: - arch: !ENV '${ARCH}' - sources: - - sourceline: !ENV '${SOURCELINE}' - key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32' - - include: - - python-defusedxml - - python-jsonrpclib - - python-msgpack - - python-qrcode - - python-qt4 - - python-setuptools - - python-sip - - python-six - - python-xdg - - sni-qt - exclude: - - libdb5.3 - - libdbus-1-3 - - libfontconfig1 - - libfreetype6 - - libglib2.0-0 - - libice6 - - libmng2 - - libncursesw5 - - libqt4-declarative - - libqt4-designer - - libqt4-help - - libqt4-script - - libqt4-scripttools - - libqt4-sql - - libqt4-test - - libqt4-xmlpatterns - - libqtassistantclient4 - - libsm6 - - libsystemd0 - - libreadline7 - - files: - exclude: - - usr/lib/x86_64-linux-gnu/gconv - - usr/share/man - - usr/share/doc - - runtime: - arch: [ !ENV '${RUNTIME}' ] - env: - # Set python home - # See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHOME - PYTHONHOME: '${APPDIR}/usr' - # Path to the site-packages dir or other modules dirs - # See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH - PYTHONPATH: '${APPDIR}/usr/lib/python2.7/site-packages' - -AppImage: - arch: !ENV '${APPIMAGE_ARCH}' diff --git a/packages/AppImage/PyBitmessage.yml b/packages/AppImage/PyBitmessage.yml deleted file mode 100644 index 3eeaef64..00000000 --- a/packages/AppImage/PyBitmessage.yml +++ /dev/null @@ -1,40 +0,0 @@ -app: PyBitmessage -binpatch: true - -ingredients: - dist: bionic - sources: - - deb http://archive.ubuntu.com/ubuntu/ bionic main universe - packages: - - python-defusedxml - - python-jsonrpclib - - python-msgpack - - python-qrcode - - python-qt4 - - python-setuptools - - python-sip - - python-six - - python-xdg - - sni-qt - exclude: - - libdb5.3 - - libglib2.0-0 - - libmng2 - - libncursesw5 - - libqt4-declarative - - libqt4-designer - - libqt4-help - - libqt4-script - - libqt4-scripttools - - libqt4-sql - - libqt4-test - - libqt4-xmlpatterns - - libqtassistantclient4 - - libreadline7 - debs: - - ../deb_dist/pybitmessage_*_amd64.deb - -script: - - rm -rf usr/share/glib-2.0/schemas - - cp usr/share/icons/hicolor/scalable/apps/pybitmessage.svg . - - mv usr/bin/python2.7 usr/bin/python2 diff --git a/packages/AppImage/qt.conf b/packages/AppImage/qt.conf deleted file mode 100644 index 0e343236..00000000 --- a/packages/AppImage/qt.conf +++ /dev/null @@ -1,2 +0,0 @@ -[Paths] -Prefix = ../lib/x86_64-linux-gnu/qt4 diff --git a/packages/README.md b/packages/README.md index 2905ec20..ed2df3cc 100644 --- a/packages/README.md +++ b/packages/README.md @@ -15,7 +15,7 @@ OSX: https://github.com/Bitmessage/PyBitmessage/releases -Works on OSX 10.7.5 or higher +Wors on OSX 10.7.5 or higher Arch linux: diff --git a/packages/android/buildozer.spec b/packages/android/buildozer.spec deleted file mode 100644 index c98717b4..00000000 --- a/packages/android/buildozer.spec +++ /dev/null @@ -1,357 +0,0 @@ -[app] - -# (str) Title of your application -title = mockone - -# (str) Package name -package.name = mock - -# (str) Package domain (needed for android/ios packaging) -package.domain = org.mock - -# (str) Source code where the main.py live -source.dir = ../../src - -# (list) Source files to include (let empty to include all the files) -source.include_exts = py,png,jpg,kv,atlas,tflite,sql - -# (list) List of inclusions using pattern matching -#source.include_patterns = assets/*,images/*.png - -# (list) Source files to exclude (let empty to not exclude anything) -#source.exclude_exts = spec - -# (list) List of directory to exclude (let empty to not exclude anything) -#source.exclude_dirs = tests, bin, venv - -# (list) List of exclusions using pattern matching -#source.exclude_patterns = license,images/*/*.jpg - -# (str) Application versioning (method 1) -version = 0.1 - -# (str) Application versioning (method 2) -# version.regex = __version__ = ['"](.*)['"] -# version.filename = %(source.dir)s/main.py - -# (list) Application requirements -# comma separated e.g. requirements = sqlite3,kivy -requirements = python3,kivy - -# (str) Custom source folders for requirements -# Sets custom source for any requirements with recipes -# requirements.source.kivy = ../../kivy - -# (str) Presplash of the application -#presplash.filename = %(source.dir)s/data/presplash.png - -# (str) Icon of the application -#icon.filename = %(source.dir)s/data/icon.png - -# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all) -orientation = portrait - -# (list) List of service to declare -#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY - -# -# OSX Specific -# - -# -# author = © Copyright Info - -# change the major version of python used by the app -osx.python_version = 3 - -# Kivy version to use -osx.kivy_version = 1.9.1 - -# -# Android specific -# - -# (bool) Indicate if the application should be fullscreen or not -fullscreen = 0 - -# (string) Presplash background color (for android toolchain) -# Supported formats are: #RRGGBB #AARRGGBB or one of the following names: -# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, -# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, -# olive, purple, silver, teal. -#android.presplash_color = #FFFFFF - -# (string) Presplash animation using Lottie format. -# see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/ -# for general documentation. -# Lottie files can be created using various tools, like Adobe After Effect or Synfig. -#android.presplash_lottie = "path/to/lottie/file.json" - -# (list) Permissions -#android.permissions = INTERNET - -# (int) Android API to use (targetSdkVersion AND compileSdkVersion) -# note: when changing, Dockerfile also needs to be changed to install corresponding build tools -android.api = 28 - -# (int) Minimum API required. You will need to set the android.ndk_api to be as low as this value. -android.minapi = 21 - -# (str) Android NDK version to use -android.ndk = 25b - -# (int) Android NDK API to use (optional). This is the minimum API your app will support. -android.ndk_api = 21 - -# (bool) Use --private data storage (True) or --dir public storage (False) -android.private_storage = True - -# (str) Android NDK directory (if empty, it will be automatically downloaded.) -android.ndk_path = /opt/android/android-ndk - -# (str) Android SDK directory (if empty, it will be automatically downloaded.) -android.sdk_path = /opt/android/android-sdk - -# (str) ANT directory (if empty, it will be automatically downloaded.) -android.ant_path = /opt/android/apache-ant - -# (bool) If True, then skip trying to update the Android sdk -# This can be useful to avoid excess Internet downloads or save time -# when an update is due and you just want to test/build your package -# android.skip_update = False - -# (bool) If True, then automatically accept SDK license -# agreements. This is intended for automation only. If set to False, -# the default, you will be shown the license when first running -# buildozer. -# android.accept_sdk_license = False - -# (str) Android entry point, default is ok for Kivy-based app -#android.entrypoint = org.renpy.android.PythonActivity - -# (str) Android app theme, default is ok for Kivy-based app -# android.apptheme = "@android:style/Theme.NoTitleBar" - -# (list) Pattern to whitelist for the whole project -#android.whitelist = - -# (str) Path to a custom whitelist file -#android.whitelist_src = - -# (str) Path to a custom blacklist file -#android.blacklist_src = - -# (list) List of Java .jar files to add to the libs so that pyjnius can access -# their classes. Don't add jars that you do not need, since extra jars can slow -# down the build process. Allows.build wildcards matching, for example: -# OUYA-ODK/libs/*.jar -#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar - -# (list) List of Java files to add to the android project (can be java or a -# directory containing the files) -#android.add_src = - -# (list) Android AAR archives to add -#android.add_aars = - -# (list) Gradle dependencies to add -#android.gradle_dependencies = - -# (list) add java compile options -# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option -# see https://developer.android.com/studio/write/java8-support for further information -#android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8" - -# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies} -# please enclose in double quotes -# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }" -#android.add_gradle_repositories = -#android.gradle_dependencies = "org.tensorflow:tensorflow-lite:+","org.tensorflow:tensorflow-lite-support:0.0.0-nightly" - -# (list) packaging options to add -# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html -# can be necessary to solve conflicts in gradle_dependencies -# please enclose in double quotes -# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'" -#android.add_packaging_options = - -# (list) Java classes to add as activities to the manifest. -#android.add_activities = com.example.ExampleActivity - -# (str) OUYA Console category. Should be one of GAME or APP -# If you leave this blank, OUYA support will not be enabled -#android.ouya.category = GAME - -# (str) Filename of OUYA Console icon. It must be a 732x412 png image. -#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png - -# (str) XML file to include as an intent filters in tag -#android.manifest.intent_filters = - -# (str) launchMode to set for the main activity -#android.manifest.launch_mode = standard - -# (list) Android additional libraries to copy into libs/armeabi -#android.add_libs_armeabi = libs/android/*.so -#android.add_libs_armeabi_v7a = libs/android-v7/*.so -#android.add_libs_arm64_v8a = libs/android-v8/*.so -#android.add_libs_x86 = libs/android-x86/*.so -#android.add_libs_mips = libs/android-mips/*.so - -# (bool) Indicate whether the screen should stay on -# Don't forget to add the WAKE_LOCK permission if you set this to True -#android.wakelock = False - -# (list) Android application meta-data to set (key=value format) -#android.meta_data = - -# (list) Android library project to add (will be added in the -# project.properties automatically.) -#android.library_references = - -# (list) Android shared libraries which will be added to AndroidManifest.xml using tag -#android.uses_library = - -# (str) Android logcat filters to use -#android.logcat_filters = *:S python:D - -# (bool) Android logcat only display log for activity's pid -#android.logcat_pid_only = False - -# (str) Android additional adb arguments -#android.adb_args = -H host.docker.internal - -# (bool) Copy library instead of making a libpymodules.so -#android.copy_libs = 1 - -# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64 -android.archs = armeabi-v7a, arm64-v8a, x86_64 - -# (int) overrides automatic versionCode computation (used in build.gradle) -# this is not the same as app version and should only be edited if you know what you're doing -# android.numeric_version = 1 - -# (bool) enables Android auto backup feature (Android API >=23) -android.allow_backup = True - -# (str) XML file for custom backup rules (see official auto backup documentation) -# android.backup_rules = - -# (str) If you need to insert variables into your AndroidManifest.xml file, -# you can do so with the manifestPlaceholders property. -# This property takes a map of key-value pairs. (via a string) -# Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"] -# android.manifest_placeholders = [:] - -# -# Python for android (p4a) specific -# - -# (str) python-for-android fork to use, defaults to upstream (kivy) -#p4a.fork = kivy - -# (str) python-for-android branch to use, defaults to master -#p4a.branch = master - -# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) -#p4a.source_dir = - -# (str) The directory in which python-for-android should look for your own build recipes (if any) -#p4a.local_recipes = - -# (str) Filename to the hook for p4a -#p4a.hook = - -# (str) Bootstrap to use for android builds -# p4a.bootstrap = sdl2 - -# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask) -#p4a.port = - -# Control passing the --use-setup-py vs --ignore-setup-py to p4a -# "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not -# Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py -# NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate -# setup.py if you're using Poetry, but you need to add "toml" to source.include_exts. -#p4a.setup_py = false - - -# -# iOS specific -# - -# (str) Path to a custom kivy-ios folder -#ios.kivy_ios_dir = ../kivy-ios -# Alternately, specify the URL and branch of a git checkout: -ios.kivy_ios_url = https://github.com/kivy/kivy-ios -ios.kivy_ios_branch = master - -# Another platform dependency: ios-deploy -# Uncomment to use a custom checkout -#ios.ios_deploy_dir = ../ios_deploy -# Or specify URL and branch -ios.ios_deploy_url = https://github.com/phonegap/ios-deploy -ios.ios_deploy_branch = 1.10.0 - -# (bool) Whether or not to sign the code -ios.codesign.allowed = false - -# (str) Name of the certificate to use for signing the debug version -# Get a list of available identities: buildozer ios list_identities -#ios.codesign.debug = "iPhone Developer: ()" - -# (str) Name of the certificate to use for signing the release version -#ios.codesign.release = %(ios.codesign.debug)s - - -[buildozer] - -# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) -log_level = 2 - -# (int) Display warning if buildozer is run as root (0 = False, 1 = True) -warn_on_root = 1 - -# (str) Path to build artifact storage, absolute or relative to spec file -# build_dir = ./.buildozer - -# (str) Path to build output (i.e. .apk, .ipa) storage -# bin_dir = ./bin - -# ----------------------------------------------------------------------------- -# List as sections -# -# You can define all the "list" as [section:key]. -# Each line will be considered as a option to the list. -# Let's take [app] / source.exclude_patterns. -# Instead of doing: -# -#[app] -#source.exclude_patterns = license,data/audio/*.wav,data/images/original/* -# -# This can be translated into: -# -#[app:source.exclude_patterns] -#license -#data/audio/*.wav -#data/images/original/* -# - - -# ----------------------------------------------------------------------------- -# Profiles -# -# You can extend section / key with a profile -# For example, you want to deploy a demo version of your application without -# HD content. You could first change the title to add "(demo)" in the name -# and extend the excluded directories to remove the HD content. -# -#[app@demo] -#title = My Application (demo) -# -#[app:source.exclude_patterns@demo] -#images/hd/* -# -# Then, invoke the command line with the "demo" profile: -# -#buildozer --profile demo android debug diff --git a/packages/apparmor/pybitmessage b/packages/apparmor/pybitmessage deleted file mode 100644 index 3ec3d237..00000000 --- a/packages/apparmor/pybitmessage +++ /dev/null @@ -1,19 +0,0 @@ -# Last Modified: Wed Apr 29 21:04:08 2020 -#include - -/usr/bin/pybitmessage { - #include - #include - #include - #include - #include - - owner /home/*/.ICEauthority r, - owner /home/*/.Xauthority r, - owner /home/*/.config/PyBitmessage/ rw, - owner /home/*/.config/PyBitmessage/* rwk, - owner /home/*/.config/Trolltech.conf rwk, - owner /home/*/.config/Trolltech.conf.* rw, - owner /proc/*/mounts r, - -} diff --git a/packages/collectd/pybitmessagestatus.py b/packages/collectd/pybitmessagestatus.py index d15c3a48..1db9f5b1 100644 --- a/packages/collectd/pybitmessagestatus.py +++ b/packages/collectd/pybitmessagestatus.py @@ -7,13 +7,11 @@ 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 = "" @@ -30,22 +28,17 @@ def config_callback(ObjConfiguration): apiInterface = node.values[0] elif key.lower() == "apiport" and node.values: apiPort = node.values[0] - pybmurl = "http://{}:{}@{}:{}/".format(apiUsername, apiPassword, apiInterface, str(int(apiPort))) + pybmurl = "http://" + apiUsername + ":" + apiPassword + "@" + apiInterface+ ":" + str(int(apiPort)) + "/" collectd.info('pybitmessagestatus.py config done') - def read_callback(): try: clientStatus = json.loads(api.clientStatus()) - except (ValueError, TypeError): - collectd.info("Exception loading or parsing JSON") - return - except: # noqa:E722 + except: collectd.info("Exception loading or parsing JSON") return - for i in ["networkConnections", "numberOfPubkeysProcessed", - "numberOfMessagesProcessed", "numberOfBroadcastsProcessed"]: + for i in ["networkConnections", "numberOfPubkeysProcessed", "numberOfMessagesProcessed", "numberOfBroadcastsProcessed"]: metric = collectd.Values() metric.plugin = "pybitmessagestatus" if i[0:6] == "number": @@ -55,11 +48,10 @@ def read_callback(): metric.type_instance = i.lower() try: metric.values = [clientStatus[i]] - except (TypeError, KeyError): + except: collectd.info("Value for %s missing" % (i)) metric.dispatch() - if __name__ == "__main__": main() else: diff --git a/packages/docker/Dockerfile.bionic b/packages/docker/Dockerfile.bionic deleted file mode 100644 index ff53e4e7..00000000 --- a/packages/docker/Dockerfile.bionic +++ /dev/null @@ -1,120 +0,0 @@ -FROM ubuntu:bionic AS base - -ENV DEBIAN_FRONTEND noninteractive - -RUN apt-get update - -# Common apt packages -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libssl-dev \ - python-all-dev python-setuptools wget xvfb - -############################################################################### - -FROM base AS appimage - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - debhelper dh-apparmor dh-python python-stdeb fakeroot - -COPY . /home/builder/src - -WORKDIR /home/builder/src - -CMD python setup.py sdist \ - && python setup.py --command-packages=stdeb.command bdist_deb \ - && dpkg-deb -I deb_dist/*.deb \ - && cp deb_dist/*.deb /dist/ \ - && ln -s /dist out \ - && buildscripts/appimage.sh - -############################################################################### - -FROM base AS tox - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - language-pack-en \ - libffi-dev python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ - python-msgpack python-pip python-qt4 python-six qt5dxcb-plugin tor - -RUN python3.8 -m pip install setuptools wheel -RUN python3.8 -m pip install --upgrade pip tox virtualenv - -RUN useradd -m -U builder - -# copy sources -COPY . /home/builder/src -RUN chown -R builder.builder /home/builder/src - -USER builder - -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - -WORKDIR /home/builder/src - -ENTRYPOINT ["tox"] - -############################################################################### - -FROM base AS snap - -RUN apt-get install -yq --no-install-suggests --no-install-recommends snapcraft - -COPY . /home/builder/src - -WORKDIR /home/builder/src - -CMD cd packages && snapcraft && cp *.snap /dist/ - -############################################################################### - -FROM base AS winebuild - -RUN dpkg --add-architecture i386 -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - mingw-w64 wine-stable winetricks wine32 wine64 - -COPY . /home/builder/src - -WORKDIR /home/builder/src - -# xvfb-run -a buildscripts/winbuild.sh -CMD xvfb-run -a i386 buildscripts/winbuild.sh \ - && cp packages/pyinstaller/dist/*.exe /dist/ - -############################################################################### - -FROM base AS buildbot - -# cleanup -RUN rm -rf /var/lib/apt/lists/* - -# travis2bash -RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh -RUN chmod +x /usr/local/bin/travis2bash.sh - -# copy entrypoint -COPY packages/docker/buildbot-entrypoint.sh entrypoint.sh -RUN chmod +x entrypoint.sh - -RUN useradd -m -U buildbot -RUN echo 'buildbot ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers - -USER buildbot - -ENTRYPOINT /entrypoint.sh "$BUILDMASTER" "$WORKERNAME" "$WORKERPASS" - -############################################################################### - -FROM base AS appandroid - -COPY . /home/builder/src - -WORKDIR /home/builder/src - -RUN chmod +x buildscripts/androiddev.sh - -RUN buildscripts/androiddev.sh diff --git a/packages/docker/Dockerfile.kivy-travis b/packages/docker/Dockerfile.kivy-travis deleted file mode 100644 index 4dcdf60b..00000000 --- a/packages/docker/Dockerfile.kivy-travis +++ /dev/null @@ -1,64 +0,0 @@ -FROM ubuntu:bionic AS pybm-kivy-travis-bionic - -ENV DEBIAN_FRONTEND noninteractive -ENV TRAVIS_SKIP_APT_UPDATE 1 - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common - -RUN dpkg --add-architecture i386 - -RUN add-apt-repository ppa:deadsnakes/ppa - -RUN apt-get -y install sudo - -RUN apt-get -y install git - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - # travis xenial bionic - python-setuptools libssl-dev libpq-dev python-prctl python-dev \ - python-dev python-virtualenv python-pip virtualenv \ - # Code quality - pylint python-pycodestyle python3-pycodestyle pycodestyle python-flake8 \ - python3-flake8 flake8 python-pyflakes python3-pyflakes pyflakes pyflakes3 \ - curl \ - # Wine - python python-pip wget wine-stable winetricks mingw-w64 wine32 wine64 xvfb \ - # Buildbot - python3-dev libffi-dev python3-setuptools \ - python3-pip \ - # python 3.7 - python3.7 python3.7-dev \ - # .travis-kivy.yml - build-essential libcap-dev tor \ - language-pack-en \ - xclip xsel \ - libzbar-dev - -# cleanup -RUN rm -rf /var/lib/apt/lists/* - -RUN useradd -m -U builder - -RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers - -# travis2bash -RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh -RUN chmod +x /usr/local/bin/travis2bash.sh - -# copy sources -COPY . /home/builder/src -RUN chown -R builder.builder /home/builder/src - -USER builder - -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - -WORKDIR /home/builder/src - - -ENTRYPOINT ["/usr/local/bin/travis2bash.sh", ".travis-kivy.yml"] diff --git a/packages/docker/buildbot-entrypoint.sh b/packages/docker/buildbot-entrypoint.sh deleted file mode 100644 index 0e6ee5c3..00000000 --- a/packages/docker/buildbot-entrypoint.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - - -buildbot-worker create-worker /var/lib/buildbot/workers/default "$1" "$2" "$3" - -unset BUILDMASTER BUILDMASTER_PORT WORKERNAME WORKERPASS - -cd /var/lib/buildbot/workers/default -/usr/bin/dumb-init buildbot-worker start --nodaemon diff --git a/packages/docker/launcher.sh b/packages/docker/launcher.sh deleted file mode 100755 index c0e48855..00000000 --- a/packages/docker/launcher.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -# Setup the environment for docker container -APIUSER=${USER:-api} -APIPASS=${PASSWORD:-$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo)} - -echo "\napiusername: $APIUSER\napipassword: $APIPASS" - -sed -i -e "s|\(apiinterface = \).*|\10\.0\.0\.0|g" \ - -e "s|\(apivariant = \).*|\1json|g" \ - -e "s|\(apiusername = \).*|\1$APIUSER|g" \ - -e "s|\(apipassword = \).*|\1$APIPASS|g" \ - -e "s|apinotifypath = .*||g" ${BITMESSAGE_HOME}/keys.dat - -# Run -exec pybitmessage "$@" diff --git a/packages/pyinstaller/bitmessagemain.spec b/packages/pyinstaller/bitmessagemain.spec index fb2b572d..06cf6e76 100644 --- a/packages/pyinstaller/bitmessagemain.spec +++ b/packages/pyinstaller/bitmessagemain.spec @@ -1,144 +1,79 @@ -# -*- mode: python -*- import ctypes import os -import sys import time -from PyInstaller.utils.hooks import copy_metadata - - -DEBUG = False -site_root = os.path.abspath(HOMEPATH) -spec_root = os.path.abspath(SPECPATH) -arch = 32 if ctypes.sizeof(ctypes.c_voidp) == 4 else 64 -cdrivePath = site_root[0:3] -srcPath = os.path.join(spec_root[:-20], "pybitmessage") -sslName = 'OpenSSL-Win%i' % arch -openSSLPath = os.path.join(cdrivePath, sslName) -msvcrDllPath = os.path.join(cdrivePath, "windows", "system32") -outPath = os.path.join(spec_root, "bitmessagemain") -qtBase = "PyQt4" - -sys.path.insert(0, srcPath) -os.chdir(srcPath) - +srcPath = "C:\\src\\PyBitmessage\\src\\" +qtPath = "C:\\Qt-4.8.7\\" +openSSLPath = "C:\\OpenSSL-1.0.2j\\bin\\" +outPath = "C:\\src\\PyInstaller-3.2.1\\bitmessagemain" +today = time.strftime("%Y%m%d") snapshot = False -hookspath = os.path.join(spec_root, 'hooks') +os.rename(os.path.join(srcPath, '__init__.py'), os.path.join(srcPath, '__init__.py.backup')) -excludes = ['bsddb', 'bz2', 'tcl', 'tk', 'Tkinter', 'tests'] -if not DEBUG: - excludes += ['pybitmessage.tests', 'pyelliptic.tests'] - -a = Analysis( - [os.path.join(srcPath, 'bitmessagemain.py')], - datas=[ - (os.path.join(spec_root[:-20], 'pybitmessage.egg-info') + '/*', - 'pybitmessage.egg-info') - ] + copy_metadata('msgpack-python') + copy_metadata('qrcode') - + copy_metadata('six') + copy_metadata('stem'), - pathex=[outPath], - hiddenimports=[ - 'bitmessageqt.languagebox', 'pyopencl', 'numpy', 'win32com', - 'setuptools.msvc', '_cffi_backend', - 'plugins.menu_qrcode', 'plugins.proxyconfig_stem' - ], - runtime_hooks=[os.path.join(hookspath, 'pyinstaller_rthook_plugins.py')], - excludes=excludes -) +# -*- mode: python -*- +a = Analysis([srcPath + 'bitmessagemain.py'], + pathex=[outPath], + hiddenimports=[], + hookspath=None, + runtime_hooks=None) +os.rename(os.path.join(srcPath, '__init__.py.backup'), os.path.join(srcPath, '__init__.py')) def addTranslations(): + import os extraDatas = [] - for file_ in os.listdir(os.path.join(srcPath, 'translations')): - if file_[-3:] != ".qm": + for file in os.listdir(srcPath + 'translations'): + if file[-3:] != ".qm": continue - extraDatas.append(( - os.path.join('translations', file_), - os.path.join(srcPath, 'translations', file_), 'DATA')) - for libdir in sys.path: - qtdir = os.path.join(libdir, qtBase, 'translations') - if os.path.isdir(qtdir): - break - if not os.path.isdir(qtdir): - return extraDatas - for file_ in os.listdir(qtdir): - if file_[0:3] != "qt_" or file_[5:8] != ".qm": + extraDatas.append((os.path.join('translations', file), os.path.join(srcPath, 'translations', file), 'DATA')) + for file in os.listdir(qtPath + 'translations'): + if file[0:3] != "qt_" or file[5:8] != ".qm": continue - extraDatas.append(( - os.path.join('translations', file_), - os.path.join(qtdir, file_), 'DATA')) + extraDatas.append((os.path.join('translations', file), os.path.join(qtPath, 'translations', file), 'DATA')) return extraDatas - -dir_append = os.path.join(srcPath, 'bitmessageqt') - -a.datas += [ - (os.path.join('ui', file_), os.path.join(dir_append, file_), 'DATA') - for file_ in os.listdir(dir_append) if file_.endswith('.ui') -] - -sql_dir = os.path.join(srcPath, 'sql') - -a.datas += [ - (os.path.join('sql', file_), os.path.join(sql_dir, file_), 'DATA') - for file_ in os.listdir(sql_dir) if file_.endswith('.sql') -] +def addUIs(): + import os + extraDatas = [] + for file in os.listdir(srcPath + 'bitmessageqt'): + if file[-3:] != ".ui": + continue + extraDatas.append((os.path.join('ui', file), os.path.join(srcPath, 'bitmessageqt', file), 'DATA')) + return extraDatas # append the translations directory a.datas += addTranslations() -a.datas += [('default.ini', os.path.join(srcPath, 'default.ini'), 'DATA')] +a.datas += addUIs() -excluded_binaries = [ - 'QtOpenGL4.dll', - 'QtSvg4.dll', - 'QtXml4.dll', -] -a.binaries = TOC([x for x in a.binaries if x[0] not in excluded_binaries]) - -a.binaries += [ - # No effect: libeay32.dll will be taken from PyQt if installed - ('libeay32.dll', os.path.join(openSSLPath, 'libeay32.dll'), 'BINARY'), - (os.path.join('bitmsghash', 'bitmsghash%i.dll' % arch), - os.path.join(srcPath, 'bitmsghash', 'bitmsghash%i.dll' % arch), - 'BINARY'), - (os.path.join('bitmsghash', 'bitmsghash.cl'), - os.path.join(srcPath, 'bitmsghash', 'bitmsghash.cl'), 'BINARY'), - (os.path.join('sslkeys', 'cert.pem'), - os.path.join(srcPath, 'sslkeys', 'cert.pem'), 'BINARY'), - (os.path.join('sslkeys', 'key.pem'), - os.path.join(srcPath, 'sslkeys', 'key.pem'), 'BINARY') -] - -from version import softwareVersion - -today = time.strftime("%Y%m%d") - -fname = '%s_%%s_%s.exe' % ( - ('Bitmessagedev', today) if snapshot else ('Bitmessage', softwareVersion) -) % ("x86" if arch == 32 else "x64") +if ctypes.sizeof(ctypes.c_voidp) == 4: + arch=32 +else: + arch=64 +a.binaries += [('libeay32.dll', openSSLPath + 'libeay32.dll', 'BINARY'), + (os.path.join('bitmsghash', 'bitmsghash%i.dll' % (arch)), os.path.join(srcPath, 'bitmsghash', 'bitmsghash%i.dll' % (arch)), 'BINARY'), + (os.path.join('bitmsghash', 'bitmsghash.cl'), os.path.join(srcPath, 'bitmsghash', 'bitmsghash.cl'), 'BINARY'), + (os.path.join('sslkeys', 'cert.pem'), os.path.join(srcPath, 'sslkeys', 'cert.pem'), 'BINARY'), + (os.path.join('sslkeys', 'key.pem'), os.path.join(srcPath, 'sslkeys', 'key.pem'), 'BINARY') + ] +with open(os.path.join(srcPath, 'version.py'), 'rt') as f: + softwareVersion = f.readline().split('\'')[1] + +fname = 'Bitmessage_%s_%s.exe' % ("x86" if arch == 32 else "x64", softwareVersion) +if snapshot: + fname = 'Bitmessagedev_%s_%s.exe' % ("x86" if arch == 32 else "x64", today) + pyz = PYZ(a.pure) -exe = EXE( - pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - name=fname, - debug=DEBUG, - strip=None, - upx=False, - console=DEBUG, icon=os.path.join(srcPath, 'images', 'can-icon.ico') -) - -coll = COLLECT( - exe, - a.binaries, - a.zipfiles, - a.datas, - strip=False, - upx=False, - name='main' -) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + a.binaries, + name=fname, + debug=False, + strip=None, + upx=False, + console=False, icon= os.path.join(srcPath, 'images', 'can-icon.ico')) diff --git a/packages/pyinstaller/hooks/pyinstaller_rthook_plugins.py b/packages/pyinstaller/hooks/pyinstaller_rthook_plugins.py deleted file mode 100644 index e796c1f5..00000000 --- a/packages/pyinstaller/hooks/pyinstaller_rthook_plugins.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Runtime PyInstaller hook to load plugins""" - -import os -import sys - -homepath = os.path.abspath(os.path.dirname(sys.argv[0])) - -os.environ['PATH'] += ';' + ';'.join([ - homepath, os.path.join(homepath, 'Tor'), - os.path.abspath(os.curdir) -]) - -try: - import pybitmessage.plugins.menu_qrcode - import pybitmessage.plugins.proxyconfig_stem # noqa:F401 -except ImportError: - pass diff --git a/packages/snap/snapcraft.yaml b/packages/snap/snapcraft.yaml deleted file mode 100644 index cc3e54fc..00000000 --- a/packages/snap/snapcraft.yaml +++ /dev/null @@ -1,76 +0,0 @@ -name: pybitmessage -base: core18 -grade: devel -confinement: strict -summary: Reference client for Bitmessage, a P2P communications protocol -description: | - Bitmessage is a P2P communication 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. BM aims to hide metadata from passive - eavesdroppers like those ongoing warrantless wiretapping programs. Hence - the sender and receiver of Bitmessages stay anonymous. -adopt-info: pybitmessage - -apps: - pybitmessage: - command: desktop-launch pybitmessage - plugs: [desktop, home, network-bind, unity7] - desktop: share/applications/pybitmessage.desktop - passthrough: - autostart: pybitmessage.desktop - -parts: - pybitmessage: - # https://wiki.ubuntu.com/snapcraft/parts - after: [qt4conf, desktop-qt4, indicator-qt4, tor] - source: https://github.com/Bitmessage/PyBitmessage.git - override-pull: | - snapcraftctl pull - snapcraftctl set-version $(git describe --tags --abbrev=0 | tr -d v) - plugin: python - python-version: python2 - build-packages: - - libssl-dev - - python-all-dev - python-packages: - - jsonrpclib - - qrcode - - pyxdg - - stem - stage-packages: - - python-qt4 - - python-sip - # parse-info: [setup.py] - tor: - source: https://dist.torproject.org/tor-0.4.6.9.tar.gz - source-checksum: sha256/c7e93380988ce20b82aa19c06cdb2f10302b72cfebec7c15b5b96bcfc94ca9a9 - source-type: tar - plugin: autotools - build-packages: - - libssl-dev - - zlib1g-dev - after: [libevent] - libevent: - source: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz - source-checksum: sha256/92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - source-type: tar - plugin: autotools - cleanup: - after: [pybitmessage] - plugin: nil - override-prime: | - set -eux - sed -ie \ - 's|.*Icon=.*|Icon=${SNAP}/share/icons/hicolor/scalable/apps/pybitmessage.svg|g' \ - $SNAPCRAFT_PRIME/share/applications/pybitmessage.desktop - rm -rf $SNAPCRAFT_PRIME/lib/python2.7/site-packages/pip - for DIR in doc man icons themes fonts mime; do - rm -rf $SNAPCRAFT_PRIME/usr/share/$DIR/* - done - LIBS="libQtDeclarative libQtDesigner libQtHelp libQtScript libQtSql \ - libQtXmlPatterns libdb-5 libicu libgdk libgio libglib libcairo" - for LIBGLOB in $LIBS; do - rm $SNAPCRAFT_PRIME/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/${LIBGLOB}* - done diff --git a/packages/unmaintained/Makefile b/packages/unmaintained/Makefile new file mode 100644 index 00000000..35e17b03 --- /dev/null +++ b/packages/unmaintained/Makefile @@ -0,0 +1,68 @@ +APP=pybitmessage +APPDIR=`basename "\`pwd\`"` +VERSION=0.6.0 +RELEASE=1 +ARCH_TYPE=`uname -m` +PREFIX?=/usr/local +LIBDIR=lib + +all: +debug: +source: + tar -cvf ../${APP}_${VERSION}.orig.tar ../${APPDIR} --exclude-vcs + gzip -f9n ../${APP}_${VERSION}.orig.tar + +install: + mkdir -p ${DESTDIR}/usr + mkdir -p ${DESTDIR}${PREFIX} + mkdir -p ${DESTDIR}${PREFIX}/bin + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/man + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/man/man1 + install -m 644 man/${APP}.1.gz ${DESTDIR}${PREFIX}/share/man/man1 + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/${APP} + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/applications + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/pixmaps + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/icons + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/icons/hicolor + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/icons/hicolor/scalable + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/icons/hicolor/scalable/apps + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/icons/hicolor/24x24 + mkdir -m 755 -p ${DESTDIR}${PREFIX}/share/icons/hicolor/24x24/apps + install -m 644 desktop/${APP}.desktop ${DESTDIR}${PREFIX}/share/applications/${APP}.desktop + install -m 644 desktop/icon24.png ${DESTDIR}${PREFIX}/share/icons/hicolor/24x24/apps/${APP}.png + cp -rf src/* ${DESTDIR}${PREFIX}/share/${APP} + echo '#!/bin/sh' > ${DESTDIR}${PREFIX}/bin/${APP} + echo "if [ -d ${PREFIX}/share/${APP} ]; then" >> ${DESTDIR}${PREFIX}/bin/${APP} + echo " cd ${PREFIX}/share/${APP}" >> ${DESTDIR}${PREFIX}/bin/${APP} + echo 'else' >> ${DESTDIR}${PREFIX}/bin/${APP} + echo " cd /usr/share/pybitmessage" >> ${DESTDIR}${PREFIX}/bin/${APP} + echo 'fi' >> ${DESTDIR}${PREFIX}/bin/${APP} + echo 'if [ -d /opt/openssl-compat-bitcoin/lib ]; then' >> ${DESTDIR}${PREFIX}/bin/${APP} + echo ' LD_LIBRARY_PATH="/opt/openssl-compat-bitcoin/lib/" exec python2 bitmessagemain.py' >> ${DESTDIR}${PREFIX}/bin/${APP} + echo 'else' >> ${DESTDIR}${PREFIX}/bin/${APP} + echo ' exec python2 bitmessagemain.py' >> ${DESTDIR}${PREFIX}/bin/${APP} + echo 'fi' >> ${DESTDIR}${PREFIX}/bin/${APP} + chmod +x ${DESTDIR}${PREFIX}/bin/${APP} + +uninstall: + rm -f ${PREFIX}/share/man/man1/${APP}.1.gz + rm -rf ${PREFIX}/share/${APP} + rm -f ${PREFIX}/bin/${APP} + rm -f ${PREFIX}/share/applications/${APP}.desktop + rm -f ${PREFIX}/share/icons/hicolor/scalable/apps/${APP}.svg + rm -f ${PREFIX}/share/pixmaps/${APP}.svg + +clean: + rm -f ${APP} \#* \.#* gnuplot* *.png debian/*.substvars debian/*.log + rm -fr deb.* debian/${APP} rpmpackage/${ARCH_TYPE} + rm -f ../${APP}*.deb ../${APP}*.changes ../${APP}*.asc ../${APP}*.dsc + rm -f rpmpackage/*.src.rpm archpackage/*.gz archpackage/*.xz + rm -f puppypackage/*.gz puppypackage/*.pet slackpackage/*.txz + +sourcedeb: + tar -cvf ../${APP}_${VERSION}.orig.tar ../${APPDIR} --exclude-vcs --exclude 'debian' + gzip -f9n ../${APP}_${VERSION}.orig.tar + +run: + cd src/ && ./bitmessagemain.py diff --git a/packages/unmaintained/debian.sh b/packages/unmaintained/debian.sh new file mode 100755 index 00000000..9caed2dc --- /dev/null +++ b/packages/unmaintained/debian.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +APP=pybitmessage +PREV_VERSION=0.4.4 +VERSION=0.6.0 +RELEASE=1 +ARCH_TYPE=all +DIR=${APP}-${VERSION} +CURDIR=`pwd` +SHORTDIR=`basename ${CURDIR}` + +if [ $ARCH_TYPE == "x86_64" ]; then + ARCH_TYPE="amd64" +fi +if [ $ARCH_TYPE == "i686" ]; then + ARCH_TYPE="i386" +fi + + +# Update version numbers automatically - so you don't have to +sed -i 's/VERSION='${PREV_VERSION}'/VERSION='${VERSION}'/g' Makefile rpm.sh arch.sh puppy.sh ebuild.sh slack.sh +sed -i 's/Version: '${PREV_VERSION}'/Version: '${VERSION}'/g' rpmpackage/${APP}.spec +sed -i 's/Release: '${RELEASE}'/Release: '${RELEASE}'/g' rpmpackage/${APP}.spec +sed -i 's/pkgrel='${RELEASE}'/pkgrel='${RELEASE}'/g' archpackage/PKGBUILD +sed -i 's/pkgver='${PREV_VERSION}'/pkgver='${VERSION}'/g' archpackage/PKGBUILD +sed -i "s/-${PREV_VERSION}-/-${VERSION}-/g" puppypackage/*.specs +sed -i "s/|${PREV_VERSION}|/|${VERSION}|/g" puppypackage/*.specs +sed -i 's/VERSION='${PREV_VERSION}'/VERSION='${VERSION}'/g' puppypackage/pinstall.sh puppypackage/puninstall.sh +sed -i 's/-'${PREV_VERSION}'.so/-'${VERSION}'.so/g' debian/*.links + +make clean +make + +# Change the parent directory name to Debian format +mv ../${SHORTDIR} ../${DIR} + +# Create a source archive +make sourcedeb + +# Build the package +dpkg-buildpackage -F -us -uc + +# Sign files +gpg -ba ../${APP}_${VERSION}-1_${ARCH_TYPE}.deb +gpg -ba ../${APP}_${VERSION}.orig.tar.gz + +# Restore the parent directory name +mv ../${DIR} ../${SHORTDIR} diff --git a/packages/unmaintained/debian/changelog b/packages/unmaintained/debian/changelog new file mode 100644 index 00000000..9fc04ddb --- /dev/null +++ b/packages/unmaintained/debian/changelog @@ -0,0 +1,483 @@ +pybitmessage (0.6.0-1) trusty; urgency=low + + * Bugfixes + * UI improvements + * performance and security improvements + * integration with email gateway (mailchuck.com) + + -- Peter Surda Mon, 2 May 2016 16:25:00 +0200 + +pybitmessage (0.4.4-1) utopic; urgency=low + + * Added ability to limit network transfer rate + * Updated to Protocol Version 3 + * Removed use of memoryview so that we can support python 2.7.3 + * Make use of l10n for localizations + + -- Bob Mottram (4096 bits) Sun, 2 November 2014 12:55:00 +0100 + +pybitmessage (0.4.3-1) saucy; urgency=low + + * Support pyelliptic's updated HMAC algorithm. We'll remove support for the old method after an upgrade period. + * Improved version check + * Refactored decodeBase58 function + * Ignore duplicate messages + * Added bytes received/sent counts and rate on the network information tab + * Fix unicode handling in 'View HTML code as formatted text' + * Refactor handling of packet headers + * Use pointMult function instead of arithmetic.privtopub since it is faster + * Fixed issue where client wasn't waiting for a verack before continuing on with the conversation + * Fixed CPU hogging by implementing tab-based refresh improvements + * Added curses interface + * Added support for IPv6 + * Added a 'trustedpeer' option to keys.dat + * Limit maximum object size to 20 MB + * Support email-like > quote characters and reply-below-quote + * Added Japanese and Dutch language files; updated Norwegian and Russian languages files + + -- Bob Mottram (4096 bits) Thu, 6 March 2014 20:23:00 +0100 + +pybitmessage (0.4.2-1) saucy; urgency=low + + * Exclude debian directory from orig.tar.gz + + * Added Norwegian, Chinese, and Arabic translations + + * sock.sendall function isn't atomic. + Let sendDataThread be the only thread which sends data. + + * Moved API code to api.py + + * Populate comboBoxSendFrom when replying + + * Added option to show recent broadcasts when subscribing + + * Fixed issue: If Windows username contained an international character, + Bitmessage wouldn't start + + * Added some code for FreeBSD compatibility + + * Moved responsibility for processing network objects + to the new ObjectProcessorThread + + * Refactored main QT module + Moved popup menus initialization to separate methods + Simplified inbox loading + Moved magic strings to the model scope constants so they won't + be created every time. + + * Updated list of defaultKnownNodes + + * Fixed issue: [Linux] When too many messages arrive too quickly, + exception occurs: "Exceeded maximum number of notifications" + + * Fixed issue: creating then deleting an Address in short time crashes + class_singleWorker.py + + * Refactored code which displays messages to improve code readability + + * load "Sent To" label from subscriptions if available + + * Removed code to add chans to our address book as it is no longer necessary + + * Added identicons + + * Modified addresses.decodeAddress so that API command decodeAddress + works properly + + * Added API commands createChan, joinChan, leaveChan, deleteAddress + + * In pyelliptic, check the return value of RAND_bytes to make sure enough + random data was generated + + * Don't store messages in UI table (and thus in memory), pull from SQL + inventory as needed + + * Fix typos in API commands addSubscription and getInboxMessagesByAddress + + * Add feature in settings menu to give up resending a message after a + specified period of time + + -- Bob Mottram (4096 bits) Thu, 6 March 2014 20:23:00 +0100 + +pybitmessage (0.4.1-1) raring; urgency=low + + * Fixed whitelist bug + + * Fixed chan bug + Added addressversion field to pubkeys table + Sending messages to a chan no longer uses anything in the pubkeys table + Sending messages to yourself is now fully supported + + * Change _verifyAddress function to support v4 addresses + + -- Bob Mottram (4096 bits) Sun, 29 September 2013 09:54:00 +0100 + +pybitmessage (0.4.0-1) raring; urgency=low + + * Raised default demanded difficulty from 1 to 2 for new addresses + + * Added v4 addresses: + pubkeys are now encrypted and tagged in the inventory + + * Use locks when accessing dictionary inventory + + * Refactored the way inv and addr messages are shared + + * Give user feedback when disk is full + + * Added chan true/false to listAddresses results + + * When replying using chan address, send to whole chan not just sender + + * Refactored of the way PyBitmessage looks for interesting new objects + in large inv messages from peers + + * Show inventory lookup rate on Network Status tab + + * Added SqlBulkExecute class + so we can update inventory with only one commit + + * Updated Russian translations + + * Move duplicated SQL code into helper + + * Allow specification of alternate settings dir + via BITMESSAGE_HOME environment variable + + * Removed use of gevent. Removed class_bgWorker.py + + * Added Sip and PyQt to includes in build_osx.py + + * Show number of each message type processed + in the API command clientStatus + + * Use fast PoW + unless we're explicitly a frozen (binary) version of the code + + * Enable user-set localization in settings + + * Fix Archlinux package creation + + * Fallback to language only localization when region doesn't match + + * Fixed brew install instructions + + * Added German translation + + * Made inbox and sent messages table panels read-only + + * Allow inbox and sent preview panels to resize + + * Count RE: as a reply header, just like Re: so we don't chain Re: RE: + + * Fix for traceback on OSX + + * Added backend ability to understand shorter addresses + + * Convert 'API Error' to raise APIError() + + * Added option in settings to allow sending to a mobile device + (app not yet done) + + * Added ability to start daemon mode when using Bitmessage as a module + + * Improved the way client detects locale + + * Added API commands: + getInboxMessageIds, getSentMessageIds, listAddressBookEntries, + trashSentMessageByAckData, addAddressBookEntry, + deleteAddressBookEntry, listAddresses2, listSubscriptions + + * Set a maximum frequency for playing sounds + + * Show Invalid Method error in same format as other API errors + + * Update status of separate broadcasts separately + even if the sent data is identical + + * Added Namecoin integration + + * Internally distinguish peers by IP and port + + * Inbox message retrieval API + functions now also returns read status + + -- Bob Mottram (4096 bits) Sat, 28 September 2013 09:54:00 +0100 + +pybitmessage (0.3.5-1) raring; urgency=low + + * Inbox message retrieval API functions now also returns read status + + * Added right-click option to mark a message as unread + + * Prompt user to connect at first startup + + * Install into /usr/local by default + + * Add a missing rm -f to the uninstall task. + + * Use system text color for enabled addresses instead of black + + * Added support for Chans + + * Start storing msgid in sent table + + * Optionally play sounds on connection/disconnection or when messages arrive + + * Adding configuration option to listen for connections when using SOCKS + + * Added packaging for multiple distros (Arch, Puppy, Slack, etc.) + + * Added Russian translation + + * Added search support in the UI + + * Added 'make uninstall' + + * To improve OSX support, use PKCS5_PBKDF2_HMAC_SHA1 + if PKCS5_PBKDF2_HMAC is unavailable + + * Added better warnings for OSX users who are using old versions of Python + + * Repaired debian packaging + + * Altered Makefile to avoid needing to chase changes + + * Added logger module + + * Added bgWorker class for background tasks + + * Added use of gevent module + + * On not-Windows: Fix insecure keyfile permissions + + * Fix 100% CPU usage issue + + -- Bob Mottram (4096 bits) Mon, 29 July 2013 22:11:00 +0100 + +pybitmessage (0.3.4-1) raring; urgency=low + + * Switched addr, msg, broadcast, and getpubkey message types + to 8 byte time. Last remaining type is pubkey. + + * Added tooltips to show the full subject of messages + + * Added Maximum Acceptable Difficulty fields in the settings + + * Send out pubkey immediately after generating deterministic + addresses rather than waiting for a request + + -- Bob Mottram (4096 bits) Sun, 30 June 2013 11:23:00 +0100 + +pybitmessage (0.3.3-1) raring; urgency=low + + * Remove inbox item from GUI when using API command trashMessage + + * Add missing trailing semicolons to pybitmessage.desktop + + * Ensure $(DESTDIR)/usr/bin exists + + * Update Makefile to correct sandbox violations when built + via Portage (Gentoo) + + * Fix message authentication bug + + -- Bob Mottram (4096 bits) Sat, 29 June 2013 11:23:00 +0100 + +pybitmessage (0.3.211-1) raring; urgency=low + + * Removed multi-core proof of work + as the multiprocessing module does not work well with + pyinstaller's --onefile option. + + -- Bob Mottram (4096 bits) Fri, 28 June 2013 11:23:00 +0100 + +pybitmessage (0.3.2-1) raring; urgency=low + + * Bugfix: Remove remaining references to the old myapp.trayIcon + + * Refactored message status-related code. API function getStatus + now returns one of these strings: notfound, msgqueued, + broadcastqueued, broadcastsent, doingpubkeypow, awaitingpubkey, + doingmsgpow, msgsent, or ackreceived + + * Moved proof of work to low-priority multi-threaded child + processes + + * Added menu option to delete all trashed messages + + * Added inv flooding attack mitigation + + * On Linux, when selecting Show Bitmessage, do not maximize + automatically + + * Store tray icons in bitmessage_icons_rc.py + + -- Bob Mottram (4096 bits) Mon, 03 June 2013 20:17:00 +0100 + +pybitmessage (0.3.1-1) raring; urgency=low + + * Added new API commands: getDeterministicAddress, + addSubscription, deleteSubscription + + * TCP Connection timeout for non-fully-established connections + now 20 seconds + + * Don't update the time we last communicated with a node unless + the connection is fully established. This will allow us to + forget about active but non-Bitmessage nodes which have made + it into our knownNodes file. + + * Prevent incoming connection flooding from crashing + singleListener thread. Client will now only accept one + connection per remote node IP + + * Bugfix: Worker thread crashed when doing a POW to send out + a v2 pubkey (bug introduced in 0.3.0) + + * Wrap all sock.shutdown functions in error handlers + + * Put all 'commit' commands within SQLLocks + + * Bugfix: If address book label is blank, Bitmessage wouldn't + show message (bug introduced in 0.3.0) + + * Messaging menu item selects the oldest unread message + + * Standardize on 'Quit' rather than 'Exit' + + * [OSX] Try to seek homebrew installation of OpenSSL + + * Prevent multiple instances of the application from running + + * Show 'Connected' or 'Connection Lost' indicators + + * Use only 9 half-open connections on Windows but 32 for + everyone else + + * Added appIndicator (a more functional tray icon) and Ubuntu + Messaging Menu integration + + * Changed Debian install directory and run script name based + on Github issue #135 + + -- Jonathan Warren (4096 bits) Sat, 25 May 2013 12:06:00 +0100 + +pybitmessage (0.3.0-1) raring; urgency=low + + * Added new API function: getStatus + + * Added error-handling around all sock.sendall() functions + in the receiveData thread so that if there is a problem + sending data, the threads will close gracefully + + * Abandoned and removed the connectionsCount data structure; + use the connectedHostsList instead because it has proved to be + more accurate than trying to maintain the connectionsCount + + * Added daemon mode. All UI code moved into a module and many + shared objects moved into shared.py + + * Truncate display of very long messages to avoid freezing the UI + + * Added encrypted broadcasts for v3 addresses or v2 addresses + after 2013-05-28 10:00 UTC + + * No longer self.sock.close() from within receiveDataThreads, + let the sendDataThreads do it + + * Swapped out the v2 announcements subscription address for a v3 + announcements subscription address + + * Vacuum the messages.dat file once a month: + will greatly reduce the file size + + * Added a settings table in message.dat + + * Implemented v3 addresses: + pubkey messages must now include two var_ints: nonce_trials_per_byte + and extra_bytes, and also be signed. When sending a message to a v3 + address, the sender must use these values in calculating its POW or + else the message will not be accepted by the receiver. + + * Display a privacy warning when selecting 'Send Broadcast from this address' + + * Added gitignore file + + * Added code in preparation for a switch from 32-bit time to 64-bit time. + Nodes will now advertise themselves as using protocol version 2. + + * Don't necessarily delete entries from the inventory after 2.5 days; + leave pubkeys there for 28 days so that we don't process the same ones + many times throughout a month. This was causing the 'pubkeys processed' + indicator on the 'Network Status' tab to not accurately reflect the + number of truly new addresses on the network. + + * Use 32 threads for outgoing connections in order to connect quickly + + * Fix typo when calling os.environ in the sys.platform=='darwin' case + + * Allow the cancelling of a message which is in the process of being + sent by trashing it then restarting Bitmessage + + * Bug fix: can't delete address from address book + + -- Bob Mottram (4096 bits) Mon, 6 May 2013 12:06:00 +0100 + +pybitmessage (0.2.8-1) unstable; urgency=low + + * Fixed Ubuntu & OS X issue: + Bitmessage wouldn't receive any objects from peers after restart. + + * Inventory flush to disk when exiting program now vastly faster. + + * Fixed address generation bug (kept Bitmessage from restarting). + + * Improve deserialization of messages + before processing (a 'best practice'). + + * Change to help Macs find OpenSSL the way Unix systems find it. + + * Do not share or accept IPs which are in the private IP ranges. + + * Added time-fuzzing + to the embedded time in pubkey and getpubkey messages. + + * Added a knownNodes lock + to prevent an exception from sometimes occurring when saving + the data-structure to disk. + + * Show unread messages in bold + and do not display new messages automatically. + + * Support selecting multiple items + in the inbox, sent box, and address book. + + * Use delete key to trash Inbox or Sent messages. + + * Display richtext(HTML) messages + from senders in address book or subscriptions (although not + pseudo-mailing-lists; use new right-click option). + + * Trim spaces + from the beginning and end of addresses when adding to + address book, subscriptions, and blacklist. + + * Improved the display of the time for foreign language users. + + -- Bob Mottram (4096 bits) Tue, 9 Apr 2013 17:44:00 +0100 + +pybitmessage (0.2.7-1) unstable; urgency=low + + * Added debian packaging + + * Script to generate debian packages + + * SVG icon for Gnome shell, etc + + * Source moved int src directory for debian standards compatibility + + * Trailing carriage return on COPYING LICENSE and README.md + + -- Bob Mottram (4096 bits) Mon, 1 Apr 2013 17:12:14 +0100 diff --git a/packages/unmaintained/debian/compat b/packages/unmaintained/debian/compat new file mode 100644 index 00000000..ec635144 --- /dev/null +++ b/packages/unmaintained/debian/compat @@ -0,0 +1 @@ +9 diff --git a/packages/unmaintained/debian/control b/packages/unmaintained/debian/control new file mode 100644 index 00000000..e72de58a --- /dev/null +++ b/packages/unmaintained/debian/control @@ -0,0 +1,21 @@ +Source: pybitmessage +Section: mail +Priority: extra +Maintainer: Bob Mottram (4096 bits) +Build-Depends: debhelper (>= 9.0.0), libqt4-dev (>= 4.8.0), python-qt4-dev, libsqlite3-dev +Standards-Version: 3.9.4 +Homepage: https://github.com/Bitmessage/PyBitmessage +Vcs-Git: https://github.com/Bitmessage/PyBitmessage.git + +Package: pybitmessage +Architecture: all +Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, python (>= 2.7), openssl, python-qt4, sqlite3, gst123 +Suggests: libmessaging-menu-dev +Description: Send encrypted messages + 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 sender and receiver of messages, from passive + eavesdroppers like those running warrantless wiretapping programs. diff --git a/packages/unmaintained/debian/copyright b/packages/unmaintained/debian/copyright new file mode 100644 index 00000000..b341b873 --- /dev/null +++ b/packages/unmaintained/debian/copyright @@ -0,0 +1,30 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: +Source: + +Files: * +Copyright: Copyright 2016 Bob Mottram (4096 bits) +License: MIT + +Files: debian/* +Copyright: Copyright 2016 Bob Mottram (4096 bits) +License: MIT + +License: MIT + 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. diff --git a/packages/unmaintained/debian/docs b/packages/unmaintained/debian/docs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/packages/unmaintained/debian/docs @@ -0,0 +1 @@ + diff --git a/packages/unmaintained/debian/manpages b/packages/unmaintained/debian/manpages new file mode 100644 index 00000000..54af5648 --- /dev/null +++ b/packages/unmaintained/debian/manpages @@ -0,0 +1 @@ +man/pybitmessage.1.gz diff --git a/packages/unmaintained/debian/pybm b/packages/unmaintained/debian/pybm new file mode 100644 index 00000000..95e61e54 --- /dev/null +++ b/packages/unmaintained/debian/pybm @@ -0,0 +1,4 @@ +#!/bin/sh +cd /usr/share/pybitmessage +exec python bitmessagemain.py + diff --git a/packages/unmaintained/debian/rules b/packages/unmaintained/debian/rules new file mode 100755 index 00000000..5b29d243 --- /dev/null +++ b/packages/unmaintained/debian/rules @@ -0,0 +1,43 @@ +#!/usr/bin/make -f + +APP=pybitmessage +PREFIX=/usr +build: build-stamp + make +build-arch: build-stamp +build-indep: build-stamp +build-stamp: + dh_testdir + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp + dh_clean + +install: build clean + dh_testdir + dh_testroot + dh_prep + dh_installdirs + ${MAKE} install -B DESTDIR=${CURDIR}/debian/${APP} PREFIX=/usr +binary-indep: build install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installexamples + dh_installman + dh_link + dh_compress + dh_fixperms + dh_installdeb + dh_gencontrol + dh_md5sums + dh_builddeb + +binary-arch: build install + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install diff --git a/packages/unmaintained/debian/source/format b/packages/unmaintained/debian/source/format new file mode 100644 index 00000000..163aaf8d --- /dev/null +++ b/packages/unmaintained/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/packages/unmaintained/debian/source/include-binaries b/packages/unmaintained/debian/source/include-binaries new file mode 100644 index 00000000..f676fce8 --- /dev/null +++ b/packages/unmaintained/debian/source/include-binaries @@ -0,0 +1,18 @@ +src/images/sent.png +src/images/can-icon-16px.png +src/images/addressbook.png +src/images/networkstatus.png +src/images/redicon.png +src/images/subscriptions.png +src/images/blacklist.png +src/images/can-icon-24px.png +src/images/can-icon-24px-red.png +src/images/can-icon-24px-yellow.png +src/images/can-icon-24px-green.png +src/images/identities.png +src/images/yellowicon.png +src/images/inbox.png +src/images/greenicon.png +src/images/can-icon.ico +src/images/send.png +desktop/can-icon.svg diff --git a/packages/unmaintained/ebuild.sh b/packages/unmaintained/ebuild.sh index f26fe58e..cd73685b 100755 --- a/packages/unmaintained/ebuild.sh +++ b/packages/unmaintained/ebuild.sh @@ -5,8 +5,8 @@ PREV_VERSION=0.4.4 VERSION=0.6.0 RELEASE=1 SOURCEDIR=. -ARCH_TYPE=$(uname -m) -CURRDIR=$(pwd) +ARCH_TYPE=`uname -m` +CURRDIR=`pwd` SOURCE=~/ebuild/${APP}-${VERSION}.tar.gz @@ -29,4 +29,5 @@ mv ../${APP} ../${APP}-${VERSION} tar -cvzf ${SOURCE} ../${APP}-${VERSION} --exclude-vcs # rename the root directory without the version number -mv ../${APP}-${VERSION} ../${APP} +mv ../${APP}-${VERSION} ../${APP} + diff --git a/packages/unmaintained/puppy.sh b/packages/unmaintained/puppy.sh index 1d3bdd31..a78e021c 100755 --- a/packages/unmaintained/puppy.sh +++ b/packages/unmaintained/puppy.sh @@ -5,7 +5,7 @@ PREV_VERSION=0.4.4 VERSION=0.6.0 RELEASE=1 BUILDDIR=~/petbuild -CURRDIR=$(pwd) +CURRDIR=`pwd` PROJECTDIR=${BUILDDIR}/${APP}-${VERSION}-${RELEASE} # Update version numbers automatically - so you don't have to diff --git a/pybitmessage b/pybitmessage deleted file mode 120000 index e8310385..00000000 --- a/pybitmessage +++ /dev/null @@ -1 +0,0 @@ -src \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 6f4a22fb..00000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -coverage -psutil -pycryptodome -PyQt5;python_version>="3.7" -mock;python_version<="2.7" -python_prctl;platform_system=="Linux" -six -xvfbwrapper;platform_system=="Linux" diff --git a/run-kivy-tests-in-docker.sh b/run-kivy-tests-in-docker.sh deleted file mode 100755 index f34bbd19..00000000 --- a/run-kivy-tests-in-docker.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -docker build -t pybm-kivy-travis-bionic -f packages/docker/Dockerfile.kivy-travis . -docker run pybm-kivy-travis-bionic diff --git a/run-tests-in-docker.sh b/run-tests-in-docker.sh deleted file mode 100755 index 174cb754..00000000 --- a/run-tests-in-docker.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -DOCKERFILE=packages/docker/Dockerfile.bionic - -# explicitly mark appimage stage because it builds in any case -docker build --target appimage -t pybm/appimage -f $DOCKERFILE . - -if [ $? -gt 0 ]; then - docker build --no-cache --target appimage -t pybm/appimage -f $DOCKERFILE . -fi - -docker build --target tox -t pybm/tox -f $DOCKERFILE . -docker run --rm -t pybm/tox diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 28ceaede..00000000 --- a/setup.cfg +++ /dev/null @@ -1,25 +0,0 @@ -# Since there is overlap in the violations that the different tools check for, it makes sense to quiesce some warnings -# in some tools if those warnings in other tools are preferred. This avoids the need to add duplicate lint warnings. - -# max-line-length should be removed ASAP! - -[pycodestyle] -max-line-length = 119 - -[flake8] -max-line-length = 119 -exclude = bitmessagecli.py,bitmessagecurses,bitmessageqt,plugins,tests,umsgpack -ignore = E722,F841,W503 -# E722: pylint is preferred for bare-except -# F841: pylint is preferred for unused-variable -# W503: deprecated: https://bugs.python.org/issue26763 - https://www.python.org/dev/peps/pep-0008/#should-a-line-break-before-or-after-a-binary-operator - -# pylint honours the [MESSAGES CONTROL] section -# as well as [MASTER] section -[MESSAGES CONTROL] -disable=invalid-name,bare-except,broad-except -# invalid-name: needs fixing during a large, project-wide refactor -# bare-except,broad-except: Need fixing once thorough testing is easier - -[MASTER] -init-hook = import sys;sys.path.append('src') diff --git a/setup.py b/setup.py index 30436bec..ba34f6df 100644 --- a/setup.py +++ b/setup.py @@ -1,35 +1,14 @@ #!/usr/bin/env python2.7 import os -import platform -import shutil import sys - -from importlib import import_module +import shutil from setuptools import setup, Extension from setuptools.command.install import install from src.version import softwareVersion - -EXTRAS_REQUIRE = { - 'docs': ['sphinx'], - 'gir': ['pygobject'], - 'json': ['jsonrpclib'], - 'notify2': ['notify2'], - 'opencl': ['pyopencl', 'numpy'], - 'prctl': ['python_prctl'], # Named threads - 'qrcode': ['qrcode'], - 'sound;platform_system=="Windows"': ['winsound'], - 'tor': ['stem'], - 'xdg': ['pyxdg'], - 'xml': ['defusedxml'] -} - - class InstallCmd(install): - """Custom setuptools install command preparing icons""" - def run(self): # prepare icons directories try: @@ -53,79 +32,37 @@ if __name__ == "__main__": with open(os.path.join(here, 'README.md')) as f: README = f.read() - with open(os.path.join(here, 'requirements.txt'), 'r') as f: - requirements = list(f.readlines()) - bitmsghash = Extension( 'pybitmessage.bitmsghash.bitmsghash', sources=['src/bitmsghash/bitmsghash.cpp'], libraries=['pthread', 'crypto'], ) - installRequires = ['six'] + installRequires = [] packages = [ 'pybitmessage', 'pybitmessage.bitmessageqt', 'pybitmessage.bitmessagecurses', - 'pybitmessage.fallback', 'pybitmessage.messagetypes', 'pybitmessage.network', - 'pybitmessage.plugins', 'pybitmessage.pyelliptic', - 'pybitmessage.storage' + 'pybitmessage.socks', + 'pybitmessage.storage', + 'pybitmessage.plugins' ] - package_data = {'': [ - 'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem', - 'translations/*.ts', 'translations/*.qm', 'default.ini', 'sql/*.sql', - 'images/*.png', 'images/*.ico', 'images/*.icns', - 'bitmessagekivy/main.kv', 'bitmessagekivy/screens_data.json', - 'bitmessagekivy/kv/*.kv', 'images/kivy/payment/*.png', 'images/kivy/*.gif', - 'images/kivy/text_images*.png' - ]} - - if sys.version_info[0] == 3: - packages.extend( - [ - 'pybitmessage.bitmessagekivy', - 'pybitmessage.bitmessagekivy.baseclass' - ] - ) - - if os.environ.get('INSTALL_TESTS', False): - packages.extend(['pybitmessage.mockbm', 'pybitmessage.backend', 'pybitmessage.bitmessagekivy.tests']) - package_data[''].extend(['bitmessagekivy/tests/sampleData/*.dat']) # this will silently accept alternative providers of msgpack # if they are already installed try: import msgpack - installRequires.append( - "msgpack-python" if msgpack.version[:2] < (0, 6) else "msgpack") + installRequires.append("msgpack-python") except ImportError: try: - import_module('umsgpack') + import umsgpack installRequires.append("umsgpack") except ImportError: - packages += ['pybitmessage.fallback.umsgpack'] - - 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']) - ] - - try: - if platform.dist()[0] in ('Debian', 'Ubuntu'): - data_files += [ - ("etc/apparmor.d/", - ['packages/apparmor/pybitmessage']) - ] - except AttributeError: - pass # FIXME: use distro for more recent python + packages += ['pybitmessage.fallback', 'pybitmessage.fallback.umsgpack'] dist = setup( name='pybitmessage', @@ -135,12 +72,19 @@ if __name__ == "__main__": long_description=README, license='MIT', # TODO: add author info + #author='', + #author_email='', url='https://bitmessage.org', # TODO: add keywords + #keywords='', install_requires=installRequires, - tests_require=requirements, - test_suite='tests.unittest_discover', - extras_require=EXTRAS_REQUIRE, + 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", @@ -151,13 +95,25 @@ if __name__ == "__main__": ], package_dir={'pybitmessage': 'src'}, packages=packages, - package_data=package_data, - data_files=data_files, + 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': [ - 'address.qrcode = pybitmessage.plugins.menu_qrcode [qrcode]' + 'popMenuYourIdentities.qrcode = ' + 'pybitmessage.plugins.qrcodeui [qrcode]' ], 'bitmessage.notification.message': [ 'notify2 = pybitmessage.plugins.notification_notify2' @@ -174,20 +130,10 @@ if __name__ == "__main__": 'libmessaging =' 'pybitmessage.plugins.indicator_libmessaging [gir]' ], - 'bitmessage.desktop': [ - 'freedesktop = pybitmessage.plugins.desktop_xdg [xdg]' - ], - 'bitmessage.proxyconfig': [ - 'stem = pybitmessage.plugins.proxyconfig_stem [tor]' - ], - 'console_scripts': [ - 'pybitmessage = pybitmessage.bitmessagemain:main' - ] if sys.platform[:3] == 'win' else [] + # 'console_scripts': [ + # 'pybitmessage = pybitmessage.bitmessagemain:main' + # ] }, scripts=['src/pybitmessage'], - cmdclass={'install': InstallCmd}, - command_options={ - 'build_sphinx': { - 'source_dir': ('setup.py', 'docs')} - } + cmdclass={'install': InstallCmd} ) diff --git a/src/addresses.py b/src/addresses.py index e48873a1..c8caaf82 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -1,170 +1,140 @@ -""" -Operations with addresses -""" -# pylint: disable=inconsistent-return-statements import hashlib -import logging +from struct import * +from pyelliptic import arithmetic from binascii import hexlify, unhexlify -from struct import pack, unpack +#from debug import logger -logger = logging.getLogger('default') +#There is another copy of this function in Bitmessagemain.py +def convertIntToString(n): + a = __builtins__.hex(n) + if a[-1:] == 'L': + a = a[:-1] + if (len(a) % 2) == 0: + return unhexlify(a[2:]) + else: + return unhexlify('0'+a[2:]) ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" - -def encodeBase58(num): +def encodeBase58(num, alphabet=ALPHABET): """Encode a number in Base X - Args: - num: The number to encode - alphabet: The alphabet to use for encoding + `num`: The number to encode + `alphabet`: The alphabet to use for encoding """ - if num < 0: - return None - if num == 0: - return ALPHABET[0] + if (num == 0): + return alphabet[0] arr = [] - base = len(ALPHABET) + base = len(alphabet) while num: - num, rem = divmod(num, base) - arr.append(ALPHABET[rem]) + rem = num % base + #print 'num is:', num + num = num // base + arr.append(alphabet[rem]) arr.reverse() return ''.join(arr) - -def decodeBase58(string): +def decodeBase58(string, alphabet=ALPHABET): """Decode a Base X encoded string into the number - Args: - string: The encoded string - alphabet: The alphabet to use for encoding + Arguments: + - `string`: The encoded string + - `alphabet`: The alphabet to use for encoding """ - base = len(ALPHABET) + base = len(alphabet) num = 0 - + try: for char in string: num *= base - num += ALPHABET.index(char) - except ValueError: - # character not found (like a space character or a 0) + num += alphabet.index(char) + except: + #character not found (like a space character or a 0) return 0 return num - -class varintEncodeError(Exception): - """Exception class for encoding varint""" - pass - - -class varintDecodeError(Exception): - """Exception class for decoding varint data""" - pass - - def encodeVarint(integer): - """Convert integer into varint bytes""" if integer < 0: - raise varintEncodeError('varint cannot be < 0') + logger.error('varint cannot be < 0') + raise SystemExit if integer < 253: - return pack('>B', integer) + return pack('>B',integer) if integer >= 253 and integer < 65536: - return pack('>B', 253) + pack('>H', integer) + return pack('>B',253) + pack('>H',integer) if integer >= 65536 and integer < 4294967296: - return pack('>B', 254) + pack('>I', integer) + return pack('>B',254) + pack('>I',integer) if integer >= 4294967296 and integer < 18446744073709551616: - return pack('>B', 255) + pack('>Q', integer) + return pack('>B',255) + pack('>Q',integer) if integer >= 18446744073709551616: - raise varintEncodeError('varint cannot be >= 18446744073709551616') - + logger.error('varint cannot be >= 18446744073709551616') + raise SystemExit + +class varintDecodeError(Exception): + pass def decodeVarint(data): """ - Decodes an encoded varint to an integer and returns it. - Per protocol v3, the encoded value must be encoded with - the minimum amount of data possible or else it is malformed. + Decodes an encoded varint to an integer and returns it. + Per protocol v3, the encoded value must be encoded with + the minimum amount of data possible or else it is malformed. Returns a tuple: (theEncodedValue, theSizeOfTheVarintInBytes) """ - - if not data: - return (0, 0) - firstByte, = unpack('>B', data[0:1]) + + if len(data) == 0: + return (0,0) + firstByte, = unpack('>B',data[0:1]) if firstByte < 253: # encodes 0 to 252 - return (firstByte, 1) # the 1 is the length of the varint + return (firstByte,1) #the 1 is the length of the varint if firstByte == 253: # encodes 253 to 65535 if len(data) < 3: - raise varintDecodeError( - 'The first byte of this varint as an integer is %s' - ' but the total length is only %s. It needs to be' - ' at least 3.' % (firstByte, len(data))) - encodedValue, = unpack('>H', data[1:3]) + raise varintDecodeError('The first byte of this varint as an integer is %s but the total length is only %s. It needs to be at least 3.' % (firstByte, len(data))) + encodedValue, = unpack('>H',data[1:3]) if encodedValue < 253: - raise varintDecodeError( - 'This varint does not encode the value with the lowest' - ' possible number of bytes.') - return (encodedValue, 3) + raise varintDecodeError('This varint does not encode the value with the lowest possible number of bytes.') + return (encodedValue,3) if firstByte == 254: # encodes 65536 to 4294967295 if len(data) < 5: - raise varintDecodeError( - 'The first byte of this varint as an integer is %s' - ' but the total length is only %s. It needs to be' - ' at least 5.' % (firstByte, len(data))) - encodedValue, = unpack('>I', data[1:5]) + raise varintDecodeError('The first byte of this varint as an integer is %s but the total length is only %s. It needs to be at least 5.' % (firstByte, len(data))) + encodedValue, = unpack('>I',data[1:5]) if encodedValue < 65536: - raise varintDecodeError( - 'This varint does not encode the value with the lowest' - ' possible number of bytes.') - return (encodedValue, 5) + raise varintDecodeError('This varint does not encode the value with the lowest possible number of bytes.') + return (encodedValue,5) if firstByte == 255: # encodes 4294967296 to 18446744073709551615 if len(data) < 9: - raise varintDecodeError( - 'The first byte of this varint as an integer is %s' - ' but the total length is only %s. It needs to be' - ' at least 9.' % (firstByte, len(data))) - encodedValue, = unpack('>Q', data[1:9]) + raise varintDecodeError('The first byte of this varint as an integer is %s but the total length is only %s. It needs to be at least 9.' % (firstByte, len(data))) + encodedValue, = unpack('>Q',data[1:9]) if encodedValue < 4294967296: - raise varintDecodeError( - 'This varint does not encode the value with the lowest' - ' possible number of bytes.') - return (encodedValue, 9) + raise varintDecodeError('This varint does not encode the value with the lowest possible number of bytes.') + return (encodedValue,9) def calculateInventoryHash(data): - """Calculate inventory hash from object data""" sha = hashlib.new('sha512') sha2 = hashlib.new('sha512') sha.update(data) sha2.update(sha.digest()) return sha2.digest()[0:32] - -def encodeAddress(version, stream, ripe): - """Convert ripe to address""" +def encodeAddress(version,stream,ripe): if version >= 2 and version < 4: if len(ripe) != 20: - raise Exception( - 'Programming error in encodeAddress: The length of' - ' a given ripe hash was not 20.' - ) - - if ripe[:2] == b'\x00\x00': + raise Exception("Programming error in encodeAddress: The length of a given ripe hash was not 20.") + if ripe[:2] == '\x00\x00': ripe = ripe[2:] - elif ripe[:1] == b'\x00': + elif ripe[:1] == '\x00': ripe = ripe[1:] elif version == 4: if len(ripe) != 20: - raise Exception( - 'Programming error in encodeAddress: The length of' - ' a given ripe hash was not 20.') - ripe = ripe.lstrip(b'\x00') + raise Exception("Programming error in encodeAddress: The length of a given ripe hash was not 20.") + ripe = ripe.lstrip('\x00') storedBinaryData = encodeVarint(version) + encodeVarint(stream) + ripe - + # Generate the checksum sha = hashlib.new('sha512') sha.update(storedBinaryData) @@ -173,20 +143,11 @@ def encodeAddress(version, stream, ripe): sha.update(currentHash) checksum = sha.digest()[0:4] - # FIXME: encodeBase58 should take binary data, to reduce conversions - # encodeBase58(storedBinaryData + checksum) - asInt = int(hexlify(storedBinaryData) + hexlify(checksum), 16) - # should it be str? If yes, it should be everywhere in the code - return 'BM-' + encodeBase58(asInt) - + asInt = int(hexlify(storedBinaryData) + hexlify(checksum),16) + return 'BM-'+ encodeBase58(asInt) def decodeAddress(address): - """ - returns (status, address version number, stream number, - data (almost certainly a ripe hash)) - """ - # pylint: disable=too-many-return-statements,too-many-statements - # pylint: disable=too-many-branches + #returns (status, address version number, stream number, data (almost certainly a ripe hash)) address = str(address).strip() @@ -196,88 +157,126 @@ def decodeAddress(address): integer = decodeBase58(address) if integer == 0: status = 'invalidcharacters' - return status, 0, 0, '' - # after converting to hex, the string will be prepended - # with a 0x and appended with a L in python2 - hexdata = hex(integer)[2:].rstrip('L') + return status,0,0,"" + #after converting to hex, the string will be prepended with a 0x and appended with a L + hexdata = hex(integer)[2:-1] if len(hexdata) % 2 != 0: hexdata = '0' + hexdata + #print 'hexdata', hexdata + data = unhexlify(hexdata) checksum = data[-4:] sha = hashlib.new('sha512') sha.update(data[:-4]) currentHash = sha.digest() + #print 'sha after first hashing: ', sha.hexdigest() sha = hashlib.new('sha512') sha.update(currentHash) + #print 'sha after second hashing: ', sha.hexdigest() if checksum != sha.digest()[0:4]: status = 'checksumfailed' - return status, 0, 0, '' + return status,0,0,"" + #else: + # print 'checksum PASSED' try: addressVersionNumber, bytesUsedByVersionNumber = decodeVarint(data[:9]) except varintDecodeError as e: logger.error(str(e)) status = 'varintmalformed' - return status, 0, 0, '' + return status,0,0,"" + #print 'addressVersionNumber', addressVersionNumber + #print 'bytesUsedByVersionNumber', bytesUsedByVersionNumber if addressVersionNumber > 4: logger.error('cannot decode address version numbers this high') status = 'versiontoohigh' - return status, 0, 0, '' + return status,0,0,"" elif addressVersionNumber == 0: logger.error('cannot decode address version numbers of zero.') status = 'versiontoohigh' - return status, 0, 0, '' + return status,0,0,"" try: - streamNumber, bytesUsedByStreamNumber = \ - decodeVarint(data[bytesUsedByVersionNumber:]) + streamNumber, bytesUsedByStreamNumber = decodeVarint(data[bytesUsedByVersionNumber:]) except varintDecodeError as e: logger.error(str(e)) status = 'varintmalformed' - return status, 0, 0, '' - + return status,0,0,"" + #print streamNumber status = 'success' if addressVersionNumber == 1: - return status, addressVersionNumber, streamNumber, data[-24:-4] + return status,addressVersionNumber,streamNumber,data[-24:-4] elif addressVersionNumber == 2 or addressVersionNumber == 3: - embeddedRipeData = \ - data[bytesUsedByVersionNumber + bytesUsedByStreamNumber:-4] + embeddedRipeData = data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] if len(embeddedRipeData) == 19: - return status, addressVersionNumber, streamNumber, \ - b'\x00' + embeddedRipeData + return status,addressVersionNumber,streamNumber,'\x00'+embeddedRipeData elif len(embeddedRipeData) == 20: - return status, addressVersionNumber, streamNumber, \ - embeddedRipeData + return status,addressVersionNumber,streamNumber,embeddedRipeData elif len(embeddedRipeData) == 18: - return status, addressVersionNumber, streamNumber, \ - b'\x00\x00' + embeddedRipeData + return status,addressVersionNumber,streamNumber,'\x00\x00'+embeddedRipeData elif len(embeddedRipeData) < 18: - return 'ripetooshort', 0, 0, '' + return 'ripetooshort',0,0,"" elif len(embeddedRipeData) > 20: - return 'ripetoolong', 0, 0, '' - return 'otherproblem', 0, 0, '' + return 'ripetoolong',0,0,"" + else: + return 'otherproblem',0,0,"" elif addressVersionNumber == 4: - embeddedRipeData = \ - data[bytesUsedByVersionNumber + bytesUsedByStreamNumber:-4] - if embeddedRipeData[0:1] == b'\x00': - # In order to enforce address non-malleability, encoded - # RIPE data must have NULL bytes removed from the front - return 'encodingproblem', 0, 0, '' + embeddedRipeData = data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4] + if embeddedRipeData[0:1] == '\x00': + # In order to enforce address non-malleability, encoded RIPE data must have NULL bytes removed from the front + return 'encodingproblem',0,0,"" elif len(embeddedRipeData) > 20: - return 'ripetoolong', 0, 0, '' + return 'ripetoolong',0,0,"" elif len(embeddedRipeData) < 4: - return 'ripetooshort', 0, 0, '' - x00string = b'\x00' * (20 - len(embeddedRipeData)) - return status, addressVersionNumber, streamNumber, \ - x00string + embeddedRipeData - + return 'ripetooshort',0,0,"" + else: + x00string = '\x00' * (20 - len(embeddedRipeData)) + return status,addressVersionNumber,streamNumber,x00string+embeddedRipeData def addBMIfNotPresent(address): - """Prepend BM- to an address if it doesn't already have it""" address = str(address).strip() - return address if address[:3] == 'BM-' else 'BM-' + address + if address[:3] != 'BM-': + return 'BM-'+address + else: + return address + +if __name__ == "__main__": + print 'Let us make an address from scratch. Suppose we generate two random 32 byte values and call the first one the signing key and the second one the encryption key:' + privateSigningKey = '93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' + privateEncryptionKey = '4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a' + print 'privateSigningKey =', privateSigningKey + print 'privateEncryptionKey =', privateEncryptionKey + print 'Now let us convert them to public keys by doing an elliptic curve point multiplication.' + publicSigningKey = arithmetic.privtopub(privateSigningKey) + publicEncryptionKey = arithmetic.privtopub(privateEncryptionKey) + print 'publicSigningKey =', publicSigningKey + print 'publicEncryptionKey =', publicEncryptionKey + + print 'Notice that they both begin with the \\x04 which specifies the encoding type. This prefix is not send over the wire. You must strip if off before you send your public key across the wire, and you must add it back when you receive a public key.' + + publicSigningKeyBinary = arithmetic.changebase(publicSigningKey,16,256,minlen=64) + publicEncryptionKeyBinary = arithmetic.changebase(publicEncryptionKey,16,256,minlen=64) + + ripe = hashlib.new('ripemd160') + sha = hashlib.new('sha512') + sha.update(publicSigningKeyBinary+publicEncryptionKeyBinary) + + ripe.update(sha.digest()) + addressVersionNumber = 2 + streamNumber = 1 + print 'Ripe digest that we will encode in the address:', hexlify(ripe.digest()) + returnedAddress = encodeAddress(addressVersionNumber,streamNumber,ripe.digest()) + print 'Encoded address:', returnedAddress + status,addressVersionNumber,streamNumber,data = decodeAddress(returnedAddress) + print '\nAfter decoding address:' + print 'Status:', status + print 'addressVersionNumber', addressVersionNumber + print 'streamNumber', streamNumber + print 'length of data(the ripe hash):', len(data) + print 'ripe data:', hexlify(data) + diff --git a/src/api.py b/src/api.py index c27a24e7..edb9e23d 100644 --- a/src/api.py +++ b/src/api.py @@ -1,378 +1,80 @@ # Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2023 The Bitmessage developers +# Copyright (c) 2012-2016 The Bitmessage developers -""" -This is not what you run to start the Bitmessage API. -Instead, `enable the API `_ -and optionally `enable daemon mode `_ -then run the PyBitmessage. - -The PyBitmessage API is provided either as -`XML-RPC `_ or -`JSON-RPC `_ like in bitcoin. -It's selected according to 'apivariant' setting in config file. - -Special value ``apivariant=legacy`` is to mimic the old pre 0.6.3 -behaviour when any results are returned as strings of json. - -.. list-table:: All config settings related to API: - :header-rows: 0 - - * - apienabled = true - - if 'false' the `singleAPI` wont start - * - apiinterface = 127.0.0.1 - - this is the recommended default - * - apiport = 8442 - - the API listens apiinterface:apiport if apiport is not used, - random in range (32767, 65535) otherwice - * - apivariant = xml - - current default for backward compatibility, 'json' is recommended - * - apiusername = username - - set the username - * - apipassword = password - - and the password - * - apinotifypath = - - not really the API setting, this sets a path for the executable to be ran - when certain internal event happens - -To use the API concider such simple example: - -.. code-block:: python - - from jsonrpclib import jsonrpc - - from pybitmessage import helper_startup - from pybitmessage.bmconfigparser import config - - helper_startup.loadConfig() # find and load local config file - api_uri = "http://%s:%s@127.0.0.1:%s/" % ( - config.safeGet('bitmessagesettings', 'apiusername'), - config.safeGet('bitmessagesettings', 'apipassword'), - config.safeGet('bitmessagesettings', 'apiport') - ) - api = jsonrpc.ServerProxy(api_uri) - print(api.clientStatus()) - - -For further examples please reference `.tests.test_api`. +comment= """ +This is not what you run to run the Bitmessage API. Instead, enable the API +( https://bitmessage.org/wiki/API ) and optionally enable daemon mode +( https://bitmessage.org/wiki/Daemon ) then run bitmessagemain.py. """ +if __name__ == "__main__": + print comment + import sys + sys.exit(0) + +from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer import base64 -import errno -import hashlib import json -import random -import socket -import subprocess # nosec B404 -import time from binascii import hexlify, unhexlify -from struct import pack - -import six -from six.moves import configparser, http_client, xmlrpc_server +import shared +import time +from addresses import decodeAddress,addBMIfNotPresent,decodeVarint,calculateInventoryHash,varintDecodeError +from bmconfigparser import BMConfigParser import defaults import helper_inbox import helper_sent -import protocol -import proofofwork -import queues -import shared +import hashlib -import shutdown import state -from addresses import ( - addBMIfNotPresent, - calculateInventoryHash, - decodeAddress, - decodeVarint, - varintDecodeError -) -from bmconfigparser import config +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 helper_sql import ( - SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready) from inventory import Inventory - -try: - from network import BMConnectionPool -except ImportError: - BMConnectionPool = None - -from network import stats, StoppableThread from version import softwareVersion -try: # TODO: write tests for XML vulnerabilities - from defusedxml.xmlrpc import monkey_patch -except ImportError: - logger.warning( - 'defusedxml not available, only use API on a secure, closed network.') -else: - monkey_patch() - +# Helper Functions +import proofofwork str_chan = '[chan]' -str_broadcast_subscribers = '[Broadcast subscribers]' -class ErrorCodes(type): - """Metaclass for :class:`APIError` documenting error codes.""" - _CODES = { - 0: 'Invalid command parameters number', - 1: 'The specified passphrase is blank.', - 2: 'The address version number currently must be 3, 4, or 0' - ' (which means auto-select).', - 3: 'The stream number must be 1 (or 0 which means' - ' auto-select). Others aren\'t supported.', - 4: 'Why would you ask me to generate 0 addresses for you?', - 5: 'You have (accidentally?) specified too many addresses to' - ' make. Maximum 999. This check only exists to prevent' - ' mischief; if you really want to create more addresses than' - ' this, contact the Bitmessage developers and we can modify' - ' the check or you can do it yourself by searching the source' - ' code for this message.', - 6: 'The encoding type must be 2 or 3.', - 7: 'Could not decode address', - 8: 'Checksum failed for address', - 9: 'Invalid characters in address', - 10: 'Address version number too high (or zero)', - 11: 'The address version number currently must be 2, 3 or 4.' - ' Others aren\'t supported. Check the address.', - 12: 'The stream number must be 1. Others aren\'t supported.' - ' Check the address.', - 13: 'Could not find this address in your keys.dat file.', - 14: 'Your fromAddress is disabled. Cannot send.', - 15: 'Invalid ackData object size.', - 16: 'You are already subscribed to that address.', - 17: 'Label is not valid UTF-8 data.', - 18: 'Chan name does not match address.', - 19: 'The length of hash should be 32 bytes (encoded in hex' - ' thus 64 characters).', - 20: 'Invalid method:', - 21: 'Unexpected API Failure', - 22: 'Decode error', - 23: 'Bool expected in eighteenByteRipe', - 24: 'Chan address is already present.', - 25: 'Specified address is not a chan address.' - ' Use deleteAddress API call instead.', - 26: 'Malformed varint in address: ', - 27: 'Message is too long.' - } - - def __new__(mcs, name, bases, namespace): - result = super(ErrorCodes, mcs).__new__(mcs, name, bases, namespace) - for code in six.iteritems(mcs._CODES): - # beware: the formatting is adjusted for list-table - result.__doc__ += """ * - %04i - - %s - """ % code - return result - - -class APIError(xmlrpc_server.Fault): - """ - APIError exception class - - .. list-table:: Possible error values - :header-rows: 1 - :widths: auto - - * - Error Number - - Message - """ - __metaclass__ = ErrorCodes - +class APIError(Exception): + def __init__(self, error_number, error_message): + super(APIError, self).__init__() + self.error_number = error_number + self.error_message = error_message def __str__(self): - return "API Error %04i: %s" % (self.faultCode, self.faultString) + return "API Error %04i: %s" % (self.error_number, self.error_message) -# This thread, of which there is only one, runs the API. -class singleAPI(StoppableThread): - """API thread""" +class StoppableXMLRPCServer(SimpleXMLRPCServer): + allow_reuse_address = True - name = "singleAPI" - - def stopThread(self): - super(singleAPI, self).stopThread() - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - s.connect(( - config.get('bitmessagesettings', 'apiinterface'), - config.getint('bitmessagesettings', 'apiport') - )) - s.shutdown(socket.SHUT_RDWR) - s.close() - except BaseException: - pass - - def run(self): - """ - The instance of `SimpleXMLRPCServer.SimpleXMLRPCServer` or - :class:`jsonrpclib.SimpleJSONRPCServer` is created and started here - with `BMRPCDispatcher` dispatcher. - """ - port = config.getint('bitmessagesettings', 'apiport') - try: - getattr(errno, 'WSAEADDRINUSE') - except AttributeError: - errno.WSAEADDRINUSE = errno.EADDRINUSE - - RPCServerBase = xmlrpc_server.SimpleXMLRPCServer - ct = 'text/xml' - if config.safeGet( - 'bitmessagesettings', 'apivariant') == 'json': - try: - from jsonrpclib.SimpleJSONRPCServer import ( - SimpleJSONRPCServer as RPCServerBase) - except ImportError: - logger.warning( - 'jsonrpclib not available, failing back to XML-RPC') - else: - ct = 'application/json-rpc' - - # Nested class. FIXME not found a better solution. - class StoppableRPCServer(RPCServerBase): - """A SimpleXMLRPCServer that honours state.shutdown""" - allow_reuse_address = True - content_type = ct - - def serve_forever(self, poll_interval=None): - """Start the RPCServer""" - sql_ready.wait() - while state.shutdown == 0: - self.handle_request() - - for attempt in range(50): - try: - if attempt > 0: - logger.warning( - 'Failed to start API listener on port %s', port) - port = random.randint(32767, 65535) # nosec B311 - se = StoppableRPCServer( - (config.get( - 'bitmessagesettings', 'apiinterface'), - port), - BMXMLRPCRequestHandler, True, encoding='UTF-8') - except socket.error as e: - if e.errno in (errno.EADDRINUSE, errno.WSAEADDRINUSE): - continue - else: - if attempt > 0: - logger.warning('Setting apiport to %s', port) - config.set( - 'bitmessagesettings', 'apiport', str(port)) - config.save() - break - - se.register_instance(BMRPCDispatcher()) - se.register_introspection_functions() - - apiNotifyPath = config.safeGet( - 'bitmessagesettings', 'apinotifypath') - - if apiNotifyPath: - logger.info('Trying to call %s', apiNotifyPath) - try: - subprocess.call([apiNotifyPath, "startingUp"]) # nosec B603 - except OSError: - logger.warning( - 'Failed to call %s, removing apinotifypath setting', - apiNotifyPath) - config.remove_option( - 'bitmessagesettings', 'apinotifypath') - - se.serve_forever() - - -class CommandHandler(type): - """ - The metaclass for `BMRPCDispatcher` which fills _handlers dict by - methods decorated with @command - """ - def __new__(mcs, name, bases, namespace): - # pylint: disable=protected-access - result = super(CommandHandler, mcs).__new__( - mcs, name, bases, namespace) - result.config = config - result._handlers = {} - apivariant = result.config.safeGet('bitmessagesettings', 'apivariant') - for func in namespace.values(): - try: - for alias in getattr(func, '_cmd'): - try: - prefix, alias = alias.split(':') - if apivariant != prefix: - continue - except ValueError: - pass - result._handlers[alias] = func - except AttributeError: - pass - return result - - -class testmode(object): # pylint: disable=too-few-public-methods - """Decorator to check testmode & route to command decorator""" - - def __init__(self, *aliases): - self.aliases = aliases - - def __call__(self, func): - """Testmode call method""" - - if not state.testmode: - return None - return command(self.aliases[0]).__call__(func) - - -class command(object): # pylint: disable=too-few-public-methods - """Decorator for API command method""" - def __init__(self, *aliases): - self.aliases = aliases - - def __call__(self, func): - - if config.safeGet( - 'bitmessagesettings', 'apivariant') == 'legacy': - def wrapper(*args): - """ - A wrapper for legacy apivariant which dumps the result - into string of json - """ - result = func(*args) - return result if isinstance(result, (int, str)) \ - else json.dumps(result, indent=4) - wrapper.__doc__ = func.__doc__ - else: - wrapper = func - # pylint: disable=protected-access - wrapper._cmd = self.aliases - wrapper.__doc__ = """Commands: *%s* - - """ % ', '.join(self.aliases) + wrapper.__doc__.lstrip() - return wrapper + def serve_forever(self): + while state.shutdown == 0: + self.handle_request() # This is one of several classes that constitute the API -# This class was written by Vaibhav Bhatia. -# Modified by Jonathan Warren (Atheros). -# Further modified by the Bitmessage developers -# http://code.activestate.com/recipes/501148 -class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler): - """The main API handler""" +# This class was written by Vaibhav Bhatia. Modified by Jonathan Warren (Atheros). +# http://code.activestate.com/recipes/501148-xmlrpc-serverclient-which-does-cookie-handling-and/ +class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): - # pylint: disable=protected-access def do_POST(self): - """ - Handles the HTTP POST request. + # Handles the HTTP POST request. + # Attempts to interpret all HTTP POST requests as XML-RPC calls, + # which are forwarded to the server's _dispatch method for handling. - Attempts to interpret all HTTP POST requests as XML-RPC calls, - which are forwarded to the server's _dispatch method for handling. - - .. note:: this method is the same as in - `SimpleXMLRPCServer.SimpleXMLRPCRequestHandler`, - just hacked to handle cookies - """ + # Note: this method is the same as in SimpleXMLRPCRequestHandler, + # just hacked to handle cookies # Check that the path is legal if not self.is_rpc_path_valid(): @@ -389,43 +91,26 @@ class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler): L = [] while size_remaining: chunk_size = min(size_remaining, max_chunk_size) - chunk = self.rfile.read(chunk_size) - if not chunk: - break - L.append(chunk) + L.append(self.rfile.read(chunk_size)) size_remaining -= len(L[-1]) - data = b''.join(L) + data = ''.join(L) - # data = self.decode_request_content(data) - # pylint: disable=attribute-defined-outside-init - self.cookies = [] - - validuser = self.APIAuthenticateClient() - if not validuser: - time.sleep(2) - self.send_response(http_client.UNAUTHORIZED) - self.end_headers() - return - # "RPC Username or password incorrect or HTTP header" - # " lacks authentication at all." - else: - # In previous versions of SimpleXMLRPCServer, _dispatch - # could be overridden in this class, instead of in - # SimpleXMLRPCDispatcher. To maintain backwards compatibility, - # check to see if a subclass implements _dispatch and dispatch - # using that method if present. - - response = self.server._marshaled_dispatch( - data, getattr(self, '_dispatch', None) - ) - except Exception: # This should only happen if the module is buggy + # In previous versions of SimpleXMLRPCServer, _dispatch + # could be overridden in this class, instead of in + # SimpleXMLRPCDispatcher. To maintain backwards compatibility, + # check to see if a subclass implements _dispatch and dispatch + # using that method if present. + response = self.server._marshaled_dispatch( + data, getattr(self, '_dispatch', None) + ) + except: # This should only happen if the module is buggy # internal error, report as HTTP server error - self.send_response(http_client.INTERNAL_SERVER_ERROR) + self.send_response(500) self.end_headers() else: # got a valid XML RPC response - self.send_response(http_client.OK) - self.send_header("Content-type", self.server.content_type) + self.send_response(200) + self.send_header("Content-type", "text/xml") self.send_header("Content-length", str(len(response))) # HACK :start -> sends cookies here @@ -441,1147 +126,945 @@ class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler): self.wfile.flush() self.connection.shutdown(1) - # actually handle shutdown command after sending response - if state.shutdown is False: - shutdown.doCleanShutdown() - def APIAuthenticateClient(self): - """ - Predicate to check for valid API credentials in the request header - """ - if 'Authorization' in self.headers: # handle Basic authentication - encstr = self.headers.get('Authorization').split()[1] - emailid, password = base64.b64decode( - encstr).decode('utf-8').split(':') - return ( - emailid == config.get( - 'bitmessagesettings', 'apiusername' - ) and password == config.get( - 'bitmessagesettings', 'apipassword')) + (enctype, encstr) = self.headers.get('Authorization').split() + (emailid, password) = encstr.decode('base64').split(':') + if emailid == BMConfigParser().get('bitmessagesettings', 'apiusername') and password == BMConfigParser().get('bitmessagesettings', 'apipassword'): + return True + else: + return False else: - logger.warning( - 'Authentication failed because header lacks' - ' Authentication field') + logger.warn('Authentication failed because header lacks Authentication field') time.sleep(2) + return False return False - -# pylint: disable=no-self-use,no-member,too-many-public-methods -@six.add_metaclass(CommandHandler) -class BMRPCDispatcher(object): - """This class is used to dispatch API commands""" - - @staticmethod - def _decode(text, decode_type): + def _decode(self, text, decode_type): try: if decode_type == 'hex': return unhexlify(text) elif decode_type == 'base64': return base64.b64decode(text) except Exception as e: - raise APIError( - 22, 'Decode error - %s. Had trouble while decoding string: %r' - % (e, text) - ) + raise APIError(22, "Decode error - " + str(e) + ". Had trouble while decoding string: " + repr(text)) def _verifyAddress(self, address): - status, addressVersionNumber, streamNumber, ripe = \ - decodeAddress(address) + status, addressVersionNumber, streamNumber, ripe = decodeAddress(address) if status != 'success': + logger.warn('API Error 0007: Could not decode address %s. Status: %s.', address, status) + if status == 'checksumfailed': raise APIError(8, 'Checksum failed for address: ' + address) if status == 'invalidcharacters': raise APIError(9, 'Invalid characters in address: ' + address) if status == 'versiontoohigh': - raise APIError( - 10, 'Address version number too high (or zero) in address: ' - + address) + raise APIError(10, 'Address version number too high (or zero) in address: ' + address) if status == 'varintmalformed': raise APIError(26, 'Malformed varint in address: ' + address) - raise APIError( - 7, 'Could not decode address: %s : %s' % (address, status)) + raise APIError(7, 'Could not decode address: ' + address + ' : ' + status) if addressVersionNumber < 2 or addressVersionNumber > 4: - raise APIError( - 11, 'The address version number currently must be 2, 3 or 4.' - ' Others aren\'t supported. Check the address.' - ) + raise APIError(11, 'The address version number currently must be 2, 3 or 4. Others aren\'t supported. Check the address.') if streamNumber != 1: - raise APIError( - 12, 'The stream number must be 1. Others aren\'t supported.' - ' Check the address.' - ) + raise APIError(12, 'The stream number must be 1. Others aren\'t supported. Check the address.') - return { - 'status': status, - 'addressVersion': addressVersionNumber, - 'streamNumber': streamNumber, - 'ripe': base64.b64encode(ripe) - } if self._method == 'decodeAddress' else ( - status, addressVersionNumber, streamNumber, ripe) + return (status, addressVersionNumber, streamNumber, ripe) - @staticmethod - def _dump_inbox_message( # pylint: disable=too-many-arguments - msgid, toAddress, fromAddress, subject, received, - message, encodingtype, read): - subject = shared.fixPotentiallyInvalidUTF8Data(subject) - message = shared.fixPotentiallyInvalidUTF8Data(message) - return { - 'msgid': hexlify(msgid), - 'toAddress': toAddress, - 'fromAddress': fromAddress, - 'subject': base64.b64encode(subject), - 'message': base64.b64encode(message), - 'encodingType': encodingtype, - 'receivedTime': received, - 'read': read - } - @staticmethod - def _dump_sent_message( # pylint: disable=too-many-arguments - msgid, toAddress, fromAddress, subject, lastactiontime, - message, encodingtype, status, ackdata): - subject = shared.fixPotentiallyInvalidUTF8Data(subject) - message = shared.fixPotentiallyInvalidUTF8Data(message) - return { - 'msgid': hexlify(msgid), - 'toAddress': toAddress, - 'fromAddress': fromAddress, - 'subject': base64.b64encode(subject), - 'message': base64.b64encode(message), - 'encodingType': encodingtype, - 'lastActionTime': lastactiontime, - 'status': status, - 'ackData': hexlify(ackdata) - } + #Request Handlers - # Request Handlers - - @command('decodeAddress') - def HandleDecodeAddress(self, address): - """ - Decode given address and return dict with - status, addressVersion, streamNumber and ripe keys - """ - return self._verifyAddress(address) - - @command('listAddresses', 'listAddresses2') - def HandleListAddresses(self): - """ - Returns dict with a list of all used addresses with their properties - in the *addresses* key. - """ - data = [] - for address in self.config.addresses(): - streamNumber = decodeAddress(address)[2] - label = self.config.get(address, 'label') - if self._method == 'listAddresses2': + def HandleListAddresses(self, method): + data = '{"addresses":[' + 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.append({ - 'label': label, - 'address': address, - 'stream': streamNumber, - 'enabled': self.config.safeGetBoolean(address, 'enabled'), - 'chan': self.config.safeGetBoolean(address, 'chan') - }) - return {'addresses': data} + data += json.dumps({'label': label, 'address': addressInKeysFile, 'stream': + streamNumber, 'enabled': BMConfigParser().getboolean(addressInKeysFile, 'enabled'), 'chan': chan}, indent=4, separators=(',', ': ')) + data += ']}' + return data - # the listAddressbook alias should be removed eventually. - @command('listAddressBookEntries', 'legacy:listAddressbook') - def HandleListAddressBookEntries(self, label=None): - """ - Returns dict with a list of all address book entries (address and label) - in the *addresses* key. - """ - queryreturn = sqlQuery( - "SELECT label, address from addressbook WHERE label = ?", - label - ) if label else sqlQuery("SELECT label, address from addressbook") - data = [] - for label, address in queryreturn: + def HandleListAddressBookEntries(self, params): + if len(params) == 1: + label, = params + label = self._decode(label, "base64") + queryreturn = sqlQuery('''SELECT label, address from addressbook WHERE label = ?''', label) + elif len(params) > 1: + raise APIError(0, "Too many paremeters, max 1") + else: + queryreturn = sqlQuery('''SELECT label, address from addressbook''') + data = '{"addresses":[' + for row in queryreturn: + label, address = row label = shared.fixPotentiallyInvalidUTF8Data(label) - data.append({ - 'label': base64.b64encode(label), - 'address': address - }) - return {'addresses': data} + if len(data) > 20: + data += ',' + data += json.dumps({'label':base64.b64encode(label), 'address': address}, indent=4, separators=(',', ': ')) + data += ']}' + return data - # the addAddressbook alias should be deleted eventually. - @command('addAddressBookEntry', 'legacy:addAddressbook') - def HandleAddAddressBookEntry(self, address, label): - """Add an entry to address book. label must be base64 encoded.""" + def HandleAddAddressBookEntry(self, params): + if len(params) != 2: + raise APIError(0, "I need label and address") + address, label = params label = self._decode(label, "base64") address = addBMIfNotPresent(address) self._verifyAddress(address) - # TODO: add unique together constraint in the table - queryreturn = sqlQuery( - "SELECT address FROM addressbook WHERE address=?", address) + queryreturn = sqlQuery("SELECT address FROM addressbook WHERE address=?", address) if queryreturn != []: - raise APIError( - 16, 'You already have this address in your address book.') + raise APIError(16, 'You already have this address in your address book.') sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, address) - queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) - queues.UISignalQueue.put(('rerenderMessagelistToLabels', '')) - queues.UISignalQueue.put(('rerenderAddressBook', '')) + queues.UISignalQueue.put(('rerenderMessagelistFromLabels','')) + queues.UISignalQueue.put(('rerenderMessagelistToLabels','')) + queues.UISignalQueue.put(('rerenderAddressBook','')) return "Added address %s to address book" % address - # the deleteAddressbook alias should be deleted eventually. - @command('deleteAddressBookEntry', 'legacy:deleteAddressbook') - def HandleDeleteAddressBookEntry(self, address): - """Delete an entry from address book.""" + def HandleDeleteAddressBookEntry(self, params): + if len(params) != 1: + raise APIError(0, "I need an address") + address, = params address = addBMIfNotPresent(address) self._verifyAddress(address) sqlExecute('DELETE FROM addressbook WHERE address=?', address) - queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) - queues.UISignalQueue.put(('rerenderMessagelistToLabels', '')) - queues.UISignalQueue.put(('rerenderAddressBook', '')) + queues.UISignalQueue.put(('rerenderMessagelistFromLabels','')) + queues.UISignalQueue.put(('rerenderMessagelistToLabels','')) + queues.UISignalQueue.put(('rerenderAddressBook','')) return "Deleted address book entry for %s if it existed" % address - @command('createRandomAddress') - def HandleCreateRandomAddress( - self, label, eighteenByteRipe=False, totalDifficulty=0, - smallMessageDifficulty=0 - ): - """ - Create one address using the random number generator. - - :param str label: base64 encoded label for the address - :param bool eighteenByteRipe: is telling Bitmessage whether to - generate an address with an 18 byte RIPE hash - (as opposed to a 19 byte hash). - """ - - nonceTrialsPerByte = self.config.get( - 'bitmessagesettings', 'defaultnoncetrialsperbyte' - ) if not totalDifficulty else int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) - payloadLengthExtraBytes = self.config.get( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes' - ) if not smallMessageDifficulty else int( - defaults.networkDefaultPayloadLengthExtraBytes - * smallMessageDifficulty) - - if not isinstance(eighteenByteRipe, bool): - raise APIError( - 23, 'Bool expected in eighteenByteRipe, saw %s instead' - % type(eighteenByteRipe)) + def HandleCreateRandomAddress(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + elif len(params) == 1: + label, = params + eighteenByteRipe = False + nonceTrialsPerByte = BMConfigParser().get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = BMConfigParser().get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 2: + label, eighteenByteRipe = params + nonceTrialsPerByte = BMConfigParser().get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = BMConfigParser().get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 3: + label, eighteenByteRipe, totalDifficulty = params + nonceTrialsPerByte = int( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + payloadLengthExtraBytes = BMConfigParser().get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 4: + label, eighteenByteRipe, totalDifficulty, smallMessageDifficulty = params + nonceTrialsPerByte = int( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + payloadLengthExtraBytes = int( + defaults.networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty) + else: + raise APIError(0, 'Too many parameters!') label = self._decode(label, "base64") try: - label.decode('utf-8') - except UnicodeDecodeError: + unicode(label, 'utf-8') + except: raise APIError(17, 'Label is not valid UTF-8 data.') queues.apiAddressGeneratorReturnQueue.queue.clear() - # FIXME hard coded stream no streamNumberForAddress = 1 queues.addressGeneratorQueue.put(( - 'createRandomAddress', 4, streamNumberForAddress, label, 1, "", - eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes - )) + 'createRandomAddress', 4, streamNumberForAddress, label, 1, "", eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes)) return queues.apiAddressGeneratorReturnQueue.get() - # pylint: disable=too-many-arguments - @command('createDeterministicAddresses') - def HandleCreateDeterministicAddresses( - self, passphrase, numberOfAddresses=1, addressVersionNumber=0, - streamNumber=0, eighteenByteRipe=False, totalDifficulty=0, - smallMessageDifficulty=0 - ): - """ - Create many addresses deterministically using the passphrase. - - :param str passphrase: base64 encoded passphrase - :param int numberOfAddresses: number of addresses to create, - up to 999 - - *addressVersionNumber* and *streamNumber* may be set to 0 - which will tell Bitmessage to use the most up-to-date - address version and the most available stream. - """ - - nonceTrialsPerByte = self.config.get( - 'bitmessagesettings', 'defaultnoncetrialsperbyte' - ) if not totalDifficulty else int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - * totalDifficulty) - payloadLengthExtraBytes = self.config.get( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes' - ) if not smallMessageDifficulty else int( - defaults.networkDefaultPayloadLengthExtraBytes - * smallMessageDifficulty) - - if not passphrase: + def HandleCreateDeterministicAddresses(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + elif len(params) == 1: + passphrase, = params + numberOfAddresses = 1 + addressVersionNumber = 0 + streamNumber = 0 + eighteenByteRipe = False + nonceTrialsPerByte = BMConfigParser().get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = BMConfigParser().get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 2: + passphrase, numberOfAddresses = params + addressVersionNumber = 0 + streamNumber = 0 + eighteenByteRipe = False + nonceTrialsPerByte = BMConfigParser().get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = BMConfigParser().get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 3: + passphrase, numberOfAddresses, addressVersionNumber = params + streamNumber = 0 + eighteenByteRipe = False + nonceTrialsPerByte = BMConfigParser().get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = BMConfigParser().get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 4: + passphrase, numberOfAddresses, addressVersionNumber, streamNumber = params + eighteenByteRipe = False + nonceTrialsPerByte = BMConfigParser().get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = BMConfigParser().get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 5: + passphrase, numberOfAddresses, addressVersionNumber, streamNumber, eighteenByteRipe = params + nonceTrialsPerByte = BMConfigParser().get( + 'bitmessagesettings', 'defaultnoncetrialsperbyte') + payloadLengthExtraBytes = BMConfigParser().get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 6: + passphrase, numberOfAddresses, addressVersionNumber, streamNumber, eighteenByteRipe, totalDifficulty = params + nonceTrialsPerByte = int( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + payloadLengthExtraBytes = BMConfigParser().get( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes') + elif len(params) == 7: + passphrase, numberOfAddresses, addressVersionNumber, streamNumber, eighteenByteRipe, totalDifficulty, smallMessageDifficulty = params + nonceTrialsPerByte = int( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + payloadLengthExtraBytes = int( + defaults.networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty) + else: + raise APIError(0, 'Too many parameters!') + if len(passphrase) == 0: raise APIError(1, 'The specified passphrase is blank.') if not isinstance(eighteenByteRipe, bool): - raise APIError( - 23, 'Bool expected in eighteenByteRipe, saw %s instead' - % type(eighteenByteRipe)) + raise APIError(23, 'Bool expected in eighteenByteRipe, saw %s instead' % type(eighteenByteRipe)) passphrase = self._decode(passphrase, "base64") - # 0 means "just use the proper addressVersionNumber" - if addressVersionNumber == 0: + if addressVersionNumber == 0: # 0 means "just use the proper addressVersionNumber" addressVersionNumber = 4 - if addressVersionNumber not in (3, 4): - raise APIError( - 2, 'The address version number currently must be 3, 4, or 0' - ' (which means auto-select). %i isn\'t supported.' - % addressVersionNumber) + if addressVersionNumber != 3 and addressVersionNumber != 4: + raise APIError(2,'The address version number currently must be 3, 4, or 0 (which means auto-select). ' + addressVersionNumber + ' isn\'t supported.') if streamNumber == 0: # 0 means "just use the most available stream" - streamNumber = 1 # FIXME hard coded stream no + streamNumber = 1 if streamNumber != 1: - raise APIError( - 3, 'The stream number must be 1 (or 0 which means' - ' auto-select). Others aren\'t supported.') + raise APIError(3,'The stream number must be 1 (or 0 which means auto-select). Others aren\'t supported.') if numberOfAddresses == 0: - raise APIError( - 4, 'Why would you ask me to generate 0 addresses for you?') + raise APIError(4, 'Why would you ask me to generate 0 addresses for you?') if numberOfAddresses > 999: - raise APIError( - 5, 'You have (accidentally?) specified too many addresses to' - ' make. Maximum 999. This check only exists to prevent' - ' mischief; if you really want to create more addresses than' - ' this, contact the Bitmessage developers and we can modify' - ' the check or you can do it yourself by searching the source' - ' code for this message.') + raise APIError(5, 'You have (accidentally?) specified too many addresses to make. Maximum 999. This check only exists to prevent mischief; if you really want to create more addresses than this, contact the Bitmessage developers and we can modify the check or you can do it yourself by searching the source code for this message.') queues.apiAddressGeneratorReturnQueue.queue.clear() - logger.debug( - 'Requesting that the addressGenerator create %s addresses.', - numberOfAddresses) - queues.addressGeneratorQueue.put(( - 'createDeterministicAddresses', addressVersionNumber, streamNumber, - 'unused API address', numberOfAddresses, passphrase, - eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes - )) - - return {'addresses': queues.apiAddressGeneratorReturnQueue.get()} - - @command('getDeterministicAddress') - def HandleGetDeterministicAddress( - self, passphrase, addressVersionNumber, streamNumber): - """ - Similar to *createDeterministicAddresses* except that the one - address that is returned will not be added to the Bitmessage - user interface or the keys.dat file. - """ + logger.debug('Requesting that the addressGenerator create %s addresses.', numberOfAddresses) + queues.addressGeneratorQueue.put( + ('createDeterministicAddresses', addressVersionNumber, streamNumber, + 'unused API address', numberOfAddresses, passphrase, eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes)) + data = '{"addresses":[' + queueReturn = queues.apiAddressGeneratorReturnQueue.get() + for item in queueReturn: + if len(data) > 20: + data += ',' + data += "\"" + item + "\"" + data += ']}' + return data + def HandleGetDeterministicAddress(self, params): + if len(params) != 3: + raise APIError(0, 'I need exactly 3 parameters.') + passphrase, addressVersionNumber, streamNumber = params numberOfAddresses = 1 eighteenByteRipe = False - if not passphrase: + if len(passphrase) == 0: raise APIError(1, 'The specified passphrase is blank.') passphrase = self._decode(passphrase, "base64") - if addressVersionNumber not in (3, 4): - raise APIError( - 2, 'The address version number currently must be 3 or 4. %i' - ' isn\'t supported.' % addressVersionNumber) + if addressVersionNumber != 3 and addressVersionNumber != 4: + raise APIError(2, 'The address version number currently must be 3 or 4. ' + addressVersionNumber + ' isn\'t supported.') if streamNumber != 1: - raise APIError( - 3, ' The stream number must be 1. Others aren\'t supported.') + raise APIError(3, ' The stream number must be 1. Others aren\'t supported.') queues.apiAddressGeneratorReturnQueue.queue.clear() - logger.debug( - 'Requesting that the addressGenerator create %s addresses.', - numberOfAddresses) - queues.addressGeneratorQueue.put(( - 'getDeterministicAddress', addressVersionNumber, streamNumber, - 'unused API address', numberOfAddresses, passphrase, - eighteenByteRipe - )) + logger.debug('Requesting that the addressGenerator create %s addresses.', numberOfAddresses) + queues.addressGeneratorQueue.put( + ('getDeterministicAddress', addressVersionNumber, + streamNumber, 'unused API address', numberOfAddresses, passphrase, eighteenByteRipe)) return queues.apiAddressGeneratorReturnQueue.get() - @command('createChan') - def HandleCreateChan(self, passphrase): - """ - Creates a new chan. passphrase must be base64 encoded. - Returns the corresponding Bitmessage address. - """ - + def HandleCreateChan(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters.') + elif len(params) == 1: + passphrase, = params passphrase = self._decode(passphrase, "base64") - if not passphrase: + if len(passphrase) == 0: raise APIError(1, 'The specified passphrase is blank.') # It would be nice to make the label the passphrase but it is # possible that the passphrase contains non-utf-8 characters. try: - passphrase.decode('utf-8') + unicode(passphrase, 'utf-8') label = str_chan + ' ' + passphrase - except UnicodeDecodeError: + except: label = str_chan + ' ' + repr(passphrase) addressVersionNumber = 4 streamNumber = 1 queues.apiAddressGeneratorReturnQueue.queue.clear() - logger.debug( - 'Requesting that the addressGenerator create chan %s.', passphrase) - queues.addressGeneratorQueue.put(( - 'createChan', addressVersionNumber, streamNumber, label, - passphrase, True - )) + logger.debug('Requesting that the addressGenerator create chan %s.', passphrase) + queues.addressGeneratorQueue.put(('createChan', addressVersionNumber, streamNumber, label, passphrase, True)) queueReturn = queues.apiAddressGeneratorReturnQueue.get() - try: - return queueReturn[0] - except IndexError: + if len(queueReturn) == 0: raise APIError(24, 'Chan address is already present.') + address = queueReturn[0] + return address - @command('joinChan') - def HandleJoinChan(self, passphrase, suppliedAddress): - """ - Join a chan. passphrase must be base64 encoded. Returns 'success'. - """ - + def HandleJoinChan(self, params): + if len(params) < 2: + raise APIError(0, 'I need two parameters.') + elif len(params) == 2: + passphrase, suppliedAddress= params passphrase = self._decode(passphrase, "base64") - if not passphrase: + if len(passphrase) == 0: raise APIError(1, 'The specified passphrase is blank.') # It would be nice to make the label the passphrase but it is # possible that the passphrase contains non-utf-8 characters. try: - passphrase.decode('utf-8') + unicode(passphrase, 'utf-8') label = str_chan + ' ' + passphrase - except UnicodeDecodeError: + except: label = str_chan + ' ' + repr(passphrase) - self._verifyAddress(suppliedAddress) + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress(suppliedAddress) suppliedAddress = addBMIfNotPresent(suppliedAddress) queues.apiAddressGeneratorReturnQueue.queue.clear() - queues.addressGeneratorQueue.put(( - 'joinChan', suppliedAddress, label, passphrase, True - )) - queueReturn = queues.apiAddressGeneratorReturnQueue.get() - try: - if queueReturn[0] == 'chan name does not match address': - raise APIError(18, 'Chan name does not match address.') - except IndexError: + queues.addressGeneratorQueue.put(('joinChan', suppliedAddress, label, passphrase, True)) + addressGeneratorReturnValue = queues.apiAddressGeneratorReturnQueue.get() + + if addressGeneratorReturnValue[0] == 'chan name does not match address': + raise APIError(18, 'Chan name does not match address.') + if len(addressGeneratorReturnValue) == 0: raise APIError(24, 'Chan address is already present.') - + #TODO: this variable is not used to anything + createdAddress = addressGeneratorReturnValue[0] # in case we ever want it for anything. return "success" - @command('leaveChan') - def HandleLeaveChan(self, address): - """ - Leave a chan. Returns 'success'. - - .. note:: at this time, the address is still shown in the UI - until a restart. - """ - self._verifyAddress(address) + def HandleLeaveChan(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters.') + elif len(params) == 1: + address, = params + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress(address) address = addBMIfNotPresent(address) - if not self.config.safeGetBoolean(address, 'chan'): - raise APIError( - 25, 'Specified address is not a chan address.' - ' Use deleteAddress API call instead.') - try: - self.config.remove_section(address) - except configparser.NoSectionError: - raise APIError( - 13, 'Could not find this address in your keys.dat file.') - self.config.save() - queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) - queues.UISignalQueue.put(('rerenderMessagelistToLabels', '')) - return "success" + if not BMConfigParser().has_section(address): + raise APIError(13, 'Could not find this address in your keys.dat file.') + if not BMConfigParser().safeGetBoolean(address, 'chan'): + raise APIError(25, 'Specified address is not a chan address. Use deleteAddress API call instead.') + BMConfigParser().remove_section(address) + with open(state.appdata + 'keys.dat', 'wb') as configfile: + BMConfigParser().write(configfile) + return 'success' - @command('deleteAddress') - def HandleDeleteAddress(self, address): - """ - Permanently delete the address from keys.dat file. Returns 'success'. - """ - self._verifyAddress(address) + def HandleDeleteAddress(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters.') + elif len(params) == 1: + address, = params + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress(address) address = addBMIfNotPresent(address) - try: - self.config.remove_section(address) - except configparser.NoSectionError: - raise APIError( - 13, 'Could not find this address in your keys.dat file.') - self.config.save() - queues.UISignalQueue.put(('writeNewAddressToTable', ('', '', ''))) + if not BMConfigParser().has_section(address): + raise APIError(13, 'Could not find this address in your keys.dat file.') + BMConfigParser().remove_section(address) + with open(state.appdata + 'keys.dat', 'wb') as configfile: + BMConfigParser().write(configfile) + queues.UISignalQueue.put(('rerenderMessagelistFromLabels','')) + queues.UISignalQueue.put(('rerenderMessagelistToLabels','')) shared.reloadMyAddressHashes() - return "success" - - @command('enableAddress') - def HandleEnableAddress(self, address, enable=True): - """Enable or disable the address depending on the *enable* value""" - self._verifyAddress(address) - address = addBMIfNotPresent(address) - config.set(address, 'enabled', str(enable)) - self.config.save() - shared.reloadMyAddressHashes() - return "success" - - @command('getAllInboxMessages') - def HandleGetAllInboxMessages(self): - """ - Returns a dict with all inbox messages in the *inboxMessages* key. - The message is a dict with such keys: - *msgid*, *toAddress*, *fromAddress*, *subject*, *message*, - *encodingType*, *receivedTime*, *read*. - *msgid* is hex encoded string. - *subject* and *message* are base64 encoded. - """ + return 'success' + def HandleGetAllInboxMessages(self, params): queryreturn = sqlQuery( - "SELECT msgid, toaddress, fromaddress, subject, received, message," - " encodingtype, read FROM inbox WHERE folder='inbox'" - " ORDER BY received" - ) - return {"inboxMessages": [ - self._dump_inbox_message(*data) for data in queryreturn - ]} - - @command('getAllInboxMessageIds', 'getAllInboxMessageIDs') - def HandleGetAllInboxMessageIds(self): - """ - The same as *getAllInboxMessages* but returns only *msgid*s, - result key - *inboxMessageIds*. - """ + '''SELECT msgid, toaddress, fromaddress, subject, received, message, encodingtype, read FROM inbox where folder='inbox' ORDER BY received''') + data = '{"inboxMessages":[' + for row in queryreturn: + msgid, toAddress, fromAddress, subject, received, message, encodingtype, read = row + subject = shared.fixPotentiallyInvalidUTF8Data(subject) + message = shared.fixPotentiallyInvalidUTF8Data(message) + if len(data) > 25: + data += ',' + 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 + def HandleGetAllInboxMessageIds(self, params): queryreturn = sqlQuery( - "SELECT msgid FROM inbox where folder='inbox' ORDER BY received") + '''SELECT msgid FROM inbox where folder='inbox' ORDER BY received''') + data = '{"inboxMessageIds":[' + for row in queryreturn: + msgid = row[0] + if len(data) > 25: + data += ',' + data += json.dumps({'msgid': hexlify(msgid)}, indent=4, separators=(',', ': ')) + data += ']}' + return data - return {"inboxMessageIds": [ - {'msgid': hexlify(msgid)} for msgid, in queryreturn - ]} - - @command('getInboxMessageById', 'getInboxMessageByID') - def HandleGetInboxMessageById(self, hid, readStatus=None): - """ - Returns a dict with list containing single message in the result - key *inboxMessage*. May also return None if message was not found. - - :param str hid: hex encoded msgid - :param bool readStatus: sets the message's read status if present - """ - - msgid = self._decode(hid, "hex") - if readStatus is not None: + def HandleGetInboxMessageById(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + elif len(params) == 1: + msgid = self._decode(params[0], "hex") + elif len(params) >= 2: + msgid = self._decode(params[0], "hex") + readStatus = params[1] if not isinstance(readStatus, bool): - raise APIError( - 23, 'Bool expected in readStatus, saw %s instead.' - % type(readStatus)) - queryreturn = sqlQuery( - "SELECT read FROM inbox WHERE msgid=?", msgid) + raise APIError(23, 'Bool expected in readStatus, saw %s instead.' % type(readStatus)) + queryreturn = sqlQuery('''SELECT read FROM inbox WHERE msgid=?''', msgid) # UPDATE is slow, only update if status is different - try: - if (queryreturn[0][0] == 1) != readStatus: - sqlExecute( - "UPDATE inbox set read = ? WHERE msgid=?", - readStatus, msgid) - queues.UISignalQueue.put(('changedInboxUnread', None)) - except IndexError: - pass - queryreturn = sqlQuery( - "SELECT msgid, toaddress, fromaddress, subject, received, message," - " encodingtype, read FROM inbox WHERE msgid=?", msgid - ) - try: - return {"inboxMessage": [ - self._dump_inbox_message(*queryreturn[0])]} - except IndexError: - pass # FIXME inconsistent + if queryreturn != [] and (queryreturn[0][0] == 1) != readStatus: + sqlExecute('''UPDATE inbox set read = ? WHERE msgid=?''', readStatus, msgid) + queues.UISignalQueue.put(('changedInboxUnread', None)) + queryreturn = sqlQuery('''SELECT msgid, toaddress, fromaddress, subject, received, message, encodingtype, read FROM inbox WHERE msgid=?''', msgid) + data = '{"inboxMessage":[' + for row in queryreturn: + 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':base64.b64encode(subject), 'message':base64.b64encode(message), 'encodingType':encodingtype, 'receivedTime':received, 'read': read}, indent=4, separators=(',', ': ')) + data += ']}' + return data - @command('getAllSentMessages') - def HandleGetAllSentMessages(self): - """ - The same as *getAllInboxMessages* but for sent, - result key - *sentMessages*. Message dict keys are: - *msgid*, *toAddress*, *fromAddress*, *subject*, *message*, - *encodingType*, *lastActionTime*, *status*, *ackData*. - *ackData* is also a hex encoded string. - """ + def HandleGetAllSentMessages(self, params): + queryreturn = sqlQuery('''SELECT msgid, toaddress, fromaddress, subject, lastactiontime, message, encodingtype, status, ackdata FROM sent where folder='sent' ORDER BY lastactiontime''') + data = '{"sentMessages":[' + for row in queryreturn: + msgid, toAddress, fromAddress, subject, lastactiontime, message, encodingtype, status, ackdata = row + subject = shared.fixPotentiallyInvalidUTF8Data(subject) + message = shared.fixPotentiallyInvalidUTF8Data(message) + if len(data) > 25: + data += ',' + 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 - queryreturn = sqlQuery( - "SELECT msgid, toaddress, fromaddress, subject, lastactiontime," - " message, encodingtype, status, ackdata FROM sent" - " WHERE folder='sent' ORDER BY lastactiontime" - ) - return {"sentMessages": [ - self._dump_sent_message(*data) for data in queryreturn - ]} + def HandleGetAllSentMessageIds(self, params): + queryreturn = sqlQuery('''SELECT msgid FROM sent where folder='sent' ORDER BY lastactiontime''') + data = '{"sentMessageIds":[' + for row in queryreturn: + msgid = row[0] + if len(data) > 25: + data += ',' + data += json.dumps({'msgid':hexlify(msgid)}, indent=4, separators=(',', ': ')) + data += ']}' + return data - @command('getAllSentMessageIds', 'getAllSentMessageIDs') - def HandleGetAllSentMessageIds(self): - """ - The same as *getAllInboxMessageIds* but for sent, - result key - *sentMessageIds*. - """ + def HandleInboxMessagesByReceiver(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + toAddress = params[0] + queryreturn = sqlQuery('''SELECT msgid, toaddress, fromaddress, subject, received, message, encodingtype FROM inbox WHERE folder='inbox' AND toAddress=?''', toAddress) + data = '{"inboxMessages":[' + for row in queryreturn: + msgid, toAddress, fromAddress, subject, received, message, encodingtype = row + subject = shared.fixPotentiallyInvalidUTF8Data(subject) + message = shared.fixPotentiallyInvalidUTF8Data(message) + if len(data) > 25: + data += ',' + 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 - queryreturn = sqlQuery( - "SELECT msgid FROM sent WHERE folder='sent'" - " ORDER BY lastactiontime" - ) - return {"sentMessageIds": [ - {'msgid': hexlify(msgid)} for msgid, in queryreturn - ]} + def HandleGetSentMessageById(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + msgid = self._decode(params[0], "hex") + queryreturn = sqlQuery('''SELECT msgid, toaddress, fromaddress, subject, lastactiontime, message, encodingtype, status, ackdata FROM sent WHERE msgid=?''', msgid) + data = '{"sentMessage":[' + for row in queryreturn: + 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':base64.b64encode(subject), 'message':base64.b64encode(message), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': ')) + data += ']}' + return data - # after some time getInboxMessagesByAddress should be removed - @command('getInboxMessagesByReceiver', 'legacy:getInboxMessagesByAddress') - def HandleInboxMessagesByReceiver(self, toAddress): - """ - The same as *getAllInboxMessages* but returns only messages - for toAddress. - """ + def HandleGetSentMessagesByAddress(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + fromAddress = params[0] + queryreturn = sqlQuery('''SELECT msgid, toaddress, fromaddress, subject, lastactiontime, message, encodingtype, status, ackdata FROM sent WHERE folder='sent' AND fromAddress=? ORDER BY lastactiontime''', + fromAddress) + data = '{"sentMessages":[' + for row in queryreturn: + msgid, toAddress, fromAddress, subject, lastactiontime, message, encodingtype, status, ackdata = row + subject = shared.fixPotentiallyInvalidUTF8Data(subject) + message = shared.fixPotentiallyInvalidUTF8Data(message) + if len(data) > 25: + data += ',' + 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 - queryreturn = sqlQuery( - "SELECT msgid, toaddress, fromaddress, subject, received," - " message, encodingtype, read FROM inbox WHERE folder='inbox'" - " AND toAddress=?", toAddress) - return {"inboxMessages": [ - self._dump_inbox_message(*data) for data in queryreturn - ]} + def HandleGetSentMessagesByAckData(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + ackData = self._decode(params[0], "hex") + queryreturn = sqlQuery('''SELECT msgid, toaddress, fromaddress, subject, lastactiontime, message, encodingtype, status, ackdata FROM sent WHERE ackdata=?''', + ackData) + data = '{"sentMessage":[' + for row in queryreturn: + 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':base64.b64encode(subject), 'message':base64.b64encode(message), 'encodingType':encodingtype, 'lastActionTime':lastactiontime, 'status':status, 'ackData':hexlify(ackdata)}, indent=4, separators=(',', ': ')) + data += ']}' + return data - @command('getSentMessageById', 'getSentMessageByID') - def HandleGetSentMessageById(self, hid): - """ - Similiar to *getInboxMessageById* but doesn't change message's - read status (sent messages have no such field). - Result key is *sentMessage* - """ + def HandleTrashMessage(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + msgid = self._decode(params[0], "hex") - msgid = self._decode(hid, "hex") - queryreturn = sqlQuery( - "SELECT msgid, toaddress, fromaddress, subject, lastactiontime," - " message, encodingtype, status, ackdata FROM sent WHERE msgid=?", - msgid - ) - try: - return {"sentMessage": [ - self._dump_sent_message(*queryreturn[0]) - ]} - except IndexError: - pass # FIXME inconsistent - - @command('getSentMessagesByAddress', 'getSentMessagesBySender') - def HandleGetSentMessagesByAddress(self, fromAddress): - """ - The same as *getAllSentMessages* but returns only messages - from fromAddress. - """ - - queryreturn = sqlQuery( - "SELECT msgid, toaddress, fromaddress, subject, lastactiontime," - " message, encodingtype, status, ackdata FROM sent" - " WHERE folder='sent' AND fromAddress=? ORDER BY lastactiontime", - fromAddress - ) - return {"sentMessages": [ - self._dump_sent_message(*data) for data in queryreturn - ]} - - @command('getSentMessageByAckData') - def HandleGetSentMessagesByAckData(self, ackData): - """ - Similiar to *getSentMessageById* but searches by ackdata - (also hex encoded). - """ - - ackData = self._decode(ackData, "hex") - queryreturn = sqlQuery( - "SELECT msgid, toaddress, fromaddress, subject, lastactiontime," - " message, encodingtype, status, ackdata FROM sent" - " WHERE ackdata=?", ackData - ) - - try: - return {"sentMessage": [ - self._dump_sent_message(*queryreturn[0]) - ]} - except IndexError: - pass # FIXME inconsistent - - @command('trashMessage') - def HandleTrashMessage(self, msgid): - """ - Trash message by msgid (encoded in hex). Returns a simple message - saying that the message was trashed assuming it ever even existed. - Prior existence is not checked. - """ - msgid = self._decode(msgid, "hex") # Trash if in inbox table helper_inbox.trash(msgid) # Trash if in sent table - sqlExecute("UPDATE sent SET folder='trash' WHERE msgid=?", msgid) + sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid) return 'Trashed message (assuming message existed).' - @command('trashInboxMessage') - def HandleTrashInboxMessage(self, msgid): - """Trash inbox message by msgid (encoded in hex).""" - msgid = self._decode(msgid, "hex") + def HandleTrashInboxMessage(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + msgid = self._decode(params[0], "hex") helper_inbox.trash(msgid) return 'Trashed inbox message (assuming message existed).' - @command('trashSentMessage') - def HandleTrashSentMessage(self, msgid): - """Trash sent message by msgid (encoded in hex).""" - msgid = self._decode(msgid, "hex") + def HandleTrashSentMessage(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + msgid = self._decode(params[0], "hex") sqlExecute('''UPDATE sent SET folder='trash' WHERE msgid=?''', msgid) return 'Trashed sent message (assuming message existed).' - @command('sendMessage') - def HandleSendMessage( - self, toAddress, fromAddress, subject, message, - encodingType=2, TTL=4 * 24 * 60 * 60 - ): - """ - Send the message and return ackdata (hex encoded string). - subject and message must be encoded in base64 which may optionally - include line breaks. TTL is specified in seconds; values outside - the bounds of 3600 to 2419200 will be moved to be within those - bounds. TTL defaults to 4 days. - """ - # pylint: disable=too-many-locals - if encodingType not in (2, 3): + def HandleSendMessage(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + elif len(params) == 4: + toAddress, fromAddress, subject, message = params + encodingType = 2 + TTL = 4*24*60*60 + elif len(params) == 5: + toAddress, fromAddress, subject, message, encodingType = params + TTL = 4*24*60*60 + elif len(params) == 6: + toAddress, fromAddress, subject, message, encodingType, TTL = params + 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): raise APIError(27, 'Message is too long.') - if TTL < 60 * 60: - TTL = 60 * 60 - if TTL > 28 * 24 * 60 * 60: - TTL = 28 * 24 * 60 * 60 + if TTL < 60*60: + TTL = 60*60 + if TTL > 28*24*60*60: + TTL = 28*24*60*60 toAddress = addBMIfNotPresent(toAddress) fromAddress = addBMIfNotPresent(fromAddress) + status, addressVersionNumber, streamNumber, toRipe = self._verifyAddress(toAddress) self._verifyAddress(fromAddress) try: - fromAddressEnabled = self.config.getboolean(fromAddress, 'enabled') - except configparser.NoSectionError: - raise APIError( - 13, 'Could not find your fromAddress in the keys.dat file.') + fromAddressEnabled = BMConfigParser().getboolean( + fromAddress, 'enabled') + except: + raise APIError(13, 'Could not find your fromAddress in the keys.dat file.') if not fromAddressEnabled: raise APIError(14, 'Your fromAddress is disabled. Cannot send.') - ackdata = helper_sent.insert( - toAddress=toAddress, fromAddress=fromAddress, - subject=subject, message=message, encoding=encodingType, ttl=TTL) + stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') + ackdata = genAckPayload(streamNumber, stealthLevel) + + t = ('', + toAddress, + toRipe, + fromAddress, + subject, + message, + ackdata, + int(time.time()), # sentTime (this won't change) + int(time.time()), # lastActionTime + 0, + 'msgqueued', + 0, + 'sent', + 2, + TTL) + helper_sent.insert(t) toLabel = '' - queryreturn = sqlQuery( - "SELECT label FROM addressbook WHERE address=?", toAddress) - try: - toLabel = queryreturn[0][0] - except IndexError: - pass - + queryreturn = sqlQuery('''select label from addressbook where address=?''', toAddress) + if queryreturn != []: + for row in queryreturn: + toLabel, = row + # apiSignalQueue.put(('displayNewSentMessage',(toAddress,toLabel,fromAddress,subject,message,ackdata))) queues.UISignalQueue.put(('displayNewSentMessage', ( toAddress, toLabel, fromAddress, subject, message, ackdata))) + queues.workerQueue.put(('sendmessage', toAddress)) return hexlify(ackdata) - @command('sendBroadcast') - def HandleSendBroadcast( - self, fromAddress, subject, message, encodingType=2, - TTL=4 * 24 * 60 * 60): - """Send the broadcast message. Similiar to *sendMessage*.""" - - if encodingType not in (2, 3): + def HandleSendBroadcast(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + if len(params) == 3: + fromAddress, subject, message = params + encodingType = 2 + TTL = 4*24*60*60 + elif len(params) == 4: + fromAddress, subject, message, encodingType = params + TTL = 4*24*60*60 + elif len(params) == 5: + fromAddress, subject, message, encodingType, TTL = params + 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): raise APIError(27, 'Message is too long.') - if TTL < 60 * 60: - TTL = 60 * 60 - if TTL > 28 * 24 * 60 * 60: - TTL = 28 * 24 * 60 * 60 + if TTL < 60*60: + TTL = 60*60 + if TTL > 28*24*60*60: + TTL = 28*24*60*60 fromAddress = addBMIfNotPresent(fromAddress) self._verifyAddress(fromAddress) try: - fromAddressEnabled = self.config.getboolean(fromAddress, 'enabled') - except configparser.NoSectionError: - raise APIError( - 13, 'Could not find your fromAddress in the keys.dat file.') - if not fromAddressEnabled: - raise APIError(14, 'Your fromAddress is disabled. Cannot send.') + fromAddressEnabled = BMConfigParser().getboolean( + fromAddress, 'enabled') + except: + raise APIError(13, 'could not find your fromAddress in the keys.dat file.') + ackdata = genAckPayload(streamNumber, 0) + toAddress = '[Broadcast subscribers]' + ripe = '' - toAddress = str_broadcast_subscribers + t = ('', + toAddress, + ripe, + fromAddress, + subject, + message, + ackdata, + int(time.time()), # sentTime (this doesn't change) + int(time.time()), # lastActionTime + 0, + 'broadcastqueued', + 0, + 'sent', + 2, + TTL) + helper_sent.insert(t) - ackdata = helper_sent.insert( - fromAddress=fromAddress, subject=subject, - message=message, status='broadcastqueued', - encoding=encodingType) - - toLabel = str_broadcast_subscribers + toLabel = '[Broadcast subscribers]' queues.UISignalQueue.put(('displayNewSentMessage', ( toAddress, toLabel, fromAddress, subject, message, ackdata))) queues.workerQueue.put(('sendbroadcast', '')) return hexlify(ackdata) - @command('getStatus') - def HandleGetStatus(self, ackdata): - """ - Get the status of sent message by its ackdata (hex encoded). - Returns one of these strings: notfound, msgqueued, - broadcastqueued, broadcastsent, doingpubkeypow, awaitingpubkey, - doingmsgpow, forcepow, msgsent, msgsentnoackexpected or ackreceived. - """ - - if len(ackdata) < 76: - # The length of ackData should be at least 38 bytes (76 hex digits) - raise APIError(15, 'Invalid ackData object size.') + def HandleGetStatus(self, params): + if len(params) != 1: + raise APIError(0, 'I need one parameter!') + ackdata, = params + if len(ackdata) != 64: + raise APIError(15, 'The length of ackData should be 32 bytes (encoded in hex thus 64 characters).') ackdata = self._decode(ackdata, "hex") queryreturn = sqlQuery( - "SELECT status FROM sent where ackdata=?", ackdata) - try: - return queryreturn[0][0] - except IndexError: + '''SELECT status FROM sent where ackdata=?''', + ackdata) + if queryreturn == []: return 'notfound' + for row in queryreturn: + status, = row + return status - @command('addSubscription') - def HandleAddSubscription(self, address, label=''): - """Subscribe to the address. label must be base64 encoded.""" - - if label: + def HandleAddSubscription(self, params): + if len(params) == 0: + raise APIError(0, 'I need parameters!') + if len(params) == 1: + address, = params + label = '' + if len(params) == 2: + address, label = params label = self._decode(label, "base64") try: - label.decode('utf-8') - except UnicodeDecodeError: + unicode(label, 'utf-8') + except: raise APIError(17, 'Label is not valid UTF-8 data.') - self._verifyAddress(address) + if len(params) > 2: + raise APIError(0, 'I need either 1 or 2 parameters!') address = addBMIfNotPresent(address) + self._verifyAddress(address) # First we must check to see if the address is already in the # subscriptions list. - queryreturn = sqlQuery( - "SELECT * FROM subscriptions WHERE address=?", address) - if queryreturn: + queryreturn = sqlQuery('''select * from subscriptions where address=?''', address) + if queryreturn != []: raise APIError(16, 'You are already subscribed to that address.') - sqlExecute( - "INSERT INTO subscriptions VALUES (?,?,?)", label, address, True) + sqlExecute('''INSERT INTO subscriptions VALUES (?,?,?)''',label, address, True) shared.reloadBroadcastSendersForWhichImWatching() queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) queues.UISignalQueue.put(('rerenderSubscriptions', '')) return 'Added subscription.' - @command('deleteSubscription') - def HandleDeleteSubscription(self, address): - """ - Unsubscribe from the address. The program does not check whether - you were subscribed in the first place. - """ - + def HandleDeleteSubscription(self, params): + if len(params) != 1: + raise APIError(0, 'I need 1 parameter!') + address, = params address = addBMIfNotPresent(address) - sqlExecute("DELETE FROM subscriptions WHERE address=?", address) + sqlExecute('''DELETE FROM subscriptions WHERE address=?''', address) shared.reloadBroadcastSendersForWhichImWatching() queues.UISignalQueue.put(('rerenderMessagelistFromLabels', '')) queues.UISignalQueue.put(('rerenderSubscriptions', '')) return 'Deleted subscription if it existed.' - @command('listSubscriptions') - def ListSubscriptions(self): - """ - Returns dict with a list of all subscriptions - in the *subscriptions* key. - """ - - queryreturn = sqlQuery( - "SELECT label, address, enabled FROM subscriptions") - data = [] - for label, address, enabled in queryreturn: + def ListSubscriptions(self, params): + queryreturn = sqlQuery('''SELECT label, address, enabled FROM subscriptions''') + data = {'subscriptions': []} + for row in queryreturn: + label, address, enabled = row label = shared.fixPotentiallyInvalidUTF8Data(label) - data.append({ - 'label': base64.b64encode(label), - 'address': address, - 'enabled': enabled == 1 - }) - return {'subscriptions': data} + data['subscriptions'].append({'label':base64.b64encode(label), 'address': address, 'enabled': enabled == 1}) + return json.dumps(data, indent=4, separators=(',',': ')) - @command('disseminatePreEncryptedMsg') - def HandleDisseminatePreEncryptedMsg( - self, encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte, - requiredPayloadLengthExtraBytes): - """Handle a request to disseminate an encrypted message""" - - # The device issuing this command to PyBitmessage supplies a msg - # object that has already been encrypted but which still needs the POW - # to be done. PyBitmessage accepts this msg object and sends it out - # to the rest of the Bitmessage network as if it had generated - # the message itself. Please do not yet add this to the api doc. - encryptedPayload = b'\x00' * 8 + self._decode(encryptedPayload, "hex") - # compatibility stub ^, since disseminatePreEncryptedMsg - # still expects the encryptedPayload without a nonce - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(encryptedPayload) - encryptedPayload = encryptedPayload[8:] - TTL = expiresTime - time.time() + 300 # a bit of extra padding + def HandleDisseminatePreEncryptedMsg(self, params): + # The device issuing this command to PyBitmessage supplies a msg object that has + # already been encrypted but which still needs the POW to be done. PyBitmessage + # accepts this msg object and sends it out to the rest of the Bitmessage network + # as if it had generated the message itself. Please do not yet add this to the + # api doc. + if len(params) != 3: + raise APIError(0, 'I need 3 parameter!') + encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte, requiredPayloadLengthExtraBytes = params + encryptedPayload = self._decode(encryptedPayload, "hex") # Let us do the POW and attach it to the front - target = 2**64 / ( - requiredAverageProofOfWorkNonceTrialsPerByte * ( - len(encryptedPayload) + 8 - + requiredPayloadLengthExtraBytes + (( - TTL * ( - len(encryptedPayload) + 8 - + requiredPayloadLengthExtraBytes - )) / (2 ** 16)) - )) - logger.debug("expiresTime: %s", expiresTime) - logger.debug("TTL: %s", TTL) - logger.debug("objectType: %s", objectType) - logger.info( - '(For msg message via API) Doing proof of work. Total required' - ' difficulty: %s\nRequired small message difficulty: %s', - float(requiredAverageProofOfWorkNonceTrialsPerByte) - / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, - float(requiredPayloadLengthExtraBytes) - / defaults.networkDefaultPayloadLengthExtraBytes, - ) + target = 2**64 / ((len(encryptedPayload)+requiredPayloadLengthExtraBytes+8) * requiredAverageProofOfWorkNonceTrialsPerByte) + with shared.printLock: + print '(For msg message via API) Doing proof of work. Total required difficulty:', float(requiredAverageProofOfWorkNonceTrialsPerByte) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, 'Required small message difficulty:', float(requiredPayloadLengthExtraBytes) / defaults.networkDefaultPayloadLengthExtraBytes powStartTime = time.time() initialHash = hashlib.sha512(encryptedPayload).digest() trialValue, nonce = proofofwork.run(target, initialHash) - logger.info( - '(For msg message via API) Found proof of work %s\nNonce: %s\n' - 'POW took %s seconds. %s nonce trials per second.', - trialValue, nonce, int(time.time() - powStartTime), - nonce / (time.time() - powStartTime) - ) + with shared.printLock: + print '(For msg message via API) Found proof of work', trialValue, 'Nonce:', nonce + try: + print 'POW took', int(time.time() - powStartTime), 'seconds.', nonce / (time.time() - powStartTime), 'nonce trials per second.' + except: + pass encryptedPayload = pack('>Q', nonce) + encryptedPayload + toStreamNumber = decodeVarint(encryptedPayload[16:26])[0] inventoryHash = calculateInventoryHash(encryptedPayload) + objectType = 2 + TTL = 2.5 * 24 * 60 * 60 Inventory()[inventoryHash] = ( - objectType, toStreamNumber, encryptedPayload, - expiresTime, b'' - ) - logger.info( - 'Broadcasting inv for msg(API disseminatePreEncryptedMsg' - ' command): %s', hexlify(inventoryHash)) + objectType, toStreamNumber, encryptedPayload, int(time.time()) + TTL,'') + with shared.printLock: + print 'Broadcasting inv for msg(API disseminatePreEncryptedMsg command):', hexlify(inventoryHash) queues.invQueue.put((toStreamNumber, inventoryHash)) - return hexlify(inventoryHash).decode() - @command('trashSentMessageByAckData') - def HandleTrashSentMessageByAckDAta(self, ackdata): - """Trash a sent message by ackdata (hex encoded)""" + def HandleTrashSentMessageByAckDAta(self, params): # This API method should only be used when msgid is not available - ackdata = self._decode(ackdata, "hex") - sqlExecute("UPDATE sent SET folder='trash' WHERE ackdata=?", ackdata) + if len(params) == 0: + raise APIError(0, 'I need parameters!') + ackdata = self._decode(params[0], "hex") + sqlExecute('''UPDATE sent SET folder='trash' WHERE ackdata=?''', ackdata) return 'Trashed sent message (assuming message existed).' - @command('disseminatePubkey') - def HandleDissimatePubKey(self, payload): - """Handle a request to disseminate a public key""" - - # The device issuing this command to PyBitmessage supplies a pubkey - # object to be disseminated to the rest of the Bitmessage network. - # PyBitmessage accepts this pubkey object and sends it out to the rest - # of the Bitmessage network as if it had generated the pubkey object - # itself. Please do not yet add this to the api doc. + def HandleDissimatePubKey(self, params): + # The device issuing this command to PyBitmessage supplies a pubkey object to be + # disseminated to the rest of the Bitmessage network. PyBitmessage accepts this + # pubkey object and sends it out to the rest of the Bitmessage network as if it + # had generated the pubkey object itself. Please do not yet add this to the api + # doc. + if len(params) != 1: + raise APIError(0, 'I need 1 parameter!') + payload, = params payload = self._decode(payload, "hex") # Let us do the POW - target = 2 ** 64 / (( - len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + 8 - ) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) - logger.info('(For pubkey message via API) Doing proof of work...') + target = 2 ** 64 / ((len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + + 8) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) + print '(For pubkey message via API) Doing proof of work...' initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) - logger.info( - '(For pubkey message via API) Found proof of work %s Nonce: %s', - trialValue, nonce - ) + print '(For pubkey message via API) Found proof of work', trialValue, 'Nonce:', nonce payload = pack('>Q', nonce) + payload - pubkeyReadPosition = 8 # bypass the nonce - if payload[pubkeyReadPosition:pubkeyReadPosition + 4] == \ - '\x00\x00\x00\x00': # if this pubkey uses 8 byte time + pubkeyReadPosition = 8 # bypass the nonce + if payload[pubkeyReadPosition:pubkeyReadPosition+4] == '\x00\x00\x00\x00': # if this pubkey uses 8 byte time pubkeyReadPosition += 8 else: pubkeyReadPosition += 4 - addressVersionLength = decodeVarint( - payload[pubkeyReadPosition:pubkeyReadPosition + 10])[1] + addressVersion, addressVersionLength = decodeVarint(payload[pubkeyReadPosition:pubkeyReadPosition+10]) pubkeyReadPosition += addressVersionLength - pubkeyStreamNumber = decodeVarint( - payload[pubkeyReadPosition:pubkeyReadPosition + 10])[0] + pubkeyStreamNumber = decodeVarint(payload[pubkeyReadPosition:pubkeyReadPosition+10])[0] inventoryHash = calculateInventoryHash(payload) - objectType = 1 # .. todo::: support v4 pubkeys + objectType = 1 + #todo: support v4 pubkeys TTL = 28 * 24 * 60 * 60 Inventory()[inventoryHash] = ( - objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, '' - ) - logger.info( - 'broadcasting inv within API command disseminatePubkey with' - ' hash: %s', hexlify(inventoryHash)) + objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL,'') + with shared.printLock: + print 'broadcasting inv within API command disseminatePubkey with hash:', hexlify(inventoryHash) queues.invQueue.put((pubkeyStreamNumber, inventoryHash)) - @command( - 'getMessageDataByDestinationHash', 'getMessageDataByDestinationTag') - def HandleGetMessageDataByDestinationHash(self, requestedHash): - """Handle a request to get message data by destination hash""" - + def HandleGetMessageDataByDestinationHash(self, params): # Method will eventually be used by a particular Android app to # select relevant messages. Do not yet add this to the api # doc. + if len(params) != 1: + raise APIError(0, 'I need 1 parameter!') + requestedHash, = params if len(requestedHash) != 32: - raise APIError( - 19, 'The length of hash should be 32 bytes (encoded in hex' - ' thus 64 characters).') + raise APIError(19, 'The length of hash should be 32 bytes (encoded in hex thus 64 characters).') requestedHash = self._decode(requestedHash, "hex") # This is not a particularly commonly used API function. Before we # use it we'll need to fill out a field in our inventory database # which is blank by default (first20bytesofencryptedmessage). queryreturn = sqlQuery( - "SELECT hash, payload FROM inventory WHERE tag = ''" - " and objecttype = 2") + '''SELECT hash, payload FROM inventory WHERE tag = '' and objecttype = 2 ; ''') with SqlBulkExecute() as sql: - for hash01, payload in queryreturn: - readPosition = 16 # Nonce length + time length - # Stream Number length - readPosition += decodeVarint( - payload[readPosition:readPosition + 10])[1] - t = (payload[readPosition:readPosition + 32], hash01) - sql.execute("UPDATE inventory SET tag=? WHERE hash=?", *t) + for row in queryreturn: + hash01, payload = row + readPosition = 16 # Nonce length + time length + readPosition += decodeVarint(payload[readPosition:readPosition+10])[1] # Stream Number length + t = (payload[readPosition:readPosition+32],hash01) + sql.execute('''UPDATE inventory SET tag=? WHERE hash=?; ''', *t) - queryreturn = sqlQuery( - "SELECT payload FROM inventory WHERE tag = ?", requestedHash) - return {"receivedMessageDatas": [ - {'data': hexlify(payload)} for payload, in queryreturn - ]} + queryreturn = sqlQuery('''SELECT payload FROM inventory WHERE tag = ?''', + requestedHash) + data = '{"receivedMessageDatas":[' + for row in queryreturn: + payload, = row + if len(data) > 25: + data += ',' + data += json.dumps({'data':hexlify(payload)}, indent=4, separators=(',', ': ')) + data += ']}' + return data - @command('clientStatus') - def HandleClientStatus(self): - """ - Returns the bitmessage status as dict with keys *networkConnections*, - *numberOfMessagesProcessed*, *numberOfBroadcastsProcessed*, - *numberOfPubkeysProcessed*, *pendingDownload*, *networkStatus*, - *softwareName*, *softwareVersion*. *networkStatus* will be one of - these strings: "notConnected", - "connectedButHaveNotReceivedIncomingConnections", - or "connectedAndReceivingIncomingConnections". - """ - - connections_num = len(stats.connectedHostsList()) - - if connections_num == 0: + def HandleClientStatus(self, params): + if len(network.stats.connectedHostsList()) == 0: networkStatus = 'notConnected' - elif state.clientHasReceivedIncomingConnections: - networkStatus = 'connectedAndReceivingIncomingConnections' - else: + elif len(network.stats.connectedHostsList()) > 0 and not shared.clientHasReceivedIncomingConnections: networkStatus = 'connectedButHaveNotReceivedIncomingConnections' - return { - 'networkConnections': connections_num, - 'numberOfMessagesProcessed': state.numberOfMessagesProcessed, - 'numberOfBroadcastsProcessed': state.numberOfBroadcastsProcessed, - 'numberOfPubkeysProcessed': state.numberOfPubkeysProcessed, - 'pendingDownload': stats.pendingDownload(), - 'networkStatus': networkStatus, - 'softwareName': 'PyBitmessage', - 'softwareVersion': softwareVersion - } + else: + networkStatus = 'connectedAndReceivingIncomingConnections' + 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=(',', ': ')) - @command('listConnections') - def HandleListConnections(self): - """ - Returns bitmessage connection information as dict with keys *inbound*, - *outbound*. - """ - if BMConnectionPool is None: - raise APIError(21, 'Could not import BMConnectionPool.') - inboundConnections = [] - outboundConnections = [] - for i in BMConnectionPool().inboundConnections.values(): - inboundConnections.append({ - 'host': i.destination.host, - 'port': i.destination.port, - 'fullyEstablished': i.fullyEstablished, - 'userAgent': str(i.userAgent) - }) - for i in BMConnectionPool().outboundConnections.values(): - outboundConnections.append({ - 'host': i.destination.host, - 'port': i.destination.port, - 'fullyEstablished': i.fullyEstablished, - 'userAgent': str(i.userAgent) - }) - return { - 'inbound': inboundConnections, - 'outbound': outboundConnections - } + def HandleDecodeAddress(self, params): + # Return a meaningful decoding of an address. + if len(params) != 1: + raise APIError(0, 'I need 1 parameter!') + address, = params + status, addressVersion, streamNumber, ripe = decodeAddress(address) + return json.dumps({'status':status, 'addressVersion':addressVersion, + 'streamNumber':streamNumber, 'ripe':base64.b64encode(ripe)}, indent=4, + separators=(',', ': ')) - @command('helloWorld') - def HandleHelloWorld(self, a, b): - """Test two string params""" + def HandleHelloWorld(self, params): + (a, b) = params return a + '-' + b - @command('add') - def HandleAdd(self, a, b): - """Test two numeric params""" + def HandleAdd(self, params): + (a, b) = params return a + b - @command('statusBar') - def HandleStatusBar(self, message): - """Update GUI statusbar message""" + def HandleStatusBar(self, params): + message, = params queues.UISignalQueue.put(('updateStatusBar', message)) - return "success" - @testmode('undeleteMessage') - def HandleUndeleteMessage(self, msgid): - """Undelete message""" - msgid = self._decode(msgid, "hex") - helper_inbox.undeleteMessage(msgid) - return "Undeleted message" + def HandleDeleteAndVacuum(self, params): + if not params: + sqlStoredProcedure('deleteandvacuume') + return 'done' - @command('deleteAndVacuum') - def HandleDeleteAndVacuum(self): - """Cleanup trashes and vacuum messages database""" - sqlStoredProcedure('deleteandvacuume') - return 'done' + def HandleShutdown(self, params): + if not params: + shutdown.doCleanShutdown() + return 'done' - @command('shutdown') - def HandleShutdown(self): - """Shutdown the bitmessage. Returns 'done'.""" - # backward compatible trick because False == 0 is True - state.shutdown = False - return 'done' + handlers = {} + handlers['helloWorld'] = HandleHelloWorld + handlers['add'] = HandleAdd + handlers['statusBar'] = HandleStatusBar + handlers['listAddresses'] = HandleListAddresses + handlers['listAddressBookEntries'] = HandleListAddressBookEntries; + handlers['listAddressbook'] = HandleListAddressBookEntries # the listAddressbook alias should be removed eventually. + handlers['addAddressBookEntry'] = HandleAddAddressBookEntry + handlers['addAddressbook'] = HandleAddAddressBookEntry # the addAddressbook alias should be deleted eventually. + handlers['deleteAddressBookEntry'] = HandleDeleteAddressBookEntry + handlers['deleteAddressbook'] = HandleDeleteAddressBookEntry # The deleteAddressbook alias should be deleted eventually. + handlers['createRandomAddress'] = HandleCreateRandomAddress + handlers['createDeterministicAddresses'] = HandleCreateDeterministicAddresses + handlers['getDeterministicAddress'] = HandleGetDeterministicAddress + handlers['createChan'] = HandleCreateChan + handlers['joinChan'] = HandleJoinChan + handlers['leaveChan'] = HandleLeaveChan + handlers['deleteAddress'] = HandleDeleteAddress + handlers['getAllInboxMessages'] = HandleGetAllInboxMessages + handlers['getAllInboxMessageIds'] = HandleGetAllInboxMessageIds + handlers['getAllInboxMessageIDs'] = HandleGetAllInboxMessageIds + handlers['getInboxMessageById'] = HandleGetInboxMessageById + handlers['getInboxMessageByID'] = HandleGetInboxMessageById + handlers['getAllSentMessages'] = HandleGetAllSentMessages + handlers['getAllSentMessageIds'] = HandleGetAllSentMessageIds + handlers['getAllSentMessageIDs'] = HandleGetAllSentMessageIds + handlers['getInboxMessagesByReceiver'] = HandleInboxMessagesByReceiver + handlers['getInboxMessagesByAddress'] = HandleInboxMessagesByReceiver #after some time getInboxMessagesByAddress should be removed + handlers['getSentMessageById'] = HandleGetSentMessageById + handlers['getSentMessageByID'] = HandleGetSentMessageById + handlers['getSentMessagesByAddress'] = HandleGetSentMessagesByAddress + handlers['getSentMessagesBySender'] = HandleGetSentMessagesByAddress + handlers['getSentMessageByAckData'] = HandleGetSentMessagesByAckData + handlers['trashMessage'] = HandleTrashMessage + handlers['trashInboxMessage'] = HandleTrashInboxMessage + handlers['trashSentMessage'] = HandleTrashSentMessage + handlers['trashSentMessageByAckData'] = HandleTrashSentMessageByAckDAta + handlers['sendMessage'] = HandleSendMessage + handlers['sendBroadcast'] = HandleSendBroadcast + handlers['getStatus'] = HandleGetStatus + handlers['addSubscription'] = HandleAddSubscription + handlers['deleteSubscription'] = HandleDeleteSubscription + handlers['listSubscriptions'] = ListSubscriptions + handlers['disseminatePreEncryptedMsg'] = HandleDisseminatePreEncryptedMsg + handlers['disseminatePubkey'] = HandleDissimatePubKey + handlers['getMessageDataByDestinationHash'] = HandleGetMessageDataByDestinationHash + handlers['getMessageDataByDestinationTag'] = HandleGetMessageDataByDestinationHash + handlers['clientStatus'] = HandleClientStatus + handlers['decodeAddress'] = HandleDecodeAddress + handlers['deleteAndVacuum'] = HandleDeleteAndVacuum + handlers['shutdown'] = HandleShutdown def _handle_request(self, method, params): - try: - # pylint: disable=attribute-defined-outside-init - self._method = method - func = self._handlers[method] - return func(self, *params) - except KeyError: + if (self.handlers.has_key(method)): + return self.handlers[method](self, params) + else: raise APIError(20, 'Invalid method: %s' % method) - except TypeError as e: - msg = 'Unexpected API Failure - %s' % e - if 'argument' not in str(e): - raise APIError(21, msg) - argcount = len(params) - maxcount = func.func_code.co_argcount - if argcount > maxcount: - msg = ( - 'Command %s takes at most %s parameters (%s given)' - % (method, maxcount, argcount)) - else: - mincount = maxcount - len(func.func_defaults or []) - if argcount < mincount: - msg = ( - 'Command %s takes at least %s parameters (%s given)' - % (method, mincount, argcount)) - raise APIError(0, msg) - finally: - state.last_api_response = time.time() def _dispatch(self, method, params): - _fault = None + self.cookies = [] + + validuser = self.APIAuthenticateClient() + if not validuser: + time.sleep(2) + return "RPC Username or password incorrect or HTTP header lacks authentication at all." try: return self._handle_request(method, params) except APIError as e: - _fault = e + return str(e) except varintDecodeError as e: logger.error(e) - _fault = APIError( - 26, 'Data contains a malformed varint. Some details: %s' % e) + return "API Error 0026: Data contains a malformed varint. Some details: %s" % e except Exception as e: logger.exception(e) - _fault = APIError(21, 'Unexpected API Failure - %s' % e) + return "API Error 0021: Unexpected API Failure - %s" % str(e) - if _fault: - if self.config.safeGet( - 'bitmessagesettings', 'apivariant') == 'legacy': - return str(_fault) - else: - raise _fault # pylint: disable=raising-bad-type - def _listMethods(self): - """List all API commands""" - return self._handlers.keys() - - def _methodHelp(self, method): - return self._handlers[method].__doc__ diff --git a/src/api_client.py b/src/api_client.py new file mode 100644 index 00000000..6dc0c7b0 --- /dev/null +++ b/src/api_client.py @@ -0,0 +1,70 @@ +# This is an example of how to connect to and use the Bitmessage API. +# See https://bitmessage.org/wiki/API_Reference + +import xmlrpclib +import json +import time + +api = xmlrpclib.ServerProxy("http://bradley:password@localhost:8442/") + +print 'Let\'s test the API first.' +inputstr1 = "hello" +inputstr2 = "world" +print api.helloWorld(inputstr1, inputstr2) +print api.add(2,3) + +print 'Let\'s set the status bar message.' +print api.statusBar("new status bar message") + +print 'Let\'s list our addresses:' +print api.listAddresses() + +print 'Let\'s list our address again, but this time let\'s parse the json data into a Python data structure:' +jsonAddresses = json.loads(api.listAddresses()) +print jsonAddresses +print 'Now that we have our address data in a nice Python data structure, let\'s look at the first address (index 0) and print its label:' +print jsonAddresses['addresses'][0]['label'] + +print 'Uncomment the next two lines to create a new random address with a slightly higher difficulty setting than normal.' +#addressLabel = 'new address label'.encode('base64') +#print api.createRandomAddress(addressLabel,False,1.05,1.1111) + +print 'Uncomment these next four lines to create new deterministic addresses.' +#passphrase = 'asdfasdfqwser'.encode('base64') +#jsonDeterministicAddresses = api.createDeterministicAddresses(passphrase, 2, 4, 1, False) +#print jsonDeterministicAddresses +#print json.loads(jsonDeterministicAddresses) + +#print 'Uncomment this next line to print the first deterministic address that would be generated with the given passphrase. This will Not add it to the Bitmessage interface or the keys.dat file.' +#print api.getDeterministicAddress('asdfasdfqwser'.encode('base64'),4,1) + +#print 'Uncomment this line to subscribe to an address. (You must use your own address, this one is invalid).' +#print api.addSubscription('2D94G5d8yp237GGqAheoecBYpdehdT3dha','test sub'.encode('base64')) + +#print 'Uncomment this line to unsubscribe from an address.' +#print api.deleteSubscription('2D94G5d8yp237GGqAheoecBYpdehdT3dha') + +print 'Let\'s now print all of our inbox messages:' +print api.getAllInboxMessages() +inboxMessages = json.loads(api.getAllInboxMessages()) +print inboxMessages + +print 'Uncomment this next line to decode the actual message data in the first message:' +#print inboxMessages['inboxMessages'][0]['message'].decode('base64') + +print 'Uncomment this next line in the code to delete a message' +#print api.trashMessage('584e5826947242a82cb883c8b39ac4a14959f14c228c0fbe6399f73e2cba5b59') + +print 'Uncomment these lines to send a message. The example addresses are invalid; you will have to put your own in.' +#subject = 'subject!'.encode('base64') +#message = 'Hello, this is the message'.encode('base64') +#ackData = api.sendMessage('BM-Gtsm7PUabZecs3qTeXbNPmqx3xtHCSXF', 'BM-2DCutnUZG16WiW3mdAm66jJUSCUv88xLgS', subject,message) +#print 'The ackData is:', ackData +#while True: +# time.sleep(2) +# print 'Current status:', api.getStatus(ackData) + +print 'Uncomment these lines to send a broadcast. The example address is invalid; you will have to put your own in.' +#subject = 'subject within broadcast'.encode('base64') +#message = 'Hello, this is the message within a broadcast.'.encode('base64') +#print api.sendBroadcast('BM-onf6V1RELPgeNN6xw9yhpAiNiRexSRD4e', subject,message) diff --git a/src/backend/address_generator.py b/src/backend/address_generator.py deleted file mode 100644 index 312c313b..00000000 --- a/src/backend/address_generator.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Common methods and functions for kivy and qt. -""" - -from pybitmessage import queues -from pybitmessage.bmconfigparser import config -from pybitmessage.defaults import ( - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes -) - - -class AddressGenerator(object): - """"Base class for address generation and validation""" - def __init__(self): - pass - - @staticmethod - def random_address_generation( - label, streamNumberForAddress=1, eighteenByteRipe=False, - nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, - payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes - ): - """Start address generation and return whether validation was successful""" - - labels = [config.get(obj, 'label') - for obj in config.addresses()] - if label and label not in labels: - queues.addressGeneratorQueue.put(( - 'createRandomAddress', 4, streamNumberForAddress, label, 1, - "", eighteenByteRipe, nonceTrialsPerByte, - payloadLengthExtraBytes)) - return True - return False - - @staticmethod - def address_validation(instance, label): - """Checking address validation while creating""" - labels = [config.get(obj, 'label') for obj in config.addresses()] - if label in labels: - instance.error = True - instance.helper_text = 'it is already exist you'\ - ' can try this Ex. ( {0}_1, {0}_2 )'.format( - label) - elif label: - instance.error = False - else: - instance.error = True - instance.helper_text = 'This field is required' diff --git a/src/bitmessagecli.py b/src/bitmessagecli.py index 84c618af..d7f4e5a9 100644 --- a/src/bitmessagecli.py +++ b/src/bitmessagecli.py @@ -1,634 +1,611 @@ -#!/usr/bin/python2.7 +#!/usr/bin/python2.7 # -*- coding: utf-8 -*- -# pylint: disable=too-many-lines,global-statement,too-many-branches,too-many-statements,inconsistent-return-statements -# pylint: disable=too-many-nested-blocks,too-many-locals,protected-access,too-many-arguments,too-many-function-args -# pylint: disable=no-member -""" -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. +# 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.6.2, by .dok (Version 0.3.1) , modified +# This is an example of a daemon client for PyBitmessage 0.6.2, by .dok (Version 0.3.1) , modified -TODO: fix the following (currently ignored) violations: -""" - -import datetime -import imghdr -import json -import ntpath -import os -import socket -import sys -import time import xmlrpclib +import datetime +#import hashlib +#import getopt +import imghdr +import ntpath +import json +import socket +import time +import sys +import os -from bmconfigparser import config - +from bmconfigparser import BMConfigParser api = '' keysName = 'keys.dat' keysPath = 'keys.dat' -usrPrompt = 0 # 0 = First Start, 1 = prompt, 2 = no prompt if the program is starting up +usrPrompt = 0 #0 = First Start, 1 = prompt, 2 = no prompt if the program is starting up knownAddresses = dict() - -def userInput(message): - """Checks input for exit or quit. Also formats for input, etc""" - +def userInput(message): #Checks input for exit or quit. Also formats for input, etc global usrPrompt - - print('\n' + message) + print '\n' + message uInput = raw_input('> ') - if uInput.lower() == 'exit': # Returns the user to the main menu + if (uInput.lower() == 'exit'): #Returns the user to the main menu usrPrompt = 1 main() - - elif uInput.lower() == 'quit': # Quits the program - print('\n Bye\n') - sys.exit(0) - + + elif (uInput.lower() == 'quit'): #Quits the program + print '\n Bye\n' + sys.exit() + os._exit() # _ else: return uInput + +def restartBmNotify(): #Prompts the user to restart Bitmessage. + print '\n *******************************************************************' + print ' WARNING: If Bitmessage is running locally, you must restart it now.' + print ' *******************************************************************\n' - -def restartBmNotify(): - """Prompt the user to restart Bitmessage""" - print('\n *******************************************************************') - print(' WARNING: If Bitmessage is running locally, you must restart it now.') - print(' *******************************************************************\n') - - -# Begin keys.dat interactions - - -def lookupAppdataFolder(): - """gets the appropriate folders for the .dat files depending on the OS. Taken from bitmessagemain.py""" - +#Begin keys.dat interactions +def lookupAppdataFolder(): #gets the appropriate folders for the .dat files depending on the OS. Taken from bitmessagemain.py APPNAME = "PyBitmessage" + from os import path, environ if sys.platform == 'darwin': - if "HOME" in os.environ: - dataFolder = os.path.join(os.environ["HOME"], "Library/Application support/", APPNAME) + '/' + if "HOME" in environ: + 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.') - sys.exit(1) + print ' Could not find home folder, please report this message and your OS X version to the Daemon Github.' + os._exit() elif 'win32' in sys.platform or 'win64' in sys.platform: - dataFolder = os.path.join(os.environ['APPDATA'], APPNAME) + '\\' + dataFolder = path.join(environ['APPDATA'], APPNAME) + '\\' else: - dataFolder = os.path.expanduser(os.path.join("~", ".config/" + APPNAME + "/")) + dataFolder = path.expanduser(path.join("~", ".config/" + APPNAME + "/")) return dataFolder - def configInit(): - """Initialised the configuration""" - - config.add_section('bitmessagesettings') - # Sets the bitmessage port to stop the warning about the api not properly - # being setup. This is in the event that the keys.dat is in a different - # directory or is created locally to connect to a machine remotely. - config.set('bitmessagesettings', 'port', '8444') - config.set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat - + BMConfigParser().add_section('bitmessagesettings') + BMConfigParser().set('bitmessagesettings', 'port', '8444') #Sets the bitmessage port to stop the warning about the api not properly being setup. This is in the event that the keys.dat is in a different directory or is created locally to connect to a machine remotely. + BMConfigParser().set('bitmessagesettings','apienabled','true') #Sets apienabled to true in keys.dat + with open(keysName, 'wb') as configfile: - config.write(configfile) - - print('\n ' + str(keysName) + ' Initalized in the same directory as daemon.py') - print(' You will now need to configure the ' + str(keysName) + ' file.\n') + BMConfigParser().write(configfile) + print '\n ' + str(keysName) + ' Initalized in the same directory as daemon.py' + print ' You will now need to configure the ' + str(keysName) + ' file.\n' def apiInit(apiEnabled): - """Initialise the API""" - global usrPrompt - config.read(keysPath) + BMConfigParser().read(keysPath) + - if apiEnabled is False: # API information there but the api is disabled. + + if (apiEnabled == False): #API information there but the api is disabled. uInput = userInput("The API is not enabled. Would you like to do that now, (Y)es or (N)o?").lower() - if uInput == "y": - config.set('bitmessagesettings', 'apienabled', 'true') # Sets apienabled to true in keys.dat + if uInput == "y": # + BMConfigParser().set('bitmessagesettings','apienabled','true') #Sets apienabled to true in keys.dat with open(keysPath, 'wb') as configfile: - config.write(configfile) - - print('Done') + BMConfigParser().write(configfile) + + print 'Done' restartBmNotify() return True - + elif uInput == "n": - print(' \n************************************************************') - print(' Daemon will not work when the API is disabled. ') - print(' Please refer to the Bitmessage Wiki on how to setup the API.') - print(' ************************************************************\n') + print ' \n************************************************************' + print ' Daemon will not work when the API is disabled. ' + print ' Please refer to the Bitmessage Wiki on how to setup the API.' + print ' ************************************************************\n' usrPrompt = 1 main() - + else: - print('\n Invalid Entry\n') + print '\n Invalid Entry\n' usrPrompt = 1 main() - - elif apiEnabled: # API correctly setup - # Everything is as it should be + elif (apiEnabled == True): #API correctly setup + #Everything is as it should be return True - - else: # API information was not present. - print('\n ' + str(keysPath) + ' not properly configured!\n') + + else: #API information was not present. + print '\n ' + str(keysPath) + ' not properly configured!\n' uInput = userInput("Would you like to do this now, (Y)es or (N)o?").lower() - if uInput == "y": # User said yes, initalize the api by writing these values to the keys.dat file - print(' ') - + if uInput == "y": #User said yes, initalize the api by writing these values to the keys.dat file + print ' ' + apiUsr = userInput("API Username") apiPwd = userInput("API Password") + #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() if (daemon != 'true' and daemon != 'false'): - print('\n Invalid Entry for Daemon.\n') + print '\n Invalid Entry for Daemon.\n' uInput = 1 main() - - print(' -----------------------------------\n') - - # sets the bitmessage port to stop the warning about the api not properly - # being setup. This is in the event that the keys.dat is in a different - # directory or is created locally to connect to a machine remotely. - config.set('bitmessagesettings', 'port', '8444') - config.set('bitmessagesettings', 'apienabled', 'true') - config.set('bitmessagesettings', 'apiport', apiPort) - config.set('bitmessagesettings', 'apiinterface', '127.0.0.1') - config.set('bitmessagesettings', 'apiusername', apiUsr) - config.set('bitmessagesettings', 'apipassword', apiPwd) - config.set('bitmessagesettings', 'daemon', daemon) + + print ' -----------------------------------\n' + + BMConfigParser().set('bitmessagesettings', 'port', '8444') #sets the bitmessage port to stop the warning about the api not properly being setup. This is in the event that the keys.dat is in a different directory or is created locally to connect to a machine remotely. + BMConfigParser().set('bitmessagesettings','apienabled','true') + BMConfigParser().set('bitmessagesettings', 'apiport', apiPort) + BMConfigParser().set('bitmessagesettings', 'apiinterface', '127.0.0.1') + BMConfigParser().set('bitmessagesettings', 'apiusername', apiUsr) + BMConfigParser().set('bitmessagesettings', 'apipassword', apiPwd) + BMConfigParser().set('bitmessagesettings', 'daemon', daemon) with open(keysPath, 'wb') as configfile: - config.write(configfile) - - print('\n Finished configuring the keys.dat file with API information.\n') + BMConfigParser().write(configfile) + + print '\n Finished configuring the keys.dat file with API information.\n' restartBmNotify() return True - + elif uInput == "n": - print('\n ***********************************************************') - print(' Please refer to the Bitmessage Wiki on how to setup the API.') - print(' ***********************************************************\n') + print '\n ***********************************************************' + print ' Please refer to the Bitmessage Wiki on how to setup the API.' + print ' ***********************************************************\n' usrPrompt = 1 main() else: - print(' \nInvalid entry\n') + print ' \nInvalid entry\n' usrPrompt = 1 main() def apiData(): - """TBC""" - global keysName global keysPath global usrPrompt - - config.read(keysPath) # First try to load the config file (the keys.dat file) from the program directory + + BMConfigParser().read(keysPath) #First try to load the config file (the keys.dat file) from the program directory try: - config.get('bitmessagesettings', 'port') + BMConfigParser().get('bitmessagesettings','port') appDataFolder = '' - except: # noqa:E722 - # Could not load the keys.dat file in the program directory. Perhaps it is in the appdata directory. + except: + #Could not load the keys.dat file in the program directory. Perhaps it is in the appdata directory. appDataFolder = lookupAppdataFolder() keysPath = appDataFolder + keysPath - config.read(keysPath) + BMConfigParser().read(keysPath) try: - config.get('bitmessagesettings', 'port') - except: # noqa:E722 - # keys.dat was not there either, something is wrong. - print('\n ******************************************************************') - print(' There was a problem trying to access the Bitmessage keys.dat file') - print(' or keys.dat is not set up correctly') - print(' Make sure that daemon is in the same directory as Bitmessage. ') - print(' ******************************************************************\n') + BMConfigParser().get('bitmessagesettings','port') + except: + #keys.dat was not there either, something is wrong. + print '\n ******************************************************************' + print ' There was a problem trying to access the Bitmessage keys.dat file' + print ' or keys.dat is not set up correctly' + print ' Make sure that daemon is in the same directory as Bitmessage. ' + print ' ******************************************************************\n' uInput = userInput("Would you like to create a keys.dat in the local directory, (Y)es or (N)o?").lower() - - if uInput in ("y", "yes"): + + if (uInput == "y" or uInput == "yes"): configInit() keysPath = keysName usrPrompt = 0 main() - elif uInput in ("n", "no"): - print('\n Trying Again.\n') + elif (uInput == "n" or uInput == "no"): + print '\n Trying Again.\n' usrPrompt = 0 main() else: - print('\n Invalid Input.\n') + print '\n Invalid Input.\n' usrPrompt = 1 main() - try: # checks to make sure that everyting is configured correctly. Excluding apiEnabled, it is checked after - config.get('bitmessagesettings', 'apiport') - config.get('bitmessagesettings', 'apiinterface') - config.get('bitmessagesettings', 'apiusername') - config.get('bitmessagesettings', 'apipassword') + try: #checks to make sure that everyting is configured correctly. Excluding apiEnabled, it is checked after + BMConfigParser().get('bitmessagesettings', 'apiport') + BMConfigParser().get('bitmessagesettings', 'apiinterface') + BMConfigParser().get('bitmessagesettings', 'apiusername') + BMConfigParser().get('bitmessagesettings', 'apipassword') + except: + apiInit("") #Initalize the keys.dat file with API information - except: # noqa:E722 - 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 - # keys.dat file was found or appropriately configured, allow information retrieval - # apiEnabled = - # apiInit(config.safeGetBoolean('bitmessagesettings','apienabled')) - # #if false it will prompt the user, if true it will return true - - config.read(keysPath) # read again since changes have been made - apiPort = int(config.get('bitmessagesettings', 'apiport')) - apiInterface = config.get('bitmessagesettings', 'apiinterface') - apiUsername = config.get('bitmessagesettings', 'apiusername') - apiPassword = config.get('bitmessagesettings', 'apipassword') - - print('\n API data successfully imported.\n') - - # Build the api credentials - return "http://" + apiUsername + ":" + apiPassword + "@" + apiInterface + ":" + str(apiPort) + "/" + BMConfigParser().read(keysPath)#read again since changes have been made + apiPort = int(BMConfigParser().get('bitmessagesettings', 'apiport')) + apiInterface = BMConfigParser().get('bitmessagesettings', 'apiinterface') + apiUsername = BMConfigParser().get('bitmessagesettings', 'apiusername') + apiPassword = BMConfigParser().get('bitmessagesettings', 'apipassword') + + print '\n API data successfully imported.\n' + + return "http://" + apiUsername + ":" + apiPassword + "@" + apiInterface+ ":" + str(apiPort) + "/" #Build the api credentials + +#End keys.dat interactions -# End keys.dat interactions - - -def apiTest(): - """Tests the API connection to bitmessage. Returns true if it is connected.""" +def apiTest(): #Tests the API connection to bitmessage. Returns true if it is connected. try: - result = api.add(2, 3) - except: # noqa:E722 + result = api.add(2,3) + except: return False - return result == 5 - - -def bmSettings(): - """Allows the viewing and modification of keys.dat settings.""" + if (result == 5): + return True + else: + return False +def bmSettings(): #Allows the viewing and modification of keys.dat settings. global keysPath global usrPrompt - keysPath = 'keys.dat' - - config.read(keysPath) # Read the keys.dat + + BMConfigParser().read(keysPath)#Read the keys.dat try: - port = config.get('bitmessagesettings', 'port') - except: # noqa:E722 - print('\n File not found.\n') + port = BMConfigParser().get('bitmessagesettings', 'port') + except: + print '\n File not found.\n' usrPrompt = 0 main() + + startonlogon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'startonlogon') + minimizetotray = BMConfigParser().safeGetBoolean('bitmessagesettings', 'minimizetotray') + showtraynotifications = BMConfigParser().safeGetBoolean('bitmessagesettings', 'showtraynotifications') + startintray = BMConfigParser().safeGetBoolean('bitmessagesettings', 'startintray') + defaultnoncetrialsperbyte = BMConfigParser().get('bitmessagesettings', 'defaultnoncetrialsperbyte') + defaultpayloadlengthextrabytes = BMConfigParser().get('bitmessagesettings', 'defaultpayloadlengthextrabytes') + daemon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon') - startonlogon = config.safeGetBoolean('bitmessagesettings', 'startonlogon') - minimizetotray = config.safeGetBoolean('bitmessagesettings', 'minimizetotray') - showtraynotifications = config.safeGetBoolean('bitmessagesettings', 'showtraynotifications') - startintray = config.safeGetBoolean('bitmessagesettings', 'startintray') - defaultnoncetrialsperbyte = config.get('bitmessagesettings', 'defaultnoncetrialsperbyte') - defaultpayloadlengthextrabytes = config.get('bitmessagesettings', 'defaultpayloadlengthextrabytes') - daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') + socksproxytype = BMConfigParser().get('bitmessagesettings', 'socksproxytype') + sockshostname = BMConfigParser().get('bitmessagesettings', 'sockshostname') + socksport = BMConfigParser().get('bitmessagesettings', 'socksport') + socksauthentication = BMConfigParser().safeGetBoolean('bitmessagesettings', 'socksauthentication') + socksusername = BMConfigParser().get('bitmessagesettings', 'socksusername') + sockspassword = BMConfigParser().get('bitmessagesettings', 'sockspassword') - socksproxytype = config.get('bitmessagesettings', 'socksproxytype') - sockshostname = config.get('bitmessagesettings', 'sockshostname') - socksport = config.get('bitmessagesettings', 'socksport') - socksauthentication = config.safeGetBoolean('bitmessagesettings', 'socksauthentication') - socksusername = config.get('bitmessagesettings', 'socksusername') - sockspassword = config.get('bitmessagesettings', 'sockspassword') - print('\n -----------------------------------') - print(' | Current Bitmessage Settings |') - print(' -----------------------------------') - print(' port = ' + port) - print(' startonlogon = ' + str(startonlogon)) - print(' minimizetotray = ' + str(minimizetotray)) - print(' showtraynotifications = ' + str(showtraynotifications)) - print(' startintray = ' + str(startintray)) - print(' defaultnoncetrialsperbyte = ' + defaultnoncetrialsperbyte) - print(' defaultpayloadlengthextrabytes = ' + defaultpayloadlengthextrabytes) - print(' daemon = ' + str(daemon)) - print('\n ------------------------------------') - print(' | Current Connection Settings |') - print(' -----------------------------------') - print(' socksproxytype = ' + socksproxytype) - print(' sockshostname = ' + sockshostname) - print(' socksport = ' + socksport) - print(' socksauthentication = ' + str(socksauthentication)) - print(' socksusername = ' + socksusername) - print(' sockspassword = ' + sockspassword) - print(' ') + print '\n -----------------------------------' + print ' | Current Bitmessage Settings |' + print ' -----------------------------------' + print ' port = ' + port + print ' startonlogon = ' + str(startonlogon) + print ' minimizetotray = ' + str(minimizetotray) + print ' showtraynotifications = ' + str(showtraynotifications) + print ' startintray = ' + str(startintray) + print ' defaultnoncetrialsperbyte = ' + defaultnoncetrialsperbyte + print ' defaultpayloadlengthextrabytes = ' + defaultpayloadlengthextrabytes + print ' daemon = ' + str(daemon) + print '\n ------------------------------------' + print ' | Current Connection Settings |' + print ' -----------------------------------' + print ' socksproxytype = ' + socksproxytype + print ' sockshostname = ' + sockshostname + print ' socksport = ' + socksport + print ' socksauthentication = ' + str(socksauthentication) + print ' socksusername = ' + socksusername + print ' sockspassword = ' + sockspassword + print ' ' uInput = userInput("Would you like to modify any of these settings, (Y)es or (N)o?").lower() - + if uInput == "y": - while True: # loops if they mistype the setting name, they can exit the loop with 'exit' + while True: #loops if they mistype the setting name, they can exit the loop with 'exit' invalidInput = False uInput = userInput("What setting would you like to modify?").lower() - print(' ') + print ' ' if uInput == "port": - print(' Current port number: ' + port) + print ' Current port number: ' + port uInput = userInput("Enter the new port number.") - config.set('bitmessagesettings', 'port', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'port', str(uInput)) elif uInput == "startonlogon": - print(' Current status: ' + str(startonlogon)) + print ' Current status: ' + str(startonlogon) uInput = userInput("Enter the new status.") - config.set('bitmessagesettings', 'startonlogon', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'startonlogon', str(uInput)) elif uInput == "minimizetotray": - print(' Current status: ' + str(minimizetotray)) + print ' Current status: ' + str(minimizetotray) uInput = userInput("Enter the new status.") - config.set('bitmessagesettings', 'minimizetotray', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'minimizetotray', str(uInput)) elif uInput == "showtraynotifications": - print(' Current status: ' + str(showtraynotifications)) + print ' Current status: ' + str(showtraynotifications) uInput = userInput("Enter the new status.") - config.set('bitmessagesettings', 'showtraynotifications', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'showtraynotifications', str(uInput)) elif uInput == "startintray": - print(' Current status: ' + str(startintray)) + print ' Current status: ' + str(startintray) uInput = userInput("Enter the new status.") - config.set('bitmessagesettings', 'startintray', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'startintray', str(uInput)) elif uInput == "defaultnoncetrialsperbyte": - print(' Current default nonce trials per byte: ' + defaultnoncetrialsperbyte) + print ' Current default nonce trials per byte: ' + defaultnoncetrialsperbyte uInput = userInput("Enter the new defaultnoncetrialsperbyte.") - config.set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(uInput)) elif uInput == "defaultpayloadlengthextrabytes": - print(' Current default payload length extra bytes: ' + defaultpayloadlengthextrabytes) + print ' Current default payload length extra bytes: ' + defaultpayloadlengthextrabytes uInput = userInput("Enter the new defaultpayloadlengthextrabytes.") - config.set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(uInput)) elif uInput == "daemon": - print(' Current status: ' + str(daemon)) + print ' Current status: ' + str(daemon) uInput = userInput("Enter the new status.").lower() - config.set('bitmessagesettings', 'daemon', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'daemon', str(uInput)) elif uInput == "socksproxytype": - print(' Current socks proxy type: ' + socksproxytype) - print("Possibilities: 'none', 'SOCKS4a', 'SOCKS5'.") + print ' Current socks proxy type: ' + socksproxytype + print "Possibilities: 'none', 'SOCKS4a', 'SOCKS5'." uInput = userInput("Enter the new socksproxytype.") - config.set('bitmessagesettings', 'socksproxytype', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'socksproxytype', str(uInput)) elif uInput == "sockshostname": - print(' Current socks host name: ' + sockshostname) + print ' Current socks host name: ' + sockshostname uInput = userInput("Enter the new sockshostname.") - config.set('bitmessagesettings', 'sockshostname', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'sockshostname', str(uInput)) elif uInput == "socksport": - print(' Current socks port number: ' + socksport) + print ' Current socks port number: ' + socksport uInput = userInput("Enter the new socksport.") - config.set('bitmessagesettings', 'socksport', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'socksport', str(uInput)) elif uInput == "socksauthentication": - print(' Current status: ' + str(socksauthentication)) + print ' Current status: ' + str(socksauthentication) uInput = userInput("Enter the new status.") - config.set('bitmessagesettings', 'socksauthentication', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'socksauthentication', str(uInput)) elif uInput == "socksusername": - print(' Current socks username: ' + socksusername) + print ' Current socks username: ' + socksusername uInput = userInput("Enter the new socksusername.") - config.set('bitmessagesettings', 'socksusername', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'socksusername', str(uInput)) elif uInput == "sockspassword": - print(' Current socks password: ' + sockspassword) + print ' Current socks password: ' + sockspassword uInput = userInput("Enter the new password.") - config.set('bitmessagesettings', 'sockspassword', str(uInput)) + BMConfigParser().set('bitmessagesettings', 'sockspassword', str(uInput)) else: - print("\n Invalid input. Please try again.\n") + print "\n Invalid input. Please try again.\n" invalidInput = True - - if invalidInput is not True: # don't prompt if they made a mistake. + + if invalidInput != True: #don't prompt if they made a mistake. uInput = userInput("Would you like to change another setting, (Y)es or (N)o?").lower() if uInput != "y": - print('\n Changes Made.\n') + print '\n Changes Made.\n' with open(keysPath, 'wb') as configfile: - config.write(configfile) + BMConfigParser().write(configfile) restartBmNotify() break - + + elif uInput == "n": usrPrompt = 1 main() else: - print("Invalid input.") + print "Invalid input." usrPrompt = 1 main() - def validAddress(address): - """Predicate to test address validity""" - address_information = json.loads(api.decodeAddress(address)) + address_information = api.decodeAddress(address) + address_information = eval(address_information) + + if 'success' in str(address_information.get('status')).lower(): + return True + else: + return False - return 'success' in str(address_information['status']).lower() - - -def getAddress(passphrase, vNumber, sNumber): - """Get a deterministic address""" - passphrase = passphrase.encode('base64') # passphrase must be encoded - - return api.getDeterministicAddress(passphrase, vNumber, sNumber) +def getAddress(passphrase,vNumber,sNumber): + passphrase = passphrase.encode('base64')#passphrase must be encoded + return api.getDeterministicAddress(passphrase,vNumber,sNumber) def subscribe(): - """Subscribe to an address""" global usrPrompt while True: address = userInput("What address would you like to subscribe to?") - if address == "c": - usrPrompt = 1 - print(' ') - main() - elif validAddress(address) is False: - print('\n Invalid. "c" to cancel. Please try again.\n') + if (address == "c"): + usrPrompt = 1 + print ' ' + main() + elif (validAddress(address)== False): + print '\n Invalid. "c" to cancel. Please try again.\n' else: break - + label = userInput("Enter a label for this address.") label = label.encode('base64') - - api.addSubscription(address, label) - print('\n You are now subscribed to: ' + address + '\n') - + + api.addSubscription(address,label) + print ('\n You are now subscribed to: ' + address + '\n') def unsubscribe(): - """Unsusbcribe from an address""" global usrPrompt - + while True: address = userInput("What address would you like to unsubscribe from?") - if address == "c": - usrPrompt = 1 - print(' ') - main() - elif validAddress(address) is False: - print('\n Invalid. "c" to cancel. Please try again.\n') + if (address == "c"): + usrPrompt = 1 + print ' ' + main() + elif (validAddress(address)== False): + print '\n Invalid. "c" to cancel. Please try again.\n' else: break - - userInput("Are you sure, (Y)es or (N)o?").lower() # uInput = - + + + userInput("Are you sure, (Y)es or (N)o?").lower() # #uInput = + api.deleteSubscription(address) - print('\n You are now unsubscribed from: ' + address + '\n') - + print ('\n You are now unsubscribed from: ' + address + '\n') def listSubscriptions(): - """List subscriptions""" - global usrPrompt - print('\nLabel, Address, Enabled\n') + #jsonAddresses = json.loads(api.listSubscriptions()) + #numAddresses = len(jsonAddresses['addresses']) #Number of addresses + print '\nLabel, Address, Enabled\n' try: - print(api.listSubscriptions()) - except: # noqa:E722 - print('\n Connection Error\n') + print api.listSubscriptions() + except: + print '\n Connection Error\n' usrPrompt = 0 main() - print(' ') + + '''for addNum in range (0, numAddresses): #processes all of the addresses and lists them out + label = jsonAddresses['addresses'][addNum]['label'] + address = jsonAddresses['addresses'][addNum]['address'] + enabled = jsonAddresses['addresses'][addNum]['enabled'] + print label, address, enabled + ''' + print ' ' def createChan(): - """Create a channel""" - global usrPrompt password = userInput("Enter channel name") password = password.encode('base64') try: - print(api.createChan(password)) - except: # noqa:E722 - print('\n Connection Error\n') + print api.createChan(password) + except: + print '\n Connection Error\n' usrPrompt = 0 main() def joinChan(): - """Join a channel""" - global usrPrompt while True: address = userInput("Enter channel address") - - if address == "c": - usrPrompt = 1 - print(' ') - main() - elif validAddress(address) is False: - print('\n Invalid. "c" to cancel. Please try again.\n') + + if (address == "c"): + usrPrompt = 1 + print ' ' + main() + elif (validAddress(address)== False): + print '\n Invalid. "c" to cancel. Please try again.\n' else: break - + password = userInput("Enter channel name") password = password.encode('base64') try: - print(api.joinChan(password, address)) - except: # noqa:E722 - print('\n Connection Error\n') + print api.joinChan(password,address) + except: + print '\n Connection Error\n' usrPrompt = 0 main() - def leaveChan(): - """Leave a channel""" - global usrPrompt while True: address = userInput("Enter channel address") - - if address == "c": - usrPrompt = 1 - print(' ') - main() - elif validAddress(address) is False: - print('\n Invalid. "c" to cancel. Please try again.\n') + + if (address == "c"): + usrPrompt = 1 + print ' ' + main() + elif (validAddress(address)== False): + print '\n Invalid. "c" to cancel. Please try again.\n' else: break - + try: - print(api.leaveChan(address)) - except: # noqa:E722 - print('\n Connection Error\n') + print api.leaveChan(address) + except: + print '\n Connection Error\n' usrPrompt = 0 main() -def listAdd(): - """List all of the addresses and their info""" +def listAdd(): #Lists all of the addresses and their info global usrPrompt try: jsonAddresses = json.loads(api.listAddresses()) - numAddresses = len(jsonAddresses['addresses']) # Number of addresses - except: # noqa:E722 - print('\n Connection Error\n') + numAddresses = len(jsonAddresses['addresses']) #Number of addresses + except: + print '\n Connection Error\n' usrPrompt = 0 main() - # print('\nAddress Number,Label,Address,Stream,Enabled\n') - print('\n --------------------------------------------------------------------------') - print(' | # | Label | Address |S#|Enabled|') - print(' |---|-------------------|-------------------------------------|--|-------|') - for addNum in range(0, numAddresses): # processes all of the addresses and lists them out - label = (jsonAddresses['addresses'][addNum]['label']).encode( - 'utf') # may still misdiplay in some consoles + #print '\nAddress Number,Label,Address,Stream,Enabled\n' + print '\n --------------------------------------------------------------------------' + print ' | # | Label | Address |S#|Enabled|' + print ' |---|-------------------|-------------------------------------|--|-------|' + for addNum in range (0, numAddresses): #processes all of the addresses and lists them out + 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: + if (len(label) > 19): label = label[:16] + '...' + + print ' |' + str(addNum).ljust(3) + '|' + label.ljust(19) + '|' + address.ljust(37) + '|' + stream.ljust(1), '|' + enabled.ljust(7) + '|' - print(''.join([ - ' |', - str(addNum).ljust(3), - '|', - label.ljust(19), - '|', - address.ljust(37), - '|', - stream.ljust(1), - '|', - enabled.ljust(7), - '|', - ])) - - print(''.join([ - ' ', - 74 * '-', - '\n', - ])) - - -def genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe): - """Generate address""" + print ' --------------------------------------------------------------------------\n' +def genAdd(lbl,deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe): #Generate address global usrPrompt - - if deterministic is False: # Generates a new address with the user defined label. non-deterministic + if deterministic == False: #Generates a new address with the user defined label. non-deterministic addressLabel = lbl.encode('base64') try: generatedAddress = api.createRandomAddress(addressLabel) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - + return generatedAddress - - elif deterministic: # Generates a new deterministic address with the user inputs. + + elif deterministic == True: #Generates a new deterministic address with the user inputs. passphrase = passphrase.encode('base64') try: generatedAddress = api.createDeterministicAddresses(passphrase, numOfAdd, addVNum, streamNum, ripe) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() return generatedAddress + else: + return 'Entry Error' - return 'Entry Error' +def delMilAddr(): #Generate address + global usrPrompt + try: + response = api.listAddresses2() + # if api is too old just return then fail + if "API Error 0020" in response: return + addresses = json.loads(response) + for entry in addresses['addresses']: + if entry['label'].decode('base64')[:6] == "random": + api.deleteAddress(entry['address']) + except: + print '\n Connection Error\n' + usrPrompt = 0 + main() +def genMilAddr(): #Generate address + global usrPrompt + maxn = 0 + try: + response = api.listAddresses2() + if "API Error 0020" in response: return + addresses = json.loads(response) + for entry in addresses['addresses']: + if entry['label'].decode('base64')[:6] == "random": + newn = int(entry['label'].decode('base64')[6:]) + if maxn < newn: + maxn = newn + except: + print "\n Some error\n" + print "\n Starting at " + str(maxn) + "\n" + for i in range(maxn, 10000): + lbl = "random" + str(i) + addressLabel = lbl.encode('base64') + try: + api.createRandomAddress(addressLabel) # generatedAddress = + except: + print '\n Connection Error\n' + usrPrompt = 0 + main() -def saveFile(fileName, fileData): - """Allows attachments and messages/broadcats to be saved""" +def saveFile(fileName, fileData): #Allows attachments and messages/broadcats to be saved - # This section finds all invalid characters and replaces them with ~ + #This section finds all invalid characters and replaces them with ~ fileName = fileName.replace(" ", "") fileName = fileName.replace("/", "~") - # fileName = fileName.replace("\\", "~") How do I get this to work...? + #fileName = fileName.replace("\\", "~") How do I get this to work...? fileName = fileName.replace(":", "~") fileName = fileName.replace("*", "~") fileName = fileName.replace("?", "~") @@ -637,219 +614,217 @@ def saveFile(fileName, fileData): fileName = fileName.replace(">", "~") fileName = fileName.replace("|", "~") - directory = os.path.abspath('attachments') + directory = 'attachments' if not os.path.exists(directory): os.makedirs(directory) + + filePath = directory +'/'+ fileName - filePath = os.path.join(directory, fileName) - - with open(filePath, 'wb+') as path_to_file: - path_to_file.write(fileData.decode("base64")) - print('\n Successfully saved ' + filePath + '\n') + '''try: #Checks if file already exists + with open(filePath): + print 'File Already Exists' + return + except IOError: pass''' -def attachment(): - """Allows users to attach a file to their message or broadcast""" + f = open(filePath, 'wb+') #Begin saving to file + f.write(fileData.decode("base64")) + f.close + print '\n Successfully saved '+ filePath + '\n' + +def attachment(): #Allows users to attach a file to their message or broadcast theAttachmentS = '' - + while True: isImage = False theAttachment = '' - - while True: # loops until valid path is entered - filePath = userInput( - '\nPlease enter the path to the attachment or just the attachment name if in this folder.') + + while True:#loops until valid path is entered + filePath = userInput('\nPlease enter the path to the attachment or just the attachment name if in this folder.') try: - with open(filePath): - break + with open(filePath): break except IOError: - print('\n %s was not found on your filesystem or can not be opened.\n' % filePath) + print '\n %s was not found on your filesystem or can not be opened.\n' % filePath + pass - # print(filesize, and encoding estimate with confirmation if file is over X size(1mb?)) + #print filesize, and encoding estimate with confirmation if file is over X size (1mb?) invSize = os.path.getsize(filePath) - invSize = (invSize / 1024) # Converts to kilobytes - round(invSize, 2) # Rounds to two decimal places + invSize = (invSize / 1024) #Converts to kilobytes + round(invSize,2) #Rounds to two decimal places - if invSize > 500.0: # If over 500KB - print(''.join([ - '\n WARNING:The file that you are trying to attach is ', - invSize, - 'KB and will take considerable time to send.\n' - ])) + if (invSize > 500.0):#If over 500KB + print '\n WARNING:The file that you are trying to attach is ', invSize, 'KB and will take considerable time to send.\n' uInput = userInput('Are you sure you still want to attach it, (Y)es or (N)o?').lower() if uInput != "y": - print('\n Attachment discarded.\n') + print '\n Attachment discarded.\n' return '' - elif invSize > 184320.0: # If larger than 180MB, discard. - print('\n Attachment too big, maximum allowed size:180MB\n') + elif (invSize > 184320.0): #If larger than 180MB, discard. + print '\n Attachment too big, maximum allowed size:180MB\n' main() - - pathLen = len(str(ntpath.basename(filePath))) # Gets the length of the filepath excluding the filename - fileName = filePath[(len(str(filePath)) - pathLen):] # reads the filename - - filetype = imghdr.what(filePath) # Tests if it is an image file + + pathLen = len(str(ntpath.basename(filePath))) #Gets the length of the filepath excluding the filename + fileName = filePath[(len(str(filePath)) - pathLen):] #reads the filename + + filetype = imghdr.what(filePath) #Tests if it is an image file if filetype is not None: - print('\n ---------------------------------------------------') - print(' Attachment detected as an Image.') - print(' tags will automatically be included,') - print(' allowing the recipient to view the image') - print(' using the "View HTML code..." option in Bitmessage.') - print(' ---------------------------------------------------\n') + print '\n ---------------------------------------------------' + print ' Attachment detected as an Image.' + print ' tags will automatically be included,' + print ' allowing the recipient to view the image' + print ' using the "View HTML code..." option in Bitmessage.' + print ' ---------------------------------------------------\n' isImage = True time.sleep(2) - - # Alert the user that the encoding process may take some time. - print('\n Encoding Attachment, Please Wait ...\n') - - with open(filePath, 'rb') as f: # Begin the actual encoding - data = f.read(188743680) # Reads files up to 180MB, the maximum size for Bitmessage. + + print '\n Encoding Attachment, Please Wait ...\n' #Alert the user that the encoding process may take some time. + + with open(filePath, 'rb') as f: #Begin the actual encoding + data = f.read(188743680) #Reads files up to 180MB, the maximum size for Bitmessage. data = data.encode("base64") - if isImage: # If it is an image, include image tags in the message + if (isImage == True): #If it is an image, include image tags in the message theAttachment = """ - -Filename:%s -Filesize:%sKB -Encoding:base64 - + +Filename:%s +Filesize:%sKB +Encoding:base64 +
%s
-
""" % (fileName, invSize, fileName, filetype, data) - else: # Else it is not an image so do not include the embedded image code. +""" % (fileName,invSize,fileName,filetype,data) + else: #Else it is not an image so do not include the embedded image code. theAttachment = """ - -Filename:%s -Filesize:%sKB -Encoding:base64 - -""" % (fileName, invSize, fileName, fileName, data) + +Filename:%s +Filesize:%sKB +Encoding:base64 + +""" % (fileName,invSize,fileName,fileName,data) uInput = userInput('Would you like to add another attachment, (Y)es or (N)o?').lower() - if uInput in ('y', 'yes'): # Allows multiple attachments to be added to one message - theAttachmentS = str(theAttachmentS) + str(theAttachment) + '\n\n' - elif uInput in ('n', 'no'): + if (uInput == 'y' or uInput == 'yes'):#Allows multiple attachments to be added to one message + theAttachmentS = str(theAttachmentS) + str(theAttachment)+ '\n\n' + elif (uInput == 'n' or uInput == 'no'): break - + theAttachmentS = theAttachmentS + theAttachment return theAttachmentS - -def sendMsg(toAddress, fromAddress, subject, message): - """ - With no arguments sent, sendMsg fills in the blanks. - subject and message must be encoded before they are passed. - """ - +def sendMsg(toAddress, fromAddress, subject, message): #With no arguments sent, sendMsg fills in the blanks. subject and message must be encoded before they are passed. global usrPrompt - if validAddress(toAddress) is False: + if (validAddress(toAddress)== False): while True: toAddress = userInput("What is the To Address?") - if toAddress == "c": + if (toAddress == "c"): usrPrompt = 1 - print(' ') + print ' ' main() - elif validAddress(toAddress) is False: - print('\n Invalid Address. "c" to cancel. Please try again.\n') + elif (validAddress(toAddress)== False): + print '\n Invalid Address. "c" to cancel. Please try again.\n' else: break - - if validAddress(fromAddress) is False: - try: + + + if (validAddress(fromAddress)== False): + try: jsonAddresses = json.loads(api.listAddresses()) - numAddresses = len(jsonAddresses['addresses']) # Number of addresses - except: # noqa:E722 - print('\n Connection Error\n') + numAddresses = len(jsonAddresses['addresses']) #Number of addresses + except: + print '\n Connection Error\n' usrPrompt = 0 main() - - if numAddresses > 1: # Ask what address to send from if multiple addresses + + if (numAddresses > 1): #Ask what address to send from if multiple addresses found = False while True: - print(' ') + print ' ' fromAddress = userInput("Enter an Address or Address Label to send from.") if fromAddress == "exit": usrPrompt = 1 main() - for addNum in range(0, numAddresses): # processes all of the addresses + for addNum in range (0, numAddresses): #processes all of the addresses label = jsonAddresses['addresses'][addNum]['label'] address = jsonAddresses['addresses'][addNum]['address'] - if fromAddress == label: # address entered was a label and is found + #stream = jsonAddresses['addresses'][addNum]['stream'] + #enabled = jsonAddresses['addresses'][addNum]['enabled'] + if (fromAddress == label): #address entered was a label and is found fromAddress = address found = True break - - if found is False: - if validAddress(fromAddress) is False: - print('\n Invalid Address. Please try again.\n') - + + if (found == False): + if(validAddress(fromAddress)== False): + print '\n Invalid Address. Please try again.\n' + else: - for addNum in range(0, numAddresses): # processes all of the addresses + for addNum in range (0, numAddresses): #processes all of the addresses + #label = jsonAddresses['addresses'][addNum]['label'] address = jsonAddresses['addresses'][addNum]['address'] - if fromAddress == address: # address entered was a found in our addressbook. + #stream = jsonAddresses['addresses'][addNum]['stream'] + #enabled = jsonAddresses['addresses'][addNum]['enabled'] + if (fromAddress == address): #address entered was a found in our addressbook. found = True break - - if found is False: - print('\n The address entered is not one of yours. Please try again.\n') - - if found: - break # Address was found - - else: # Only one address in address book - print('\n Using the only address in the addressbook to send from.\n') + + if (found == False): + print '\n The address entered is not one of yours. Please try again.\n' + + if (found == True): + break #Address was found + + else: #Only one address in address book + print '\n Using the only address in the addressbook to send from.\n' fromAddress = jsonAddresses['addresses'][0]['address'] - if not subject: + if (subject == ''): subject = userInput("Enter your Subject.") subject = subject.encode('base64') - if not message: + if (message == ''): message = userInput("Enter your Message.") uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower() if uInput == "y": message = message + '\n\n' + attachment() - + message = message.encode('base64') - + try: ackData = api.sendMessage(toAddress, fromAddress, subject, message) - print('\n Message Status:', api.getStatus(ackData), '\n') - except: # noqa:E722 - print('\n Connection Error\n') + print '\n Message Status:', api.getStatus(ackData), '\n' + except: + print '\n Connection Error\n' usrPrompt = 0 main() -def sendBrd(fromAddress, subject, message): - """Send a broadcast""" - +def sendBrd(fromAddress, subject, message): #sends a broadcast global usrPrompt - if not fromAddress: + if (fromAddress == ''): try: jsonAddresses = json.loads(api.listAddresses()) - numAddresses = len(jsonAddresses['addresses']) # Number of addresses - except: # noqa:E722 - print('\n Connection Error\n') + numAddresses = len(jsonAddresses['addresses']) #Number of addresses + except: + print '\n Connection Error\n' usrPrompt = 0 main() - - if numAddresses > 1: # Ask what address to send from if multiple addresses + + if (numAddresses > 1): #Ask what address to send from if multiple addresses found = False while True: fromAddress = userInput("\nEnter an Address or Address Label to send from.") @@ -858,362 +833,316 @@ def sendBrd(fromAddress, subject, message): usrPrompt = 1 main() - for addNum in range(0, numAddresses): # processes all of the addresses + for addNum in range (0, numAddresses): #processes all of the addresses label = jsonAddresses['addresses'][addNum]['label'] address = jsonAddresses['addresses'][addNum]['address'] - if fromAddress == label: # address entered was a label and is found + #stream = jsonAddresses['addresses'][addNum]['stream'] + #enabled = jsonAddresses['addresses'][addNum]['enabled'] + if (fromAddress == label): #address entered was a label and is found fromAddress = address found = True break - - if found is False: - if validAddress(fromAddress) is False: - print('\n Invalid Address. Please try again.\n') - + + if (found == False): + if(validAddress(fromAddress)== False): + print '\n Invalid Address. Please try again.\n' + else: - for addNum in range(0, numAddresses): # processes all of the addresses + for addNum in range (0, numAddresses): #processes all of the addresses + #label = jsonAddresses['addresses'][addNum]['label'] address = jsonAddresses['addresses'][addNum]['address'] - if fromAddress == address: # address entered was a found in our addressbook. + #stream = jsonAddresses['addresses'][addNum]['stream'] + #enabled = jsonAddresses['addresses'][addNum]['enabled'] + if (fromAddress == address): #address entered was a found in our addressbook. found = True break - - if found is False: - print('\n The address entered is not one of yours. Please try again.\n') - - if found: - break # Address was found - - else: # Only one address in address book - print('\n Using the only address in the addressbook to send from.\n') + + if (found == False): + print '\n The address entered is not one of yours. Please try again.\n' + + if (found == True): + break #Address was found + + else: #Only one address in address book + print '\n Using the only address in the addressbook to send from.\n' fromAddress = jsonAddresses['addresses'][0]['address'] - if not subject: - subject = userInput("Enter your Subject.") - subject = subject.encode('base64') - if not message: - message = userInput("Enter your Message.") + if (subject == ''): + subject = userInput("Enter your Subject.") + subject = subject.encode('base64') + if (message == ''): + message = userInput("Enter your Message.") - uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower() - if uInput == "y": - message = message + '\n\n' + attachment() - - message = message.encode('base64') + uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower() + if uInput == "y": + message = message + '\n\n' + attachment() + + message = message.encode('base64') try: ackData = api.sendBroadcast(fromAddress, subject, message) - print('\n Message Status:', api.getStatus(ackData), '\n') - except: # noqa:E722 - print('\n Connection Error\n') + print '\n Message Status:', api.getStatus(ackData), '\n' + except: + print '\n Connection Error\n' usrPrompt = 0 main() - -def inbox(unreadOnly=False): - """Lists the messages by: Message Number, To Address Label, From Address Label, Subject, Received Time)""" - +def inbox(unreadOnly = False): #Lists the messages by: Message Number, To Address Label, From Address Label, Subject, Received Time) global usrPrompt try: inboxMessages = json.loads(api.getAllInboxMessages()) numMessages = len(inboxMessages['inboxMessages']) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() messagesPrinted = 0 messagesUnread = 0 - for msgNum in range(0, numMessages): # processes all of the messages in the inbox + for msgNum in range (0, numMessages): #processes all of the messages in the inbox message = inboxMessages['inboxMessages'][msgNum] # if we are displaying all messages or if this message is unread then display it if not unreadOnly or not message['read']: - print(' -----------------------------------\n') - print(' Message Number:', msgNum) # Message Number) - print(' To:', getLabelForAddress(message['toAddress'])) # Get the to address) - print(' From:', getLabelForAddress(message['fromAddress'])) # Get the from address) - print(' Subject:', message['subject'].decode('base64')) # Get the subject) - print(''.join([ - ' Received:', - datetime.datetime.fromtimestamp( - float(message['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S'), - ])) + print ' -----------------------------------\n' + print ' Message Number:',msgNum #Message Number + print ' To:', getLabelForAddress(message['toAddress']) #Get the to address + print ' From:', getLabelForAddress(message['fromAddress']) #Get the from address + print ' Subject:', message['subject'].decode('base64') #Get the subject + print ' Received:', datetime.datetime.fromtimestamp(float(message['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S') messagesPrinted += 1 - if not message['read']: - messagesUnread += 1 - - if messagesPrinted % 20 == 0 and messagesPrinted != 0: - 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)) - print(' -----------------------------------\n') + if not message['read']: messagesUnread += 1 + if (messagesPrinted%20 == 0 and messagesPrinted != 0): + 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) + print ' -----------------------------------\n' def outbox(): - """TBC""" - global usrPrompt try: outboxMessages = json.loads(api.getAllSentMessages()) numMessages = len(outboxMessages['sentMessages']) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - for msgNum in range(0, numMessages): # processes all of the messages in the outbox - print('\n -----------------------------------\n') - print(' Message Number:', msgNum) # Message Number) - # print(' Message ID:', outboxMessages['sentMessages'][msgNum]['msgid']) - print(' To:', getLabelForAddress( - outboxMessages['sentMessages'][msgNum]['toAddress'] - )) # Get the to address) - # Get the from address - print(' From:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['fromAddress'])) - print(' Subject:', outboxMessages['sentMessages'][msgNum]['subject'].decode('base64')) # Get the subject) - print(' Status:', outboxMessages['sentMessages'][msgNum]['status']) # Get the subject) + for msgNum in range (0, numMessages): #processes all of the messages in the outbox + print '\n -----------------------------------\n' + print ' Message Number:',msgNum #Message Number + #print ' Message ID:', outboxMessages['sentMessages'][msgNum]['msgid'] + print ' To:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['toAddress']) #Get the to address + print ' From:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['fromAddress']) #Get the from address + print ' Subject:', outboxMessages['sentMessages'][msgNum]['subject'].decode('base64') #Get the subject + print ' Status:', outboxMessages['sentMessages'][msgNum]['status'] #Get the subject + + print ' Last Action Time:', datetime.datetime.fromtimestamp(float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S') - # print(''.join([ - # ' Last Action Time:', - # datetime.datetime.fromtimestamp( - # float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S'), - # ])) - print(''.join([ - ' 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): + userInput('(Press Enter to continue or type (Exit) to return to the main menu.)').lower() # uInput = - if msgNum % 20 == 0 and msgNum != 0: - 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.') - print(' -----------------------------------\n') - - -def readSentMsg(msgNum): - """Opens a sent message for reading""" + print '\n -----------------------------------' + print ' There are ',numMessages,' messages in the outbox.' + print ' -----------------------------------\n' +def readSentMsg(msgNum): #Opens a sent message for reading global usrPrompt try: outboxMessages = json.loads(api.getAllSentMessages()) numMessages = len(outboxMessages['sentMessages']) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() + + print ' ' - print(' ') - - if msgNum >= numMessages: - print('\n Invalid Message Number.\n') + if (msgNum >= numMessages): + print '\n Invalid Message Number.\n' main() - # Begin attachment detection + #Begin attachment detection message = outboxMessages['sentMessages'][msgNum]['message'].decode('base64') - while True: # Allows multiple messages to be downloaded/saved - if ';base64,' in message: # Found this text in the message, there is probably an attachment. - attPos = message.index(";base64,") # Finds the attachment position - attEndPos = message.index("' />") # Finds the end of the attachment - # attLen = attEndPos - attPos #Finds the length of the message + while True: #Allows multiple messages to be downloaded/saved + if (';base64,' in message): #Found this text in the message, there is probably an attachment. + attPos= message.index(";base64,") #Finds the attachment position + attEndPos = message.index("' />") #Finds the end of the attachment + #attLen = attEndPos - attPos #Finds the length of the message - if 'alt = "' in message: # We can get the filename too - fnPos = message.index('alt = "') # Finds position of the filename - fnEndPos = message.index('" src=') # Finds the end position - # fnLen = fnEndPos - fnPos #Finds the length of the filename - fileName = message[fnPos + 7:fnEndPos] + if ('alt = "' in message): #We can get the filename too + fnPos = message.index('alt = "') #Finds position of the filename + fnEndPos = message.index('" src=') #Finds the end position + #fnLen = fnEndPos - fnPos #Finds the length of the filename + + fileName = message[fnPos+7:fnEndPos] else: fnPos = attPos fileName = 'Attachment' - uInput = userInput( - '\n Attachment Detected. Would you like to save the attachment, (Y)es or (N)o?').lower() - if uInput in ("y", 'yes'): + uInput = userInput('\n Attachment Detected. Would you like to save the attachment, (Y)es or (N)o?').lower() + if (uInput == "y" or uInput == 'yes'): + + attachment = message[attPos+9:attEndPos] + saveFile(fileName,attachment) - this_attachment = message[attPos + 9:attEndPos] - saveFile(fileName, this_attachment) - - message = message[:fnPos] + '~~' + message[(attEndPos + 4):] + message = message[:fnPos] + '~~' + message[(attEndPos+4):] else: break + + #End attachment Detection + + print '\n To:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['toAddress']) #Get the to address + print ' From:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['fromAddress']) #Get the from address + print ' Subject:', outboxMessages['sentMessages'][msgNum]['subject'].decode('base64') #Get the subject + print ' Status:', outboxMessages['sentMessages'][msgNum]['status'] #Get the subject + print ' Last Action Time:', datetime.datetime.fromtimestamp(float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S') + print ' Message:\n' + print message #inboxMessages['inboxMessages'][msgNum]['message'].decode('base64') + print ' ' - # End attachment Detection - - print('\n To:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['toAddress'])) # Get the to address) - # Get the from address - print(' From:', getLabelForAddress(outboxMessages['sentMessages'][msgNum]['fromAddress'])) - print(' Subject:', outboxMessages['sentMessages'][msgNum]['subject'].decode('base64')) # Get the subject) - print(' Status:', outboxMessages['sentMessages'][msgNum]['status']) # Get the subject) - print(''.join([ - ' Last Action Time:', - datetime.datetime.fromtimestamp( - float(outboxMessages['sentMessages'][msgNum]['lastActionTime'])).strftime('%Y-%m-%d %H:%M:%S'), - ])) - print(' Message:\n') - print(message) # inboxMessages['inboxMessages'][msgNum]['message'].decode('base64')) - print(' ') - - -def readMsg(msgNum): - """Open a message for reading""" +def readMsg(msgNum): #Opens a message for reading global usrPrompt try: inboxMessages = json.loads(api.getAllInboxMessages()) numMessages = len(inboxMessages['inboxMessages']) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - if msgNum >= numMessages: - print('\n Invalid Message Number.\n') + if (msgNum >= numMessages): + print '\n Invalid Message Number.\n' main() - # Begin attachment detection + #Begin attachment detection message = inboxMessages['inboxMessages'][msgNum]['message'].decode('base64') - while True: # Allows multiple messages to be downloaded/saved - if ';base64,' in message: # Found this text in the message, there is probably an attachment. - attPos = message.index(";base64,") # Finds the attachment position - attEndPos = message.index("' />") # Finds the end of the attachment - # attLen = attEndPos - attPos #Finds the length of the message + while True: #Allows multiple messages to be downloaded/saved + if (';base64,' in message): #Found this text in the message, there is probably an attachment. + attPos= message.index(";base64,") #Finds the attachment position + attEndPos = message.index("' />") #Finds the end of the attachment + #attLen = attEndPos - attPos #Finds the length of the message - if 'alt = "' in message: # We can get the filename too - fnPos = message.index('alt = "') # Finds position of the filename - fnEndPos = message.index('" src=') # Finds the end position - # fnLen = fnEndPos - fnPos #Finds the length of the filename - fileName = message[fnPos + 7:fnEndPos] + if ('alt = "' in message): #We can get the filename too + fnPos = message.index('alt = "') #Finds position of the filename + fnEndPos = message.index('" src=') #Finds the end position + #fnLen = fnEndPos - fnPos #Finds the length of the filename + + fileName = message[fnPos+7:fnEndPos] else: fnPos = attPos fileName = 'Attachment' - uInput = userInput( - '\n Attachment Detected. Would you like to save the attachment, (Y)es or (N)o?').lower() - if uInput in ("y", 'yes'): + uInput = userInput('\n Attachment Detected. Would you like to save the attachment, (Y)es or (N)o?').lower() + if (uInput == "y" or uInput == 'yes'): + + attachment = message[attPos+9:attEndPos] + saveFile(fileName,attachment) - this_attachment = message[attPos + 9:attEndPos] - saveFile(fileName, this_attachment) - - message = message[:fnPos] + '~~' + message[attEndPos + 4:] + message = message[:fnPos] + '~~' + message[(attEndPos+4):] else: break - - # End attachment Detection - print('\n To:', getLabelForAddress(inboxMessages['inboxMessages'][msgNum]['toAddress'])) # Get the to address) - # Get the from address - print(' From:', getLabelForAddress(inboxMessages['inboxMessages'][msgNum]['fromAddress'])) - print(' Subject:', inboxMessages['inboxMessages'][msgNum]['subject'].decode('base64')) # Get the subject) - print(''.join([ - ' Received:', datetime.datetime.fromtimestamp( - float(inboxMessages['inboxMessages'][msgNum]['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S'), - ])) - print(' Message:\n') - print(message) # inboxMessages['inboxMessages'][msgNum]['message'].decode('base64')) - print(' ') + + #End attachment Detection + print '\n To:', getLabelForAddress(inboxMessages['inboxMessages'][msgNum]['toAddress']) #Get the to address + print ' From:', getLabelForAddress(inboxMessages['inboxMessages'][msgNum]['fromAddress']) #Get the from address + print ' Subject:', inboxMessages['inboxMessages'][msgNum]['subject'].decode('base64') #Get the subject + print ' Received:',datetime.datetime.fromtimestamp(float(inboxMessages['inboxMessages'][msgNum]['receivedTime'])).strftime('%Y-%m-%d %H:%M:%S') + print ' Message:\n' + print message #inboxMessages['inboxMessages'][msgNum]['message'].decode('base64') + print ' ' return inboxMessages['inboxMessages'][msgNum]['msgid'] - -def replyMsg(msgNum, forwardORreply): - """Allows you to reply to the message you are currently on. Saves typing in the addresses and subject.""" - +def replyMsg(msgNum,forwardORreply): #Allows you to reply to the message you are currently on. Saves typing in the addresses and subject. global usrPrompt - forwardORreply = forwardORreply.lower() # makes it lowercase + forwardORreply = forwardORreply.lower() #makes it lowercase try: inboxMessages = json.loads(api.getAllInboxMessages()) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - - fromAdd = inboxMessages['inboxMessages'][msgNum]['toAddress'] # Address it was sent To, now the From address - message = inboxMessages['inboxMessages'][msgNum]['message'].decode('base64') # Message that you are replying too. - + + fromAdd = inboxMessages['inboxMessages'][msgNum]['toAddress']#Address it was sent To, now the From address + message = inboxMessages['inboxMessages'][msgNum]['message'].decode('base64') #Message that you are replying too. + subject = inboxMessages['inboxMessages'][msgNum]['subject'] subject = subject.decode('base64') - - if forwardORreply == 'reply': - toAdd = inboxMessages['inboxMessages'][msgNum]['fromAddress'] # Address it was From, now the To address + + if (forwardORreply == 'reply'): + toAdd = inboxMessages['inboxMessages'][msgNum]['fromAddress'] #Address it was From, now the To address subject = "Re: " + subject - - elif forwardORreply == 'forward': + + elif (forwardORreply == 'forward'): subject = "Fwd: " + subject - + while True: toAdd = userInput("What is the To Address?") - if toAdd == "c": + if (toAdd == "c"): usrPrompt = 1 - print(' ') + print ' ' main() - elif validAddress(toAdd) is False: - print('\n Invalid Address. "c" to cancel. Please try again.\n') + elif (validAddress(toAdd)== False): + print '\n Invalid Address. "c" to cancel. Please try again.\n' else: break else: - print('\n Invalid Selection. Reply or Forward only') + print '\n Invalid Selection. Reply or Forward only' usrPrompt = 0 main() - + subject = subject.encode('base64') - + newMessage = userInput("Enter your Message.") uInput = userInput('Would you like to add an attachment, (Y)es or (N)o?').lower() if uInput == "y": newMessage = newMessage + '\n\n' + attachment() - + newMessage = newMessage + '\n\n------------------------------------------------------\n' newMessage = newMessage + message newMessage = newMessage.encode('base64') sendMsg(toAdd, fromAdd, subject, newMessage) - + main() - -def delMsg(msgNum): - """Deletes a specified message from the inbox""" - +def delMsg(msgNum): #Deletes a specified message from the inbox global usrPrompt try: inboxMessages = json.loads(api.getAllInboxMessages()) - # gets the message ID via the message index number - msgId = inboxMessages['inboxMessages'][int(msgNum)]['msgid'] - + msgId = inboxMessages['inboxMessages'][int(msgNum)]['msgid'] #gets the message ID via the message index number + msgAck = api.trashMessage(msgId) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - + return msgAck - -def delSentMsg(msgNum): - """Deletes a specified message from the outbox""" - +def delSentMsg(msgNum): #Deletes a specified message from the outbox global usrPrompt try: outboxMessages = json.loads(api.getAllSentMessages()) - # gets the message ID via the message index number - msgId = outboxMessages['sentMessages'][int(msgNum)]['msgid'] + msgId = outboxMessages['sentMessages'][int(msgNum)]['msgid'] #gets the message ID via the message index number msgAck = api.trashSentMessage(msgId) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - + return msgAck - def getLabelForAddress(address): - """Get label for an address""" - if address in knownAddresses: return knownAddresses[address] else: @@ -1223,24 +1152,18 @@ def getLabelForAddress(address): return address - def buildKnownAddresses(): - """Build known addresses""" - - global usrPrompt - # add from address book try: response = api.listAddressBookEntries() # if api is too old then fail - if "API Error 0020" in response: - return + if "API Error 0020" in response: return addressBook = json.loads(response) for entry in addressBook['addresses']: if entry['address'] not in knownAddresses: knownAddresses[entry['address']] = "%s (%s)" % (entry['label'].decode('base64'), entry['address']) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() @@ -1248,314 +1171,275 @@ def buildKnownAddresses(): try: response = api.listAddresses2() # if api is too old just return then fail - if "API Error 0020" in response: - return + if "API Error 0020" in response: return addresses = json.loads(response) for entry in addresses['addresses']: if entry['address'] not in knownAddresses: knownAddresses[entry['address']] = "%s (%s)" % (entry['label'].decode('base64'), entry['address']) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - def listAddressBookEntries(): - """List addressbook entries""" - - global usrPrompt - try: response = api.listAddressBookEntries() if "API Error" in response: return getAPIErrorCode(response) addressBook = json.loads(response) - print(' --------------------------------------------------------------') - print(' | Label | Address |') - print(' |--------------------|---------------------------------------|') + print + print ' --------------------------------------------------------------' + print ' | Label | Address |' + print ' |--------------------|---------------------------------------|' for entry in addressBook['addresses']: label = entry['label'].decode('base64') address = entry['address'] - if len(label) > 19: - label = label[:16] + '...' - print(' | ' + label.ljust(19) + '| ' + address.ljust(37) + ' |') - print(' --------------------------------------------------------------') - except: # noqa:E722 - print('\n Connection Error\n') + if (len(label) > 19): label = label[:16] + '...' + print ' | ' + label.ljust(19) + '| ' + address.ljust(37) + ' |' + print ' --------------------------------------------------------------' + print + + except: + print '\n Connection Error\n' usrPrompt = 0 main() - def addAddressToAddressBook(address, label): - """Add an address to an addressbook""" - - global usrPrompt - try: response = api.addAddressBookEntry(address, label.encode('base64')) if "API Error" in response: return getAPIErrorCode(response) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - def deleteAddressFromAddressBook(address): - """Delete an address from an addressbook""" - - global usrPrompt - try: response = api.deleteAddressBookEntry(address) if "API Error" in response: return getAPIErrorCode(response) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - def getAPIErrorCode(response): - """Get API error code""" - if "API Error" in response: # if we got an API error return the number by getting the number # after the second space and removing the trailing colon return int(response.split()[2][:-1]) - def markMessageRead(messageID): - """Mark a message as read""" - - global usrPrompt - try: response = api.getInboxMessageByID(messageID, True) if "API Error" in response: return getAPIErrorCode(response) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - def markMessageUnread(messageID): - """Mark a mesasge as unread""" - - global usrPrompt - try: response = api.getInboxMessageByID(messageID, False) if "API Error" in response: return getAPIErrorCode(response) - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() - def markAllMessagesRead(): - """Mark all messages as read""" - - global usrPrompt - try: inboxMessages = json.loads(api.getAllInboxMessages())['inboxMessages'] - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() for message in inboxMessages: if not message['read']: markMessageRead(message['msgid']) - def markAllMessagesUnread(): - """Mark all messages as unread""" - - global usrPrompt - try: inboxMessages = json.loads(api.getAllInboxMessages())['inboxMessages'] - except: # noqa:E722 - print('\n Connection Error\n') + except: + print '\n Connection Error\n' usrPrompt = 0 main() for message in inboxMessages: if message['read']: markMessageUnread(message['msgid']) - def clientStatus(): - """Print (the client status""" - - global usrPrompt - try: - client_status = json.loads(api.clientStatus()) - except: # noqa:E722 - print('\n Connection Error\n') + clientStatus = json.loads(api.clientStatus()) + except: + print '\n Connection Error\n' usrPrompt = 0 main() - - print("\nnetworkStatus: " + client_status['networkStatus'] + "\n") - print("\nnetworkConnections: " + str(client_status['networkConnections']) + "\n") - print("\nnumberOfPubkeysProcessed: " + str(client_status['numberOfPubkeysProcessed']) + "\n") - print("\nnumberOfMessagesProcessed: " + str(client_status['numberOfMessagesProcessed']) + "\n") - print("\nnumberOfBroadcastsProcessed: " + str(client_status['numberOfBroadcastsProcessed']) + "\n") - + print "\nnetworkStatus: " + clientStatus['networkStatus'] + "\n" + print "\nnetworkConnections: " + str(clientStatus['networkConnections']) + "\n" + print "\nnumberOfPubkeysProcessed: " + str(clientStatus['numberOfPubkeysProcessed']) + "\n" + print "\nnumberOfMessagesProcessed: " + str(clientStatus['numberOfMessagesProcessed']) + "\n" + print "\nnumberOfBroadcastsProcessed: " + str(clientStatus['numberOfBroadcastsProcessed']) + "\n" def shutdown(): - """Shutdown the API""" - try: api.shutdown() except socket.error: pass - print("\nShutdown command relayed\n") + print "\nShutdown command relayed\n" -def UI(usrInput): - """Main user menu""" - +def UI(usrInput): #Main user menu global usrPrompt - - if usrInput in ("help", "h", "?"): - print(' ') - print(' -------------------------------------------------------------------------') - print(' | https://github.com/Dokument/PyBitmessage-Daemon |') - print(' |-----------------------------------------------------------------------|') - print(' | Command | Description |') - print(' |------------------------|----------------------------------------------|') - print(' | help | This help file. |') - print(' | apiTest | Tests the API |') - print(' | addInfo | Returns address information (If valid) |') - print(' | bmSettings | BitMessage settings |') - print(' | exit | Use anytime to return to main menu |') - print(' | quit | Quits the program |') - print(' |------------------------|----------------------------------------------|') - print(' | listAddresses | Lists all of the users addresses |') - print(' | generateAddress | Generates a new address |') - print(' | getAddress | Get determinist address from passphrase |') - print(' |------------------------|----------------------------------------------|') - print(' | listAddressBookEntries | Lists entries from the Address Book |') - print(' | addAddressBookEntry | Add address to the Address Book |') - print(' | deleteAddressBookEntry | Deletes address from the Address Book |') - print(' |------------------------|----------------------------------------------|') - print(' | subscribe | Subscribes to an address |') - print(' | unsubscribe | Unsubscribes from an address |') - print(' |------------------------|----------------------------------------------|') - print(' | create | Creates a channel |') - print(' | join | Joins a channel |') - print(' | leave | Leaves a channel |') - 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 |') - print(' | unread | Lists all unread inbox messages |') - print(' | read | Reads a message from the inbox or outbox |') - print(' | save | Saves message to text file |') - print(' | delete | Deletes a message or all messages |') - print(' -------------------------------------------------------------------------') - print(' ') + + if usrInput == "help" or usrInput == "h" or usrInput == "?": + print ' ' + print ' -------------------------------------------------------------------------' + print ' | https://github.com/Dokument/PyBitmessage-Daemon |' + print ' |-----------------------------------------------------------------------|' + print ' | Command | Description |' + print ' |------------------------|----------------------------------------------|' + print ' | help | This help file. |' + print ' | apiTest | Tests the API |' + print ' | addInfo | Returns address information (If valid) |' + print ' | bmSettings | BitMessage settings |' + print ' | exit | Use anytime to return to main menu |' + print ' | quit | Quits the program |' + print ' |------------------------|----------------------------------------------|' + print ' | listAddresses | Lists all of the users addresses |' + print ' | generateAddress | Generates a new address |' + print ' | getAddress | Get determinist address from passphrase |' + print ' |------------------------|----------------------------------------------|' + print ' | listAddressBookEntries | Lists entries from the Address Book |' + print ' | addAddressBookEntry | Add address to the Address Book |' + print ' | deleteAddressBookEntry | Deletes address from the Address Book |' + print ' |------------------------|----------------------------------------------|' + print ' | subscribe | Subscribes to an address |' + print ' | unsubscribe | Unsubscribes from an address |' + #print' | listSubscriptions | Lists all of the subscriptions. |' + print ' |------------------------|----------------------------------------------|' + print ' | create | Creates a channel |' + print ' | join | Joins a channel |' + print ' | leave | Leaves a channel |' + 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 |' + print ' | unread | Lists all unread inbox messages |' + print ' | read | Reads a message from the inbox or outbox |' + print ' | save | Saves message to text file |' + print ' | delete | Deletes a message or all messages |' + print ' -------------------------------------------------------------------------' + print ' ' main() - elif usrInput == "apitest": # tests the API Connection. - if apiTest(): - print('\n API connection test has: PASSED\n') + elif usrInput == "apitest": #tests the API Connection. + if (apiTest() == True): + print '\n API connection test has: PASSED\n' else: - print('\n API connection test has: FAILED\n') + print '\n API connection test has: FAILED\n' main() elif usrInput == "addinfo": tmp_address = userInput('\nEnter the Bitmessage Address.') - address_information = json.loads(api.decodeAddress(tmp_address)) + address_information = api.decodeAddress(tmp_address) + address_information = eval(address_information) - print('\n------------------------------') - - if 'success' in str(address_information['status']).lower(): - print(' Valid Address') - print(' Address Version: %s' % str(address_information['addressVersion'])) - print(' Stream Number: %s' % str(address_information['streamNumber'])) + print '\n------------------------------' + + if 'success' in str(address_information.get('status')).lower(): + print ' Valid Address' + print ' Address Version: %s' % str(address_information.get('addressVersion')) + print ' Stream Number: %s' % str(address_information.get('streamNumber')) else: - print(' Invalid Address !') + print ' Invalid Address !' - print('------------------------------\n') + print '------------------------------\n' main() - - elif usrInput == "bmsettings": # tests the API Connection. + + elif usrInput == "bmsettings": #tests the API Connection. bmSettings() - print(' ') + print ' ' main() - - elif usrInput == "quit": # Quits the application - print('\n Bye\n') - sys.exit(0) - - elif usrInput == "listaddresses": # Lists all of the identities in the addressbook + + elif usrInput == "quit": #Quits the application + print '\n Bye\n' + sys.exit() + os._exit() + + elif usrInput == "listaddresses": #Lists all of the identities in the addressbook listAdd() main() - - elif usrInput == "generateaddress": # Generates a new address + + elif usrInput == "generateaddress": #Generates a new address uInput = userInput('\nWould you like to create a (D)eterministic or (R)andom address?').lower() - if uInput in ("d", "deterministic"): # Creates a deterministic address + if uInput == "d" or uInput == "determinstic": #Creates a deterministic address deterministic = True + #lbl = raw_input('Label the new address:') #currently not possible via the api lbl = '' - passphrase = userInput('Enter the Passphrase.') # .encode('base64') + passphrase = userInput('Enter the Passphrase.')#.encode('base64') numOfAdd = int(userInput('How many addresses would you like to generate?')) + #addVNum = int(raw_input('Address version number (default "0"):')) + #streamNum = int(raw_input('Stream number (default "0"):')) addVNum = 3 streamNum = 1 isRipe = userInput('Shorten the address, (Y)es or (N)o?').lower() if isRipe == "y": ripe = True - print(genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe)) + print genAdd(lbl,deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe) main() elif isRipe == "n": ripe = False - print(genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe)) + print genAdd(lbl, deterministic, passphrase, numOfAdd, addVNum, streamNum, ripe) main() elif isRipe == "exit": usrPrompt = 1 main() else: - print('\n Invalid input\n') + print '\n Invalid input\n' main() - elif uInput == "r" or uInput == "random": # Creates a random address with user-defined label + + elif uInput == "r" or uInput == "random": #Creates a random address with user-defined label deterministic = False null = '' lbl = userInput('Enter the label for the new address.') - - print(genAdd(lbl, deterministic, null, null, null, null, null)) + + print genAdd(lbl,deterministic, null,null, null, null, null) main() - + else: - print('\n Invalid input\n') + print '\n Invalid input\n' main() - - elif usrInput == "getaddress": # Gets the address for/from a passphrase + + elif usrInput == "getaddress": #Gets the address for/from a passphrase phrase = userInput("Enter the address passphrase.") - print('\n Working...\n') - address = getAddress(phrase, 4, 1) # ,vNumber,sNumber) - print('\n Address: ' + address + '\n') + print '\n Working...\n' + #vNumber = int(raw_input("Enter the address version number:")) + #sNumber = int(raw_input("Enter the address stream number:")) + + address = getAddress(phrase,4,1)#,vNumber,sNumber) + print ('\n Address: ' + address + '\n') + usrPrompt = 1 main() - elif usrInput == "subscribe": # Subsribe to an address + elif usrInput == "subscribe": #Subsribe to an address subscribe() usrPrompt = 1 main() - - elif usrInput == "unsubscribe": # Unsubscribe from an address + elif usrInput == "unsubscribe": #Unsubscribe from an address unsubscribe() usrPrompt = 1 main() - - elif usrInput == "listsubscriptions": # Unsubscribe from an address + elif usrInput == "listsubscriptions": #Unsubscribe from an address listSubscriptions() usrPrompt = 1 main() @@ -1569,239 +1453,233 @@ def UI(usrInput): joinChan() usrPrompt = 1 main() - + elif usrInput == "leave": leaveChan() usrPrompt = 1 main() - + elif usrInput == "inbox": - print('\n Loading...\n') + print '\n Loading...\n' inbox() main() elif usrInput == "unread": - print('\n Loading...\n') + print '\n Loading...\n' inbox(True) main() elif usrInput == "outbox": - print('\n Loading...\n') + print '\n Loading...\n' outbox() main() - elif usrInput == 'send': # Sends a message or broadcast + elif usrInput == 'send': #Sends a message or broadcast uInput = userInput('Would you like to send a (M)essage or (B)roadcast?').lower() - if uInput in ('m', 'message'): + if (uInput == 'm' or uInput == 'message'): null = '' - sendMsg(null, null, null, null) + sendMsg(null,null,null,null) main() - elif uInput in ('b', 'broadcast'): + elif (uInput =='b' or uInput == 'broadcast'): null = '' - sendBrd(null, null, null) + sendBrd(null,null,null) main() - elif usrInput == "read": # Opens a message from the inbox for viewing. + elif usrInput == "read": #Opens a message from the inbox for viewing. + uInput = userInput("Would you like to read a message from the (I)nbox or (O)utbox?").lower() - if uInput not in ('i', 'inbox', 'o', 'outbox'): - print('\n Invalid Input.\n') + if (uInput != 'i' and uInput != 'inbox' and uInput != 'o' and uInput != 'outbox'): + print '\n Invalid Input.\n' usrPrompt = 1 main() msgNum = int(userInput("What is the number of the message you wish to open?")) - if uInput in ('i', 'inbox'): - print('\n Loading...\n') + if (uInput == 'i' or uInput == 'inbox'): + print '\n Loading...\n' messageID = readMsg(msgNum) uInput = userInput("\nWould you like to keep this message unread, (Y)es or (N)o?").lower() - if uInput not in ('y', 'yes'): + if not (uInput == 'y' or uInput == 'yes'): markMessageRead(messageID) usrPrompt = 1 uInput = userInput("\nWould you like to (D)elete, (F)orward, (R)eply to, or (Exit) this message?").lower() - if uInput in ('r', 'reply'): - print('\n Loading...\n') - print(' ') - replyMsg(msgNum, 'reply') + if (uInput == 'r' or uInput == 'reply'): + print '\n Loading...\n' + print ' ' + replyMsg(msgNum,'reply') + usrPrompt = 1 + + elif (uInput == 'f' or uInput == 'forward'): + print '\n Loading...\n' + print ' ' + replyMsg(msgNum,'forward') usrPrompt = 1 - elif uInput in ('f', 'forward'): - print('\n Loading...\n') - print(' ') - replyMsg(msgNum, 'forward') - usrPrompt = 1 - - elif uInput in ("d", 'delete'): - uInput = userInput("Are you sure, (Y)es or (N)o?").lower() # Prevent accidental deletion + elif (uInput == "d" or uInput == 'delete'): + uInput = userInput("Are you sure, (Y)es or (N)o?").lower()#Prevent accidental deletion if uInput == "y": delMsg(msgNum) - print('\n Message Deleted.\n') + print '\n Message Deleted.\n' usrPrompt = 1 else: usrPrompt = 1 else: - print('\n Invalid entry\n') + print '\n Invalid entry\n' usrPrompt = 1 - - elif uInput in ('o', 'outbox'): + + elif (uInput == 'o' or uInput == 'outbox'): readSentMsg(msgNum) - # Gives the user the option to delete the message - uInput = userInput("Would you like to (D)elete, or (Exit) this message?").lower() + uInput = userInput("Would you like to (D)elete, or (Exit) this message?").lower() #Gives the user the option to delete the message - if uInput in ("d", 'delete'): - uInput = userInput('Are you sure, (Y)es or (N)o?').lower() # Prevent accidental deletion + if (uInput == "d" or uInput == 'delete'): + uInput = userInput('Are you sure, (Y)es or (N)o?').lower() #Prevent accidental deletion if uInput == "y": delSentMsg(msgNum) - print('\n Message Deleted.\n') + print '\n Message Deleted.\n' usrPrompt = 1 else: usrPrompt = 1 else: - print('\n Invalid Entry\n') + print '\n Invalid Entry\n' usrPrompt = 1 - + main() - + elif usrInput == "save": - + uInput = userInput("Would you like to save a message from the (I)nbox or (O)utbox?").lower() - if uInput not in ('i', 'inbox', 'o', 'outbox'): - print('\n Invalid Input.\n') + if (uInput != 'i' and uInput == 'inbox' and uInput != 'o' and uInput == 'outbox'): + print '\n Invalid Input.\n' usrPrompt = 1 main() - if uInput in ('i', 'inbox'): + if (uInput == 'i' or uInput == 'inbox'): inboxMessages = json.loads(api.getAllInboxMessages()) numMessages = len(inboxMessages['inboxMessages']) while True: msgNum = int(userInput("What is the number of the message you wish to save?")) - if msgNum >= numMessages: - print('\n Invalid Message Number.\n') + if (msgNum >= numMessages): + print '\n Invalid Message Number.\n' else: break - - subject = inboxMessages['inboxMessages'][msgNum]['subject'].decode('base64') - # Don't decode since it is done in the saveFile function - message = inboxMessages['inboxMessages'][msgNum]['message'] - - elif uInput == 'o' or uInput == 'outbox': + + subject = inboxMessages['inboxMessages'][msgNum]['subject'].decode('base64') + message = inboxMessages['inboxMessages'][msgNum]['message']#Don't decode since it is done in the saveFile function + + elif (uInput == 'o' or uInput == 'outbox'): outboxMessages = json.loads(api.getAllSentMessages()) numMessages = len(outboxMessages['sentMessages']) while True: msgNum = int(userInput("What is the number of the message you wish to save?")) - if msgNum >= numMessages: - print('\n Invalid Message Number.\n') + if (msgNum >= numMessages): + print '\n Invalid Message Number.\n' else: break - - subject = outboxMessages['sentMessages'][msgNum]['subject'].decode('base64') - # Don't decode since it is done in the saveFile function - message = outboxMessages['sentMessages'][msgNum]['message'] - - subject = subject + '.txt' - saveFile(subject, message) - + + subject = outboxMessages['sentMessages'][msgNum]['subject'].decode('base64') + message = outboxMessages['sentMessages'][msgNum]['message']#Don't decode since it is done in the saveFile function + + subject = subject +'.txt' + saveFile(subject,message) + usrPrompt = 1 main() - - elif usrInput == "delete": # will delete a message from the system, not reflected on the UI. - + + elif usrInput == "delete": #will delete a message from the system, not reflected on the UI. + uInput = userInput("Would you like to delete a message from the (I)nbox or (O)utbox?").lower() - if uInput in ('i', 'inbox'): + if (uInput == 'i' or uInput == 'inbox'): inboxMessages = json.loads(api.getAllInboxMessages()) numMessages = len(inboxMessages['inboxMessages']) + + while True: + msgNum = userInput('Enter the number of the message you wish to delete or (A)ll to empty the inbox.').lower() - while True: - msgNum = userInput( - 'Enter the number of the message you wish to delete or (A)ll to empty the inbox.').lower() - - if msgNum == 'a' or msgNum == 'all': + if (msgNum == 'a' or msgNum == 'all'): break - elif int(msgNum) >= numMessages: - print('\n Invalid Message Number.\n') + elif (int(msgNum) >= numMessages): + print '\n Invalid Message Number.\n' else: break - - uInput = userInput("Are you sure, (Y)es or (N)o?").lower() # Prevent accidental deletion + + uInput = userInput("Are you sure, (Y)es or (N)o?").lower()#Prevent accidental deletion if uInput == "y": - if msgNum in ('a', 'all'): - print(' ') - for msgNum in range(0, numMessages): # processes all of the messages in the inbox - print(' Deleting message ', msgNum + 1, ' of ', numMessages) + if (msgNum == 'a' or msgNum == 'all'): + print ' ' + for msgNum in range (0, numMessages): #processes all of the messages in the inbox + print ' Deleting message ', msgNum+1, ' of ', numMessages delMsg(0) - print('\n Inbox is empty.') + print '\n Inbox is empty.' usrPrompt = 1 else: delMsg(int(msgNum)) - - print('\n Notice: Message numbers may have changed.\n') + + print '\n Notice: Message numbers may have changed.\n' main() else: usrPrompt = 1 - - elif uInput in ('o', 'outbox'): + elif (uInput == 'o' or uInput == 'outbox'): outboxMessages = json.loads(api.getAllSentMessages()) numMessages = len(outboxMessages['sentMessages']) - + while True: - msgNum = userInput( - 'Enter the number of the message you wish to delete or (A)ll to empty the inbox.').lower() + msgNum = userInput('Enter the number of the message you wish to delete or (A)ll to empty the inbox.').lower() - if msgNum in ('a', 'all'): + if (msgNum == 'a' or msgNum == 'all'): break - elif int(msgNum) >= numMessages: - print('\n Invalid Message Number.\n') + elif (int(msgNum) >= numMessages): + print '\n Invalid Message Number.\n' else: break - uInput = userInput("Are you sure, (Y)es or (N)o?").lower() # Prevent accidental deletion + uInput = userInput("Are you sure, (Y)es or (N)o?").lower()#Prevent accidental deletion if uInput == "y": - if msgNum in ('a', 'all'): - print(' ') - for msgNum in range(0, numMessages): # processes all of the messages in the outbox - print(' Deleting message ', msgNum + 1, ' of ', numMessages) + if (msgNum == 'a' or msgNum == 'all'): + print ' ' + for msgNum in range (0, numMessages): #processes all of the messages in the outbox + print ' Deleting message ', msgNum+1, ' of ', numMessages delSentMsg(0) - print('\n Outbox is empty.') + print '\n Outbox is empty.' usrPrompt = 1 else: delSentMsg(int(msgNum)) - print('\n Notice: Message numbers may have changed.\n') + print '\n Notice: Message numbers may have changed.\n' main() else: usrPrompt = 1 else: - print('\n Invalid Entry.\n') + print '\n Invalid Entry.\n' usrPrompt = 1 main() elif usrInput == "exit": - print('\n You are already at the main menu. Use "quit" to quit.\n') + print '\n You are already at the main menu. Use "quit" to quit.\n' usrPrompt = 1 main() elif usrInput == "listaddressbookentries": res = listAddressBookEntries() - if res == 20: - print('\n Error: API function not supported.\n') + if res == 20: print '\n Error: API function not supported.\n' usrPrompt = 1 main() @@ -1809,18 +1687,15 @@ def UI(usrInput): address = userInput('Enter address') label = userInput('Enter label') res = addAddressToAddressBook(address, label) - if res == 16: - print('\n Error: Address already exists in Address Book.\n') - if res == 20: - print('\n Error: API function not supported.\n') + if res == 16: print '\n Error: Address already exists in Address Book.\n' + if res == 20: print '\n Error: API function not supported.\n' usrPrompt = 1 main() elif usrInput == "deleteaddressbookentry": address = userInput('Enter address') res = deleteAddressFromAddressBook(address) - if res == 20: - print('\n Error: API function not supported.\n') + if res == 20: print '\n Error: API function not supported.\n' usrPrompt = 1 main() @@ -1844,37 +1719,49 @@ def UI(usrInput): usrPrompt = 1 main() - else: - print('\n "', usrInput, '" is not a command.\n') + elif usrInput == "million+": + genMilAddr() usrPrompt = 1 main() - + elif usrInput == "million-": + delMilAddr() + usrPrompt = 1 + main() + + else: + print '\n "',usrInput,'" is not a command.\n' + usrPrompt = 1 + main() + def main(): - """Entrypoint for the CLI app""" - global api global usrPrompt + + if (usrPrompt == 0): + print '\n ------------------------------' + print ' | Bitmessage Daemon by .dok |' + print ' | Version 0.3.1 for BM 0.6.2 |' + print ' ------------------------------' + api = xmlrpclib.ServerProxy(apiData()) #Connect to BitMessage using these api credentials - if usrPrompt == 0: - print('\n ------------------------------') - print(' | Bitmessage Daemon by .dok |') - print(' | Version 0.3.1 for BM 0.6.2 |') - print(' ------------------------------') - api = xmlrpclib.ServerProxy(apiData()) # Connect to BitMessage using these api credentials - - if apiTest() is False: - print('\n ****************************************************************') - print(' WARNING: You are not connected to the Bitmessage client.') - print(' Either Bitmessage is not running or your settings are incorrect.') - print(' Use the command "apiTest" or "bmSettings" to resolve this issue.') - print(' ****************************************************************\n') - - print('Type (H)elp for a list of commands.') # Startup message) + if (apiTest() == False): + print '\n ****************************************************************' + print ' WARNING: You are not connected to the Bitmessage client.' + print ' Either Bitmessage is not running or your settings are incorrect.' + print ' Use the command "apiTest" or "bmSettings" to resolve this issue.' + print ' ****************************************************************\n' + + print 'Type (H)elp for a list of commands.' #Startup message usrPrompt = 2 - - elif usrPrompt == 1: - print('\nType (H)elp for a list of commands.') # Startup message) + + #if (apiTest() == False):#Preform a connection test #taken out until I get the error handler working + # print '*************************************' + # print 'WARNING: No connection to Bitmessage.' + # print '*************************************' + # print ' ' + elif (usrPrompt == 1): + print '\nType (H)elp for a list of commands.' #Startup message usrPrompt = 2 try: @@ -1882,6 +1769,5 @@ def main(): except EOFError: UI("quit") - if __name__ == "__main__": - main() + main() diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index 542b8e53..fc1d74b2 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -1,41 +1,38 @@ -""" -Bitmessage commandline interface -""" # Copyright (c) 2014 Luke Montalvo # This file adds a alternative commandline interface, feel free to critique and fork -# +# # This has only been tested on Arch Linux and Linux Mint # Dependencies: # * from python2-pip # * python2-pythondialog # * dialog -import ConfigParser -import curses import os import sys +import StringIO +from textwrap import * + import time -from textwrap import fill +from time import strftime, localtime from threading import Timer +import curses +import dialog from dialog import Dialog -import helper_sent +from helper_sql import * +from helper_ackPayload import genAckPayload + +from addresses import * +import ConfigParser +from bmconfigparser import BMConfigParser +from inventory import Inventory import l10n -import network.stats +from pyelliptic.openssl import OpenSSL import queues import shared import shutdown -import state -from addresses import addBMIfNotPresent, decodeAddress -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -from inventory import Inventory - -# pylint: disable=global-statement - - -quit_ = False +quit = False menutab = 1 menu = ["Inbox", "Send", "Sent", "Your Identities", "Subscriptions", "Address Book", "Blacklist", "Network Status"] naptime = 100 @@ -61,206 +58,163 @@ bwtype = "black" BROADCAST_STR = "[Broadcast subscribers]" - -class printLog(object): - """Printing logs""" - # pylint: disable=no-self-use - +class printLog: def write(self, output): - """Write logs""" global log log += output - def flush(self): - """Flush logs""" pass - - -class errLog(object): - """Error logs""" - # pylint: disable=no-self-use - +class errLog: def write(self, output): - """Write error logs""" global log - log += "!" + output - + log += "!"+output def flush(self): - """Flush error logs""" pass - - printlog = printLog() errlog = errLog() def cpair(a): - """Color pairs""" r = curses.color_pair(a) - if r not in range(1, curses.COLOR_PAIRS - 1): + if r not in range(1, curses.COLOR_PAIRS-1): r = curses.color_pair(0) return r - - def ascii(s): - """ASCII values""" r = "" for c in s: if ord(c) in range(128): r += c return r - def drawmenu(stdscr): - """Creating menu's""" menustr = " " - for i, _ in enumerate(menu): - if menutab == i + 1: + for i in range(0, len(menu)): + if menutab == i+1: menustr = menustr[:-1] menustr += "[" - menustr += str(i + 1) + menu[i] - if menutab == i + 1: + menustr += str(i+1)+menu[i] + if menutab == i+1: menustr += "] " - elif i != len(menu) - 1: + elif i != len(menu)-1: menustr += " " stdscr.addstr(2, 5, menustr, curses.A_UNDERLINE) - def set_background_title(d, title): - """Setting background title""" try: d.set_background_title(title) - except: # noqa:E722 + except: d.add_persistent_args(("--backtitle", title)) - def scrollbox(d, text, height=None, width=None): - """Setting scroll box""" try: - d.scrollbox(text, height, width, exit_label="Continue") - except: # noqa:E722 - d.msgbox(text, height or 0, width or 0, ok_label="Continue") - + d.scrollbox(text, height, width, exit_label = "Continue") + except: + d.msgbox(text, height or 0, width or 0, ok_label = "Continue") def resetlookups(): - """Reset the Inventory Lookups""" global inventorydata inventorydata = Inventory().numberOfInventoryLookupsPerformed Inventory().numberOfInventoryLookupsPerformed = 0 Timer(1, resetlookups, ()).start() - - def drawtab(stdscr): - """Method for drawing different tabs""" - # pylint: disable=too-many-branches, too-many-statements - if menutab in range(1, len(menu) + 1): - if menutab == 1: # Inbox + if menutab in range(1, len(menu)+1): + if menutab == 1: # Inbox stdscr.addstr(3, 5, "To", curses.A_BOLD) stdscr.addstr(3, 40, "From", curses.A_BOLD) stdscr.addstr(3, 80, "Subject", curses.A_BOLD) stdscr.addstr(3, 120, "Time Received", curses.A_BOLD) stdscr.hline(4, 5, '-', 121) - for i, item in enumerate(inbox[max(min(len(inbox) - curses.LINES + 6, inboxcur - 5), 0):]): - if 6 + i < curses.LINES: + for i, item in enumerate(inbox[max(min(len(inbox)-curses.LINES+6, inboxcur-5), 0):]): + if 6+i < curses.LINES: a = 0 - if i == inboxcur - max(min(len(inbox) - curses.LINES + 6, inboxcur - 5), 0): - # Highlight current address + if i == inboxcur - max(min(len(inbox)-curses.LINES+6, inboxcur-5), 0): # Highlight current address a = a | curses.A_REVERSE - if item[7] is False: # If not read, highlight + if item[7] == False: # If not read, highlight a = a | curses.A_BOLD - stdscr.addstr(5 + i, 5, item[1][:34], a) - stdscr.addstr(5 + i, 40, item[3][:39], a) - stdscr.addstr(5 + i, 80, item[5][:39], a) - stdscr.addstr(5 + i, 120, item[6][:39], a) - elif menutab == 3: # Sent + stdscr.addstr(5+i, 5, item[1][:34], a) + stdscr.addstr(5+i, 40, item[3][:39], a) + stdscr.addstr(5+i, 80, item[5][:39], a) + stdscr.addstr(5+i, 120, item[6][:39], a) + elif menutab == 3: # Sent stdscr.addstr(3, 5, "To", curses.A_BOLD) stdscr.addstr(3, 40, "From", curses.A_BOLD) stdscr.addstr(3, 80, "Subject", curses.A_BOLD) stdscr.addstr(3, 120, "Status", curses.A_BOLD) stdscr.hline(4, 5, '-', 121) - for i, item in enumerate(sentbox[max(min(len(sentbox) - curses.LINES + 6, sentcur - 5), 0):]): - if 6 + i < curses.LINES: + for i, item in enumerate(sentbox[max(min(len(sentbox)-curses.LINES+6, sentcur-5), 0):]): + if 6+i < curses.LINES: a = 0 - if i == sentcur - max(min(len(sentbox) - curses.LINES + 6, sentcur - 5), 0): - # Highlight current address + if i == sentcur - max(min(len(sentbox)-curses.LINES+6, sentcur-5), 0): # Highlight current address a = a | curses.A_REVERSE - stdscr.addstr(5 + i, 5, item[0][:34], a) - stdscr.addstr(5 + i, 40, item[2][:39], a) - stdscr.addstr(5 + i, 80, item[4][:39], a) - stdscr.addstr(5 + i, 120, item[5][:39], a) - elif menutab == 2 or menutab == 4: # Send or Identities + stdscr.addstr(5+i, 5, item[0][:34], a) + stdscr.addstr(5+i, 40, item[2][:39], a) + stdscr.addstr(5+i, 80, item[4][:39], a) + stdscr.addstr(5+i, 120, item[5][:39], a) + elif menutab == 2 or menutab == 4: # Send or Identities stdscr.addstr(3, 5, "Label", curses.A_BOLD) stdscr.addstr(3, 40, "Address", curses.A_BOLD) stdscr.addstr(3, 80, "Stream", curses.A_BOLD) stdscr.hline(4, 5, '-', 81) - for i, item in enumerate(addresses[max(min(len(addresses) - curses.LINES + 6, addrcur - 5), 0):]): - if 6 + i < curses.LINES: + for i, item in enumerate(addresses[max(min(len(addresses)-curses.LINES+6, addrcur-5), 0):]): + if 6+i < curses.LINES: a = 0 - if i == addrcur - max(min(len(addresses) - curses.LINES + 6, addrcur - 5), 0): - # Highlight current address + if i == addrcur - max(min(len(addresses)-curses.LINES+6, addrcur-5), 0): # Highlight current address a = a | curses.A_REVERSE - if item[1] and item[3] not in [8, 9]: # Embolden enabled, non-special addresses + if item[1] == True and item[3] not in [8,9]: # Embolden enabled, non-special addresses a = a | curses.A_BOLD - stdscr.addstr(5 + i, 5, item[0][:34], a) - stdscr.addstr(5 + i, 40, item[2][:39], cpair(item[3]) | a) - stdscr.addstr(5 + i, 80, str(1)[:39], a) - elif menutab == 5: # Subscriptions + stdscr.addstr(5+i, 5, item[0][:34], a) + stdscr.addstr(5+i, 40, item[2][:39], cpair(item[3]) | a) + stdscr.addstr(5+i, 80, str(1)[:39], a) + elif menutab == 5: # Subscriptions stdscr.addstr(3, 5, "Label", curses.A_BOLD) stdscr.addstr(3, 80, "Address", curses.A_BOLD) stdscr.addstr(3, 120, "Enabled", curses.A_BOLD) stdscr.hline(4, 5, '-', 121) - for i, item in enumerate(subscriptions[max(min(len(subscriptions) - curses.LINES + 6, subcur - 5), 0):]): - if 6 + i < curses.LINES: + for i, item in enumerate(subscriptions[max(min(len(subscriptions)-curses.LINES+6, subcur-5), 0):]): + if 6+i < curses.LINES: a = 0 - if i == subcur - max(min(len(subscriptions) - curses.LINES + 6, subcur - 5), 0): - # Highlight current address + if i == subcur - max(min(len(subscriptions)-curses.LINES+6, subcur-5), 0): # Highlight current address a = a | curses.A_REVERSE - if item[2]: # Embolden enabled subscriptions + if item[2] == True: # Embolden enabled subscriptions a = a | curses.A_BOLD - stdscr.addstr(5 + i, 5, item[0][:74], a) - stdscr.addstr(5 + i, 80, item[1][:39], a) - stdscr.addstr(5 + i, 120, str(item[2]), a) - elif menutab == 6: # Address book + stdscr.addstr(5+i, 5, item[0][:74], a) + stdscr.addstr(5+i, 80, item[1][:39], a) + stdscr.addstr(5+i, 120, str(item[2]), a) + elif menutab == 6: # Address book stdscr.addstr(3, 5, "Label", curses.A_BOLD) stdscr.addstr(3, 40, "Address", curses.A_BOLD) stdscr.hline(4, 5, '-', 41) - for i, item in enumerate(addrbook[max(min(len(addrbook) - curses.LINES + 6, abookcur - 5), 0):]): - if 6 + i < curses.LINES: + for i, item in enumerate(addrbook[max(min(len(addrbook)-curses.LINES+6, abookcur-5), 0):]): + if 6+i < curses.LINES: a = 0 - if i == abookcur - max(min(len(addrbook) - curses.LINES + 6, abookcur - 5), 0): - # Highlight current address + if i == abookcur - max(min(len(addrbook)-curses.LINES+6, abookcur-5), 0): # Highlight current address a = a | curses.A_REVERSE - stdscr.addstr(5 + i, 5, item[0][:34], a) - stdscr.addstr(5 + i, 40, item[1][:39], a) - elif menutab == 7: # Blacklist - stdscr.addstr(3, 5, "Type: " + bwtype) + stdscr.addstr(5+i, 5, item[0][:34], a) + stdscr.addstr(5+i, 40, item[1][:39], a) + elif menutab == 7: # Blacklist + stdscr.addstr(3, 5, "Type: "+bwtype) stdscr.addstr(4, 5, "Label", curses.A_BOLD) stdscr.addstr(4, 80, "Address", curses.A_BOLD) stdscr.addstr(4, 120, "Enabled", curses.A_BOLD) stdscr.hline(5, 5, '-', 121) - for i, item in enumerate(blacklist[max(min(len(blacklist) - curses.LINES + 6, blackcur - 5), 0):]): - if 7 + i < curses.LINES: + for i, item in enumerate(blacklist[max(min(len(blacklist)-curses.LINES+6, blackcur-5), 0):]): + if 7+i < curses.LINES: a = 0 - if i == blackcur - max(min(len(blacklist) - curses.LINES + 6, blackcur - 5), 0): - # Highlight current address + if i == blackcur - max(min(len(blacklist)-curses.LINES+6, blackcur-5), 0): # Highlight current address a = a | curses.A_REVERSE - if item[2]: # Embolden enabled subscriptions + if item[2] == True: # Embolden enabled subscriptions a = a | curses.A_BOLD - stdscr.addstr(6 + i, 5, item[0][:74], a) - stdscr.addstr(6 + i, 80, item[1][:39], a) - stdscr.addstr(6 + i, 120, str(item[2]), a) - elif menutab == 8: # Network status + stdscr.addstr(6+i, 5, item[0][:74], a) + stdscr.addstr(6+i, 80, item[1][:39], a) + stdscr.addstr(6+i, 120, str(item[2]), a) + elif menutab == 8: # Network status # Connection data - connected_hosts = network.stats.connectedHostsList() - stdscr.addstr( - 4, 5, "Total Connections: " + - str(len(connected_hosts)).ljust(2) - ) + stdscr.addstr(4, 5, "Total Connections: "+str(len(shared.connectedHostsList)).ljust(2)) stdscr.addstr(6, 6, "Stream #", curses.A_BOLD) stdscr.addstr(6, 18, "Connections", curses.A_BOLD) stdscr.hline(7, 6, '-', 23) streamcount = [] - for host, stream in connected_hosts: + for host, stream in shared.connectedHostsList.items(): if stream >= len(streamcount): streamcount.append(1) else: @@ -268,97 +222,73 @@ def drawtab(stdscr): for i, item in enumerate(streamcount): if i < 4: if i == 0: - stdscr.addstr(8 + i, 6, "?") + stdscr.addstr(8+i, 6, "?") else: - stdscr.addstr(8 + i, 6, str(i)) - stdscr.addstr(8 + i, 18, str(item).ljust(2)) - + stdscr.addstr(8+i, 6, str(i)) + stdscr.addstr(8+i, 18, str(item).ljust(2)) + # Uptime and processing data - stdscr.addstr( - 6, 35, "Since startup on " + l10n.formatTimestamp(startuptime)) - stdscr.addstr(7, 40, "Processed " + str( - state.numberOfMessagesProcessed).ljust(4) + " person-to-person messages.") - stdscr.addstr(8, 40, "Processed " + str( - state.numberOfBroadcastsProcessed).ljust(4) + " broadcast messages.") - stdscr.addstr(9, 40, "Processed " + str( - state.numberOfPubkeysProcessed).ljust(4) + " public keys.") - + stdscr.addstr(6, 35, "Since startup on "+l10n.formatTimestamp(startuptime, False)) + stdscr.addstr(7, 40, "Processed "+str(shared.numberOfMessagesProcessed).ljust(4)+" person-to-person messages.") + stdscr.addstr(8, 40, "Processed "+str(shared.numberOfBroadcastsProcessed).ljust(4)+" broadcast messages.") + stdscr.addstr(9, 40, "Processed "+str(shared.numberOfPubkeysProcessed).ljust(4)+" public keys.") + # Inventory data - stdscr.addstr(11, 35, "Inventory lookups per second: " + str(inventorydata).ljust(3)) - + stdscr.addstr(11, 35, "Inventory lookups per second: "+str(inventorydata).ljust(3)) + # Log stdscr.addstr(13, 6, "Log", curses.A_BOLD) n = log.count('\n') if n > 0: - lg = log.split('\n') + l = log.split('\n') if n > 512: - del lg[:(n - 256)] + del l[:(n-256)] logpad.erase() - n = len(lg) - for i, item in enumerate(lg): + n = len(l) + for i, item in enumerate(l): a = 0 - if item and item[0] == '!': + if len(item) > 0 and item[0] == '!': a = curses.color_pair(1) item = item[1:] logpad.addstr(i, 0, item, a) - logpad.refresh(n - curses.LINES + 2, 0, 14, 6, curses.LINES - 2, curses.COLS - 7) + logpad.refresh(n-curses.LINES+2, 0, 14, 6, curses.LINES-2, curses.COLS-7) stdscr.refresh() - def redraw(stdscr): - """Redraw menu""" stdscr.erase() stdscr.border() drawmenu(stdscr) stdscr.refresh() - - def dialogreset(stdscr): - """Resetting dialogue""" stdscr.clear() stdscr.keypad(1) curses.curs_set(0) - - -# pylint: disable=too-many-branches, too-many-statements def handlech(c, stdscr): - """Handle character given on the command-line interface""" - # pylint: disable=redefined-outer-name, too-many-nested-blocks, too-many-locals if c != curses.ERR: global inboxcur, addrcur, sentcur, subcur, abookcur, blackcur - if c in range(256): + if c in range(256): if chr(c) in '12345678': global menutab menutab = int(chr(c)) elif chr(c) == 'q': - global quit_ - quit_ = True + global quit + quit = True elif chr(c) == '\n': curses.curs_set(1) d = Dialog(dialog="dialog") if menutab == 1: set_background_title(d, "Inbox Message Dialog Box") - r, t = d.menu( - "Do what with \"" + inbox[inboxcur][5] + "\" from \"" + inbox[inboxcur][3] + "\"?", - choices=[ - ("1", "View message"), + r, t = d.menu("Do what with \""+inbox[inboxcur][5]+"\" from \""+inbox[inboxcur][3]+"\"?", + choices=[("1", "View message"), ("2", "Mark message as unread"), ("3", "Reply"), ("4", "Add sender to Address Book"), ("5", "Save message as text file"), ("6", "Move to trash")]) if r == d.DIALOG_OK: - if t == "1": # View - set_background_title( - d, - "\"" + - inbox[inboxcur][5] + - "\" from \"" + - inbox[inboxcur][3] + - "\" to \"" + - inbox[inboxcur][1] + - "\"") - data = "" # pyint: disable=redefined-outer-name + if t == "1": # View + set_background_title(d, "\""+inbox[inboxcur][5]+"\" from \""+inbox[inboxcur][3]+"\" to \""+inbox[inboxcur][1]+"\"") + data = "" ret = sqlQuery("SELECT message FROM inbox WHERE msgid=?", inbox[inboxcur][0]) if ret != []: for row in ret: @@ -366,16 +296,16 @@ def handlech(c, stdscr): data = shared.fixPotentiallyInvalidUTF8Data(data) msg = "" for i, item in enumerate(data.split("\n")): - msg += fill(item, replace_whitespace=False) + "\n" + msg += fill(item, replace_whitespace=False)+"\n" scrollbox(d, unicode(ascii(msg)), 30, 80) sqlExecute("UPDATE inbox SET read=1 WHERE msgid=?", inbox[inboxcur][0]) inbox[inboxcur][7] = 1 else: scrollbox(d, unicode("Could not fetch message.")) - elif t == "2": # Mark unread + elif t == "2": # Mark unread sqlExecute("UPDATE inbox SET read=0 WHERE msgid=?", inbox[inboxcur][0]) inbox[inboxcur][7] = 0 - elif t == "3": # Reply + elif t == "3": # Reply curses.curs_set(1) m = inbox[inboxcur] fromaddr = m[4] @@ -384,31 +314,29 @@ def handlech(c, stdscr): if fromaddr == item[2] and item[3] != 0: ischan = True break - if not addresses[i][1]: # pylint: disable=undefined-loop-variable - scrollbox(d, unicode( - "Sending address disabled, please either enable it" - "or choose a different address.")) + if not addresses[i][1]: + scrollbox(d, unicode("Sending address disabled, please either enable it or choose a different address.")) return toaddr = m[2] if ischan: toaddr = fromaddr - + subject = m[5] if not m[5][:4] == "Re: ": - subject = "Re: " + m[5] + subject = "Re: "+m[5] body = "" ret = sqlQuery("SELECT message FROM inbox WHERE msgid=?", m[0]) if ret != []: body = "\n\n------------------------------------------------------\n" for row in ret: body, = row - + sendMessage(fromaddr, toaddr, ischan, subject, body, True) dialogreset(stdscr) - elif t == "4": # Add to Address Book + elif t == "4": # Add to Address Book addr = inbox[inboxcur][4] - if addr not in [item[1] for i, item in enumerate(addrbook)]: - r, t = d.inputbox("Label for address \"" + addr + "\"") + if addr not in [item[1] for i,item in enumerate(addrbook)]: + r, t = d.inputbox("Label for address \""+addr+"\"") if r == d.DIALOG_OK: label = t sqlExecute("INSERT INTO addressbook VALUES (?,?)", label, addr) @@ -418,85 +346,61 @@ def handlech(c, stdscr): addrbook.reverse() else: scrollbox(d, unicode("The selected address is already in the Address Book.")) - elif t == "5": # Save message - set_background_title(d, "Save \"" + inbox[inboxcur][5] + "\" as text file") - r, t = d.inputbox("Filename", init=inbox[inboxcur][5] + ".txt") + elif t == "5": # Save message + set_background_title(d, "Save \""+inbox[inboxcur][5]+"\" as text file") + r, t = d.inputbox("Filename", init=inbox[inboxcur][5]+".txt") if r == d.DIALOG_OK: msg = "" ret = sqlQuery("SELECT message FROM inbox WHERE msgid=?", inbox[inboxcur][0]) if ret != []: for row in ret: msg, = row - fh = open(t, "a") # Open in append mode just in case + fh = open(t, "a") # Open in append mode just in case fh.write(msg) fh.close() else: scrollbox(d, unicode("Could not fetch message.")) - elif t == "6": # Move to trash + elif t == "6": # Move to trash sqlExecute("UPDATE inbox SET folder='trash' WHERE msgid=?", inbox[inboxcur][0]) del inbox[inboxcur] - scrollbox(d, unicode( - "Message moved to trash. There is no interface to view your trash," - " \nbut the message is still on disk if you are desperate to recover it.")) + scrollbox(d, unicode("Message moved to trash. There is no interface to view your trash, \nbut the message is still on disk if you are desperate to recover it.")) elif menutab == 2: a = "" - if addresses[addrcur][3] != 0: # if current address is a chan + if addresses[addrcur][3] != 0: # if current address is a chan a = addresses[addrcur][2] sendMessage(addresses[addrcur][2], a) elif menutab == 3: set_background_title(d, "Sent Messages Dialog Box") - r, t = d.menu( - "Do what with \"" + sentbox[sentcur][4] + "\" to \"" + sentbox[sentcur][0] + "\"?", - choices=[ - ("1", "View message"), + r, t = d.menu("Do what with \""+sentbox[sentcur][4]+"\" to \""+sentbox[sentcur][0]+"\"?", + choices=[("1", "View message"), ("2", "Move to trash")]) if r == d.DIALOG_OK: - if t == "1": # View - set_background_title( - d, - "\"" + - sentbox[sentcur][4] + - "\" from \"" + - sentbox[sentcur][3] + - "\" to \"" + - sentbox[sentcur][1] + - "\"") + if t == "1": # View + set_background_title(d, "\""+sentbox[sentcur][4]+"\" from \""+sentbox[sentcur][3]+"\" to \""+sentbox[sentcur][1]+"\"") data = "" - ret = sqlQuery( - "SELECT message FROM sent WHERE subject=? AND ackdata=?", - sentbox[sentcur][4], - sentbox[sentcur][6]) + ret = sqlQuery("SELECT message FROM sent WHERE subject=? AND ackdata=?", sentbox[sentcur][4], sentbox[sentcur][6]) if ret != []: for row in ret: data, = row data = shared.fixPotentiallyInvalidUTF8Data(data) msg = "" for i, item in enumerate(data.split("\n")): - msg += fill(item, replace_whitespace=False) + "\n" + msg += fill(item, replace_whitespace=False)+"\n" scrollbox(d, unicode(ascii(msg)), 30, 80) else: scrollbox(d, unicode("Could not fetch message.")) - elif t == "2": # Move to trash - sqlExecute( - "UPDATE sent SET folder='trash' WHERE subject=? AND ackdata=?", - sentbox[sentcur][4], - sentbox[sentcur][6]) + elif t == "2": # Move to trash + sqlExecute("UPDATE sent SET folder='trash' WHERE subject=? AND ackdata=?", sentbox[sentcur][4], sentbox[sentcur][6]) del sentbox[sentcur] - scrollbox(d, unicode( - "Message moved to trash. There is no interface to view your trash" - " \nbut the message is still on disk if you are desperate to recover it.")) + scrollbox(d, unicode("Message moved to trash. There is no interface to view your trash, \nbut the message is still on disk if you are desperate to recover it.")) elif menutab == 4: set_background_title(d, "Your Identities Dialog Box") if len(addresses) <= addrcur: - r, t = d.menu( - "Do what with addresses?", - choices=[ - ("1", "Create new address")]) + r, t = d.menu("Do what with addresses?", + choices=[("1", "Create new address")]) else: - r, t = d.menu( - "Do what with \"" + addresses[addrcur][0] + "\" : \"" + addresses[addrcur][2] + "\"?", - choices=[ - ("1", "Create new address"), + r, t = d.menu("Do what with \""+addresses[addrcur][0]+"\" : \""+addresses[addrcur][2]+"\"?", + choices=[("1", "Create new address"), ("2", "Send a message from this address"), ("3", "Rename"), ("4", "Enable"), @@ -504,41 +408,31 @@ def handlech(c, stdscr): ("6", "Delete"), ("7", "Special address behavior")]) if r == d.DIALOG_OK: - if t == "1": # Create new address + if t == "1": # Create new address set_background_title(d, "Create new address") - scrollbox( - d, unicode( - "Here you may generate as many addresses as you like.\n" - "Indeed, creating and abandoning addresses is encouraged.\n" - "Deterministic addresses have several pros and cons:\n" - "\nPros:\n" - " * You can recreate your addresses on any computer from memory\n" - " * You need not worry about backing up your keys.dat file as long as you" - " \n can remember your passphrase\n" - "Cons:\n" - " * You must remember (or write down) your passphrase in order to recreate" - " \n your keys if they are lost\n" - " * You must also remember the address version and stream numbers\n" - " * If you choose a weak passphrase someone may be able to brute-force it" - " \n and then send and receive messages as you")) - r, t = d.menu( - "Choose an address generation technique", - choices=[ - ("1", "Use a random number generator"), + scrollbox(d, unicode("Here you may generate as many addresses as you like.\n" + "Indeed, creating and abandoning addresses is encouraged.\n" + "Deterministic addresses have several pros and cons:\n" + "\nPros:\n" + " * You can recreate your addresses on any computer from memory\n" + " * You need not worry about backing up your keys.dat file as long as you \n can remember your passphrase\n" + "Cons:\n" + " * You must remember (or write down) your passphrase in order to recreate \n your keys if they are lost\n" + " * You must also remember the address version and stream numbers\n" + " * If you choose a weak passphrase someone may be able to brute-force it \n and then send and receive messages as you")) + r, t = d.menu("Choose an address generation technique", + choices=[("1", "Use a random number generator"), ("2", "Use a passphrase")]) if r == d.DIALOG_OK: if t == "1": set_background_title(d, "Randomly generate address") r, t = d.inputbox("Label (not shown to anyone except you)") label = "" - if r == d.DIALOG_OK and t: + if r == d.DIALOG_OK and len(t) > 0: label = t - r, t = d.menu( - "Choose a stream", - choices=[("1", "Use the most available stream"), - ("", "(Best if this is the first of many addresses you will create)"), - ("2", "Use the same stream as an existing address"), - ("", "(Saves you some bandwidth and processing power)")]) + r, t = d.menu("Choose a stream", + choices=[("1", "Use the most available stream"),("", "(Best if this is the first of many addresses you will create)"), + ("2", "Use the same stream as an existing address"),("", "(Saves you some bandwidth and processing power)")]) if r == d.DIALOG_OK: if t == "1": stream = 1 @@ -550,151 +444,117 @@ def handlech(c, stdscr): if r == d.DIALOG_OK: stream = decodeAddress(addrs[int(t)][1])[2] shorten = False - r, t = d.checklist( - "Miscellaneous options", - choices=[( - "1", - "Spend time shortening the address", - 1 if shorten else 0)]) + r, t = d.checklist("Miscellaneous options", + choices=[("1", "Spend time shortening the address", 1 if shorten else 0)]) if r == d.DIALOG_OK and "1" in t: shorten = True - queues.addressGeneratorQueue.put(( - "createRandomAddress", - 4, - stream, - label, - 1, - "", - shorten)) + queues.addressGeneratorQueue.put(("createRandomAddress", 4, stream, label, 1, "", shorten)) elif t == "2": set_background_title(d, "Make deterministic addresses") - r, t = d.passwordform( - "Enter passphrase", - [ - ("Passphrase", 1, 1, "", 2, 1, 64, 128), - ("Confirm passphrase", 3, 1, "", 4, 1, 64, 128)], + r, t = d.passwordform("Enter passphrase", + [("Passphrase", 1, 1, "", 2, 1, 64, 128), + ("Confirm passphrase", 3, 1, "", 4, 1, 64, 128)], form_height=4, insecure=True) if r == d.DIALOG_OK: if t[0] == t[1]: passphrase = t[0] - r, t = d.rangebox( - "Number of addresses to generate", - width=48, - min=1, - max=99, - init=8) + r, t = d.rangebox("Number of addresses to generate", + width=48, min=1, max=99, init=8) if r == d.DIALOG_OK: number = t stream = 1 shorten = False - r, t = d.checklist( - "Miscellaneous options", - choices=[( - "1", - "Spend time shortening the address", - 1 if shorten else 0)]) + r, t = d.checklist("Miscellaneous options", + choices=[("1", "Spend time shortening the address", 1 if shorten else 0)]) if r == d.DIALOG_OK and "1" in t: shorten = True - scrollbox( - d, unicode( - "In addition to your passphrase, be sure to remember the" - " following numbers:\n" - "\n * Address version number: " + str(4) + "\n" - " * Stream number: " + str(stream))) - queues.addressGeneratorQueue.put( - ('createDeterministicAddresses', 4, stream, - "unused deterministic address", number, - str(passphrase), shorten)) + scrollbox(d, unicode("In addition to your passphrase, be sure to remember the following numbers:\n" + "\n * Address version number: "+str(4)+"\n" + " * Stream number: "+str(stream))) + queues.addressGeneratorQueue.put(('createDeterministicAddresses', 4, stream, "unused deterministic address", number, str(passphrase), shorten)) else: scrollbox(d, unicode("Passphrases do not match")) - elif t == "2": # Send a message + elif t == "2": # Send a message a = "" - if addresses[addrcur][3] != 0: # if current address is a chan + if addresses[addrcur][3] != 0: # if current address is a chan a = addresses[addrcur][2] sendMessage(addresses[addrcur][2], a) - elif t == "3": # Rename address label + elif t == "3": # Rename address label a = addresses[addrcur][2] label = addresses[addrcur][0] r, t = d.inputbox("New address label", init=label) if r == d.DIALOG_OK: label = t - config.set(a, "label", label) + BMConfigParser().set(a, "label", label) # Write config - config.save() + BMConfigParser().save() addresses[addrcur][0] = label - elif t == "4": # Enable address + elif t == "4": # Enable address a = addresses[addrcur][2] - config.set(a, "enabled", "true") # Set config + BMConfigParser().set(a, "enabled", "true") # Set config # Write config - config.save() + BMConfigParser().save() # Change color - if config.safeGetBoolean(a, 'chan'): - addresses[addrcur][3] = 9 # orange - elif config.safeGetBoolean(a, 'mailinglist'): - addresses[addrcur][3] = 5 # magenta + if BMConfigParser().safeGetBoolean(a, 'chan'): + addresses[addrcur][3] = 9 # orange + elif BMConfigParser().safeGetBoolean(a, 'mailinglist'): + addresses[addrcur][3] = 5 # magenta else: - addresses[addrcur][3] = 0 # black + addresses[addrcur][3] = 0 # black addresses[addrcur][1] = True - shared.reloadMyAddressHashes() # Reload address hashes - elif t == "5": # Disable address + shared.reloadMyAddressHashes() # Reload address hashes + elif t == "5": # Disable address a = addresses[addrcur][2] - config.set(a, "enabled", "false") # Set config - addresses[addrcur][3] = 8 # Set color to gray + BMConfigParser().set(a, "enabled", "false") # Set config + addresses[addrcur][3] = 8 # Set color to gray # Write config - config.save() + BMConfigParser().save() addresses[addrcur][1] = False - shared.reloadMyAddressHashes() # Reload address hashes - elif t == "6": # Delete address + shared.reloadMyAddressHashes() # Reload address hashes + elif t == "6": # Delete address r, t = d.inputbox("Type in \"I want to delete this address\"", width=50) if r == d.DIALOG_OK and t == "I want to delete this address": - config.remove_section(addresses[addrcur][2]) - config.save() - del addresses[addrcur] - elif t == "7": # Special address behavior + BMConfigParser().remove_section(addresses[addrcur][2]) + BMConfigParser().save() + del addresses[addrcur] + elif t == "7": # Special address behavior a = addresses[addrcur][2] set_background_title(d, "Special address behavior") - if config.safeGetBoolean(a, "chan"): - scrollbox(d, unicode( - "This is a chan address. You cannot use it as a pseudo-mailing list.")) + if BMConfigParser().safeGetBoolean(a, "chan"): + scrollbox(d, unicode("This is a chan address. You cannot use it as a pseudo-mailing list.")) else: - m = config.safeGetBoolean(a, "mailinglist") - r, t = d.radiolist( - "Select address behavior", - choices=[ - ("1", "Behave as a normal address", not m), + m = BMConfigParser().safeGetBoolean(a, "mailinglist") + r, t = d.radiolist("Select address behavior", + choices=[("1", "Behave as a normal address", not m), ("2", "Behave as a pseudo-mailing-list address", m)]) if r == d.DIALOG_OK: - if t == "1" and m: - config.set(a, "mailinglist", "false") + if t == "1" and m == True: + BMConfigParser().set(a, "mailinglist", "false") if addresses[addrcur][1]: - addresses[addrcur][3] = 0 # Set color to black + addresses[addrcur][3] = 0 # Set color to black else: - addresses[addrcur][3] = 8 # Set color to gray - elif t == "2" and m is False: + addresses[addrcur][3] = 8 # Set color to gray + elif t == "2" and m == False: try: - mn = config.get(a, "mailinglistname") + mn = BMConfigParser().get(a, "mailinglistname") except ConfigParser.NoOptionError: - mn = "" + mn = "" r, t = d.inputbox("Mailing list name", init=mn) if r == d.DIALOG_OK: mn = t - config.set(a, "mailinglist", "true") - config.set(a, "mailinglistname", mn) - addresses[addrcur][3] = 6 # Set color to magenta + BMConfigParser().set(a, "mailinglist", "true") + BMConfigParser().set(a, "mailinglistname", mn) + addresses[addrcur][3] = 6 # Set color to magenta # Write config - config.save() + BMConfigParser().save() elif menutab == 5: set_background_title(d, "Subscriptions Dialog Box") if len(subscriptions) <= subcur: - r, t = d.menu( - "Do what with subscription to \"" + subscriptions[subcur][0] + "\"?", - choices=[ - ("1", "Add new subscription")]) + r, t = d.menu("Do what with subscription to \""+subscriptions[subcur][0]+"\"?", + choices=[("1", "Add new subscription")]) else: - r, t = d.menu( - "Do what with subscription to \"" + subscriptions[subcur][0] + "\"?", - choices=[ - ("1", "Add new subscription"), + r, t = d.menu("Do what with subscription to \""+subscriptions[subcur][0]+"\"?", + choices=[("1", "Add new subscription"), ("2", "Delete this subscription"), ("3", "Enable"), ("4", "Disable")]) @@ -715,39 +575,27 @@ def handlech(c, stdscr): sqlExecute("INSERT INTO subscriptions VALUES (?,?,?)", label, addr, True) shared.reloadBroadcastSendersForWhichImWatching() elif t == "2": - r, t = d.inputbox("Type in \"I want to delete this subscription\"") + r, t = d.inpuxbox("Type in \"I want to delete this subscription\"") if r == d.DIALOG_OK and t == "I want to delete this subscription": - sqlExecute( - "DELETE FROM subscriptions WHERE label=? AND address=?", - subscriptions[subcur][0], - subscriptions[subcur][1]) - shared.reloadBroadcastSendersForWhichImWatching() - del subscriptions[subcur] + sqlExecute("DELETE FROM subscriptions WHERE label=? AND address=?", subscriptions[subcur][0], subscriptions[subcur][1]) + shared.reloadBroadcastSendersForWhichImWatching() + del subscriptions[subcur] elif t == "3": - sqlExecute( - "UPDATE subscriptions SET enabled=1 WHERE label=? AND address=?", - subscriptions[subcur][0], - subscriptions[subcur][1]) + sqlExecute("UPDATE subscriptions SET enabled=1 WHERE label=? AND address=?", subscriptions[subcur][0], subscriptions[subcur][1]) shared.reloadBroadcastSendersForWhichImWatching() subscriptions[subcur][2] = True elif t == "4": - sqlExecute( - "UPDATE subscriptions SET enabled=0 WHERE label=? AND address=?", - subscriptions[subcur][0], - subscriptions[subcur][1]) + sqlExecute("UPDATE subscriptions SET enabled=0 WHERE label=? AND address=?", subscriptions[subcur][0], subscriptions[subcur][1]) shared.reloadBroadcastSendersForWhichImWatching() subscriptions[subcur][2] = False elif menutab == 6: set_background_title(d, "Address Book Dialog Box") if len(addrbook) <= abookcur: - r, t = d.menu( - "Do what with addressbook?", + r, t = d.menu("Do what with addressbook?", choices=[("3", "Add new address to Address Book")]) else: - r, t = d.menu( - "Do what with \"" + addrbook[abookcur][0] + "\" : \"" + addrbook[abookcur][1] + "\"", - choices=[ - ("1", "Send a message to this address"), + r, t = d.menu("Do what with \""+addrbook[abookcur][0]+"\" : \""+addrbook[abookcur][1]+"\"", + choices=[("1", "Send a message to this address"), ("2", "Subscribe to this address"), ("3", "Add new address to Address Book"), ("4", "Delete this address")]) @@ -769,8 +617,8 @@ def handlech(c, stdscr): r, t = d.inputbox("Input new address") if r == d.DIALOG_OK: addr = t - if addr not in [item[1] for i, item in enumerate(addrbook)]: - r, t = d.inputbox("Label for address \"" + addr + "\"") + if addr not in [item[1] for i,item in enumerate(addrbook)]: + r, t = d.inputbox("Label for address \""+addr+"\"") if r == d.DIALOG_OK: sqlExecute("INSERT INTO addressbook VALUES (?,?)", t, addr) # Prepend entry @@ -782,39 +630,25 @@ def handlech(c, stdscr): elif t == "4": r, t = d.inputbox("Type in \"I want to delete this Address Book entry\"") if r == d.DIALOG_OK and t == "I want to delete this Address Book entry": - sqlExecute( - "DELETE FROM addressbook WHERE label=? AND address=?", - addrbook[abookcur][0], - addrbook[abookcur][1]) + sqlExecute("DELETE FROM addressbook WHERE label=? AND address=?", addrbook[abookcur][0], addrbook[abookcur][1]) del addrbook[abookcur] elif menutab == 7: set_background_title(d, "Blacklist Dialog Box") - r, t = d.menu( - "Do what with \"" + blacklist[blackcur][0] + "\" : \"" + blacklist[blackcur][1] + "\"?", - choices=[ - ("1", "Delete"), + r, t = d.menu("Do what with \""+blacklist[blackcur][0]+"\" : \""+blacklist[blackcur][1]+"\"?", + choices=[("1", "Delete"), ("2", "Enable"), ("3", "Disable")]) if r == d.DIALOG_OK: if t == "1": r, t = d.inputbox("Type in \"I want to delete this Blacklist entry\"") if r == d.DIALOG_OK and t == "I want to delete this Blacklist entry": - sqlExecute( - "DELETE FROM blacklist WHERE label=? AND address=?", - blacklist[blackcur][0], - blacklist[blackcur][1]) + sqlExecute("DELETE FROM blacklist WHERE label=? AND address=?", blacklist[blackcur][0], blacklist[blackcur][1]) del blacklist[blackcur] elif t == "2": - sqlExecute( - "UPDATE blacklist SET enabled=1 WHERE label=? AND address=?", - blacklist[blackcur][0], - blacklist[blackcur][1]) + sqlExecute("UPDATE blacklist SET enabled=1 WHERE label=? AND address=?", blacklist[blackcur][0], blacklist[blackcur][1]) blacklist[blackcur][2] = True - elif t == "3": - sqlExecute( - "UPDATE blacklist SET enabled=0 WHERE label=? AND address=?", - blacklist[blackcur][0], - blacklist[blackcur][1]) + elif t== "3": + sqlExecute("UPDATE blacklist SET enabled=0 WHERE label=? AND address=?", blacklist[blackcur][0], blacklist[blackcur][1]) blacklist[blackcur][2] = False dialogreset(stdscr) else: @@ -832,17 +666,17 @@ def handlech(c, stdscr): if menutab == 7 and blackcur > 0: blackcur -= 1 elif c == curses.KEY_DOWN: - if menutab == 1 and inboxcur < len(inbox) - 1: + if menutab == 1 and inboxcur < len(inbox)-1: inboxcur += 1 - if (menutab == 2 or menutab == 4) and addrcur < len(addresses) - 1: + if (menutab == 2 or menutab == 4) and addrcur < len(addresses)-1: addrcur += 1 - if menutab == 3 and sentcur < len(sentbox) - 1: + if menutab == 3 and sentcur < len(sentbox)-1: sentcur += 1 - if menutab == 5 and subcur < len(subscriptions) - 1: + if menutab == 5 and subcur < len(subscriptions)-1: subcur += 1 - if menutab == 6 and abookcur < len(addrbook) - 1: + if menutab == 6 and abookcur < len(addrbook)-1: abookcur += 1 - if menutab == 7 and blackcur < len(blacklist) - 1: + if menutab == 7 and blackcur < len(blacklist)-1: blackcur += 1 elif c == curses.KEY_HOME: if menutab == 1: @@ -859,47 +693,38 @@ def handlech(c, stdscr): blackcur = 0 elif c == curses.KEY_END: if menutab == 1: - inboxcur = len(inbox) - 1 + inboxcur = len(inbox)-1 if menutab == 2 or menutab == 4: - addrcur = len(addresses) - 1 + addrcur = len(addresses)-1 if menutab == 3: - sentcur = len(sentbox) - 1 + sentcur = len(sentbox)-1 if menutab == 5: - subcur = len(subscriptions) - 1 + subcur = len(subscriptions)-1 if menutab == 6: - abookcur = len(addrbook) - 1 + abookcur = len(addrbook)-1 if menutab == 7: - blackcur = len(blackcur) - 1 + blackcur = len(blackcur)-1 redraw(stdscr) - - -# pylint: disable=too-many-locals, too-many-arguments def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=False): - """Method for message sending""" if sender == "": return d = Dialog(dialog="dialog") set_background_title(d, "Send a message") if recv == "": - r, t = d.inputbox( - "Recipient address (Cancel to load from the Address Book or leave blank to broadcast)", - 10, - 60) + r, t = d.inputbox("Recipient address (Cancel to load from the Address Book or leave blank to broadcast)", 10, 60) if r != d.DIALOG_OK: global menutab menutab = 6 return recv = t - if broadcast is None and sender != recv: - r, t = d.radiolist( - "How to send the message?", - choices=[ - ("1", "Send to one or more specific people", 1), + if broadcast == None and sender != recv: + r, t = d.radiolist("How to send the message?", + choices=[("1", "Send to one or more specific people", 1), ("2", "Broadcast to everyone who is subscribed to your address", 0)]) if r != d.DIALOG_OK: return broadcast = False - if t == "2": # Broadcast + if t == "2": # Broadcast broadcast = True if subject == "" or reply: r, t = d.inputbox("Message subject", width=60, init=subject) @@ -915,12 +740,12 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F if not broadcast: recvlist = [] - for _, item in enumerate(recv.replace(",", ";").split(";")): + for i, item in enumerate(recv.replace(",", ";").split(";")): recvlist.append(item.strip()) - list(set(recvlist)) # Remove exact duplicates + list(set(recvlist)) # Remove exact duplicates for addr in recvlist: if addr != "": - status, version, stream = decodeAddress(addr)[:3] + status, version, stream, ripe = decodeAddress(addr) if status != "success": set_background_title(d, "Recipient address error") err = "Could not decode" + addr + " : " + status + "\n\n" @@ -931,17 +756,13 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F elif status == "invalidcharacters": err += "The address contains invalid characters." elif status == "versiontoohigh": - err += ("The address version is too high. Either you need to upgrade your Bitmessage software" - " or your acquaintance is doing something clever.") + err += "The address version is too high. Either you need to upgrade your Bitmessage software or your acquaintance is doing something clever." elif status == "ripetooshort": - err += ("Some data encoded in the address is too short. There might be something wrong with" - " the software of your acquaintance.") + err += "Some data encoded in the address is too short. There might be something wrong with the software of your acquaintance." elif status == "ripetoolong": - err += ("Some data encoded in the address is too long. There might be something wrong with" - " the software of your acquaintance.") + err += "Some data encoded in the address is too long. There might be something wrong with the software of your acquaintance." elif status == "varintmalformed": - err += ("Some data encoded in the address is malformed. There might be something wrong with" - " the software of your acquaintance.") + err += "Some data encoded in the address is malformed. There might be something wrong with the software of your acquaintance." else: err += "It is unknown what is wrong with the address." scrollbox(d, unicode(err)) @@ -949,44 +770,68 @@ def sendMessage(sender="", recv="", broadcast=None, subject="", body="", reply=F addr = addBMIfNotPresent(addr) if version > 4 or version <= 1: set_background_title(d, "Recipient address error") - scrollbox(d, unicode( - "Could not understand version number " + - version + - "of address" + - addr + - ".")) + scrollbox(d, unicode("Could not understand version number " + version + "of address" + addr + ".")) continue if stream > 1 or stream == 0: set_background_title(d, "Recipient address error") - scrollbox(d, unicode( - "Bitmessage currently only supports stream numbers of 1," - "unlike as requested for address " + addr + ".")) + scrollbox(d, unicode("Bitmessage currently only supports stream numbers of 1, unlike as requested for address " + addr + ".")) continue - if not network.stats.connectedHostsList(): + if len(shared.connectedHostsList) == 0: set_background_title(d, "Not connected warning") scrollbox(d, unicode("Because you are not currently connected to the network, ")) - helper_sent.insert( - toAddress=addr, fromAddress=sender, subject=subject, message=body) + stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') + ackdata = genAckPayload(streamNumber, stealthLevel) + sqlExecute( + "INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + "", + addr, + ripe, + sender, + subject, + body, + ackdata, + int(time.time()), # sentTime (this will never change) + int(time.time()), # lastActionTime + 0, # sleepTill time. This will get set when the POW gets done. + "msgqueued", + 0, # retryNumber + "sent", + 2, # encodingType + BMConfigParser().getint('bitmessagesettings', 'ttl')) queues.workerQueue.put(("sendmessage", addr)) - else: # Broadcast + else: # Broadcast if recv == "": set_background_title(d, "Empty sender error") scrollbox(d, unicode("You must specify an address to send the message from.")) else: # dummy ackdata, no need for stealth - helper_sent.insert( - fromAddress=sender, subject=subject, - message=body, status='broadcastqueued') + ackdata = genAckPayload(streamNumber, 0) + recv = BROADCAST_STR + ripe = "" + sqlExecute( + "INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + "", + recv, + ripe, + sender, + subject, + body, + ackdata, + int(time.time()), # sentTime (this will never change) + int(time.time()), # lastActionTime + 0, # sleepTill time. This will get set when the POW gets done. + "broadcastqueued", + 0, # retryNumber + "sent", # folder + 2, # encodingType + BMConfigParser().getint('bitmessagesettings', 'ttl')) queues.workerQueue.put(('sendbroadcast', '')) - -# pylint: disable=redefined-outer-name, too-many-locals def loadInbox(): - """Load the list of messages""" sys.stdout = sys.__stdout__ print("Loading inbox messages...") sys.stdout = printlog - + where = "toaddress || fromaddress || subject || message" what = "%%" ret = sqlQuery("""SELECT msgid, toaddress, fromaddress, subject, received, read @@ -996,29 +841,29 @@ def loadInbox(): for row in ret: msgid, toaddr, fromaddr, subject, received, read = row subject = ascii(shared.fixPotentiallyInvalidUTF8Data(subject)) - + # Set label for to address try: if toaddr == BROADCAST_STR: tolabel = BROADCAST_STR else: - tolabel = config.get(toaddr, "label") - except: # noqa:E722 + tolabel = BMConfigParser().get(toaddr, "label") + except: tolabel = "" if tolabel == "": tolabel = toaddr tolabel = shared.fixPotentiallyInvalidUTF8Data(tolabel) - + # Set label for from address fromlabel = "" - if config.has_section(fromaddr): - fromlabel = config.get(fromaddr, "label") - if fromlabel == "": # Check Address Book + if BMConfigParser().has_section(fromaddr): + fromlabel = BMConfigParser().get(fromaddr, "label") + if fromlabel == "": # Check Address Book qr = sqlQuery("SELECT label FROM addressbook WHERE address=?", fromaddr) if qr != []: for r in qr: fromlabel, = r - if fromlabel == "": # Check Subscriptions + if fromlabel == "": # Check Subscriptions qr = sqlQuery("SELECT label FROM subscriptions WHERE address=?", fromaddr) if qr != []: for r in qr: @@ -1026,20 +871,16 @@ def loadInbox(): if fromlabel == "": fromlabel = fromaddr fromlabel = shared.fixPotentiallyInvalidUTF8Data(fromlabel) - + # Load into array - inbox.append([ - msgid, tolabel, toaddr, fromlabel, fromaddr, subject, - l10n.formatTimestamp(received), read]) + inbox.append([msgid, tolabel, toaddr, fromlabel, fromaddr, subject, + l10n.formatTimestamp(received, False), read]) inbox.reverse() - - def loadSent(): - """Load the messages that sent""" sys.stdout = sys.__stdout__ print("Loading sent messages...") sys.stdout = printlog - + where = "toaddress || fromaddress || subject || message" what = "%%" ret = sqlQuery("""SELECT toaddress, fromaddress, subject, status, ackdata, lastactiontime @@ -1049,7 +890,7 @@ def loadSent(): for row in ret: toaddr, fromaddr, subject, status, ackdata, lastactiontime = row subject = ascii(shared.fixPotentiallyInvalidUTF8Data(subject)) - + # Set label for to address tolabel = "" qr = sqlQuery("SELECT label FROM addressbook WHERE address=?", toaddr) @@ -1062,18 +903,18 @@ def loadSent(): for r in qr: tolabel, = r if tolabel == "": - if config.has_section(toaddr): - tolabel = config.get(toaddr, "label") + if BMConfigParser().has_section(toaddr): + tolabel = BMConfigParser().get(toaddr, "label") if tolabel == "": tolabel = toaddr - + # Set label for from address fromlabel = "" - if config.has_section(fromaddr): - fromlabel = config.get(fromaddr, "label") + if BMConfigParser().has_section(fromaddr): + fromlabel = BMConfigParser().get(fromaddr, "label") if fromlabel == "": fromlabel = fromaddr - + # Set status string if status == "awaitingpubkey": statstr = "Waiting for their public key. Will request it again soon" @@ -1082,21 +923,21 @@ def loadSent(): elif status == "msgqueued": statstr = "Message queued" elif status == "msgsent": - t = l10n.formatTimestamp(lastactiontime) - statstr = "Message sent at " + t + ".Waiting for acknowledgement." + t = l10n.formatTimestamp(lastactiontime, False) + statstr = "Message sent at "+t+".Waiting for acknowledgement." elif status == "msgsentnoackexpected": - t = l10n.formatTimestamp(lastactiontime) - statstr = "Message sent at " + t + "." + t = l10n.formatTimestamp(lastactiontime, False) + statstr = "Message sent at "+t+"." elif status == "doingmsgpow": statstr = "The proof of work required to send the message has been queued." elif status == "ackreceived": - t = l10n.formatTimestamp(lastactiontime) - statstr = "Acknowledgment of the message received at " + t + "." + t = l10n.formatTimestamp(lastactiontime, False) + statstr = "Acknowledgment of the message received at "+t+"." elif status == "broadcastqueued": statstr = "Broadcast queued." elif status == "broadcastsent": - t = l10n.formatTimestamp(lastactiontime) - statstr = "Broadcast sent at " + t + "." + t = l10n.formatTimestamp(lastactiontime, False) + statstr = "Broadcast sent at "+t+"." elif status == "forcepow": statstr = "Forced difficulty override. Message will start sending soon." elif status == "badkey": @@ -1104,49 +945,33 @@ def loadSent(): elif status == "toodifficult": statstr = "Error: The work demanded by the recipient is more difficult than you are willing to do." else: - t = l10n.formatTimestamp(lastactiontime) - statstr = "Unknown status " + status + " at " + t + "." - + t = l10n.formatTimestamp(lastactiontime, False) + statstr = "Unknown status "+status+" at "+t+"." + # Load into array - sentbox.append([ - tolabel, - toaddr, - fromlabel, - fromaddr, - subject, - statstr, - ackdata, - l10n.formatTimestamp(lastactiontime)]) + sentbox.append([tolabel, toaddr, fromlabel, fromaddr, subject, statstr, ackdata, + l10n.formatTimestamp(lastactiontime, False)]) sentbox.reverse() - - def loadAddrBook(): - """Load address book""" sys.stdout = sys.__stdout__ print("Loading address book...") sys.stdout = printlog - + ret = sqlQuery("SELECT label, address FROM addressbook") for row in ret: label, addr = row label = shared.fixPotentiallyInvalidUTF8Data(label) addrbook.append([label, addr]) addrbook.reverse() - - def loadSubscriptions(): - """Load subscription functionality""" ret = sqlQuery("SELECT label, address, enabled FROM subscriptions") for row in ret: label, address, enabled = row subscriptions.append([label, address, enabled]) subscriptions.reverse() - - def loadBlackWhiteList(): - """load black/white list""" global bwtype - bwtype = config.get("bitmessagesettings", "blackwhitelist") + bwtype = BMConfigParser().get("bitmessagesettings", "blackwhitelist") if bwtype == "black": ret = sqlQuery("SELECT label, address, enabled FROM blacklist") else: @@ -1156,83 +981,79 @@ def loadBlackWhiteList(): blacklist.append([label, address, enabled]) blacklist.reverse() - def runwrapper(): - """Main method""" sys.stdout = printlog - # sys.stderr = errlog - + #sys.stderr = errlog + + # Load messages from database loadInbox() loadSent() loadAddrBook() loadSubscriptions() loadBlackWhiteList() - + stdscr = curses.initscr() - + global logpad logpad = curses.newpad(1024, curses.COLS) - + stdscr.nodelay(0) curses.curs_set(0) stdscr.timeout(1000) - + curses.wrapper(run) doShutdown() - def run(stdscr): - """Main loop""" # Schedule inventory lookup data resetlookups() - + # Init color pairs if curses.has_colors(): - curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) # red - curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) # green - curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK) # yellow - curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK) # blue - curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK) # magenta - curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK) # cyan - curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_BLACK) # white + curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK) # red + curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) # green + curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK) # yellow + curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK) # blue + curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK) # magenta + curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK) # cyan + curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_BLACK) # white if curses.can_change_color(): - curses.init_color(8, 500, 500, 500) # gray + curses.init_color(8, 500, 500, 500) # gray curses.init_pair(8, 8, 0) - curses.init_color(9, 844, 465, 0) # orange + curses.init_color(9, 844, 465, 0) # orange curses.init_pair(9, 9, 0) else: - curses.init_pair(8, curses.COLOR_WHITE, curses.COLOR_BLACK) # grayish - curses.init_pair(9, curses.COLOR_YELLOW, curses.COLOR_BLACK) # orangish - + curses.init_pair(8, curses.COLOR_WHITE, curses.COLOR_BLACK) # grayish + curses.init_pair(9, curses.COLOR_YELLOW, curses.COLOR_BLACK) # orangish + # Init list of address in 'Your Identities' tab - configSections = config.addresses() + configSections = BMConfigParser().addressses() for addressInKeysFile in configSections: - isEnabled = config.getboolean(addressInKeysFile, "enabled") - addresses.append([config.get(addressInKeysFile, "label"), isEnabled, addressInKeysFile]) + 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 config.safeGetBoolean(addressInKeysFile, 'chan'): - addresses[len(addresses) - 1].append(9) # orange - elif config.safeGetBoolean(addressInKeysFile, 'mailinglist'): - addresses[len(addresses) - 1].append(5) # magenta + 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[len(addresses)-1].append(0) # black addresses.reverse() - + stdscr.clear() redraw(stdscr) - while quit_ is False: + while quit == False: drawtab(stdscr) handlech(stdscr.getch(), stdscr) - def doShutdown(): - """Shutting the app down""" sys.stdout = sys.__stdout__ print("Shutting down...") sys.stdout = printlog shutdown.doCleanShutdown() sys.stdout = sys.__stdout__ sys.stderr = sys.__stderr__ - os._exit(0) # pylint: disable=protected-access + + os._exit(0) diff --git a/src/bitmessagekivy/__init__.py b/src/bitmessagekivy/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/bitmessagekivy/base_navigation.py b/src/bitmessagekivy/base_navigation.py deleted file mode 100644 index 5f6b1aa5..00000000 --- a/src/bitmessagekivy/base_navigation.py +++ /dev/null @@ -1,110 +0,0 @@ -# pylint: disable=unused-argument, no-name-in-module, too-few-public-methods -""" - Base class for Navigation Drawer -""" - -from kivy.lang import Observable - -from kivy.properties import ( - BooleanProperty, - NumericProperty, - StringProperty -) -from kivy.metrics import dp -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.spinner import Spinner - -from kivy.clock import Clock -from kivy.core.window import Window - -from kivymd.uix.list import ( - OneLineAvatarIconListItem, - OneLineListItem -) - -from pybitmessage.bmconfigparser import config - - -class BaseLanguage(Observable): - """UI Language""" - observers = [] - lang = None - - def __init__(self, defaultlang): - super(BaseLanguage, self).__init__() - self.ugettext = None - self.lang = defaultlang - - @staticmethod - def _(text): - return text - - -class BaseNavigationItem(OneLineAvatarIconListItem): - """NavigationItem class for kivy Ui""" - badge_text = StringProperty() - icon = StringProperty() - active = BooleanProperty(False) - - def currentlyActive(self): - """Currenly active""" - for nav_obj in self.parent.children: - nav_obj.active = False - self.active = True - - -class BaseNavigationDrawerDivider(OneLineListItem): - """ - A small full-width divider that can be placed - in the :class:`MDNavigationDrawer` - """ - - disabled = True - divider = None - _txt_top_pad = NumericProperty(dp(8)) - _txt_bot_pad = NumericProperty(dp(8)) - - def __init__(self, **kwargs): - super(BaseNavigationDrawerDivider, self).__init__(**kwargs) - self.height = dp(16) - - -class BaseNavigationDrawerSubheader(OneLineListItem): - """ - A subheader for separating content in :class:`MDNavigationDrawer` - - Works well alongside :class:`NavigationDrawerDivider` - """ - - disabled = True - divider = None - theme_text_color = 'Secondary' - - -class BaseContentNavigationDrawer(BoxLayout): - """ContentNavigationDrawer class for kivy Uir""" - - def __init__(self, *args, **kwargs): - """Method used for contentNavigationDrawer""" - super(BaseContentNavigationDrawer, self).__init__(*args, **kwargs) - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schdule for class contentNavigationDrawer""" - self.ids.scroll_y.bind(scroll_y=self.check_scroll_y) - - def check_scroll_y(self, instance, somethingelse): - """show data on scroll down""" - if self.ids.identity_dropdown.is_open: - self.ids.identity_dropdown.is_open = False - - -class BaseIdentitySpinner(Spinner): - """Base Class for Identity Spinner(Dropdown)""" - - def __init__(self, *args, **kwargs): - """Method used for setting size of spinner""" - super(BaseIdentitySpinner, self).__init__(*args, **kwargs) - self.dropdown_cls.max_height = Window.size[1] / 3 - self.values = list(addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')) diff --git a/src/bitmessagekivy/baseclass/__init__.py b/src/bitmessagekivy/baseclass/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py deleted file mode 100644 index f18a0142..00000000 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ /dev/null @@ -1,164 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, import-error -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module - -""" -addressbook.py -============== - -All saved addresses are managed in Addressbook - -""" - -import os -import logging -from functools import partial - -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy import kivy_helper_search -from pybitmessage.bitmessagekivy.baseclass.common import ( - avatar_image_first_letter, toast, empty_screen_label, - ThemeClsColor, SwipeToDeleteItem, kivy_state_variables -) -from pybitmessage.bitmessagekivy.baseclass.popup import SavedAddressDetailPopup -from pybitmessage.bitmessagekivy.baseclass.addressbook_widgets import HelperAddressBook -from pybitmessage.helper_sql import sqlExecute - -logger = logging.getLogger('default') - - -class AddressBook(Screen, HelperAddressBook): - """AddressBook Screen class for kivy Ui""" - - queryreturn = ListProperty() - has_refreshed = True - address_label = StringProperty() - address = StringProperty() - label_str = "No contact Address found yet......" - no_search_res_found = "No search result found" - - def __init__(self, *args, **kwargs): - """Getting AddressBook Details""" - super(AddressBook, self).__init__(*args, **kwargs) - self.addbook_popup = None - self.kivy_state = kivy_state_variables() - - def loadAddresslist(self, account, where="", what=""): - """Clock Schdule for method AddressBook""" - if self.kivy_state.searching_text: - self.ids.scroll_y.scroll_y = 1.0 - where = ['label', 'address'] - what = self.kivy_state.searching_text - xAddress = '' - self.ids.tag_label.text = '' - self.queryreturn = kivy_helper_search.search_sql( - xAddress, account, "addressbook", where, what, False) - self.queryreturn = [obj for obj in reversed(self.queryreturn)] - if self.queryreturn: - self.ids.tag_label.text = 'Address Book' - self.has_refreshed = True - self.set_mdList(0, 20) - self.ids.scroll_y.bind(scroll_y=self.check_scroll_y) - else: - self.ids.ml.add_widget(empty_screen_label(self.label_str, self.no_search_res_found)) - - def set_mdList(self, start_index, end_index): - """Creating the mdList""" - for item in self.queryreturn[start_index:end_index]: - message_row = SwipeToDeleteItem( - text=item[0], - ) - listItem = message_row.ids.content - listItem.secondary_text = item[1] - listItem.theme_text_color = "Custom" - listItem.text_color = ThemeClsColor - image = os.path.join( - self.kivy_state.imageDir, "text_images", "{}.png".format(avatar_image_first_letter(item[0].strip())) - ) - message_row.ids.avater_img.source = image - listItem.bind(on_release=partial( - self.addBook_detail, item[1], item[0], message_row)) - message_row.ids.delete_msg.bind(on_press=partial(self.delete_address, item[1])) - self.ids.ml.add_widget(message_row) - - def check_scroll_y(self, instance, somethingelse): - """Load data on scroll""" - if self.ids.scroll_y.scroll_y <= -0.0 and self.has_refreshed: - self.ids.scroll_y.scroll_y = 0.06 - exist_addresses = len(self.ids.ml.children) - if exist_addresses != len(self.queryreturn): - self.update_addressBook_on_scroll(exist_addresses) - self.has_refreshed = ( - True if exist_addresses != len(self.queryreturn) else False - ) - - def update_addressBook_on_scroll(self, exist_addresses): - """Load more data on scroll down""" - self.set_mdList(exist_addresses, exist_addresses + 5) - - @staticmethod - def refreshs(*args): - """Refresh the Widget""" - - # @staticmethod - def addBook_detail(self, address, label, instance, *args): - """Addressbook details""" - if instance.state == 'closed': - instance.ids.delete_msg.disabled = True - if instance.open_progress == 0.0: - obj = SavedAddressDetailPopup() - self.address_label = obj.address_label = label - self.address = obj.address = address - width = .9 if platform == 'android' else .8 - self.addbook_popup = self.address_detail_popup( - obj, self.send_message_to, self.update_addbook_label, - self.close_pop, width) - self.addbook_popup.auto_dismiss = False - self.addbook_popup.open() - else: - instance.ids.delete_msg.disabled = False - - def delete_address(self, address, instance, *args): - """Delete inbox mail from inbox listing""" - self.ids.ml.remove_widget(instance.parent.parent) - # if len(self.ids.ml.children) == 0: - if self.ids.ml.children is not None: - self.ids.tag_label.text = '' - sqlExecute( - "DELETE FROM addressbook WHERE address = ?", address) - toast('Address Deleted') - - def close_pop(self, instance): - """Pop is Canceled""" - self.addbook_popup.dismiss() - toast('Canceled') - - def update_addbook_label(self, instance): - """Updating the label of address book address""" - address_list = kivy_helper_search.search_sql(folder="addressbook") - stored_labels = [labels[0] for labels in address_list] - add_dict = dict(address_list) - label = str(self.addbook_popup.content_cls.ids.add_label.text) - if label in stored_labels and self.address == add_dict[label]: - stored_labels.remove(label) - if label and label not in stored_labels: - sqlExecute(""" - UPDATE addressbook - SET label = ? - WHERE address = ?""", label, self.addbook_popup.content_cls.address) - App.get_running_app().root.ids.id_addressbook.ids.ml.clear_widgets() - App.get_running_app().root.ids.id_addressbook.loadAddresslist(None, 'All', '') - self.addbook_popup.dismiss() - toast('Saved') - - def send_message_to(self, instance): - """Method used to fill to_address of composer autofield""" - App.get_running_app().set_navbar_for_composer() - self.compose_message(None, self.address) - self.addbook_popup.dismiss() diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py deleted file mode 100644 index 3654dfa3..00000000 --- a/src/bitmessagekivy/baseclass/addressbook_widgets.py +++ /dev/null @@ -1,50 +0,0 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods -""" -Addressbook widgets are here. -""" - -from kivy.app import App -from kivymd.uix.button import MDRaisedButton -from kivymd.uix.dialog import MDDialog - - -class HelperAddressBook(object): - """Widget used in Addressbook are here""" - - @staticmethod - def address_detail_popup(obj, send_message, update_address, close_popup, width): - """This function shows the address's details and opens the popup.""" - show_dialogue = MDDialog( - type="custom", - size_hint=(width, .25), - content_cls=obj, - buttons=[ - MDRaisedButton( - text="Send message to", - on_release=send_message, - ), - MDRaisedButton( - text="Save", - on_release=update_address, - ), - MDRaisedButton( - text="Cancel", - on_release=close_popup, - ), - ], - ) - return show_dialogue - - @staticmethod - def compose_message(from_addr=None, to_addr=None): - """This UI independent method for message sending to reciever""" - window_obj = App.get_runnint_app().root.ids - if to_addr: - window_obj.id_create.children[1].ids.txt_input.text = to_addr - if from_addr: - window_obj.id_create.children[1].ids.txt_input.text = from_addr - window_obj.id_create.children[1].ids.ti.text = '' - window_obj.id_create.children[1].ids.composer_dropdown.text = 'Select' - window_obj.id_create.children[1].ids.subject.text = '' - window_obj.id_create.children[1].ids.body.text = '' - window_obj.scr_mngr.current = 'create' diff --git a/src/bitmessagekivy/baseclass/allmail.py b/src/bitmessagekivy/baseclass/allmail.py deleted file mode 100644 index d30310d8..00000000 --- a/src/bitmessagekivy/baseclass/allmail.py +++ /dev/null @@ -1,67 +0,0 @@ -# pylint: disable=import-error, no-name-in-module -# pylint: disable=unused-argument, no-member, attribute-defined-outside-init - -""" -allmail.py -============== - -All mails are managed in allmail screen - -""" - -from kivy.clock import Clock -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.baseclass.common import ( - show_limited_cnt, empty_screen_label, kivy_state_variables, -) - -import logging -logger = logging.getLogger('default') - - -class Allmails(Screen): - """Allmails Screen for kivy Ui""" - data = ListProperty() - has_refreshed = True - all_mails = ListProperty() - account = StringProperty() - label_str = 'yet no message for this account!!!!!!!!!!!!!' - - def __init__(self, *args, **kwargs): - """Method Parsing the address""" - super(Allmails, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schdule for method all mails""" - self.loadMessagelist() - logger.debug(dt) - - def loadMessagelist(self): - """Load Inbox, Sent anf Draft list of messages""" - self.account = self.kivy_state.selected_address - self.ids.tag_label.text = '' - if self.all_mails: - self.ids.tag_label.text = 'All Mails' - self.kivy_state.all_count = str( - int(self.kivy_state.sent_count) + int(self.kivy_state.inbox_count)) - self.set_AllmailCnt(self.kivy_state.all_count) - else: - self.set_AllmailCnt('0') - self.ids.ml.add_widget(empty_screen_label(self.label_str)) - - @staticmethod - def set_AllmailCnt(Count): - """This method is used to set allmails message count""" - allmailCnt_obj = App.get_running_app().root.ids.content_drawer.ids.allmail_cnt - allmailCnt_obj.ids.badge_txt.text = show_limited_cnt(int(Count)) diff --git a/src/bitmessagekivy/baseclass/chat.py b/src/bitmessagekivy/baseclass/chat.py deleted file mode 100644 index c5f94b8a..00000000 --- a/src/bitmessagekivy/baseclass/chat.py +++ /dev/null @@ -1,11 +0,0 @@ -# pylint: disable=import-error, no-name-in-module, too-few-public-methods, too-many-ancestors - -''' - Chats are managed in this screen -''' - -from kivy.uix.screenmanager import Screen - - -class Chat(Screen): - """Chat Screen class for kivy Ui""" diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py deleted file mode 100644 index 2cf4d76b..00000000 --- a/src/bitmessagekivy/baseclass/common.py +++ /dev/null @@ -1,236 +0,0 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error, unused-argument -""" - All Common widgets of kivy are managed here. -""" - -import os -from datetime import datetime - -from kivy.core.window import Window -from kivy.metrics import dp -from kivy.uix.image import Image -from kivy.properties import ( - NumericProperty, - StringProperty, - ListProperty -) -from kivy.app import App - -from kivymd.uix.list import ( - ILeftBody, - IRightBodyTouch, -) -from kivymd.uix.label import MDLabel -from kivymd.toast import kivytoast -from kivymd.uix.card import MDCardSwipe -from kivymd.uix.chip import MDChip -from kivymd.uix.dialog import MDDialog -from kivymd.uix.button import MDFlatButton - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bmconfigparser import config - -ThemeClsColor = [0.12, 0.58, 0.95, 1] - - -data_screens = { - "MailDetail": { - "kv_string": "maildetail", - "Factory": "MailDetail()", - "name_screen": "mailDetail", - "object": 0, - "Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail", - }, -} - - -def load_image_path(): - """Return the path of kivy images""" - image_path = os.path.abspath(os.path.join('pybitmessage', 'images', 'kivy')) - return image_path - - -def get_identity_list(): - """Get list of identities and access 'identity_list' variable in .kv file""" - identity_list = ListProperty( - addr for addr in config.addresses() if config.getboolean(str(addr), 'enabled') - ) - return identity_list - - -def kivy_state_variables(): - """Return kivy_state variable""" - kivy_running_app = App.get_running_app() - kivy_state = kivy_running_app.kivy_state_obj - return kivy_state - - -def chip_tag(text): - """Create a new ChipTag""" - obj = MDChip() - # obj.size_hint = (None, None) - obj.size_hint = (0.16 if platform == "android" else 0.08, None) - obj.text = text - obj.icon = "" - obj.pos_hint = { - "center_x": 0.91 if platform == "android" else 0.94, - "center_y": 0.3 - } - obj.height = dp(18) - obj.text_color = (1, 1, 1, 1) - obj.radius = [8] - return obj - - -def toast(text): - """Method will display the toast message""" - kivytoast.toast(text) - - -def show_limited_cnt(total_msg): - """This method set the total count limit in badge_text""" - max_msg_count = '99+' - total_msg_limit = 99 - return max_msg_count if total_msg > total_msg_limit else str(total_msg) - - -def avatar_image_first_letter(letter_string): - """Returns first letter for the avatar image""" - try: - image_letter = letter_string.title()[0] - if image_letter.isalnum(): - return image_letter - return '!' - except IndexError: - return '!' - - -def add_time_widget(time): # pylint: disable=redefined-outer-name, W0201 - """This method is used to create TimeWidget""" - action_time = TimeTagRightSampleWidget( - text=str(show_time_history(time)), - font_style="Caption", - size=[120, 140] if platform == "android" else [64, 80], - ) - action_time.font_size = "11sp" - return action_time - - -def show_time_history(act_time): - """This method is used to return the message sent or receive time""" - action_time = datetime.fromtimestamp(int(act_time)) - crnt_date = datetime.now() - duration = crnt_date - action_time - if duration.days < 1: - return action_time.strftime("%I:%M %p") - if duration.days < 365: - return action_time.strftime("%d %b") - return action_time.strftime("%d/%m/%Y") - - -# pylint: disable=too-few-public-methods -class AvatarSampleWidget(ILeftBody, Image): - """AvatarSampleWidget class for kivy Ui""" - - -class TimeTagRightSampleWidget(IRightBodyTouch, MDLabel): - """TimeTagRightSampleWidget class for Ui""" - - -class SwipeToDeleteItem(MDCardSwipe): - """Swipe delete class for App UI""" - text = StringProperty() - cla = Window.size[0] / 2 - # cla = 800 - swipe_distance = NumericProperty(cla) - opening_time = NumericProperty(0.5) - - -class CustomSwipeToDeleteItem(MDCardSwipe): - """Custom swipe delete class for App UI""" - text = StringProperty() - cla = Window.size[0] / 2 - swipe_distance = NumericProperty(cla) - opening_time = NumericProperty(0.5) - - -def empty_screen_label(label_str=None, no_search_res_found=None): - """Returns default text on screen when no address is there.""" - kivy_state = kivy_state_variables() - content = MDLabel( - font_style='Caption', - theme_text_color='Primary', - text=no_search_res_found if kivy_state.searching_text else label_str, - halign='center', - size_hint_y=None, - valign='top') - return content - - -def retrieve_secondary_text(mail): - """Retriving mail details""" - secondary_txt_len = 10 - third_txt_len = 25 - dot_str = '...........' - dot_str2 = '...!' - third_text = mail[3].replace('\n', ' ') - - if len(third_text) > third_txt_len: - if len(mail[2]) > secondary_txt_len: # pylint: disable=no-else-return - return mail[2][:secondary_txt_len] + dot_str - else: - return mail[2] + '\n' + " " + (third_text[:third_txt_len] + dot_str2) - else: - return third_text - - -def set_mail_details(mail): - """Setting mail details""" - mail_details_data = { - 'text': mail[1].strip(), - 'secondary_text': retrieve_secondary_text(mail), - 'ackdata': mail[5], - 'senttime': mail[6] - } - return mail_details_data - - -def mdlist_message_content(queryreturn, data): - """Set Mails details in MD_list""" - for mail in queryreturn: - mdlist_data = set_mail_details(mail) - data.append(mdlist_data) - - -def msg_content_length(body, subject, max_length=50): - """This function concatinate body and subject if len(subject) > 50""" - continue_str = '........' - if len(subject) >= max_length: - subject = subject[:max_length] + continue_str - else: - subject = ((subject + ',' + body)[0:50] + continue_str).replace('\t', '').replace(' ', '') - return subject - - -def composer_common_dialog(alert_msg): - """Common alert popup for message composer""" - is_android_width = .8 - other_platform_width = .55 - dialog_height = .25 - width = is_android_width if platform == 'android' else other_platform_width - - dialog_box = MDDialog( - text=alert_msg, - size_hint=(width, dialog_height), - buttons=[ - MDFlatButton( - text="Ok", on_release=lambda x: callback_for_menu_items("Ok") - ), - ], - ) - dialog_box.open() - - def callback_for_menu_items(text_item, *arg): - """Callback of alert box""" - dialog_box.dismiss() - toast(text_item) diff --git a/src/bitmessagekivy/baseclass/common_mail_detail.py b/src/bitmessagekivy/baseclass/common_mail_detail.py deleted file mode 100644 index 78ae88d5..00000000 --- a/src/bitmessagekivy/baseclass/common_mail_detail.py +++ /dev/null @@ -1,22 +0,0 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error -""" - All Common widgets of kivy are managed here. -""" - -from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - - -def mail_detail_screen(screen_name, msg_id, instance, folder, *args): # pylint: disable=unused-argument - """Common function for all screens to open Mail detail.""" - kivy_state = kivy_state_variables() - if instance.open_progress == 0.0: - kivy_state.detail_page_type = folder - kivy_state.mail_id = msg_id - if screen_name.manager: - src_mng_obj = screen_name.manager - else: - src_mng_obj = screen_name.parent.parent - src_mng_obj.screens[11].clear_widgets() - src_mng_obj.screens[11].add_widget(MailDetail()) - src_mng_obj.current = "mailDetail" diff --git a/src/bitmessagekivy/baseclass/draft.py b/src/bitmessagekivy/baseclass/draft.py deleted file mode 100644 index 1b3ddf21..00000000 --- a/src/bitmessagekivy/baseclass/draft.py +++ /dev/null @@ -1,58 +0,0 @@ -# pylint: disable=unused-argument, import-error, too-many-arguments -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module - -""" -draft.py -============== - -Draft screen - -""" -from kivy.clock import Clock -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App -from pybitmessage.bitmessagekivy.baseclass.common import ( - show_limited_cnt, empty_screen_label, - kivy_state_variables -) -import logging -logger = logging.getLogger('default') - - -class Draft(Screen): - """Draft screen class for kivy Ui""" - - data = ListProperty() - account = StringProperty() - queryreturn = ListProperty() - has_refreshed = True - label_str = "yet no message for this account!!!!!!!!!!!!!" - - def __init__(self, *args, **kwargs): - """Method used for storing draft messages""" - super(Draft, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schedule for method draft accounts""" - self.load_draft() - logger.debug(dt) - - def load_draft(self, where="", what=""): - """Load draft list for Draft messages""" - self.set_draft_count('0') - self.ids.ml.add_widget(empty_screen_label(self.label_str)) - - @staticmethod - def set_draft_count(Count): - """Set the count of draft mails""" - draftCnt_obj = App.get_running_app().root.ids.content_drawer.ids.draft_cnt - draftCnt_obj.ids.badge_txt.text = show_limited_cnt(int(Count)) diff --git a/src/bitmessagekivy/baseclass/inbox.py b/src/bitmessagekivy/baseclass/inbox.py deleted file mode 100644 index 2e145871..00000000 --- a/src/bitmessagekivy/baseclass/inbox.py +++ /dev/null @@ -1,64 +0,0 @@ -# pylint: disable=unused-import, too-many-public-methods, unused-variable, too-many-ancestors -# pylint: disable=no-name-in-module, too-few-public-methods, import-error, unused-argument, too-many-arguments -# pylint: disable=attribute-defined-outside-init, global-variable-not-assigned, too-many-instance-attributes - -""" -Kivy UI for inbox screen -""" -from kivy.clock import Clock -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.app import App -from kivy.uix.screenmanager import Screen - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables, load_image_path - - -class Inbox(Screen): - """Inbox Screen class for kivy Ui""" - - queryreturn = ListProperty() - has_refreshed = True - account = StringProperty() - no_search_res_found = "No search result found" - label_str = "Yet no message for this account!" - - def __init__(self, *args, **kwargs): - """Initialize kivy variables""" - super(Inbox, self).__init__(*args, **kwargs) - self.kivy_running_app = App.get_running_app() - self.kivy_state = kivy_state_variables() - self.image_dir = load_image_path() - Clock.schedule_once(self.init_ui, 0) - - def set_defaultAddress(self): - """Set default address""" - if self.kivy_state.selected_address == "": - if self.kivy_running_app.identity_list: - self.kivy_state.selected_address = self.kivy_running_app.identity_list[0] - - def init_ui(self, dt=0): - """loadMessagelist() call at specific interval""" - self.loadMessagelist() - - def loadMessagelist(self, where="", what=""): - """Load inbox list for inbox messages""" - self.set_defaultAddress() - self.account = self.kivy_state.selected_address - - def refresh_callback(self, *args): - """Load inbox messages while wating-loader spins & called in inbox.kv""" - - def refresh_on_scroll_down(interval): - """Reset fields and load data on scrolling upside down""" - self.kivy_state.searching_text = "" - self.children[2].children[1].ids.search_field.text = "" - self.ids.ml.clear_widgets() - self.loadMessagelist(self.kivy_state.selected_address) - self.has_refreshed = True - self.ids.refresh_layout.refresh_done() - self.tick = 0 - - Clock.schedule_once(refresh_on_scroll_down, 1) diff --git a/src/bitmessagekivy/baseclass/login.py b/src/bitmessagekivy/baseclass/login.py deleted file mode 100644 index c5dd9ef4..00000000 --- a/src/bitmessagekivy/baseclass/login.py +++ /dev/null @@ -1,97 +0,0 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods -# pylint: disable=no-name-in-module, unused-argument, arguments-differ - -""" -Login screen appears when the App is first time starts and when new Address is generated. -""" - - -from kivy.clock import Clock -from kivy.properties import StringProperty, BooleanProperty -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from kivymd.uix.behaviors.elevation import RectangularElevationBehavior - -from pybitmessage.backend.address_generator import AddressGenerator -from pybitmessage.bitmessagekivy.baseclass.common import toast -from pybitmessage.bmconfigparser import config - - -class Login(Screen): - """Login Screeen class for kivy Ui""" - log_text1 = ( - '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:') - log_text2 = ('If talk about 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 and aside talk about cons' - ' You must remember (or write down) your 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') - - -class Random(Screen): - """Random Screen class for Ui""" - - is_active = BooleanProperty(False) - checked = StringProperty("") - - def generateaddress(self): - """Method for Address Generator""" - entered_label = str(self.ids.add_random_bx.children[0].ids.lab.text).strip() - if not entered_label: - self.ids.add_random_bx.children[0].ids.lab.focus = True - is_address = AddressGenerator.random_address_generation( - entered_label, streamNumberForAddress=1, eighteenByteRipe=False, - nonceTrialsPerByte=1000, payloadLengthExtraBytes=1000 - ) - if is_address: - toast('Creating New Address ...') - self.parent.parent.ids.toolbar.opacity = 1 - self.parent.parent.ids.toolbar.disabled = False - App.get_running_app().loadMyAddressScreen(True) - self.manager.current = 'myaddress' - Clock.schedule_once(self.address_created_callback, 6) - - def address_created_callback(self, dt=0): - """New address created""" - App.get_running_app().loadMyAddressScreen(False) - App.get_running_app().root.ids.id_myaddress.ids.ml.clear_widgets() - App.get_running_app().root.ids.id_myaddress.is_add_created = True - App.get_running_app().root.ids.id_myaddress.init_ui() - self.reset_address_spinner() - toast('New address created') - - def reset_address_spinner(self): - """reseting spinner address and UI""" - addresses = [addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')] - self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = [] - self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = addresses - self.manager.parent.ids.id_create.children[1].ids.composer_dropdown.values = [] - self.manager.parent.ids.id_create.children[1].ids.composer_dropdown.values = addresses - - @staticmethod - def add_validation(instance): - """Retrieve created labels and validate""" - entered_label = str(instance.text.strip()) - AddressGenerator.address_validation(instance, entered_label) - - def reset_address_label(self): - """Resetting address labels""" - if not self.ids.add_random_bx.children: - self.ids.add_random_bx.add_widget(RandomBoxlayout()) - - -class InfoLayout(BoxLayout, RectangularElevationBehavior): - """InfoLayout class for kivy Ui""" - - -class RandomBoxlayout(BoxLayout): - """RandomBoxlayout class for BoxLayout behaviour""" diff --git a/src/bitmessagekivy/baseclass/maildetail.py b/src/bitmessagekivy/baseclass/maildetail.py deleted file mode 100644 index 6ddf322d..00000000 --- a/src/bitmessagekivy/baseclass/maildetail.py +++ /dev/null @@ -1,242 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods - -""" -Maildetail screen for inbox, sent, draft and trash. -""" - -import os -from datetime import datetime - -from kivy.core.clipboard import Clipboard -from kivy.clock import Clock -from kivy.properties import ( - StringProperty, - NumericProperty -) -from kivy.uix.screenmanager import Screen -from kivy.factory import Factory -from kivy.app import App - -from kivymd.uix.button import MDFlatButton, MDIconButton -from kivymd.uix.dialog import MDDialog -from kivymd.uix.list import ( - OneLineListItem, - IRightBodyTouch -) - -from pybitmessage.bitmessagekivy.baseclass.common import ( - toast, avatar_image_first_letter, show_time_history, kivy_state_variables -) -from pybitmessage.bitmessagekivy.baseclass.popup import SenderDetailPopup -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.helper_sql import sqlQuery - - -class OneLineListTitle(OneLineListItem): - """OneLineListTitle class for kivy Ui""" - __events__ = ('on_long_press', ) - long_press_time = NumericProperty(1) - - def on_state(self, instance, value): - """On state""" - if value == 'down': - lpt = self.long_press_time - self._clockev = Clock.schedule_once(self._do_long_press, lpt) - else: - self._clockev.cancel() - - def _do_long_press(self, dt): - """Do long press""" - self.dispatch('on_long_press') - - def on_long_press(self, *largs): - """On long press""" - self.copymessageTitle(self.text) - - def copymessageTitle(self, title_text): - """this method is for displaying dialog box""" - self.title_text = title_text - width = .8 if platform == 'android' else .55 - self.dialog_box = MDDialog( - text=title_text, - size_hint=(width, .25), - buttons=[ - MDFlatButton( - text="Copy", on_release=self.callback_for_copy_title - ), - MDFlatButton( - text="Cancel", on_release=self.callback_for_copy_title, - ), - ],) - self.dialog_box.open() - - def callback_for_copy_title(self, instance): - """Callback of alert box""" - if instance.text == 'Copy': - Clipboard.copy(self.title_text) - self.dialog_box.dismiss() - toast(instance.text) - - -class IconRightSampleWidget(IRightBodyTouch, MDIconButton): - """IconRightSampleWidget class for kivy Ui""" - - -class MailDetail(Screen): # pylint: disable=too-many-instance-attributes - """MailDetail Screen class for kivy Ui""" - - to_addr = StringProperty() - from_addr = StringProperty() - subject = StringProperty() - message = StringProperty() - status = StringProperty() - page_type = StringProperty() - time_tag = StringProperty() - avatarImg = StringProperty() - no_subject = '(no subject)' - - def __init__(self, *args, **kwargs): - """Mail Details method""" - super(MailDetail, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schdule for method MailDetail mails""" - self.page_type = self.kivy_state.detail_page_type if self.kivy_state.detail_page_type else '' - try: - if self.kivy_state.detail_page_type in ('sent', 'draft'): - App.get_running_app().set_mail_detail_header() - elif self.kivy_state.detail_page_type == 'inbox': - data = sqlQuery( - "select toaddress, fromaddress, subject, message, received from inbox" - " where msgid = ?", self.kivy_state.mail_id) - self.assign_mail_details(data) - App.get_running_app().set_mail_detail_header() - except Exception as e: # pylint: disable=unused-variable - print('Something wents wrong!!') - - def assign_mail_details(self, data): - """Assigning mail details""" - subject = data[0][2].decode() if isinstance(data[0][2], bytes) else data[0][2] - body = data[0][3].decode() if isinstance(data[0][2], bytes) else data[0][3] - self.to_addr = data[0][0] if len(data[0][0]) > 4 else ' ' - self.from_addr = data[0][1] - - self.subject = subject.capitalize( - ) if subject.capitalize() else self.no_subject - self.message = body - if len(data[0]) == 7: - self.status = data[0][4] - self.time_tag = show_time_history(data[0][4]) if self.kivy_state.detail_page_type == 'inbox' \ - else show_time_history(data[0][6]) - self.avatarImg = os.path.join(self.kivy_state.imageDir, 'draft-icon.png') \ - if self.kivy_state.detail_page_type == 'draft' \ - else (os.path.join(self.kivy_state.imageDir, 'text_images', '{0}.png'.format(avatar_image_first_letter( - self.subject.strip())))) - self.timeinseconds = data[0][4] if self.kivy_state.detail_page_type == 'inbox' else data[0][6] - - def delete_mail(self): - """Method for mail delete""" - msg_count_objs = App.get_running_app().root.ids.content_drawer.ids - self.kivy_state.searching_text = '' - self.children[0].children[0].active = True - if self.kivy_state.detail_page_type == 'sent': - App.get_running_app().root.ids.id_sent.ids.sent_search.ids.search_field.text = '' - msg_count_objs.send_cnt.ids.badge_txt.text = str(int(self.kivy_state.sent_count) - 1) - self.kivy_state.sent_count = str(int(self.kivy_state.sent_count) - 1) - self.parent.screens[2].ids.ml.clear_widgets() - self.parent.screens[2].loadSent(self.kivy_state.selected_address) - elif self.kivy_state.detail_page_type == 'inbox': - App.get_running_app().root.ids.id_inbox.ids.inbox_search.ids.search_field.text = '' - msg_count_objs.inbox_cnt.ids.badge_txt.text = str( - int(self.kivy_state.inbox_count) - 1) - self.kivy_state.inbox_count = str(int(self.kivy_state.inbox_count) - 1) - self.parent.screens[0].ids.ml.clear_widgets() - self.parent.screens[0].loadMessagelist(self.kivy_state.selected_address) - - elif self.kivy_state.detail_page_type == 'draft': - msg_count_objs.draft_cnt.ids.badge_txt.text = str( - int(self.kivy_state.draft_count) - 1) - self.kivy_state.draft_count = str(int(self.kivy_state.draft_count) - 1) - self.parent.screens[13].clear_widgets() - self.parent.screens[13].add_widget(Factory.Draft()) - - if self.kivy_state.detail_page_type != 'draft': - msg_count_objs.trash_cnt.ids.badge_txt.text = str( - int(self.kivy_state.trash_count) + 1) - msg_count_objs.allmail_cnt.ids.badge_txt.text = str( - int(self.kivy_state.all_count) - 1) - self.kivy_state.trash_count = str(int(self.kivy_state.trash_count) + 1) - self.kivy_state.all_count = str(int(self.kivy_state.all_count) - 1) if \ - int(self.kivy_state.all_count) else '0' - self.parent.screens[3].clear_widgets() - self.parent.screens[3].add_widget(Factory.Trash()) - self.parent.screens[14].clear_widgets() - self.parent.screens[14].add_widget(Factory.Allmails()) - Clock.schedule_once(self.callback_for_delete, 4) - - def callback_for_delete(self, dt=0): - """Delete method from allmails""" - if self.kivy_state.detail_page_type: - self.children[0].children[0].active = False - App.get_running_app().set_common_header() - self.parent.current = 'allmails' \ - if self.kivy_state.is_allmail else self.kivy_state.detail_page_type - self.kivy_state.detail_page_type = '' - toast('Deleted') - - def get_message_details_to_reply(self, data): - """Getting message details and fill into fields when reply""" - sender_address = ' wrote:--------------\n' - message_time = '\n\n --------------On ' - composer_obj = self.parent.screens[1].children[1].ids - composer_obj.ti.text = data[0][0] - composer_obj.composer_dropdown.text = data[0][0] - composer_obj.txt_input.text = data[0][1] - split_subject = data[0][2].split('Re:', 1) - composer_obj.subject.text = 'Re: ' + (split_subject[1] if len(split_subject) > 1 else split_subject[0]) - time_obj = datetime.fromtimestamp(int(data[0][4])) - time_tag = time_obj.strftime("%d %b %Y, %I:%M %p") - sender_name = data[0][1] - composer_obj.body.text = ( - message_time + time_tag + ', ' + sender_name + sender_address + data[0][3]) - composer_obj.body.focus = True - composer_obj.body.cursor = (0, 0) - - def inbox_reply(self): - """Reply inbox messages""" - self.kivy_state.in_composer = True - App.get_running_app().root.ids.id_create.children[1].ids.rv.data = '' - App.get_running_app().root.ids.sc3.children[1].ids.rv.data = '' - self.parent.current = 'create' - App.get_running_app().set_navbar_for_composer() - - def get_message_details_for_draft_reply(self, data): - """Getting and setting message details fill into fields when draft reply""" - composer_ids = ( - self.parent.parent.ids.id_create.children[1].ids) - composer_ids.ti.text = data[0][1] - composer_ids.btn.text = data[0][1] - composer_ids.txt_input.text = data[0][0] - composer_ids.subject.text = data[0][2] if data[0][2] != self.no_subject else '' - composer_ids.body.text = data[0][3] - - def write_msg(self, navApp): - """Write on draft mail""" - self.kivy_state.send_draft_mail = self.kivy_state.mail_id - self.parent.current = 'create' - navApp.set_navbar_for_composer() - - def detailedPopup(self): - """Detailed popup""" - obj = SenderDetailPopup() - obj.open() - arg = (self.to_addr, self.from_addr, self.timeinseconds) - obj.assignDetail(*arg) - - @staticmethod - def callback_for_menu_items(text_item, *arg): - """Callback of alert box""" - toast(text_item) diff --git a/src/bitmessagekivy/baseclass/msg_composer.py b/src/bitmessagekivy/baseclass/msg_composer.py deleted file mode 100644 index a36996e0..00000000 --- a/src/bitmessagekivy/baseclass/msg_composer.py +++ /dev/null @@ -1,188 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, too-many-ancestors -# pylint: disable=no-member, no-name-in-module, too-few-public-methods, no-name-in-module -""" - Message composer screen UI -""" - -import logging - -from kivy.app import App -from kivy.properties import ( - BooleanProperty, - ListProperty, - NumericProperty, - ObjectProperty, -) -from kivy.uix.behaviors import FocusBehavior -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.label import Label -from kivy.uix.recycleview import RecycleView -from kivy.uix.recycleboxlayout import RecycleBoxLayout -from kivy.uix.recycleview.layout import LayoutSelectionBehavior -from kivy.uix.recycleview.views import RecycleDataViewBehavior -from kivy.uix.screenmanager import Screen - -from kivymd.uix.textfield import MDTextField - -from pybitmessage import state -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import ( - toast, kivy_state_variables, composer_common_dialog -) - -logger = logging.getLogger('default') - - -class Create(Screen): - """Creates Screen class for kivy Ui""" - - def __init__(self, **kwargs): - """Getting Labels and address from addressbook""" - super(Create, self).__init__(**kwargs) - self.kivy_running_app = App.get_running_app() - self.kivy_state = kivy_state_variables() - self.dropdown_widget = DropDownWidget() - self.dropdown_widget.ids.txt_input.starting_no = 2 - self.add_widget(self.dropdown_widget) - self.children[0].ids.id_scroll.bind(scroll_y=self.check_scroll_y) - - def check_scroll_y(self, instance, somethingelse): # pylint: disable=unused-argument - """show data on scroll down""" - if self.children[1].ids.composer_dropdown.is_open: - self.children[1].ids.composer_dropdown.is_open = False - - -class RV(RecycleView): - """Recycling View class for kivy Ui""" - - def __init__(self, **kwargs): - """Recycling Method""" - super(RV, self).__init__(**kwargs) - - -class SelectableRecycleBoxLayout( - FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout -): - """Adds selection and focus behaviour to the view""" - # pylint: disable = duplicate-bases - - -class DropDownWidget(BoxLayout): - """DropDownWidget class for kivy Ui""" - - # pylint: disable=too-many-statements - - txt_input = ObjectProperty() - rv = ObjectProperty() - - def __init__(self, **kwargs): - super(DropDownWidget, self).__init__(**kwargs) - self.kivy_running_app = App.get_running_app() - self.kivy_state = kivy_state_variables() - - @staticmethod - def callback_for_msgsend(dt=0): # pylint: disable=unused-argument - """Callback method for messagesend""" - state.kivyapp.root.ids.id_create.children[0].active = False - state.in_sent_method = True - state.kivyapp.back_press() - toast("sent") - - def reset_composer(self): - """Method will reset composer""" - self.ids.ti.text = "" - self.ids.composer_dropdown.text = "Select" - self.ids.txt_input.text = "" - self.ids.subject.text = "" - self.ids.body.text = "" - toast("Reset message") - - def auto_fill_fromaddr(self): - """Fill the text automatically From Address""" - self.ids.ti.text = self.ids.composer_dropdown.text - self.ids.ti.focus = True - - def is_camara_attached(self): - """Checks the camera availability in device""" - self.parent.parent.parent.ids.id_scanscreen.check_camera() - is_available = self.parent.parent.parent.ids.id_scanscreen.camera_available - return is_available - - @staticmethod - def camera_alert(): - """Show camera availability alert message""" - feature_unavailable = 'Currently this feature is not available!' - cam_not_available = 'Camera is not available!' - alert_text = feature_unavailable if platform == 'android' else cam_not_available - composer_common_dialog(alert_text) - - -class MyTextInput(MDTextField): - """MyTextInput class for kivy Ui""" - - txt_input = ObjectProperty() - flt_list = ObjectProperty() - word_list = ListProperty() - starting_no = NumericProperty(3) - suggestion_text = '' - - def __init__(self, **kwargs): - """Getting Text Input.""" - super(MyTextInput, self).__init__(**kwargs) - self.__lineBreak__ = 0 - - def on_text(self, instance, value): # pylint: disable=unused-argument - """Find all the occurrence of the word""" - self.parent.parent.parent.parent.parent.ids.rv.data = [] - max_recipient_len = 10 - box_height = 250 - box_max_height = 400 - - matches = [self.word_list[i] for i in range( - len(self.word_list)) if self.word_list[ - i][:self.starting_no] == value[:self.starting_no]] - display_data = [] - for i in matches: - display_data.append({'text': i}) - self.parent.parent.parent.parent.parent.ids.rv.data = display_data - if len(matches) <= max_recipient_len: - self.parent.height = (box_height + (len(matches) * 20)) - else: - self.parent.height = box_max_height - - def keyboard_on_key_down(self, window, keycode, text, modifiers): - """Keyboard on key Down""" - if self.suggestion_text and keycode[1] == 'tab' and modifiers is None: - self.insert_text(self.suggestion_text + ' ') - return True - return super(MyTextInput, self).keyboard_on_key_down( - window, keycode, text, modifiers) - - -class SelectableLabel(RecycleDataViewBehavior, Label): - """Add selection support to the Label""" - - index = None - selected = BooleanProperty(False) - selectable = BooleanProperty(True) - - def refresh_view_attrs(self, rv, index, data): - """Catch and handle the view changes""" - self.index = index - return super(SelectableLabel, self).refresh_view_attrs(rv, index, data) - - def on_touch_down(self, touch): # pylint: disable=inconsistent-return-statements - """Add selection on touch down""" - if super(SelectableLabel, self).on_touch_down(touch): - return True - if self.collide_point(*touch.pos) and self.selectable: - return self.parent.select_with_touch(self.index, touch) - - def apply_selection(self, rv, index, is_selected): - """Respond to the selection of items in the view""" - self.selected = is_selected - if is_selected: - logger.debug("selection changed to %s", rv.data[index]) - rv.parent.txt_input.text = rv.parent.txt_input.text.replace( - rv.parent.txt_input.text, rv.data[index]["text"] - ) diff --git a/src/bitmessagekivy/baseclass/myaddress.py b/src/bitmessagekivy/baseclass/myaddress.py deleted file mode 100644 index 0a46bae9..00000000 --- a/src/bitmessagekivy/baseclass/myaddress.py +++ /dev/null @@ -1,230 +0,0 @@ -# pylint: disable=unused-argument, import-error, no-member, attribute-defined-outside-init -# pylint: disable=no-name-in-module, too-few-public-methods, too-many-instance-attributes - -""" -myaddress.py -============== -All generated addresses are managed in MyAddress -""" - -import os -from functools import partial - -from kivy.clock import Clock -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen, ScreenManagerException -from kivy.app import App - -from kivymd.uix.list import ( - IRightBodyTouch, - TwoLineAvatarIconListItem, -) -from kivymd.uix.selectioncontrol import MDSwitch - -from pybitmessage.bmconfigparser import config - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import ( - avatar_image_first_letter, AvatarSampleWidget, ThemeClsColor, - toast, empty_screen_label, load_image_path -) - -from pybitmessage.bitmessagekivy.baseclass.popup import MyaddDetailPopup -from pybitmessage.bitmessagekivy.baseclass.myaddress_widgets import HelperMyAddress - - -class ToggleBtn(IRightBodyTouch, MDSwitch): - """ToggleBtn class for kivy UI""" - - -class CustomTwoLineAvatarIconListItem(TwoLineAvatarIconListItem): - """CustomTwoLineAvatarIconListItem class for kivy Ui""" - - -class MyAddress(Screen, HelperMyAddress): - """MyAddress screen class for kivy Ui""" - - address_label = StringProperty() - text_address = StringProperty() - addresses_list = ListProperty() - has_refreshed = True - is_add_created = False - label_str = "Yet no address is created by user!!!!!!!!!!!!!" - no_search_res_found = "No search result found" - min_scroll_y_limit = -0.0 - scroll_y_step = 0.06 - number_of_addresses = 20 - addresses_at_a_time = 15 - canvas_color_black = [0, 0, 0, 0] - canvas_color_gray = [0.5, 0.5, 0.5, 0.5] - is_android_width = .9 - other_platform_width = .6 - disabled_addr_width = .8 - other_platform_disabled_addr_width = .55 - max_scroll_limit = 1.0 - - def __init__(self, *args, **kwargs): - """Clock schdule for method Myaddress accounts""" - super(MyAddress, self).__init__(*args, **kwargs) - self.image_dir = load_image_path() - self.kivy_running_app = App.get_running_app() - self.kivy_state = self.kivy_running_app.kivy_state_obj - - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock schdule for method Myaddress accounts""" - self.addresses_list = config.addresses() - if self.kivy_state.searching_text: - self.ids.refresh_layout.scroll_y = self.max_scroll_limit - filtered_list = [ - x for x in config.addresses() - if self.filter_address(x) - ] - self.addresses_list = filtered_list - self.addresses_list = [obj for obj in reversed(self.addresses_list)] - self.ids.tag_label.text = '' - if self.addresses_list: - self.ids.tag_label.text = 'My Addresses' - self.has_refreshed = True - self.set_mdList(0, self.addresses_at_a_time) - self.ids.refresh_layout.bind(scroll_y=self.check_scroll_y) - else: - self.ids.ml.add_widget(empty_screen_label(self.label_str, self.no_search_res_found)) - if not self.kivy_state.searching_text and not self.is_add_created: - try: - self.manager.current = 'login' - except ScreenManagerException: - pass - - def get_address_list(self, first_index, last_index, data): - """Getting address and append to the list""" - for address in self.addresses_list[first_index:last_index]: - data.append({ - 'text': config.get(address, 'label'), - 'secondary_text': address} - ) - return data - - def set_address_to_widget(self, item): - """Setting address to the widget""" - is_enable = config.getboolean(item['secondary_text'], 'enabled') - meny = CustomTwoLineAvatarIconListItem( - text=item['text'], secondary_text=item['secondary_text'], - theme_text_color='Custom' if is_enable else 'Primary', - text_color=ThemeClsColor,) - meny.canvas.children[3].rgba = \ - self.canvas_color_black if is_enable else self.canvas_color - meny.add_widget(AvatarSampleWidget( - source=os.path.join( - self.image_dir, "text_images", "{}.png".format(avatar_image_first_letter( - item["text"].strip()))) - )) - meny.bind(on_press=partial( - self.myadd_detail, item['secondary_text'], item['text'])) - self.set_address_status(item, meny, is_enable) - - def set_address_status(self, item, meny, is_enable): - """Setting the identity status enable/disable on UI""" - if self.kivy_state.selected_address == item['secondary_text'] and is_enable: - meny.add_widget(self.is_active_badge()) - else: - meny.add_widget(ToggleBtn(active=True if is_enable else False)) - self.ids.ml.add_widget(meny) - - def set_mdList(self, first_index, last_index): - """Creating the mdlist""" - data = [] - self.get_address_list(first_index, last_index, data) - for item in data: - self.set_address_to_widget(item) - - def check_scroll_y(self, instance, somethingelse): - """Load data on Scroll down""" - if self.ids.refresh_layout.scroll_y <= self.min_scroll_y_limit and self.has_refreshed: - self.ids.refresh_layout.scroll_y = self.scroll_y_step - my_addresses = len(self.ids.ml.children) - if my_addresses != len(self.addresses_list): - self.update_addressBook_on_scroll(my_addresses) - self.has_refreshed = ( - True if my_addresses != len(self.addresses_list) else False - ) - - def update_addressBook_on_scroll(self, my_addresses): - """Loads more data on scroll down""" - self.set_mdList(my_addresses, my_addresses + self.number_of_addresses) - - def myadd_detail(self, fromaddress, label, *args): - """Load myaddresses details""" - if config.getboolean(fromaddress, 'enabled'): - obj = MyaddDetailPopup() - self.address_label = obj.address_label = label - self.text_address = obj.address = fromaddress - width = self.is_android_width if platform == 'android' else self.other_platform_width - self.myadddetail_popup = self.myaddress_detail_popup(obj, width) - self.myadddetail_popup.auto_dismiss = False - self.myadddetail_popup.open() - else: - width = self.disabled_addr_width if platform == 'android' else self.other_platform_disabled_addr_width - self.dialog_box = self.inactive_address_popup(width, self.callback_for_menu_items) - self.dialog_box.open() - - def callback_for_menu_items(self, text_item, *arg): - """Callback of inactive address alert box""" - self.dialog_box.dismiss() - toast(text_item) - - def refresh_callback(self, *args): - """Method updates the state of application, - While the spinner remains on the screen""" - def refresh_callback(interval): - """Method used for loading the myaddress screen data""" - self.kivy_state.searching_text = '' - self.ids.search_bar.ids.search_field.text = '' - self.has_refreshed = True - self.ids.ml.clear_widgets() - self.init_ui() - self.ids.refresh_layout.refresh_done() - Clock.schedule_once(self.address_permision_callback, 0) - Clock.schedule_once(refresh_callback, 1) - - @staticmethod - def filter_address(address): - """It will return True if search is matched""" - searched_text = App.get_running_app().kivy_state_obj.searching_text.lower() - return bool(config.search_addresses(address, searched_text)) - - def disable_address_ui(self, address, instance): - """This method is used to disable addresses from UI""" - config.disable_address(address) - instance.parent.parent.theme_text_color = 'Primary' - instance.parent.parent.canvas.children[3].rgba = MyAddress.canvas_color_gray - toast('Address disabled') - Clock.schedule_once(self.address_permision_callback, 0) - - def enable_address_ui(self, address, instance): - """This method is used to enable addresses from UI""" - config.enable_address(address) - instance.parent.parent.theme_text_color = 'Custom' - instance.parent.parent.canvas.children[3].rgba = MyAddress.canvas_color_black - toast('Address Enabled') - Clock.schedule_once(self.address_permision_callback, 0) - - def address_permision_callback(self, dt=0): - """callback for enable or disable addresses""" - addresses = [addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')] - self.parent.parent.ids.content_drawer.ids.identity_dropdown.values = addresses - self.parent.parent.ids.id_create.children[1].ids.composer_dropdown.values = addresses - self.kivy_running_app.identity_list = addresses - - def toggleAction(self, instance): - """This method is used for enable or disable address""" - addr = instance.parent.parent.secondary_text - if instance.active: - self.enable_address_ui(addr, instance) - else: - self.disable_address_ui(addr, instance) diff --git a/src/bitmessagekivy/baseclass/myaddress_widgets.py b/src/bitmessagekivy/baseclass/myaddress_widgets.py deleted file mode 100644 index 23e2342f..00000000 --- a/src/bitmessagekivy/baseclass/myaddress_widgets.py +++ /dev/null @@ -1,64 +0,0 @@ -# pylint: disable=too-many-arguments, no-name-in-module, import-error -# pylint: disable=too-few-public-methods, no-member, too-many-ancestors - -""" -MyAddress widgets are here. -""" - -from kivymd.uix.button import MDFlatButton -from kivymd.uix.dialog import MDDialog -from kivymd.uix.label import MDLabel -from kivymd.uix.list import IRightBodyTouch - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import ThemeClsColor - - -class BadgeText(IRightBodyTouch, MDLabel): - """BadgeText class for kivy UI""" - - -class HelperMyAddress(object): - """Widget used in MyAddress are here""" - dialog_height = .25 - - @staticmethod - def is_active_badge(): - """This function show the 'active' label of active Address.""" - active_status = 'Active' - is_android_width = 90 - width = 50 - height = 60 - badge_obj = BadgeText( - size_hint=(None, None), - size=[is_android_width if platform == 'android' else width, height], - text=active_status, halign='center', - font_style='Body1', theme_text_color='Custom', - text_color=ThemeClsColor, font_size='13sp' - ) - return badge_obj - - @staticmethod - def myaddress_detail_popup(obj, width): - """This method show the details of address as popup opens.""" - show_myaddress_dialogue = MDDialog( - type="custom", - size_hint=(width, HelperMyAddress.dialog_height), - content_cls=obj, - ) - return show_myaddress_dialogue - - @staticmethod - def inactive_address_popup(width, callback_for_menu_items): - """This method shows the warning popup if the address is inactive""" - dialog_text = 'Address is not currently active. Please click on Toggle button to active it.' - dialog_box = MDDialog( - text=dialog_text, - size_hint=(width, HelperMyAddress.dialog_height), - buttons=[ - MDFlatButton( - text="Ok", on_release=lambda x: callback_for_menu_items("Ok") - ), - ], - ) - return dialog_box diff --git a/src/bitmessagekivy/baseclass/network.py b/src/bitmessagekivy/baseclass/network.py deleted file mode 100644 index dcb3f082..00000000 --- a/src/bitmessagekivy/baseclass/network.py +++ /dev/null @@ -1,54 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string -# pylint: disable=no-name-in-module, too-few-public-methods - -""" - Network status -""" - -import os - -from kivy.clock import Clock -from kivy.properties import StringProperty -from kivy.uix.screenmanager import Screen - -from pybitmessage import state - -if os.environ.get('INSTALL_TESTS', False) and not state.backend_py3_compatible: - from pybitmessage.mockbm import kivy_main - stats = kivy_main.network.stats - objectracker = kivy_main.network.objectracker -else: - from pybitmessage.network import stats, objectracker - - -class NetworkStat(Screen): - """NetworkStat class for kivy Ui""" - - text_variable_1 = StringProperty( - '{0}::{1}'.format('Total Connections', '0')) - text_variable_2 = StringProperty( - 'Processed {0} per-to-per messages'.format('0')) - text_variable_3 = StringProperty( - 'Processed {0} brodcast messages'.format('0')) - text_variable_4 = StringProperty( - 'Processed {0} public keys'.format('0')) - text_variable_5 = StringProperty( - 'Processed {0} object to be synced'.format('0')) - - def __init__(self, *args, **kwargs): - """Init method for network stat""" - super(NetworkStat, self).__init__(*args, **kwargs) - Clock.schedule_interval(self.init_ui, 1) - - def init_ui(self, dt=0): - """Clock Schdule for method networkstat screen""" - self.text_variable_1 = '{0} :: {1}'.format( - 'Total Connections', str(len(stats.connectedHostsList()))) - self.text_variable_2 = 'Processed {0} per-to-per messages'.format( - str(state.numberOfMessagesProcessed)) - self.text_variable_3 = 'Processed {0} brodcast messages'.format( - str(state.numberOfBroadcastsProcessed)) - self.text_variable_4 = 'Processed {0} public keys'.format( - str(state.numberOfPubkeysProcessed)) - self.text_variable_5 = '{0} object to be synced'.format( - len(objectracker.missingObjects)) diff --git a/src/bitmessagekivy/baseclass/payment.py b/src/bitmessagekivy/baseclass/payment.py deleted file mode 100644 index 6749e1bb..00000000 --- a/src/bitmessagekivy/baseclass/payment.py +++ /dev/null @@ -1,66 +0,0 @@ -# pylint: disable=import-error, no-name-in-module, too-few-public-methods, too-many-ancestors - -''' - Payment/subscription frontend -''' - -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from kivymd.uix.behaviors.elevation import RectangularElevationBehavior -from kivymd.uix.label import MDLabel -from kivymd.uix.list import ( - IRightBodyTouch, - OneLineAvatarIconListItem -) - -from pybitmessage.bitmessagekivy.baseclass.common import toast, kivy_state_variables - - -class Payment(Screen): - """Payment Screen class for kivy Ui""" - - def __init__(self, *args, **kwargs): - """Instantiate kivy state variable""" - super(Payment, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - - # TODO: get_free_credits() is not used anywhere, will be used later for Payment/subscription. - def get_free_credits(self, instance): # pylint: disable=unused-argument - """Get the available credits""" - # pylint: disable=no-self-use - self.kivy_state.available_credit = 0 - existing_credits = 0 - if existing_credits > 0: - toast( - 'We already have added free credit' - ' for the subscription to your account!') - else: - toast('Credit added to your account!') - # TODO: There is no sc18 screen id is available, - # need to create sc18 for Credits screen inside main.kv - App.get_running_app().root.ids.sc18.ids.cred.text = '{0}'.format( - self.kivy_state.available_credit) - - -class Category(BoxLayout, RectangularElevationBehavior): - """Category class for kivy Ui""" - elevation_normal = .01 - - -class ProductLayout(BoxLayout, RectangularElevationBehavior): - """ProductLayout class for kivy Ui""" - elevation_normal = .01 - - -class PaymentMethodLayout(BoxLayout): - """PaymentMethodLayout class for kivy Ui""" - - -class ListItemWithLabel(OneLineAvatarIconListItem): - """ListItemWithLabel class for kivy Ui""" - - -class RightLabel(IRightBodyTouch, MDLabel): - """RightLabel class for kivy Ui""" diff --git a/src/bitmessagekivy/baseclass/popup.py b/src/bitmessagekivy/baseclass/popup.py deleted file mode 100644 index d2a4c859..00000000 --- a/src/bitmessagekivy/baseclass/popup.py +++ /dev/null @@ -1,231 +0,0 @@ -# pylint: disable=import-error, attribute-defined-outside-init -# pylint: disable=no-member, no-name-in-module, unused-argument, too-few-public-methods - -""" -All the popup are managed here. - -""" -import logging -from datetime import datetime - -from kivy.clock import Clock -from kivy.metrics import dp -from kivy.properties import StringProperty -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.popup import Popup -from kivy.app import App - -from pybitmessage.bitmessagekivy import kivy_helper_search -from pybitmessage.bitmessagekivy.get_platform import platform - -from pybitmessage.bitmessagekivy.baseclass.common import toast - -from pybitmessage.addresses import decodeAddress - -logger = logging.getLogger('default') - - -class AddressChangingLoader(Popup): - """Run a Screen Loader when changing the Identity for kivy UI""" - - def __init__(self, **kwargs): - super(AddressChangingLoader, self).__init__(**kwargs) - Clock.schedule_once(self.dismiss_popup, 0.5) - - def dismiss_popup(self, dt): - """Dismiss popups""" - self.dismiss() - - -class AddAddressPopup(BoxLayout): - """Popup for adding new address to addressbook""" - - validation_dict = { - "missingbm": "The address should start with ''BM-''", - "checksumfailed": "The address is not typed or copied correctly", - "versiontoohigh": "The version number of this address is higher than this" - " software can support. Please upgrade Bitmessage.", - "invalidcharacters": "The address contains invalid characters.", - "ripetooshort": "Some data encoded in the address is too short.", - "ripetoolong": "Some data encoded in the address is too long.", - "varintmalformed": "Some data encoded in the address is malformed." - } - valid = False - - def __init__(self, **kwargs): - super(AddAddressPopup, self).__init__(**kwargs) - - def checkAddress_valid(self, instance): - """Checking address is valid or not""" - my_addresses = ( - App.get_running_app().root.ids.content_drawer.ids.identity_dropdown.values) - add_book = [addr[1] for addr in kivy_helper_search.search_sql( - folder="addressbook")] - entered_text = str(instance.text).strip() - if entered_text in add_book: - text = 'Address is already in the addressbook.' - elif entered_text in my_addresses: - text = 'You can not save your own address.' - elif entered_text: - text = self.addressChanged(entered_text) - - if entered_text in my_addresses or entered_text in add_book: - self.ids.address.error = True - self.ids.address.helper_text = text - elif entered_text and self.valid: - self.ids.address.error = False - elif entered_text: - self.ids.address.error = True - self.ids.address.helper_text = text - else: - self.ids.address.error = True - self.ids.address.helper_text = 'This field is required' - - def checkLabel_valid(self, instance): - """Checking address label is unique or not""" - entered_label = instance.text.strip() - addr_labels = [labels[0] for labels in kivy_helper_search.search_sql( - folder="addressbook")] - if entered_label in addr_labels: - self.ids.label.error = True - self.ids.label.helper_text = 'Label name already exists.' - elif entered_label: - self.ids.label.error = False - else: - self.ids.label.error = True - self.ids.label.helper_text = 'This field is required' - - def _onSuccess(self, addressVersion, streamNumber, ripe): - pass - - def addressChanged(self, addr): - """Address validation callback, performs validation and gives feedback""" - status, addressVersion, streamNumber, ripe = decodeAddress( - str(addr)) - self.valid = status == 'success' - - if self.valid: - text = "Address is valid." - self._onSuccess(addressVersion, streamNumber, ripe) - return text - return self.validation_dict.get(status) - - -class SavedAddressDetailPopup(BoxLayout): - """Pop-up for Saved Address details for kivy UI""" - - address_label = StringProperty() - address = StringProperty() - - def __init__(self, **kwargs): - """Set screen of address detail page""" - super(SavedAddressDetailPopup, self).__init__(**kwargs) - - def checkLabel_valid(self, instance): - """Checking address label is unique of not""" - entered_label = str(instance.text.strip()) - address_list = kivy_helper_search.search_sql(folder="addressbook") - addr_labels = [labels[0] for labels in address_list] - add_dict = dict(address_list) - if self.address and entered_label in addr_labels \ - and self.address != add_dict[entered_label]: - self.ids.add_label.error = True - self.ids.add_label.helper_text = 'label name already exists.' - elif entered_label: - self.ids.add_label.error = False - else: - self.ids.add_label.error = True - self.ids.add_label.helper_text = 'This field is required' - - -class MyaddDetailPopup(BoxLayout): - """MyaddDetailPopup class for kivy Ui""" - - address_label = StringProperty() - address = StringProperty() - - def __init__(self, **kwargs): - """My Address Details screen setting""" - super(MyaddDetailPopup, self).__init__(**kwargs) - - def send_message_from(self): - """Method used to fill from address of composer autofield""" - App.get_running_app().set_navbar_for_composer() - window_obj = App.get_running_app().root.ids - window_obj.id_create.children[1].ids.ti.text = self.address - window_obj.id_create.children[1].ids.composer_dropdown.text = self.address - window_obj.id_create.children[1].ids.txt_input.text = '' - window_obj.id_create.children[1].ids.subject.text = '' - window_obj.id_create.children[1].ids.body.text = '' - window_obj.scr_mngr.current = 'create' - self.parent.parent.parent.dismiss() - - def close_pop(self): - """Pop is Cancelled""" - self.parent.parent.parent.dismiss() - toast('Cancelled') - - -class AppClosingPopup(Popup): - """AppClosingPopup class for kivy Ui""" - - def __init__(self, **kwargs): - super(AppClosingPopup, self).__init__(**kwargs) - - def closingAction(self, text): - """Action on closing window""" - exit_message = "*******************EXITING FROM APPLICATION*******************" - if text == 'Yes': - logger.debug(exit_message) - import shutdown - shutdown.doCleanShutdown() - else: - self.dismiss() - toast(text) - - -class SenderDetailPopup(Popup): - """SenderDetailPopup class for kivy Ui""" - - to_addr = StringProperty() - from_addr = StringProperty() - time_tag = StringProperty() - - def __init__(self, **kwargs): - """this metthod initialized the send message detial popup""" - super(SenderDetailPopup, self).__init__(**kwargs) - - def assignDetail(self, to_addr, from_addr, timeinseconds): - """Detailes assigned""" - self.to_addr = to_addr - self.from_addr = from_addr - time_obj = datetime.fromtimestamp(int(timeinseconds)) - self.time_tag = time_obj.strftime("%d %b %Y, %I:%M %p") - device_type = 2 if platform == 'android' else 1.5 - pop_height = 1.2 * device_type * (self.ids.sd_label.height + self.ids.dismiss_btn.height) - if len(to_addr) > 3: - self.height = pop_height - self.ids.to_addId.size_hint_y = None - self.ids.to_addId.height = 50 - self.ids.to_addtitle.add_widget(ToAddressTitle()) - frmaddbox = ToAddrBoxlayout() - frmaddbox.set_toAddress(to_addr) - self.ids.to_addId.add_widget(frmaddbox) - else: - self.ids.space_1.height = dp(0) - self.ids.space_2.height = dp(0) - self.ids.myadd_popup_box.spacing = dp(8 if platform == 'android' else 3) - self.height = pop_height / 1.2 - - -class ToAddrBoxlayout(BoxLayout): - """ToAddrBoxlayout class for kivy Ui""" - to_addr = StringProperty() - - def set_toAddress(self, to_addr): - """This method is use to set to address""" - self.to_addr = to_addr - - -class ToAddressTitle(BoxLayout): - """ToAddressTitle class for BoxLayout behaviour""" diff --git a/src/bitmessagekivy/baseclass/qrcode.py b/src/bitmessagekivy/baseclass/qrcode.py deleted file mode 100644 index 4c6d99a0..00000000 --- a/src/bitmessagekivy/baseclass/qrcode.py +++ /dev/null @@ -1,34 +0,0 @@ -# pylint: disable=import-error, no-name-in-module, too-few-public-methods - -""" -Generate QRcode of saved addresses in addressbook. -""" - -import logging - -from kivy.app import App -from kivy.uix.screenmanager import Screen -from kivy.properties import StringProperty -from kivy_garden.qrcode import QRCodeWidget - -logger = logging.getLogger('default') - - -class ShowQRCode(Screen): - """ShowQRCode Screen class for kivy Ui""" - address = StringProperty() - - def __init__(self, *args, **kwargs): - """Instantiate kivy state variable""" - super(ShowQRCode, self).__init__(*args, **kwargs) - self.kivy_running_app = App.get_running_app() - - def qrdisplay(self, instance, address): - """Method used for showing QR Code""" - self.ids.qr.clear_widgets() - self.kivy_running_app.set_toolbar_for_QrCode() - self.address = address # used for label - self.ids.qr.add_widget(QRCodeWidget(data=self.address)) - self.ids.qr.children[0].show_border = False - instance.parent.parent.parent.dismiss() - logger.debug('Show QR code') diff --git a/src/bitmessagekivy/baseclass/scan_screen.py b/src/bitmessagekivy/baseclass/scan_screen.py deleted file mode 100644 index 3321a4fa..00000000 --- a/src/bitmessagekivy/baseclass/scan_screen.py +++ /dev/null @@ -1,105 +0,0 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods -# pylint: disable=no-name-in-module, unused-argument, arguments-differ - -""" -QR code Scan Screen used in message composer to get recipient address - -""" - -import os -import logging -import cv2 - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.properties import ( - BooleanProperty, - ObjectProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen - -from pybitmessage.bitmessagekivy.get_platform import platform - -logger = logging.getLogger('default') - - -class ScanScreen(Screen): - """ScanScreen is for scaning Qr code""" - # pylint: disable=W0212 - camera_available = BooleanProperty(False) - previous_open_screen = StringProperty() - pop_up_instance = ObjectProperty() - - def __init__(self, *args, **kwargs): - """Getting AddressBook Details""" - super(ScanScreen, self).__init__(*args, **kwargs) - self.check_camera() - - def check_camera(self): - """This method is used for checking camera avaibility""" - if platform != "android": - cap = cv2.VideoCapture(0) - is_cam_open = cap.isOpened() - while is_cam_open: - logger.debug('Camera is available!') - self.camera_available = True - break - else: - logger.debug("Camera is not available!") - self.camera_available = False - else: - self.camera_available = True - - def get_screen(self, screen_name, instance=None): - """This method is used for getting previous screen name""" - self.previous_open_screen = screen_name - if screen_name != 'composer': - self.pop_up_instance = instance - - def on_pre_enter(self): - """ - on_pre_enter works little better on android - It affects screen transition on linux - """ - if not self.children: - tmp = Builder.load_file( - os.path.join( - os.path.dirname(os.path.dirname(__file__)), "kv", "{}.kv").format("scanner") - ) - self.add_widget(tmp) - if platform == "android": - Clock.schedule_once(self.start_camera, 0) - - def on_enter(self): - """ - on_enter works better on linux - It creates a black screen on android until camera gets loaded - """ - if platform != "android": - Clock.schedule_once(self.start_camera, 0) - - def on_leave(self): - """This method will call on leave""" - Clock.schedule_once(self.stop_camera, 0) - - def start_camera(self, *args): - """Its used for starting camera for scanning qrcode""" - # pylint: disable=attribute-defined-outside-init - self.xcam = self.children[0].ids.zbarcam.ids.xcamera - if platform == "android": - self.xcam.play = True - else: - Clock.schedule_once(self.open_cam, 0) - - def stop_camera(self, *args): - """Its used for stop the camera""" - self.xcam.play = False - if platform != "android": - self.xcam._camera._device.release() - - def open_cam(self, *args): - """It will open up the camera""" - if not self.xcam._camera._device.isOpened(): - self.xcam._camera._device.open(self.xcam._camera._index) - self.xcam.play = True diff --git a/src/bitmessagekivy/baseclass/sent.py b/src/bitmessagekivy/baseclass/sent.py deleted file mode 100644 index 59db0ab9..00000000 --- a/src/bitmessagekivy/baseclass/sent.py +++ /dev/null @@ -1,47 +0,0 @@ -# pylint: disable=import-error, attribute-defined-outside-init, too-many-arguments -# pylint: disable=no-member, no-name-in-module, unused-argument, too-few-public-methods - -""" - Sent screen; All sent message managed here. -""" - -from kivy.properties import StringProperty, ListProperty -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - - -class Sent(Screen): - """Sent Screen class for kivy UI""" - - queryreturn = ListProperty() - account = StringProperty() - has_refreshed = True - no_search_res_found = "No search result found" - label_str = "Yet no message for this account!" - - def __init__(self, *args, **kwargs): - """Association with the screen""" - super(Sent, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - - def init_ui(self, dt=0): - """Clock Schdule for method sent accounts""" - self.loadSent() - print(dt) - - def set_defaultAddress(self): - """Set default address""" - if self.kivy_state.selected_address == "": - if self.kivy_running_app.identity_list: - self.kivy_state.selected_address = self.kivy_running_app.identity_list[0] - - def loadSent(self, where="", what=""): - """Load Sent list for Sent messages""" - self.set_defaultAddress() - self.account = self.kivy_state.selected_address diff --git a/src/bitmessagekivy/baseclass/settings.py b/src/bitmessagekivy/baseclass/settings.py deleted file mode 100644 index 1ceb35ee..00000000 --- a/src/bitmessagekivy/baseclass/settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# pylint: disable=unused-argument, no-name-in-module, too-few-public-methods -""" -Settings screen UI -""" - -from kivy.uix.screenmanager import Screen - - -class Setting(Screen): - """Setting Screen for kivy Ui""" diff --git a/src/bitmessagekivy/baseclass/trash.py b/src/bitmessagekivy/baseclass/trash.py deleted file mode 100644 index eb62fdaa..00000000 --- a/src/bitmessagekivy/baseclass/trash.py +++ /dev/null @@ -1,33 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods - -""" - Trash screen -""" - -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - - -class Trash(Screen): - """Trash Screen class for kivy Ui""" - - trash_messages = ListProperty() - has_refreshed = True - delete_index = None - table_name = StringProperty() - no_msg_found_str = "Yet no trashed message for this account!" - - def __init__(self, *args, **kwargs): - """Trash method, delete sent message and add in Trash""" - super(Trash, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] diff --git a/src/bitmessagekivy/get_platform.py b/src/bitmessagekivy/get_platform.py deleted file mode 100644 index 654b31f4..00000000 --- a/src/bitmessagekivy/get_platform.py +++ /dev/null @@ -1,31 +0,0 @@ -# pylint: disable=no-else-return, too-many-return-statements - -"""To check the platform""" - -from sys import platform as _sys_platform -from os import environ - - -def _get_platform(): - kivy_build = environ.get("KIVY_BUILD", "") - if kivy_build in {"android", "ios"}: - return kivy_build - elif "P4A_BOOTSTRAP" in environ: - return "android" - elif "ANDROID_ARGUMENT" in environ: - return "android" - elif _sys_platform in ("win32", "cygwin"): - return "win" - elif _sys_platform == "darwin": - return "macosx" - elif _sys_platform.startswith("linux"): - return "linux" - elif _sys_platform.startswith("freebsd"): - return "linux" - return "unknown" - - -platform = _get_platform() - -if platform not in ("android", "unknown"): - environ["KIVY_CAMERA"] = "opencv" diff --git a/src/bitmessagekivy/identiconGeneration.py b/src/bitmessagekivy/identiconGeneration.py deleted file mode 100644 index 2e2f2e93..00000000 --- a/src/bitmessagekivy/identiconGeneration.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Core classes for loading images and converting them to a Texture. -The raw image data can be keep in memory for further access -""" -import hashlib -from io import BytesIO - -from PIL import Image -from kivy.core.image import Image as CoreImage -from kivy.uix.image import Image as kiImage - - -# constants -RESOLUTION = 300, 300 -V_RESOLUTION = 7, 7 -BACKGROUND_COLOR = 255, 255, 255, 255 -MODE = "RGB" - - -def generate(Generate_string=None): - """Generating string""" - hash_string = generate_hash(Generate_string) - color = random_color(hash_string) - image = Image.new(MODE, V_RESOLUTION, BACKGROUND_COLOR) - image = generate_image(image, color, hash_string) - image = image.resize(RESOLUTION, 0) - data = BytesIO() - image.save(data, format='png') - data.seek(0) - # yes you actually need this - im = CoreImage(BytesIO(data.read()), ext='png') - beeld = kiImage() - # only use this line in first code instance - beeld.texture = im.texture - return beeld - - -def generate_hash(string): - """Generating hash""" - try: - # make input case insensitive - string = str.lower(string) - hash_object = hashlib.md5( # nosec B324, B303 - str.encode(string)) - print(hash_object.hexdigest()) - # returned object is a hex string - return hash_object.hexdigest() - except IndexError: - print("Error: Please enter a string as an argument.") - - -def random_color(hash_string): - """Getting random color""" - # remove first three digits from hex string - split = 6 - rgb = hash_string[:split] - split = 2 - r = rgb[:split] - g = rgb[split:2 * split] - b = rgb[2 * split:3 * split] - color = (int(r, 16), int(g, 16), int(b, 16), 0xFF) - return color - - -def generate_image(image, color, hash_string): - """Generating images""" - hash_string = hash_string[6:] - lower_x = 1 - lower_y = 1 - upper_x = int(V_RESOLUTION[0] / 2) + 1 - upper_y = V_RESOLUTION[1] - 1 - limit_x = V_RESOLUTION[0] - 1 - index = 0 - for x in range(lower_x, upper_x): - for y in range(lower_y, upper_y): - if int(hash_string[index], 16) % 2 == 0: - image.putpixel((x, y), color) - image.putpixel((limit_x - x, y), color) - index = index + 1 - return image diff --git a/src/bitmessagekivy/kivy_helper_search.py b/src/bitmessagekivy/kivy_helper_search.py deleted file mode 100644 index c48ca3ad..00000000 --- a/src/bitmessagekivy/kivy_helper_search.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -Sql queries for bitmessagekivy -""" -from pybitmessage.helper_sql import sqlQuery - - -def search_sql( - xAddress="toaddress", account=None, folder="inbox", where=None, - what=None, unreadOnly=False, start_indx=0, end_indx=20): - # pylint: disable=too-many-arguments, too-many-branches - """Method helping for searching mails""" - if what is not None and what != "": - what = "%" + what + "%" - else: - what = None - if folder in ("sent", "draft"): - sqlStatementBase = ( - '''SELECT toaddress, fromaddress, subject, message, status,''' - ''' ackdata, senttime FROM sent ''' - ) - elif folder == "addressbook": - sqlStatementBase = '''SELECT label, address From addressbook ''' - else: - sqlStatementBase = ( - '''SELECT folder, msgid, toaddress, message, fromaddress,''' - ''' subject, received, read FROM inbox ''' - ) - sqlStatementParts = [] - sqlArguments = [] - if account is not None: - if xAddress == 'both': - sqlStatementParts.append("(fromaddress = ? OR toaddress = ?)") - sqlArguments.append(account) - sqlArguments.append(account) - else: - sqlStatementParts.append(xAddress + " = ? ") - sqlArguments.append(account) - if folder != "addressbook": - if folder is not None: - if folder == "new": - folder = "inbox" - unreadOnly = True - sqlStatementParts.append("folder = ? ") - sqlArguments.append(folder) - else: - sqlStatementParts.append("folder != ?") - sqlArguments.append("trash") - if what is not None: - for colmns in where: - if len(where) > 1: - if where[0] == colmns: - filter_col = "(%s LIKE ?" % (colmns) - else: - filter_col += " or %s LIKE ? )" % (colmns) - else: - filter_col = "%s LIKE ?" % (colmns) - sqlArguments.append(what) - sqlStatementParts.append(filter_col) - if unreadOnly: - sqlStatementParts.append("read = 0") - if sqlStatementParts: - sqlStatementBase += "WHERE " + " AND ".join(sqlStatementParts) - if folder in ("sent", "draft"): - sqlStatementBase += \ - "ORDER BY senttime DESC limit {0}, {1}".format( - start_indx, end_indx) - elif folder == "inbox": - sqlStatementBase += \ - "ORDER BY received DESC limit {0}, {1}".format( - start_indx, end_indx) - return sqlQuery(sqlStatementBase, sqlArguments) diff --git a/src/bitmessagekivy/kivy_state.py b/src/bitmessagekivy/kivy_state.py deleted file mode 100644 index 42051ff9..00000000 --- a/src/bitmessagekivy/kivy_state.py +++ /dev/null @@ -1,42 +0,0 @@ -# pylint: disable=too-many-instance-attributes, too-few-public-methods - -""" -Kivy State variables are assigned here, they are separated from state.py -================================= -""" - -import os -import threading - - -class KivyStateVariables(object): - """This Class hold all the kivy state variables""" - - def __init__(self): - self.selected_address = '' - self.navinstance = None - self.mail_id = 0 - self.my_address_obj = None - self.detail_page_type = None - self.ackdata = None - self.status = None - self.screen_density = None - self.msg_counter_objs = None - self.check_sent_acc = None - self.sent_count = 0 - self.inbox_count = 0 - self.trash_count = 0 - self.draft_count = 0 - self.all_count = 0 - self.searching_text = '' - self.search_screen = '' - self.send_draft_mail = None - self.is_allmail = False - self.in_composer = False - self.available_credit = 0 - self.in_sent_method = False - self.in_search_mode = False - self.image_dir = os.path.abspath(os.path.join('images', 'kivy')) - self.kivyui_ready = threading.Event() - self.file_manager = None - self.manager_open = False diff --git a/src/bitmessagekivy/kv/addressbook.kv b/src/bitmessagekivy/kv/addressbook.kv deleted file mode 100644 index 73b4c1ef..00000000 --- a/src/bitmessagekivy/kv/addressbook.kv +++ /dev/null @@ -1,26 +0,0 @@ -: - name: 'addressbook' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - SearchBar: - id: address_search - GridLayout: - id: identi_tag - padding: [20, 0, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/allmails.kv b/src/bitmessagekivy/kv/allmails.kv deleted file mode 100644 index f1b9387e..00000000 --- a/src/bitmessagekivy/kv/allmails.kv +++ /dev/null @@ -1,25 +0,0 @@ -: - name: 'allmails' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - GridLayout: - id: identi_tag - padding: [20, 20, 0, 5] - spacing: dp(5) - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/chat.kv b/src/bitmessagekivy/kv/chat.kv deleted file mode 100644 index e21ed503..00000000 --- a/src/bitmessagekivy/kv/chat.kv +++ /dev/null @@ -1,82 +0,0 @@ -#:import C kivy.utils.get_color_from_hex -#:import MDTextField kivymd.uix.textfield.MDTextField -: - name: 'chat' - BoxLayout: - orientation: 'vertical' - canvas.before: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - ScrollView: - Label: - id: chat_logs - text: '' - color: C('#101010') - text_size: (self.width, None) - halign: 'left' - valign: 'top' - padding: (0, 0) # fixed in Kivy 1.8.1 - size_hint: (1, None) - height: self.texture_size[1] - markup: True - font_size: sp(20) - MDBoxLayout: - size_hint_y: None - spacing:5 - orientation: 'horizontal' - pos_hint: {'center_y': 1, 'center_x': 1} - halign: 'right' - pos_hint: {'left': 0} - pos_hint: {'x':.8} - height: dp(50) + self.minimum_height - MDFillRoundFlatButton: - text: app.tr._("First message") - opposite_colors: True - pos_hint: {'center_x':0.8,'center_y':0.7} - - - - BoxLayout: - height: 50 - orientation: 'horizontal' - padding: 0 - size_hint: (1, None) - - MDTextField: - id:'id_message_body' - hint_text: 'Empty field' - icon_left: "message" - hint_text: "please enter your text" - mode: "fill" - fill_color: 1/255, 144/255, 254/255, 0.1 - multiline: True - font_color_normal: 0, 0, 0, .4 - icon_right: 'grease-pencil' - icon_right_color: app.theme_cls.primary_light - pos_hint: {'center_x':0.2,'center_y':0.7} - - MDIconButton: - id: file_manager - icon: "attachment" - opposite_colors: True - on_release: app.file_manager_open() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - - MDIconButton: - icon: 'camera' - opposite_colors: True - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - MDIconButton: - id: send_message - icon: "send" - # x: root.parent.x + dp(10) - # pos_hint: {"top": 1, 'left': 1} - color: [1,0,0,1] - on_release: app.rest_default_avatar_img() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color diff --git a/src/bitmessagekivy/kv/chat_list.kv b/src/bitmessagekivy/kv/chat_list.kv deleted file mode 100644 index e59c32d7..00000000 --- a/src/bitmessagekivy/kv/chat_list.kv +++ /dev/null @@ -1,58 +0,0 @@ -: - name: 'chlist' - canvas.before: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - MDTabs: - id: chat_panel - tab_display_mode:'text' - - Tab: - text: app.tr._("Chats") - BoxLayout: - id: chat_box - orientation: 'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - MDLabel: - font_style: 'Caption' - theme_text_color: 'Primary' - text: app.tr._('No Chat') - halign: 'center' - size_hint_y: None - bold: True - valign: 'top' - # OneLineAvatarListItem: - # text: "Single-line item with avatar" - # divider: None - # _no_ripple_effect: True - # ImageLeftWidget: - # source: './images/text_images/A.png' - # OneLineAvatarListItem: - # text: "Single-line item with avatar" - # divider: None - # _no_ripple_effect: True - # ImageLeftWidget: - # source: './images/text_images/B.png' - # OneLineAvatarListItem: - # text: "Single-line item with avatar" - # divider: None - # _no_ripple_effect: True - # ImageLeftWidget: - # source: './images/text_images/A.png' - Tab: - text: app.tr._("Contacts") - BoxLayout: - id: contact_box - orientation: 'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml diff --git a/src/bitmessagekivy/kv/chat_room.kv b/src/bitmessagekivy/kv/chat_room.kv deleted file mode 100644 index 40843c47..00000000 --- a/src/bitmessagekivy/kv/chat_room.kv +++ /dev/null @@ -1,45 +0,0 @@ -#:import C kivy.utils.get_color_from_hex - -: - name: 'chroom' - BoxLayout: - orientation: 'vertical' - canvas.before: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - ScrollView: - Label: - id: chat_logs - text: '' - color: C('#101010') - text_size: (self.width, None) - halign: 'left' - valign: 'top' - padding: (0, 0) # fixed in Kivy 1.8.1 - size_hint: (1, None) - height: self.texture_size[1] - markup: True - font_size: sp(20) - BoxLayout: - height: 50 - orientation: 'horizontal' - padding: 0 - size_hint: (1, None) - - TextInput: - id: message - size_hint: (1, 1) - multiline: False - font_size: sp(20) - on_text_validate: root.send_msg() - - MDRaisedButton: - text: app.tr._("Send") - elevation_normal: 2 - opposite_colors: True - size_hint: (0.3, 1) - pos_hint: {"center_x": .5} - on_press: root.send_msg() diff --git a/src/bitmessagekivy/kv/common_widgets.kv b/src/bitmessagekivy/kv/common_widgets.kv deleted file mode 100644 index 275bd12c..00000000 --- a/src/bitmessagekivy/kv/common_widgets.kv +++ /dev/null @@ -1,62 +0,0 @@ -: - source: app.image_path +('/down-arrow.png' if self.parent.is_open == True else '/right-arrow.png') - size: 15, 15 - x: self.parent.x + self.parent.width - self.width - 5 - y: self.parent.y + self.parent.height/2 - self.height + 5 - -: - # id: search_bar - size_hint_y: None - height: self.minimum_height - - MDIconButton: - icon: 'magnify' - - MDTextField: - id: search_field - hint_text: 'Search' - on_text: app.searchQuery(self) - canvas.before: - Color: - rgba: (0,0,0,1) - -: - id: spinner - size_hint: None, None - size: dp(46), dp(46) - pos_hint: {'center_x': 0.5, 'center_y': 0.5} - active: False - -: - size_hint_y: None - height: dp(56) - spacing: '10dp' - pos_hint: {'center_x':0.45, 'center_y': .1} - - Widget: - - MDFloatingActionButton: - icon: 'plus' - opposite_colors: True - elevation_normal: 8 - md_bg_color: [0.941, 0, 0,1] - on_press: app.set_screen('create') - on_press: app.clear_composer() - - - -: - size_hint: None, None - size: dp(36), dp(48) - pos_hint: {'center_x': .95, 'center_y': .4} - on_active: app.root.ids.id_myaddress.toggleAction(self) - -: - canvas: - Color: - id: set_clr - # rgba: 0.5, 0.5, 0.5, 0.5 - rgba: 0,0,0,0 - Rectangle: #woohoo!!! - size: self.size - pos: self.pos \ No newline at end of file diff --git a/src/bitmessagekivy/kv/credits.kv b/src/bitmessagekivy/kv/credits.kv deleted file mode 100644 index 1680d6f0..00000000 --- a/src/bitmessagekivy/kv/credits.kv +++ /dev/null @@ -1,28 +0,0 @@ -: - name: 'credits' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - OneLineListTitle: - id: cred - text: app.tr._("Available Credits") - divider: None - theme_text_color: 'Primary' - _no_ripple_effect: True - long_press_time: 1 - - OneLineListTitle: - id: cred - text: app.tr._(root.available_credits) - divider: None - font_style: 'H5' - theme_text_color: 'Primary' - _no_ripple_effect: True - long_press_time: 1 - AnchorLayout: - MDRaisedButton: - height: dp(38) - text: app.tr._("+Add more credits") - on_press: app.set_screen('payment') \ No newline at end of file diff --git a/src/bitmessagekivy/kv/draft.kv b/src/bitmessagekivy/kv/draft.kv deleted file mode 100644 index 56682d2b..00000000 --- a/src/bitmessagekivy/kv/draft.kv +++ /dev/null @@ -1,23 +0,0 @@ -: - name: 'draft' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - GridLayout: - id: identi_tag - padding: [20, 20, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/inbox.kv b/src/bitmessagekivy/kv/inbox.kv deleted file mode 100644 index b9cc8566..00000000 --- a/src/bitmessagekivy/kv/inbox.kv +++ /dev/null @@ -1,39 +0,0 @@ -: - name: 'inbox' - #transition: NoTransition() - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - SearchBar: - id:inbox_search - GridLayout: - id: identi_tag - padding: [20, 0, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - #FloatLayout: - # MDScrollViewRefreshLayout: - # id: refresh_layout - # refresh_callback: root.refresh_callback - # root_layout: root.set_root_layout() - # MDList: - # id: ml - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: - -: - size_hint:(None, None) - font_style: 'Caption' - halign: 'center' diff --git a/src/bitmessagekivy/kv/login.kv b/src/bitmessagekivy/kv/login.kv deleted file mode 100644 index d3e2f7f9..00000000 --- a/src/bitmessagekivy/kv/login.kv +++ /dev/null @@ -1,264 +0,0 @@ -#:import SlideTransition kivy.uix.screenmanager.SlideTransition -: - name:"login" - BoxLayout: - orientation: "vertical" - - #buttons-area-outer - BoxLayout: - size_hint_y: .53 - canvas: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - - ScreenManager: - id: check_screenmgr - Screen: - name: "check_screen" - BoxLayout: - orientation: "vertical" - padding: 0, dp(5), 0, dp(5) - spacing: dp(5) - - #label area - AnchorLayout: - size_hint_y: None - height: dp(50) - MDLabel: - text: app.tr._("Select method to make an address:") - bold: True - halign: "center" - theme_text_color: "Custom" - text_color: .4,.4,.4,1 - - #upper-checkbor-area - AnchorLayout: - size_hint_y: None - height: dp(40) - BoxLayout: - size_hint_x: None - width: self.minimum_width - - #check-container - AnchorLayout: - size_hint_x: None - width: dp(40) - Check: - active: True - - #text-container - AnchorLayout: - size_hint_x: None - width: dp(200) - MDLabel: - text: app.tr._("Pseudorandom Generator") - - AnchorLayout: - size_hint_y: None - height: dp(40) - BoxLayout: - size_hint_x: None - width: self.minimum_width - - #check-container - AnchorLayout: - size_hint_x: None - width: dp(40) - Check: - - #text-container - AnchorLayout: - size_hint_x: None - width: dp(200) - MDLabel: - text: app.tr._("Passphrase (deterministic) Generator") - AnchorLayout: - MDFillRoundFlatIconButton: - icon: "chevron-double-right" - text: app.tr._("Proceed Next") - on_release: - app.set_screen('random') - on_press: - app.root.ids.id_newidentity.reset_address_label() - - #info-area-outer - BoxLayout: - size_hint_y: .47 - padding: dp(7) - InfoLayout: - orientation:"vertical" - padding: 0, dp(5), 0, dp(5) - canvas: - Color: - rgba:1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - Color: - rgba: app.theme_cls.primary_color - Line: - rounded_rectangle: (self.pos[0]+4, self.pos[1]+4, self.width-8,self.height-8, 10, 10, 10, 10, 50) - width: dp(1) - ScreenManager: - id: info_screenmgr - - Screen: - name: "info1" - ScrollView: - bar_width:0 - do_scroll_x: False - - BoxLayout: - orientation: "vertical" - size_hint_y: None - height: self.minimum_height - - #note area - ContentHead: - section_name: "NOTE:" - ContentBody: - section_text: ("You may generate addresses by using either random numbers or by using a pass-phrase.If you use a pass-phrase, the address is called a deterministic address. The Random Number option is selected by default but deterministic addresses may have several pros and cons.") - - - #pros area - ContentHead: - section_name: "PROS:" - ContentBody: - section_text: ("You can re-create your addresses on any computer from memory you need-not-to worry about backing up your keys.dat file as long as you can remember your pass-phrase.") - - #cons area - ContentHead: - section_name: "CONS:" - ContentBody: - section_text: ("You must remember (or write down) your address version number and the stream number along with your pass-phrase.If you choose a weak pass-phrase and someone on the internet can brute-force it, they can read your messages and send messages as you.") - -: - name:"random" - ScrollView: - id:add_random_bx - -: - orientation: "vertical" - #buttons-area-outer - BoxLayout: - orientation: "vertical" - # padding: 0, dp(5), 0, dp(5) - # spacing: dp(5) - size_hint_y: .53 - canvas: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - - #label area - AnchorLayout: - size_hint_y: None - height: dp(50) - MDLabel: - text: app.tr._("Enter a label to generate address for:") - bold: True - halign: "center" - theme_text_color: "Custom" - text_color: .4,.4,.4,1 - - AnchorLayout: - size_hint_y: None - height: dp(40) - MDTextField: - id:lab - hint_text: "Label" - required: True - size_hint_x: None - width: dp(190) - helper_text_mode: "on_error" - # helper_text: "Please enter your label name" - on_text: app.root.ids.id_newidentity.add_validation(self) - canvas.before: - Color: - rgba: (0,0,0,1) - - AnchorLayout: - MDFillRoundFlatIconButton: - icon: "chevron-double-right" - text: app.tr._("Proceed Next") - on_release: app.root.ids.id_newidentity.generateaddress() - - Widget: - - #info-area-outer - BoxLayout: - size_hint_y: .47 - padding: dp(7) - InfoLayout: - orientation:"vertical" - padding: 0, dp(5), 0, dp(5) - canvas: - Color: - rgba:1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - Color: - rgba: app.theme_cls.primary_color - Line: - rounded_rectangle: (self.pos[0]+4, self.pos[1]+4, self.width-8,self.height-8, 10, 10, 10, 10, 50) - width: dp(1) - ScreenManager: - id: info_screenmgr - - Screen: - name: "info2" - ScrollView: - bar_width:0 - do_scroll_x: False - - BoxLayout: - orientation: "vertical" - size_hint_y: None - height: self.minimum_height - - #note area - ContentHead: - section_name: "NOTE:" - ContentBody: - section_text: ("Here you may generate as many addresses as you like..Indeed creating and abandoning addresses is not encouraged.") - -: - group: 'group' - size_hint: None, None - size: dp(48), dp(48) - -: - section_name: "" - orientation: "vertical" - size_hint_y: None - height: dp(50) - padding: dp(20), 0, 0, 0 - Widget: - size_hint_y: None - height: dp(25) - MDLabel: - theme_text_color: "Custom" - text_color: .1,.1,.1,.9 - text: app.tr._(root.section_name) - bold: True - font_style: "Button" - -: - section_text: "" - size_hint_y: None - height: self.minimum_height - padding: dp(50), 0, dp(10), 0 - - MDLabel: - size_hint_y: None - height: self.texture_size[1]+dp(10) - theme_text_color: "Custom" - text_color: 0.3,0.3,0.3,1 - font_style: "Body1" - text: app.tr._(root.section_text) diff --git a/src/bitmessagekivy/kv/maildetail.kv b/src/bitmessagekivy/kv/maildetail.kv deleted file mode 100644 index e98b8661..00000000 --- a/src/bitmessagekivy/kv/maildetail.kv +++ /dev/null @@ -1,87 +0,0 @@ -: - name: 'mailDetail' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - # height: dp(bod.height) + self.minimum_height - height: self.minimum_height - padding: dp(10) - # MDLabel: - # size_hint_y: None - # id: subj - # text: root.subject - # theme_text_color: 'Primary' - # halign: 'left' - # font_style: 'H5' - # height: dp(40) - # on_touch_down: root.allclick(self) - OneLineListTitle: - id: subj - text: app.tr._(root.subject) - divider: None - font_style: 'H5' - theme_text_color: 'Primary' - _no_ripple_effect: True - long_press_time: 1 - TwoLineAvatarIconListItem: - id: subaft - text: app.tr._(root.from_addr) - secondary_text: app.tr._('to ' + root.to_addr) - divider: None - on_press: root.detailedPopup() - BadgeText: - size_hint:(None, None) - size:[120, 140] if app.app_platform == 'android' else [64, 80] - text: app.tr._(root.time_tag) - halign:'center' - font_style:'Caption' - pos_hint: {'center_y': .8} - _txt_right_pad: dp(70) - font_size: '11sp' - MDChip: - size_hint: (.16 if app.app_platform == 'android' else .08 , None) - text: app.tr._(root.page_type) - icon: '' - text_color: (1,1,1,1) - pos_hint: {'center_x': .91 if app.app_platform == 'android' else .95, 'center_y': .3} - radius: [8] - height: self.parent.height/4 - AvatarSampleWidget: - source: root.avatarImg - MDLabel: - text: root.status - disabled: True - font_style: 'Body2' - halign:'left' - padding_x: 20 - # MDLabel: - # id: bod - # font_style: 'Subtitle2' - # theme_text_color: 'Primary' - # text: root.message - # halign: 'left' - # height: self.texture_size[1] - MyMDTextField: - id: bod - size_hint_y: None - font_style: 'Subtitle2' - text: root.message - multiline: True - readonly: True - line_color_normal: [0,0,0,0] - _current_line_color: [0,0,0,0] - line_color_focus: [0,0,0,0] - markup: True - font_size: '15sp' - canvas.before: - Color: - rgba: (0,0,0,1) - Loader: - - -: - canvas.before: - Color: - rgba: (0,0,0,1) diff --git a/src/bitmessagekivy/kv/msg_composer.kv b/src/bitmessagekivy/kv/msg_composer.kv deleted file mode 100644 index 13db4f4e..00000000 --- a/src/bitmessagekivy/kv/msg_composer.kv +++ /dev/null @@ -1,161 +0,0 @@ -: - name: 'create' - Loader: - - -: - ScrollView: - id: id_scroll - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: self.minimum_height + 3 * self.parent.height/5 - padding: dp(20) - spacing: 15 - BoxLayout: - orientation: 'vertical' - MDTextField: - id: ti - size_hint_y: None - hint_text: 'Type or Select sender address' - icon_right: 'account' - icon_right_color: app.theme_cls.primary_light - font_size: '15sp' - multiline: False - required: True - height: 100 - current_hint_text_color: 0,0,0,0.5 - helper_text_mode: "on_error" - canvas.before: - Color: - rgba: (0,0,0,1) - - - BoxLayout: - size_hint_y: None - height: dp(40) - IdentitySpinner: - id: composer_dropdown - background_color: app.theme_cls.primary_dark - values: app.identity_list - on_text: root.auto_fill_fromaddr() if self.text != 'Select' else '' - option_cls: Factory.get("ComposerSpinnerOption") - background_normal: '' - background_color: app.theme_cls.primary_color - color: color_font - font_size: '13.5sp' - ArrowImg: - - - RelativeLayout: - orientation: 'horizontal' - BoxLayout: - orientation: 'vertical' - txt_input: txt_input - rv: rv - size : (890, 60) - MyTextInput: - id: txt_input - size_hint_y: None - font_size: '15sp' - color: color_font - current_hint_text_color: 0,0,0,0.5 - height: 100 - hint_text: app.tr._('Type or Scan QR code for recipients address') - canvas.before: - Color: - rgba: (0,0,0,1) - - RV: - id: rv - MDIconButton: - icon: 'qrcode-scan' - pos_hint: {'center_x': 0.95, 'y': 0.6} - on_release: - if root.is_camara_attached(): app.set_screen('scanscreen') - else: root.camera_alert() - on_press: - app.root.ids.id_scanscreen.get_screen('composer') - - MyMDTextField: - id: subject - hint_text: 'Subject' - height: 100 - font_size: '15sp' - icon_right: 'notebook-outline' - icon_right_color: app.theme_cls.primary_light - current_hint_text_color: 0,0,0,0.5 - font_color_normal: 0, 0, 0, 1 - size_hint_y: None - required: True - multiline: False - helper_text_mode: "on_focus" - canvas.before: - Color: - rgba: (0,0,0,1) - - ScrollView: - id: scrlv - MDTextField: - id: body - hint_text: 'Body' - mode: "fill" - fill_color: 1/255, 144/255, 254/255, 0.1 - multiline: True - font_color_normal: 0, 0, 0, .4 - icon_right: 'grease-pencil' - icon_right_color: app.theme_cls.primary_light - size_hint: 1, 1 - height: app.window_size[1]/4 - canvas.before: - Color: - rgba: 125/255, 125/255, 125/255, 1 - BoxLayout: - spacing:50 - -: - readonly: False - multiline: False - - -: - # Draw a background to indicate selection - color: 0,0,0,1 - canvas.before: - Color: - rgba: app.theme_cls.primary_dark if self.selected else (1, 1, 1, 0) - Rectangle: - pos: self.pos - size: self.size - -: - canvas: - Color: - rgba: 0,0,0,.2 - - Line: - rectangle: self.x +1 , self.y, self.width - 2, self.height -2 - bar_width: 10 - scroll_type:['bars'] - viewclass: 'SelectableLabel' - SelectableRecycleBoxLayout: - default_size: None, dp(20) - default_size_hint: 1, None - size_hint_y: None - height: self.minimum_height - orientation: 'vertical' - multiselect: False - - -: - canvas.before: - Color: - rgba: (0,0,0,1) - - - -: - font_size: '13.5sp' - background_normal: 'atlas://data/images/defaulttheme/textinput_active' - background_color: app.theme_cls.primary_color - color: color_font diff --git a/src/bitmessagekivy/kv/myaddress.kv b/src/bitmessagekivy/kv/myaddress.kv deleted file mode 100644 index 71dde54a..00000000 --- a/src/bitmessagekivy/kv/myaddress.kv +++ /dev/null @@ -1,33 +0,0 @@ -: - name: 'myaddress' - BoxLayout: - id: main_box - orientation: 'vertical' - spacing: dp(5) - SearchBar: - id: search_bar - GridLayout: - id: identi_tag - padding: [20, 0, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: app.tr._('My Addresses') - font_style: 'Subtitle2' - FloatLayout: - MDScrollViewRefreshLayout: - id: refresh_layout - refresh_callback: root.refresh_callback - root_layout: root - MDList: - id: ml - Loader: - - -: - size_hint: None, None - size: dp(36), dp(48) - pos_hint: {'center_x': .95, 'center_y': .4} - on_active: app.root.ids.id_myaddress.toggleAction(self) diff --git a/src/bitmessagekivy/kv/network.kv b/src/bitmessagekivy/kv/network.kv deleted file mode 100644 index 17211e98..00000000 --- a/src/bitmessagekivy/kv/network.kv +++ /dev/null @@ -1,131 +0,0 @@ -: - name: 'networkstat' - MDTabs: - id: tab_panel - tab_display_mode:'text' - - Tab: - title: app.tr._("Total connections") - ScrollView: - do_scroll_x: False - MDList: - id: ml - size_hint_y: None - height: dp(200) - OneLineListItem: - text: app.tr._("Total Connections") - _no_ripple_effect: True - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - # height: dp(40) - text: app.tr._(root.text_variable_1) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_1) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' - Tab: - title: app.tr._('Processes') - ScrollView: - do_scroll_x: False - MDList: - id: ml - size_hint_y: None - height: dp(500) - OneLineListItem: - text: app.tr._("person-to-person") - _no_ripple_effect: True - - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - # height: dp(40) - text: app.tr._(root.text_variable_2) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_2) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' - OneLineListItem: - text: app.tr._("Brodcast") - _no_ripple_effect: True - - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - # height: dp(40) - text: app.tr._(root.text_variable_3) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_3) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' - OneLineListItem: - text: app.tr._("publickeys") - _no_ripple_effect: True - - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - # height: dp(40) - text: app.tr._(root.text_variable_4) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_4) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' - OneLineListItem: - text: app.tr._("objects") - _no_ripple_effect: True - - BoxLayout: - orientation: 'vertical' - size_hint_y: None - height: dp(58) - MDRaisedButton: - _no_ripple_effect: True - # size_hint: .6, 0 - #height: dp(40) - text: app.tr._(root.text_variable_5) - elevation_normal: 2 - opposite_colors: True - pos_hint: {'center_x': .5} - # MDLabel: - # font_style: 'H6' - # text: app.tr._(root.text_variable_5) - # font_size: '13sp' - # color: (1,1,1,1) - # halign: 'center' diff --git a/src/bitmessagekivy/kv/payment.kv b/src/bitmessagekivy/kv/payment.kv deleted file mode 100644 index 6d475f56..00000000 --- a/src/bitmessagekivy/kv/payment.kv +++ /dev/null @@ -1,325 +0,0 @@ -: - name: "payment" - id: id_payment_screen - payment_plan_id: "" - MDTabs: - id: tab_panel - tab_display_mode:'text' - Tab: - title: app.tr._("Payment") - id: id_payment plan - padding: "12dp" - spacing: "12dp" - BoxLayout: - ScrollView: - bar_width:0 - do_scroll_x: False - BoxLayout: - spacing: dp(5) - padding: dp(5) - size_hint_y: None - height: self.minimum_height - orientation: "vertical" - ProductCategoryLayout: - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "40dp" - pos_hint: {"center_x": .5, "center_y": .5} - MDLabel: - text: f"You have {app.encrypted_messages_per_month} messages left" - bold: True - halign:'center' - size_hint_y: None - pos_hint: {"center_x": .5, "center_y": .5} - - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "300dp" - md_bg_color: [1, 0.6, 0,0.5] - pos_hint: {"center_x": .5, "center_y": .5} - MDLabel: - text: "Free" - bold: True - halign:'center' - size_hint_y: None - pos_hint: {"center_x": .5, "center_y": .5} - MDRectangleFlatIconButton: - text: "[Currently this plan is active.]" - icon: 'shield-check' - line_color: 0, 0, 0, 0 - text_color: 'ffffff' - pos_hint: {"center_x": .5, "center_y": .5} - font_size: '18sp' - MDSeparator: - height: "1dp" - MDLabel: - text: "You can get zero encrypted message per month" - halign:'center' - bold: True - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "300dp" - md_bg_color: [0, 0.6, 0.8,0.8] - pos_hint: {"center_x": .5, "center_y": .5} - payment_plan_id: "sub_standard" - MDLabel: - text: "Standard" - bold: True - halign:'center' - size_hint_y: None - MDSeparator: - height: "1dp" - MDLabel: - text: "You can get 100 encrypted message per month" - halign:'center' - MDRaisedButton: - text: "Get it now" - theme_text_color: 'Primary' - md_bg_color: [1, 1, 1,1] - pos_hint: {'center_x': .5} - on_release:app.open_payment_layout(root.payment_plan_id) - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "300dp" - md_bg_color: [1, 0.6, 0.8,0.5] - pos_hint: {"center_x": .5, "center_y": .5} - payment_plan_id: "sub_premium" - MDLabel: - text: "Premium" - bold: True - halign:'center' - size_hint_y: None - MDSeparator: - height: "1dp" - MDLabel: - text: "You can get 1000 encrypted message per month" - halign:'center' - MDRaisedButton: - text: "Get it now" - theme_text_color: 'Primary' - md_bg_color: [1, 1, 1,1] - pos_hint: {'center_x': .5} - on_release:app.open_payment_layout(root.payment_plan_id) - Tab: - title: app.tr._("Extra-Messages") - id: id_payment_tab - BoxLayout: - ScrollView: - bar_width:0 - do_scroll_x: False - BoxLayout: - spacing: dp(8) - padding: dp(5) - size_hint_y: None - height: self.minimum_height - orientation: "vertical" - ProductCategoryLayout: - category_text: "Extra-Messages" - ProductLayout: - heading_text: "100 Encrypted messages " - price_text: "$0.99" - source: app.image_path + "/payment/buynew1.png" - description_text: "Buy extra one hundred encrypted messages!" - product_id: "SKUGASBILLING" - ProductLayout: - heading_text: "1000 Encrypted messages " - price_text: "$1.49" - source: app.image_path + "/payment/buynew1.png" - description_text: "Buy extra one thousand encrypted messages!" - product_id: "SKUUPGRADECAR" -: - size_hint_y: None - height: self.minimum_height - category_text:"" - - orientation: "vertical" - spacing: 2 - - #category area - Category: - text_: root.category_text - -: - canvas: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - text_: "" - size_hint_y: None - height: dp(30) - Widget: - size_hint_x: None - width: dp(20) - MDLabel: - text: root.text_ - font_size: sp(15) -: - heading_text: "" - price_text: "" - source: "" - description_text: "" - - product_id: "" - - canvas: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - - size_hint_y: None - height: dp(200) - orientation: "vertical" - - #heading area - BoxLayout: - size_hint_y: 0.3 - #text heading - BoxLayout: - Widget: - size_hint_x: None - width: dp(20) - MDLabel: - text: root.heading_text - bold: True - - #price text - BoxLayout: - size_hint_x:.3 - MDLabel: - text: root.price_text - bold: True - halign: "right" - theme_text_color: "Custom" - text_color: 0,0,1,1 - Widget: - size_hint_x: None - width: dp(20) - - #details area - BoxLayout: - size_hint_y: 0.3 - Widget: - size_hint_x: None - width: dp(20) - - #image area - AnchorLayout: - size_hint_x: None - width: self.height - BoxLayout: - canvas: - Color: - rgba: 1,1,1,1 - Ellipse: - size: self.size - pos: self.pos - source: root.source - Widget: - size_hint_x: None - width: dp(10) - - #description text - BoxLayout: - #size_hint_x: 1 - MDLabel: - text: root.description_text - font_size: sp(15) - #Button Area - BoxLayout: - size_hint_y: 0.4 - Widget: - - AnchorLayout: - anchor_x: "right" - MDRaisedButton: - elevation_normal: 5 - text: "BUY" - on_release: - #print(app) - app.open_payment_layout(root.product_id) - - Widget: - size_hint_x: None - width: dp(20) - -: - on_release: app.initiate_purchase(self.method_name) - recent: False - source: "" - method_name: "" - right_label_text: "Recent" if self.recent else "" - - ImageLeftWidget: - source: root.source - - RightLabel: - text: root.right_label_text - theme_text_color: "Custom" - text_color: 0,0,0,0.5 - font_size: sp(12) - -: - orientation: "vertical" - size_hint_y: None - height: "200dp" - - BoxLayout: - size_hint_y: None - height: dp(40) - - Widget: - size_hint_x: None - width: dp(20) - MDLabel: - text: "Select Payment Method" - font_size: sp(14) - bold: True - theme_text_color: "Custom" - text_color: 0,0,0,.5 - - - ScrollView: - - GridLayout: - cols: 1 - size_hint_y:None - height:self.minimum_height - - ListItemWithLabel: - source: app.image_path + "/payment/gplay.png" - text: "Google Play" - method_name: "gplay" - recent: True - - ListItemWithLabel: - source: app.image_path + "/payment/btc.png" - text: "BTC (Currently this feature is not available)" - method_name: "btc" - theme_text_color: 'Secondary' - md_bg_color: [0, 0, 0,1] - ListItemWithLabel: - source: app.image_path + "/payment/paypal.png" - text: "Paypal (Currently this feature is not available)" - method_name: "som" - theme_text_color: 'Secondary' - md_bg_color: [0, 0, 0,1] - ListItemWithLabel: - source: app.image_path + "/payment/buy.png" - text: "One more method" - method_name: "omm" diff --git a/src/bitmessagekivy/kv/popup.kv b/src/bitmessagekivy/kv/popup.kv deleted file mode 100644 index 2db74525..00000000 --- a/src/bitmessagekivy/kv/popup.kv +++ /dev/null @@ -1,333 +0,0 @@ -: - separator_color: 1, 1, 1, 1 - background: "White.png" - Button: - id: btn - disabled: True - background_disabled_normal: "White.png" - Image: - source: app.image_path + '/loader.gif' - anim_delay: 0 - #mipmap: True - size: root.size - - -: - id: popup_box - orientation: 'vertical' - # spacing:dp(20) - # spacing: "12dp" - size_hint_y: None - # height: "120dp" - height: label.height+address.height - BoxLayout: - orientation: 'vertical' - MDTextField: - id: label - multiline: False - hint_text: app.tr._("Label") - required: True - icon_right: 'label' - helper_text_mode: "on_error" - # TODO: on_text: root.checkLabel_valid(self) is not used now but it will be used later - canvas.before: - Color: - rgba: (0,0,0,1) - MDTextField: - id: address - hint_text: app.tr._("Address") - required: True - icon_right: 'book-plus' - helper_text_mode: "on_error" - multiline: False - # TODO: on_text: root.checkAddress_valid(self) is not used now but it will be used later - canvas.before: - Color: - rgba: (0,0,0,1) - -: - id: addbook_popup_box - size_hint_y: None - height: 2.5*(add_label.height) - orientation: 'vertical' - spacing:dp(5) - MDLabel - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Label") - font_size: '17sp' - halign: 'left' - MDTextField: - id: add_label - font_style: 'Body1' - font_size: '15sp' - halign: 'left' - text: app.tr._(root.address_label) - theme_text_color: 'Primary' - required: True - helper_text_mode: "on_error" - on_text: root.checkLabel_valid(self) - canvas.before: - Color: - rgba: (0,0,0,1) - MDLabel: - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Address") - font_size: '17sp' - halign: 'left' - Widget: - size_hint_y: None - height: dp(1) - BoxLayout: - orientation: 'horizontal' - MDLabel: - id: address - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._(root.address) - font_size: '15sp' - halign: 'left' - IconRightSampleWidget: - pos_hint: {'center_x': 0, 'center_y': 1} - icon: 'content-copy' - on_press: app.copy_composer_text(root.address) - - -: - id: myadd_popup - size_hint_y: None - height: "130dp" - spacing:dp(25) - - #height: dp(1.5*(myaddr_label.height)) - orientation: 'vertical' - MDLabel: - id: myaddr_label - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Label") - font_size: '17sp' - halign: 'left' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: root.address_label - font_size: '15sp' - halign: 'left' - MDLabel: - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Address") - font_size: '17sp' - halign: 'left' - BoxLayout: - orientation: 'horizontal' - MDLabel: - id: label_address - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._(root.address) - font_size: '15sp' - halign: 'left' - IconRightSampleWidget: - pos_hint: {'center_x': 0, 'center_y': 1} - icon: 'content-copy' - on_press: app.copy_composer_text(root.address) - BoxLayout: - id: my_add_btn - spacing:5 - orientation: 'horizontal' - size_hint_y: None - height: self.minimum_height - MDRaisedButton: - size_hint: 2, None - height: dp(40) - on_press: root.send_message_from() - MDLabel: - font_style: 'H6' - text: app.tr._('Send message from') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - on_press: app.set_screen('showqrcode') - on_press: app.root.ids.id_showqrcode.qrdisplay(root, root.address) - MDLabel: - font_style: 'H6' - text: app.tr._('Show QR code') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - on_press: root.close_pop() - MDLabel: - font_style: 'H6' - text: app.tr._('Cancel') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - -: - id: closing_popup - size_hint : (None,None) - height: 1.4*(popup_label.height+ my_add_btn.children[0].height) - width :app.window_size[0] - (app.window_size[0]/10 if app.app_platform == 'android' else app.window_size[0]/4) - background: app.image_path + '/popup.jpeg' - auto_dismiss: False - separator_height: 0 - BoxLayout: - id: myadd_popup_box - size_hint_y: None - spacing:dp(70) - orientation: 'vertical' - BoxLayout: - size_hint_y: None - orientation: 'vertical' - spacing:dp(25) - MDLabel: - id: popup_label - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("Bitmessage isn't connected to the network.\n If you quit now, it may cause delivery delays.\n Wait until connected and the synchronisation finishes?") - font_size: '17sp' - halign: 'center' - BoxLayout: - id: my_add_btn - spacing:5 - orientation: 'horizontal' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - on_press: root.closingAction(self.children[0].text) - on_press: app.stop() - MDLabel: - font_style: 'H6' - text: app.tr._('Yes') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - on_press: root.closingAction(self.children[0].text) - MDLabel: - font_style: 'H6' - text: app.tr._('No') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - MDRaisedButton: - size_hint: 1.5, None - height: dp(40) - #on_press: root.dismiss() - on_press: root.closingAction(self.children[0].text) - MDLabel: - font_style: 'H6' - text: app.tr._('Cancel') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - -: - id: myadd_popup - size_hint : (None,None) - # height: 2*(sd_label.height+ sd_btn.children[0].height) - width :app.window_size[0] - (app.window_size[0]/10 if app.app_platform == 'android' else app.window_size[0]/4) - background: app.image_path + '/popup.jpeg' - auto_dismiss: False - separator_height: 0 - BoxLayout: - id: myadd_popup_box - size_hint_y: None - orientation: 'vertical' - spacing:dp(8 if app.app_platform == 'android' else 3) - BoxLayout: - orientation: 'vertical' - MDLabel: - id: from_add_label - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: app.tr._("From :") - font_size: '15sp' - halign: 'left' - Widget: - size_hint_y: None - height: dp(1 if app.app_platform == 'android' else 0) - BoxLayout: - size_hint_y: None - height: 50 - orientation: 'horizontal' - MDLabel: - id: sd_label - font_style: 'Body2' - theme_text_color: 'Primary' - text: app.tr._("[b]" + root.from_addr + "[/b]") - font_size: '15sp' - halign: 'left' - markup: True - IconRightSampleWidget: - icon: 'content-copy' - on_press: app.copy_composer_text(root.from_addr) - Widget: - id: space_1 - size_hint_y: None - height: dp(2 if app.app_platform == 'android' else 0) - BoxLayout: - id: to_addtitle - Widget: - id:space_2 - size_hint_y: None - height: dp(1 if app.app_platform == 'android' else 0) - BoxLayout: - id: to_addId - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: 50 - MDLabel: - font_style: 'Body2' - theme_text_color: 'Primary' - text: app.tr._("Date : " + root.time_tag) - font_size: '15sp' - halign: 'left' - BoxLayout: - id: sd_btn - orientation: 'vertical' - MDRaisedButton: - id: dismiss_btn - on_press: root.dismiss() - size_hint: .2, 0 - pos_hint: {'x': 0.8, 'y': 0} - MDLabel: - font_style: 'H6' - text: app.tr._('Cancel') - font_size: '13sp' - color: (1,1,1,1) - halign: 'center' - -: - orientation: 'horizontal' - MDLabel: - font_style: 'Body2' - theme_text_color: 'Primary' - text: app.tr._(root.to_addr) - font_size: '15sp' - halign: 'left' - IconRightSampleWidget: - icon: 'content-copy' - on_press: app.copy_composer_text(root.to_addr) - -: - orientation: 'vertical' - MDLabel: - id: to_add_label - font_style: 'Subtitle2' - theme_text_color: 'Primary' - text: "To :" - font_size: '15sp' - halign: 'left' \ No newline at end of file diff --git a/src/bitmessagekivy/kv/qrcode.kv b/src/bitmessagekivy/kv/qrcode.kv deleted file mode 100644 index cadaa996..00000000 --- a/src/bitmessagekivy/kv/qrcode.kv +++ /dev/null @@ -1,33 +0,0 @@ -: - name: 'showqrcode' - BoxLayout: - orientation: 'vertical' - size_hint: (None, None) - pos_hint:{'center_x': .5, 'top': 0.9} - size: (app.window_size[0]/1.8, app.window_size[0]/1.8) - id: qr - BoxLayout: - orientation: 'vertical' - MyMDTextField: - size_hint_y: None - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._(root.address) - multiline: True - readonly: True - line_color_normal: [0,0,0,0] - _current_line_color: [0,0,0,0] - line_color_focus: [0,0,0,0] - halign: 'center' - font_size: dp(15) - bold: True - canvas.before: - Color: - rgba: (0,0,0,1) - # MDLabel: - # size_hint_y: None - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: "[b]BM-2cV7Y8imvAevK6z6YmhYRcj2t7rghBtDSZ[/b]" - # markup: True - # pos_hint: {'x': .28, 'y': 0.6} \ No newline at end of file diff --git a/src/bitmessagekivy/kv/scan_screen.kv b/src/bitmessagekivy/kv/scan_screen.kv deleted file mode 100644 index dbcff5a1..00000000 --- a/src/bitmessagekivy/kv/scan_screen.kv +++ /dev/null @@ -1,2 +0,0 @@ -: - name:'scanscreen' \ No newline at end of file diff --git a/src/bitmessagekivy/kv/scanner.kv b/src/bitmessagekivy/kv/scanner.kv deleted file mode 100644 index 1c56f6c2..00000000 --- a/src/bitmessagekivy/kv/scanner.kv +++ /dev/null @@ -1,37 +0,0 @@ -#:import ZBarSymbol pyzbar.pyzbar.ZBarSymbol - -BoxLayout: - orientation: 'vertical' - ZBarCam: - id: zbarcam - # optional, by default checks all types - code_types: ZBarSymbol.QRCODE, ZBarSymbol.EAN13 - scan_callback: app._after_scan - scanner_line_y_initial: self.size[1]/2 +self.qrwidth/2 - scanner_line_y_final: self.size[1]/2-self.qrwidth/2 - - canvas: - Color: - rgba: 0,0,0,.25 - - #left rect - Rectangle: - pos: self.pos[0], self.pos[1] - size: self.size[0]/2-self.qrwidth/2, self.size[1] - - #right rect - Rectangle: - pos: self.size[0]/2+self.qrwidth/2, 0 - size: self.size[0]/2-self.qrwidth/2, self.size[1] - - #top rect - Rectangle: - pos: self.size[0]/2-self.qrwidth/2, self.size[1]/2+self.qrwidth/2 - size: self.qrwidth, self.size[1]/2-self.qrwidth/2 - - #bottom rect - Rectangle: - pos: self.size[0]/2-self.qrwidth/2, 0 - size: self.qrwidth, self.size[1]/2-self.qrwidth/2 - - \ No newline at end of file diff --git a/src/bitmessagekivy/kv/sent.kv b/src/bitmessagekivy/kv/sent.kv deleted file mode 100644 index 11477ed6..00000000 --- a/src/bitmessagekivy/kv/sent.kv +++ /dev/null @@ -1,26 +0,0 @@ -: - name: 'sent' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - SearchBar: - id: sent_search - GridLayout: - id: identi_tag - padding: [20, 0, 0, 5] - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/settings.kv b/src/bitmessagekivy/kv/settings.kv deleted file mode 100644 index f5796060..00000000 --- a/src/bitmessagekivy/kv/settings.kv +++ /dev/null @@ -1,948 +0,0 @@ -: - name: 'set' - MDTabs: - id: tab_panel - tab_display_mode:'text' - - Tab: - title: app.tr._("User Interface") - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(250) + self.minimum_height - padding: 10 - BoxLayout: - size_hint_y: None - orientation: 'horizontal' - height: self.minimum_height - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - disabled: True - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Start-on-login not yet supported on your OS") - halign: 'left' - pos_hint: {'center_x': 0, 'center_y': 0.6} - disabled: True - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [20, 0, 0, 0] - spacing: dp(10) - height: dp(100) + self.minimum_height - # pos_hint: {'center_x': 0, 'center_y': 0.6} - BoxLayout: - id: box_height - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Tray") - halign: 'left' - bold: True - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Start Bitmessage in the tray(don't show main window)") - halign: 'left' - pos_hint: {'x': 0, 'y': .5} - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Minimize to tray") - halign: 'left' - pos_hint: {'x': 0, 'y': .5} - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Close to tray") - halign: 'left' - pos_hint: {'x': 0, 'y': .5} - BoxLayout: - size_hint_y: None - orientation: 'vertical' - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Hide connection notifications") - halign: 'left' - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Show notification when message received") - halign: 'left' - - BoxLayout: - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._('In portable Mode, messages and config files are stored in the same directory as the program rather then the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive.') - # text: 'huiiiii' - halign: 'left' - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(100) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Willingly include unencrypted destination address when sending to a mobile device") - halign: 'left' - pos_hint: {'x': 0, 'y': 0.2} - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Use identicons") - halign: 'left' - pos_hint: {'x': 0, 'y': 0.2} - BoxLayout: - orientation: 'horizontal' - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Reply below Quote") - halign: 'left' - pos_hint: {'x': 0, 'y': 0.2} - Widget: - size_hint_y: None - height: 10 - BoxLayout: - size_hint_y: None - orientation: 'vertical' - # padding: [0, 10, 0, 0] - spacing: 10 - padding: [20, 0, 0, 0] - height: dp(20) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Interface Language") - # halign: 'right' - bold: True - MDDropDownItem: - id: dropdown_item - text: "System Setting" - # pos_hint: {"center_x": .5, "center_y": .6} - # current_item: "Item 0" - # on_release: root.menu.open() - BoxLayout: - spacing:5 - orientation: 'horizontal' - # pos_hint: {'x':.76} - BoxLayout: - orientation: 'horizontal' - spacing: 10 - MDRaisedButton: - text: app.tr._('Apply') - # on_press: root.change_language() - Tab: - title: 'Network Settings' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(500) + self.minimum_height - padding: 10 - BoxLayout: - id: box_height - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Listening port") - halign: 'left' - bold: True - BoxLayout: - orientation: 'horizontal' - padding: [10, 0, 0, 0] - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Listen for connections on port:") - halign: 'left' - BoxLayout: - orientation: 'horizontal' - MDTextFieldRect: - size_hint: None, None - size: dp(100), dp(30) - text: app.tr._('8444') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - orientation: 'horizontal' - padding_left: 10 - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("UPnP") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - BoxLayout: - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Proxy server / Tor") - halign: 'left' - bold: True - - GridLayout: - cols: 2 - padding: [10, 0, 0, 0] - MDLabel: - size_hint_x: None - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Type:") - halign: 'left' - MDDropDownItem: - id: dropdown_item2 - dropdown_bg: [1, 1, 1, 1] - text: 'none' - pos_hint: {'x': 0.9, 'y': 0} - items: [f"{i}" for i in ['System Setting','U.S. English']] - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [30, 0, 0, 0] - spacing: 10 - height: dp(100) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Server hostname:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('localhost') - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Port:") - halign: 'left' - # TextInput: - # size_hint: None, None - # hint_text: '9050' - # size: dp(app.window_size[0]/4), dp(30) - # input_filter: "int" - # readonly: False - # multiline: False - # font_size: '15sp' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('9050') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Username:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Pass:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - padding: [30, 0, 0, 0] - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Authentication") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - BoxLayout: - orientation: 'horizontal' - padding: [30, 0, 0, 0] - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Listen for incoming connections when using proxy") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - BoxLayout: - orientation: 'horizontal' - padding: [30, 0, 0, 0] - MDCheckbox: - id: chkbox - size_hint: None, None - size: dp(48), dp(50) - # active: True - halign: 'center' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Only connect to onion services(*.onion)") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - BoxLayout: - orientation: 'vertical' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Bandwidth limit") - halign: 'left' - bold: True - BoxLayout: - size_hint_y: None - orientation: 'horizontal' - padding: [30, 0, 0, 0] - height: dp(30) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Maximum download rate (kB/s):[0:unlimited]") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: app.window_size[0]/2, dp(30) - hint_text: app.tr._('0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - size_hint_y: None - orientation: 'horizontal' - padding: [30, 0, 0, 0] - height: dp(30) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Maximum upload rate (kB/s):[0:unlimited]") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: app.window_size[0]/2, dp(30) - hint_text: '0' - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - size_hint_y: None - orientation: 'horizontal' - padding: [30, 0, 0, 0] - height: dp(30) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Maximum outbound connections:[0:none]") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: app.window_size[0]/2, dp(30) - hint_text: '8' - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - spacing:5 - orientation: 'horizontal' - # pos_hint: {'x':.76} - - MDRaisedButton: - text: app.tr._('Apply') - Tab: - title: 'Demanded Difficulty' - ScrollView: - do_scroll_x: False - - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(300) + self.minimum_height - padding: 10 - BoxLayout: - id: box_height - orientation: 'vertical' - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("Listening port") - # halign: 'left' - # bold: True - - # BoxLayout: - # size_hint_y: None - # orientation: 'vertical' - # height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height - # padding: 20 - # # spacing: 10 - # BoxLayout: - # # size_hint_y: None - # id: box1_height - # # orientation: 'vertical' - # # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "\n\n\nWhen 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.\n\n" - halign: 'left' - - BoxLayout: - orientation: 'horizontal' - padding: 5 - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Total difficulty:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('00000.0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - - BoxLayout: - # size_hint_y: None - id: box1_height - orientation: 'vertical' - padding: 5 - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work." - halign: 'left' - - BoxLayout: - orientation: 'horizontal' - spacing: 0 - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Small message difficulty:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('00000.0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - - - BoxLayout: - size_hint_y: None - padding: 0 - id: box1_height - orientation: 'vertical' - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "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." - halign: 'left' - - - # BoxLayout: - # id: box2_height - # size_hint_y: None - # orientation: 'vertical' - # height: dp(30) + self.minimum_height - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("Leave these input fields blank for the default behavior.") - # halign: 'left' - # BoxLayout: - # size_hint_y: None - # orientation: 'vertical' - # padding: [10, 0, 0, 0] - # height: dp(50) + self.minimum_height - # BoxLayout: - # orientation: 'horizontal' - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("Give up after") - # halign: 'left' - # MDTextFieldRect: - # size_hint: None, None - # size: dp(70), dp(30) - # text: app.tr._('0') - # # pos_hint: {'center_y': .5, 'center_x': .5} - # input_filter: "int" - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("days and") - # halign: 'left' - # MDTextFieldRect: - # size_hint: None, None - # size: dp(70), dp(30) - # text: '0' - # # pos_hint: {'center_y': .5, 'center_x': .5} - # input_filter: "int" - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: "months" - # halign: 'left' - BoxLayout: - size_hint_y: None - spacing:10 - orientation: 'horizontal' - # pos_hint: {'left': 0} - # pos_hint: {'x':.75} - height: dp(10) + self.minimum_height - MDRaisedButton: - text: app.tr._('Cancel') - MDRaisedButton: - text: app.tr._('Apply') - - Tab: - title: 'Max acceptable Difficulty' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height - padding: 20 - - # spacing: 10 - BoxLayout: - # size_hint_y: None - id: box1_height - orientation: 'vertical' - spacing: 10 - - # pos_hint: {'x': 0, 'y': 0.2} - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "\n\n\nHere 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." - halign: 'left' - # BoxLayout: - # id: box2_height - # size_hint_y: None - # orientation: 'vertical' - # height: dp(40) + self.minimum_height - # BoxLayout: - # size_hint_y: None - # orientation: 'vertical' - # padding: [10, 0, 0, 0] - # height: dp(50) + self.minimum_height - - GridLayout: - cols: 2 - padding: [10, 0, 0, 0] - - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [10, 0, 0, 0] - spacing: 10 - height: dp(50) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Maximum acceptable total difficulty:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('00000.0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Hardware GPU acceleration (OpenCL):") - halign: 'left' - MDDropDownItem: - id: dropdown_item - text: "None" - pos_hint: {"center_x": 0, "center_y": 0} - # current_item: "Item 0" - # on_release: root.menu.open() - - # BoxLayout: - # size_hint_y: None - # spacing:5 - # orientation: 'horizontal' - # pos_hint: {'center_y': .4, 'center_x': 1.15} - # halign: 'right' - - BoxLayout: - size_hint_y: None - spacing:5 - orientation: 'horizontal' - pos_hint: {'center_y': 1, 'center_x': 1.15} - halign: 'right' - # pos_hint: {'left': 0} - # pos_hint: {'x':.75} - height: dp(50) + self.minimum_height - MDRaisedButton: - text: app.tr._('Cancel') - MDRaisedButton: - text: app.tr._('OK') - Tab: - title: 'Resends Expire' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height - padding: 20 - # spacing: 10 - BoxLayout: - # size_hint_y: None - id: box1_height - orientation: 'vertical' - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "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." - halign: 'left' - BoxLayout: - id: box2_height - size_hint_y: None - orientation: 'vertical' - height: dp(30) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Leave these input fields blank for the default behavior.") - halign: 'left' - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [10, 0, 0, 0] - height: dp(50) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Give up after") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(70), dp(30) - text: app.tr._('0') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("days and") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(70), dp(30) - text: '0' - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: "months" - halign: 'left' - BoxLayout: - size_hint_y: None - spacing:5 - orientation: 'horizontal' - # pos_hint: {'left': 0} - # pos_hint: {'x':.75} - height: dp(50) + self.minimum_height - # MDRaisedButton: - # text: app.tr._('Cancel') - MDRaisedButton: - text: app.tr._('Apply') - - Tab: - title: 'Namecoin Integration' - ScrollView: - do_scroll_x: False - BoxLayout: - size_hint_y: None - orientation: 'vertical' - height: dp(210 if app.app_platform == 'android' else 100)+ self.minimum_height - padding: 20 - - # spacing: 10 - BoxLayout: - # size_hint_y: None - id: box1_height - orientation: 'vertical' - spacing: 10 - - # pos_hint: {'x': 0, 'y': 0.2} - # height: dp(100) + self.minimum_height - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - # text: app.tr._(root.exp_text) - text: "\n\n\n\n\n\nBitmessage can utilize a different Bitcoin-based program called Namecoin to make addresses human-friendly. For example, instead of having to tell your friend your long Bitmessage address, you can simply tell him to send a message to test.\n\n(Getting your own Bitmessage address into Namecoin is still rather difficult).\n\nBitmessage can use either namecoind directly or a running nmcontrol instance\n\n" - halign: 'left' - - BoxLayout: - id: box2_height - size_hint_y: None - orientation: 'vertical' - height: dp(40) + self.minimum_height - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [10, 0, 0, 0] - height: dp(50) + self.minimum_height - - BoxLayout: - orientation: 'horizontal' - padding: [10, 0, 0, 0] - - BoxLayout: - orientation: 'horizontal' - - # padding_left: 10 - # MDCheckbox: - # id: chkbox - # size_hint: None, None - # size: dp(48), dp(50) - # # active: True - # halign: 'center' - # MDLabel: - # font_style: 'Body1' - # theme_text_color: 'Primary' - # text: app.tr._("UPnP") - # halign: 'left' - # pos_hint: {'x': 0, 'y': 0} - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Connect to:") - halign: 'left' - - # MDCheckbox: - # id: chkbox - # size_hint: None, None - # size: dp(48), dp(50) - # # active: True - # halign: 'center' - Check: - active: True - pos_hint: {'x': 0, 'y': -0.2} - - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Namecoind") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - - Check: - active: False - pos_hint: {'x': 0, 'y': -0.2} - - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("NMControl") - halign: 'left' - pos_hint: {'x': 0, 'y': 0} - - GridLayout: - cols: 2 - padding: [10, 0, 0, 0] - - BoxLayout: - size_hint_y: None - orientation: 'vertical' - padding: [30, 0, 0, 0] - spacing: 10 - height: dp(100) + self.minimum_height - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("hostname:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('localhost') - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Port:") - halign: 'left' - # TextInput: - # size_hint: None, None - # hint_text: '9050' - # size: dp(app.window_size[0]/4), dp(30) - # input_filter: "int" - # readonly: False - # multiline: False - # font_size: '15sp' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - hint_text: app.tr._('9050') - pos_hint: {'center_y': .5, 'center_x': .5} - input_filter: "int" - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Username:") - halign: 'left' - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - pos_hint: {'center_y': .5, 'center_x': .5} - BoxLayout: - orientation: 'horizontal' - MDLabel: - font_style: 'Body1' - theme_text_color: 'Primary' - text: app.tr._("Password:") - halign: 'left' - - MDTextFieldRect: - size_hint: None, None - size: dp(app.window_size[0]/4), dp(30) - pos_hint: {'center_y': .5, 'center_x': .5} - password: True - - - BoxLayout: - size_hint_y: None - spacing:5 - orientation: 'horizontal' - pos_hint: {'center_y': .4, 'center_x': 1.15} - halign: 'right' - # pos_hint: {'left': 0} - # pos_hint: {'x':.75} - height: dp(50) + self.minimum_height - MDRaisedButton: - text: app.tr._('Cancel') - MDRaisedButton: - text: app.tr._('Apply') - MDRaisedButton: - text: app.tr._('OK') - Loader: \ No newline at end of file diff --git a/src/bitmessagekivy/kv/trash.kv b/src/bitmessagekivy/kv/trash.kv deleted file mode 100644 index 97bcf7d7..00000000 --- a/src/bitmessagekivy/kv/trash.kv +++ /dev/null @@ -1,25 +0,0 @@ -: - name: 'trash' - BoxLayout: - orientation: 'vertical' - spacing: dp(5) - GridLayout: - id: identi_tag - padding: [20, 20, 0, 5] - spacing: dp(5) - cols: 1 - size_hint_y: None - height: self.minimum_height - MDLabel: - id: tag_label - text: '' - font_style: 'Subtitle2' - BoxLayout: - orientation:'vertical' - ScrollView: - id: scroll_y - do_scroll_x: False - MDList: - id: ml - Loader: - ComposerButton: diff --git a/src/bitmessagekivy/load_kivy_screens_data.py b/src/bitmessagekivy/load_kivy_screens_data.py deleted file mode 100644 index b4132927..00000000 --- a/src/bitmessagekivy/load_kivy_screens_data.py +++ /dev/null @@ -1,25 +0,0 @@ -""" - Load kivy screens data from json -""" -import os -import json -import importlib - - -data_screen_dict = {} - - -def load_screen_json(data_file="screens_data.json"): - """Load screens data from json""" - - with open(os.path.join(os.path.dirname(__file__), data_file)) as read_file: - all_data = json.load(read_file) - data_screens = list(all_data.keys()) - - for key in all_data: - if all_data[key]['Import']: - import_data = all_data.get(key)['Import'] - import_to = import_data.split("import")[1].strip() - import_from = import_data.split("import")[0].split('from')[1].strip() - data_screen_dict[import_to] = importlib.import_module(import_from, import_to) - return data_screens, all_data, data_screen_dict, 'success' diff --git a/src/bitmessagekivy/main.kv b/src/bitmessagekivy/main.kv deleted file mode 100644 index 42e66762..00000000 --- a/src/bitmessagekivy/main.kv +++ /dev/null @@ -1,388 +0,0 @@ -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import Factory kivy.factory.Factory -#:import Spinner kivy.uix.spinner.Spinner - -#:import colors kivymd.color_definitions.colors -#:import images_path kivymd.images_path - -#:import IconLeftWidget kivymd.uix.list.IconLeftWidget -#:import MDCard kivymd.uix.card.MDCard -#:import MDCheckbox kivymd.uix.selectioncontrol.MDCheckbox -#:import MDFloatingActionButton kivymd.uix.button.MDFloatingActionButton -#:import MDList kivymd.uix.list.MDList -#:import MDScrollViewRefreshLayout kivymd.uix.refreshlayout.MDScrollViewRefreshLayout -#:import MDSpinner kivymd.uix.spinner.MDSpinner -#:import MDTextField kivymd.uix.textfield.MDTextField -#:import MDTabs kivymd.uix.tab.MDTabs -#:import MDTabsBase kivymd.uix.tab.MDTabsBase -#:import OneLineListItem kivymd.uix.list.OneLineListItem - - - -#:set color_button (0.784, 0.443, 0.216, 1) # brown -#:set color_button_pressed (0.659, 0.522, 0.431, 1) # darker brown -#:set color_font (0.957, 0.890, 0.843, 1) # off white - -: - font_size: '12.5sp' - background_normal: 'atlas://data/images/defaulttheme/textinput_active' - background_color: app.theme_cls.primary_color - color: color_font - - - on_press: root.currentlyActive() - active_color: root.theme_cls.primary_color if root.active else root.theme_cls.text_color - - IconLeftWidget: - icon: root.icon - theme_text_color: "Custom" - text_color: root.active_color - - BadgeText: - id: badge_txt - text: f"{root.badge_text}" - theme_text_color: "Custom" - halign: 'right' - -: - canvas: - Color: - rgba: self.theme_cls.divider_color - Line: - points: root.x, root.y + dp(8), root.x + self.width, root.y + dp(8) - - - - BoxLayout: - orientation: 'vertical' - - FloatLayout: - size_hint_y: None - height: "200dp" - - MDIconButton: - id: reset_image - icon: "refresh" - x: root.parent.x + dp(10) - pos_hint: {"top": 1, 'left': 1} - color: [1,0,0,1] - on_release: app.rest_default_avatar_img() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - opacity: 1 if app.have_any_address() else 0 - disabled: False if app.have_any_address() else True - - MDIconButton: - id: file_manager - icon: "file-image" - x: root.parent.x + dp(10) - pos_hint: {"top": 1, 'right': 1} - color: [1,0,0,1] - on_release: app.file_manager_open() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - opacity: 1 if app.have_any_address() else 0 - disabled: False if app.have_any_address() else True - - BoxLayout: - id: top_box - size_hint_y: None - height: "200dp" - x: root.parent.x - pos_hint: {"top": 1} - Image: - source: app.get_default_logo(self) - - ScrollView: - id: scroll_y - pos_hint: {"top": 1} - - GridLayout: - id: box_item - cols: 1 - size_hint_y: None - height: self.minimum_height - NavigationDrawerDivider: - NavigationDrawerSubheader: - text: app.tr._('Accounts') - height:"35dp" - NavigationItem: - text: 'dropdown_nav_item' - height: dp(48) - IdentitySpinner: - id: identity_dropdown - pos_hint:{"x":0,"y":0} - name: "identity_dropdown" - option_cls: Factory.get("MySpinnerOption") - font_size: '12.5sp' - text: app.getDefaultAccData(self) - color: color_font - background_normal: '' - background_color: app.theme_cls.primary_color - on_text: app.getCurrentAccountData(self.text) - ArrowImg: - NavigationItem: - id: inbox_cnt - text: app.tr._('Inbox') - icon: 'email-open' - divider: None - on_release: app.set_screen('inbox') - on_release: root.parent.set_state() - on_press: app.load_screen(self) - NavigationItem: - id: send_cnt - text: app.tr._('Sent') - icon: 'send' - divider: None - on_release: app.set_screen('sent') - on_release: root.parent.set_state() - NavigationItem: - id: draft_cnt - text: app.tr._('Draft') - icon: 'message-draw' - divider: None - on_release: app.root.ids.scr_mngr.current = 'draft' - on_release: root.parent.set_state() - NavigationItem: - id: trash_cnt - text: app.tr._('Trash') - icon: 'delete' - divider: None - on_release: app.root.ids.scr_mngr.current = 'trash' - on_press: root.parent.set_state() - on_press: app.load_screen(self) - NavigationItem: - id: allmail_cnt - text: app.tr._('All Mails') - icon: 'mailbox' - divider: None - on_release: app.root.ids.scr_mngr.current = 'allmails' - on_release: root.parent.set_state() - on_press: app.load_screen(self) - NavigationDrawerDivider: - NavigationDrawerSubheader: - text: app.tr._('Chat') - NavigationItem: - id: draft_cnt - text: app.tr._('Chat') - icon: 'chat' - divider: None - on_release: app.root.ids.scr_mngr.current = 'chat' - on_release: root.parent.set_state() - NavigationDrawerDivider: - NavigationDrawerSubheader: - text: app.tr._("All labels") - NavigationItem: - text: app.tr._('Address Book') - icon: 'book-multiple' - divider: None - on_release: app.root.ids.scr_mngr.current = 'addressbook' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('Settings') - icon: 'application-settings' - divider: None - on_release: app.root.ids.scr_mngr.current = 'set' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('Payment plan') - icon: 'shopping' - divider: None - on_release: app.root.ids.scr_mngr.current = 'payment' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('New address') - icon: 'account-plus' - divider: None - on_release: app.root.ids.scr_mngr.current = 'login' - on_release: root.parent.set_state() - on_press: app.reset_login_screen() - NavigationItem: - text: app.tr._('Network status') - icon: 'server-network' - divider: None - on_release: app.root.ids.scr_mngr.current = 'networkstat' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('My addresses') - icon: 'account-multiple' - divider: None - on_release: app.root.ids.scr_mngr.current = 'myaddress' - on_release: root.parent.set_state() - -MDNavigationLayout: - id: nav_layout - - MDTopAppBar: - id: toolbar - title: app.format_address_and_label() - opacity: 1 if app.have_any_address() else 0 - disabled: False if app.have_any_address() else True - pos_hint: {"top": 1} - md_bg_color: app.theme_cls.primary_color - elevation: 10 - left_action_items: [['menu', lambda x: nav_drawer.set_state("toggle")]] - right_action_items: [['account-plus', lambda x: app.addingtoaddressbook()]] - - ScreenManager: - id: scr_mngr - size_hint_y: None - height: root.height - toolbar.height - Inbox: - id:id_inbox - Login: - id:sc6 - Random: - id:id_newidentity - MyAddress: - id:id_myaddress - ScanScreen: - id:id_scanscreen - Payment: - id:id_payment - Create: - id:id_create - NetworkStat: - id:id_networkstat - Setting: - id:id_settings - Sent: - id:id_sent - Trash: - id:id_trash - Allmails: - id:id_allmail - Draft: - id:id_draft - AddressBook: - id:id_addressbook - ShowQRCode: - id:id_showqrcode - Chat: - id: id_chat - - MDNavigationDrawer: - id: nav_drawer - - ContentNavigationDrawer: - id: content_drawer - -: - source: app.image_path +('/down-arrow.png' if self.parent.is_open == True else '/right-arrow.png') - size: 15, 15 - x: self.parent.x + self.parent.width - self.width - 5 - y: self.parent.y + self.parent.height/2 - self.height + 5 - - -: - size_hint_y: None - height: self.minimum_height - - MDIconButton: - icon: 'magnify' - - MDTextField: - id: search_field - hint_text: 'Search' - canvas.before: - Color: - rgba: (0,0,0,1) - - -: - id: spinner - size_hint: None, None - size: dp(46), dp(46) - pos_hint: {'center_x': 0.5, 'center_y': 0.5} - active: False - -: - size_hint_y: None - height: dp(56) - spacing: '10dp' - pos_hint: {'center_x':0.45, 'center_y': .1} - - Widget: - - MDFloatingActionButton: - icon: 'plus' - opposite_colors: True - elevation_normal: 8 - md_bg_color: [0.941, 0, 0,1] - on_press: app.root.ids.scr_mngr.current = 'create' - on_press: app.clear_composer() - - -: - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - padding: "8dp" - - MDIconButton: - id: delete_msg - icon: "trash-can" - pos_hint: {"center_y": .5} - md_bg_color: (1, 0, 0, 1) - disabled: True - - MDCardSwipeFrontBox: - - TwoLineAvatarIconListItem: - id: content - text: root.text - _no_ripple_effect: True - - AvatarSampleWidget: - id: avater_img - source: None - - TimeTagRightSampleWidget: - id: time_tag - text: '' - font_size: "11sp" - font_style: "Caption" - size: [120, 140] if app.app_platform == "android" else [64, 80] - - -: - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - padding: "8dp" - - MDIconButton: - id: delete_msg - icon: "trash-can" - pos_hint: {"center_y": .5} - md_bg_color: (1, 0, 0, 1) - disabled: True - - MDCardSwipeFrontBox: - - TwoLineAvatarIconListItem: - id: content - text: root.text - _no_ripple_effect: True - - AvatarSampleWidget: - id: avater_img - source: None - - TimeTagRightSampleWidget: - id: time_tag - text: 'time' - font_size: "11sp" - font_style: "Caption" - size: [120, 140] if app.app_platform == "android" else [64, 80] - MDChip: - id: chip_tag - size_hint: (0.16 if app.app_platform == "android" else 0.08, None) - text: 'test' - icon: "" - pos_hint: {"center_x": 0.91 if app.app_platform == "android" else 0.94, "center_y": 0.3} - height: '18dp' - text_color: (1,1,1,1) - radius: [8] diff --git a/src/bitmessagekivy/mpybit.py b/src/bitmessagekivy/mpybit.py deleted file mode 100644 index 8c3ab4ab..00000000 --- a/src/bitmessagekivy/mpybit.py +++ /dev/null @@ -1,489 +0,0 @@ -# pylint: disable=too-many-public-methods, unused-variable, too-many-ancestors -# pylint: disable=no-name-in-module, too-few-public-methods, unused-argument -# pylint: disable=attribute-defined-outside-init, too-many-instance-attributes - -""" -Bitmessage android(mobile) interface -""" - -import os -import logging -import sys -from functools import partial -from PIL import Image as PilImage - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.core.window import Window -from kivy.uix.boxlayout import BoxLayout - -from kivymd.app import MDApp -from kivymd.uix.label import MDLabel -from kivymd.uix.dialog import MDDialog -from kivymd.uix.list import ( - IRightBodyTouch -) -from kivymd.uix.button import MDRaisedButton -from kivymd.uix.bottomsheet import MDCustomBottomSheet -from kivymd.uix.filemanager import MDFileManager - -from pybitmessage.bitmessagekivy.kivy_state import KivyStateVariables -from pybitmessage.bitmessagekivy.base_navigation import ( - BaseLanguage, BaseNavigationItem, BaseNavigationDrawerDivider, - BaseNavigationDrawerSubheader, BaseContentNavigationDrawer, - BaseIdentitySpinner -) -from pybitmessage.bmconfigparser import config # noqa: F401 -from pybitmessage.bitmessagekivy import identiconGeneration -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import toast, load_image_path, get_identity_list -from pybitmessage.bitmessagekivy.load_kivy_screens_data import load_screen_json - -from pybitmessage.bitmessagekivy.baseclass.popup import ( - AddAddressPopup, AppClosingPopup, AddressChangingLoader -) -from pybitmessage.bitmessagekivy.baseclass.login import * # noqa: F401, F403 -from pybitmessage.bitmessagekivy.uikivysignaler import UIkivySignaler - -from pybitmessage.mockbm.helper_startup import loadConfig, total_encrypted_messages_per_month - -logger = logging.getLogger('default') - - -class Lang(BaseLanguage): - """UI Language""" - - -class NavigationItem(BaseNavigationItem): - """NavigationItem class for kivy Ui""" - - -class NavigationDrawerDivider(BaseNavigationDrawerDivider): - """ - A small full-width divider that can be placed - in the :class:`MDNavigationDrawer` - """ - - -class NavigationDrawerSubheader(BaseNavigationDrawerSubheader): - """ - A subheader for separating content in :class:`MDNavigationDrawer` - - Works well alongside :class:`NavigationDrawerDivider` - """ - - -class ContentNavigationDrawer(BaseContentNavigationDrawer): - """ContentNavigationDrawer class for kivy Uir""" - - -class BadgeText(IRightBodyTouch, MDLabel): - """BadgeText class for kivy Ui""" - - -class IdentitySpinner(BaseIdentitySpinner): - """Identity Dropdown in Side Navigation bar""" - - -class NavigateApp(MDApp): - """Navigation Layout of class""" - - kivy_state = KivyStateVariables() - title = "PyBitmessage" - identity_list = get_identity_list() - image_path = load_image_path() - app_platform = platform - encrypted_messages_per_month = total_encrypted_messages_per_month() - tr = Lang("en") # for changing in franch replace en with fr - - def __init__(self): - super(NavigateApp, self).__init__() - # workaround for relative imports - sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) - self.data_screens, self.all_data, self.data_screen_dict, response = load_screen_json() - self.kivy_state_obj = KivyStateVariables() - self.image_dir = load_image_path() - self.kivy_state_obj.screen_density = Window.size - self.window_size = self.kivy_state_obj.screen_density - - def build(self): - """Method builds the widget""" - for kv in self.data_screens: - Builder.load_file( - os.path.join( - os.path.dirname(__file__), - 'kv', - '{0}.kv'.format(self.all_data[kv]["kv_string"]), - ) - ) - Window.bind(on_request_close=self.on_request_close) - return Builder.load_file(os.path.join(os.path.dirname(__file__), 'main.kv')) - - def set_screen(self, screen_name): - """Set the screen name when navigate to other screens""" - self.root.ids.scr_mngr.current = screen_name - - def run(self): - """Running the widgets""" - loadConfig() - kivysignalthread = UIkivySignaler() - kivysignalthread.daemon = True - kivysignalthread.start() - self.kivy_state_obj.kivyui_ready.set() - super(NavigateApp, self).run() - - def addingtoaddressbook(self): - """Dialog for saving address""" - width = .85 if platform == 'android' else .8 - self.add_popup = MDDialog( - title='Add contact', - type="custom", - size_hint=(width, .23), - content_cls=AddAddressPopup(), - buttons=[ - MDRaisedButton( - text="Save", - on_release=self.savecontact, - ), - MDRaisedButton( - text="Cancel", - on_release=self.close_pop, - ), - MDRaisedButton( - text="Scan QR code", - on_release=self.scan_qr_code, - ), - ], - ) - self.add_popup.auto_dismiss = False - self.add_popup.open() - - def scan_qr_code(self, instance): - """this method is used for showing QR code scanner""" - if self.is_camara_attached(): - self.add_popup.dismiss() - self.root.ids.id_scanscreen.get_screen(self.root.ids.scr_mngr.current, self.add_popup) - self.root.ids.scr_mngr.current = 'scanscreen' - else: - alert_text = ( - 'Currently this feature is not avaialbe!' if platform == 'android' else 'Camera is not available!') - self.add_popup.dismiss() - toast(alert_text) - - def is_camara_attached(self): - """This method is for checking the camera is available or not""" - self.root.ids.id_scanscreen.check_camera() - is_available = self.root.ids.id_scanscreen.camera_available - return is_available - - def savecontact(self, instance): - """Method is used for saving contacts""" - popup_obj = self.add_popup.content_cls - label = popup_obj.ids.label.text.strip() - address = popup_obj.ids.address.text.strip() - popup_obj.ids.label.focus = not label - # default focus on address field - popup_obj.ids.address.focus = label or not address - - def close_pop(self, instance): - """Close the popup""" - self.add_popup.dismiss() - toast('Canceled') - - def loadMyAddressScreen(self, action): - """loadMyAddressScreen method spin the loader""" - if len(self.root.ids.id_myaddress.children) <= 2: - self.root.ids.id_myaddress.children[0].active = action - else: - self.root.ids.id_myaddress.children[1].active = action - - def load_screen(self, instance): - """This method is used for loading screen on every click""" - if instance.text == 'Inbox': - self.root.ids.scr_mngr.current = 'inbox' - self.root.ids.id_inbox.children[1].active = True - elif instance.text == 'Trash': - self.root.ids.scr_mngr.current = 'trash' - try: - self.root.ids.id_trash.children[1].active = True - except Exception as e: - self.root.ids.id_trash.children[0].children[1].active = True - Clock.schedule_once(partial(self.load_screen_callback, instance), 1) - - def load_screen_callback(self, instance, dt=0): - """This method is rotating loader for few seconds""" - if instance.text == 'Inbox': - self.root.ids.id_inbox.ids.ml.clear_widgets() - self.root.ids.id_inbox.loadMessagelist(self.kivy_state_obj.selected_address) - self.root.ids.id_inbox.children[1].active = False - elif instance.text == 'Trash': - self.root.ids.id_trash.clear_widgets() - self.root.ids.id_trash.add_widget(self.data_screen_dict['Trash'].Trash()) - try: - self.root.ids.id_trash.children[1].active = False - except Exception as e: - self.root.ids.id_trash.children[0].children[1].active = False - - @staticmethod - def get_enabled_addresses(): - """Getting list of all the enabled addresses""" - addresses = [addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')] - return addresses - - @staticmethod - def format_address(address): - """Formatting address""" - return " ({0})".format(address) - - @staticmethod - def format_label(label): - """Formatting label""" - if label: - f_name = label.split() - truncate_string = '...' - f_name_max_length = 15 - formatted_label = f_name[0][:14].capitalize() + truncate_string if len( - f_name[0]) > f_name_max_length else f_name[0].capitalize() - return formatted_label - return '' - - @staticmethod - def format_address_and_label(address=None): - """Getting formatted address information""" - if not address: - try: - address = NavigateApp.get_enabled_addresses()[0] - except IndexError: - return '' - return "{0}{1}".format( - NavigateApp.format_label(config.get(address, "label")), - NavigateApp.format_address(address) - ) - - def getDefaultAccData(self, instance): - """Getting Default Account Data""" - if self.identity_list: - self.kivy_state_obj.selected_address = first_addr = self.identity_list[0] - return first_addr - return 'Select Address' - - def getCurrentAccountData(self, text): - """Get Current Address Account Data""" - if text != '': - if os.path.exists(os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(text)) - ): - self.load_selected_Image(text) - else: - self.set_identicon(text) - self.root.ids.content_drawer.ids.reset_image.opacity = 0 - self.root.ids.content_drawer.ids.reset_image.disabled = True - address_label = self.format_address_and_label(text) - self.root_window.children[1].ids.toolbar.title = address_label - self.kivy_state_obj.selected_address = text - AddressChangingLoader().open() - for nav_obj in self.root.ids.content_drawer.children[ - 0].children[0].children[0].children: - nav_obj.active = True if nav_obj.text == 'Inbox' else False - self.fileManagerSetting() - Clock.schedule_once(self.setCurrentAccountData, 0.5) - - def setCurrentAccountData(self, dt=0): - """This method set the current accout data on all the screens""" - self.root.ids.id_inbox.ids.ml.clear_widgets() - self.root.ids.id_inbox.loadMessagelist(self.kivy_state_obj.selected_address) - - self.root.ids.id_sent.ids.ml.clear_widgets() - self.root.ids.id_sent.children[2].children[2].ids.search_field.text = '' - self.root.ids.id_sent.loadSent(self.kivy_state_obj.selected_address) - - def fileManagerSetting(self): - """This method is for file manager setting""" - if not self.root.ids.content_drawer.ids.file_manager.opacity and \ - self.root.ids.content_drawer.ids.file_manager.disabled: - self.root.ids.content_drawer.ids.file_manager.opacity = 1 - self.root.ids.content_drawer.ids.file_manager.disabled = False - - def on_request_close(self, *args): # pylint: disable=no-self-use - """This method is for app closing request""" - AppClosingPopup().open() - return True - - def clear_composer(self): - """If slow down, the new composer edit screen""" - self.set_navbar_for_composer() - composer_obj = self.root.ids.id_create.children[1].ids - composer_obj.ti.text = '' - composer_obj.composer_dropdown.text = 'Select' - composer_obj.txt_input.text = '' - composer_obj.subject.text = '' - composer_obj.body.text = '' - self.kivy_state_obj.in_composer = True - self.kivy_state_obj = False - - def set_navbar_for_composer(self): - """Clearing toolbar data when composer open""" - self.root.ids.toolbar.left_action_items = [ - ['arrow-left', lambda x: self.back_press()]] - self.root.ids.toolbar.right_action_items = [ - ['refresh', - lambda x: self.root.ids.id_create.children[1].reset_composer()], - ['send', - lambda x: self.root.ids.id_create.children[1].send(self)]] - - def set_identicon(self, text): - """Show identicon in address spinner""" - img = identiconGeneration.generate(text) - self.root.ids.content_drawer.ids.top_box.children[0].texture = (img.texture) - - # pylint: disable=import-outside-toplevel - def file_manager_open(self): - """This method open the file manager of local system""" - if not self.kivy_state_obj.file_manager: - self.file_manager = MDFileManager( - exit_manager=self.exit_manager, - select_path=self.select_path, - ext=['.png', '.jpg'] - ) - self.file_manager.previous = False - self.file_manager.current_path = '/' - if platform == 'android': - # pylint: disable=import-error - from android.permissions import request_permissions, Permission, check_permission - if check_permission(Permission.WRITE_EXTERNAL_STORAGE) and \ - check_permission(Permission.READ_EXTERNAL_STORAGE): - self.file_manager.show(os.getenv('EXTERNAL_STORAGE')) - self.kivy_state_obj.manager_open = True - else: - request_permissions([Permission.WRITE_EXTERNAL_STORAGE, Permission.READ_EXTERNAL_STORAGE]) - else: - self.file_manager.show(os.environ["HOME"]) - self.kivy_state_obj.manager_open = True - - def select_path(self, path): - """This method is used to set the select image""" - try: - newImg = PilImage.open(path).resize((300, 300)) - if platform == 'android': - android_path = os.path.join( - os.path.join(os.environ['ANDROID_PRIVATE'], 'app', 'images', 'kivy') - ) - if not os.path.exists(os.path.join(android_path, 'default_identicon')): - os.makedirs(os.path.join(android_path, 'default_identicon')) - newImg.save(os.path.join(android_path, 'default_identicon', '{}.png'.format( - self.kivy_state_obj.selected_address)) - ) - else: - if not os.path.exists(os.path.join(self.image_dir, 'default_identicon')): - os.makedirs(os.path.join(self.image_dir, 'default_identicon')) - newImg.save(os.path.join(self.image_dir, 'default_identicon', '{0}.png'.format( - self.kivy_state_obj.selected_address)) - ) - self.load_selected_Image(self.kivy_state_obj.selected_address) - toast('Image changed') - except Exception: - toast('Exit') - self.exit_manager() - - def exit_manager(self, *args): - """Called when the user reaches the root of the directory tree.""" - self.kivy_state_obj.manager_open = False - self.file_manager.close() - - def load_selected_Image(self, curerentAddr): - """This method load the selected image on screen""" - top_box_obj = self.root.ids.content_drawer.ids.top_box.children[0] - top_box_obj.source = os.path.join(self.image_dir, 'default_identicon', '{0}.png'.format(curerentAddr)) - self.root.ids.content_drawer.ids.reset_image.opacity = 1 - self.root.ids.content_drawer.ids.reset_image.disabled = False - top_box_obj.reload() - - def rest_default_avatar_img(self): - """set default avatar generated image""" - self.set_identicon(self.kivy_state_obj.selected_address) - img_path = os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(self.kivy_state_obj.selected_address) - ) - if os.path.exists(img_path): - os.remove(img_path) - self.root.ids.content_drawer.ids.reset_image.opacity = 0 - self.root.ids.content_drawer.ids.reset_image.disabled = True - toast('Avatar reset') - - def get_default_logo(self, instance): - """Getting default logo image""" - if self.identity_list: - first_addr = self.identity_list[0] - if config.getboolean(str(first_addr), 'enabled'): - if os.path.exists( - os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(first_addr) - ) - ): - return os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(first_addr) - ) - else: - img = identiconGeneration.generate(first_addr) - instance.texture = img.texture - return - return os.path.join(self.image_dir, 'drawer_logo1.png') - - @staticmethod - def have_any_address(): - """Checking existance of any address""" - if config.addresses(): - return True - return False - - def reset_login_screen(self): - """This method is used for clearing the widgets of random screen""" - if self.root.ids.id_newidentity.ids.add_random_bx.children: - self.root.ids.id_newidentity.ids.add_random_bx.clear_widgets() - - def reset(self, *args): - """Set transition direction""" - self.root.ids.scr_mngr.transition.direction = 'left' - self.root.ids.scr_mngr.transition.unbind(on_complete=self.reset) - - def back_press(self): - """Method for, reverting composer to previous page""" - if self.root.ids.scr_mngr.current == 'showqrcode': - self.set_common_header() - self.root.ids.scr_mngr.current = 'myaddress' - self.root.ids.scr_mngr.transition.bind(on_complete=self.reset) - self.kivy_state.in_composer = False - - def set_toolbar_for_QrCode(self): - """This method is use for setting Qr code toolbar.""" - self.root.ids.toolbar.left_action_items = [ - ['arrow-left', lambda x: self.back_press()]] - self.root.ids.toolbar.right_action_items = [] - - def set_common_header(self): - """Common header for all the Screens""" - self.root.ids.toolbar.right_action_items = [ - ['account-plus', lambda x: self.addingtoaddressbook()]] - self.root.ids.toolbar.left_action_items = [ - ['menu', lambda x: self.root.ids.nav_drawer.set_state("toggle")]] - return - - def open_payment_layout(self, sku): - """It basically open up a payment layout for kivy UI""" - pml = PaymentMethodLayout() - self.product_id = sku - self.custom_sheet = MDCustomBottomSheet(screen=pml) - self.custom_sheet.open() - - def initiate_purchase(self, method_name): - """initiate_purchase module""" - logger.debug("Purchasing %s through %s", self.product_id, method_name) - - -class PaymentMethodLayout(BoxLayout): - """PaymentMethodLayout class for kivy Ui""" - - -if __name__ == '__main__': - NavigateApp().run() diff --git a/src/bitmessagekivy/screens_data.json b/src/bitmessagekivy/screens_data.json deleted file mode 100644 index 86ddea19..00000000 --- a/src/bitmessagekivy/screens_data.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "Inbox": { - "kv_string": "inbox", - "name_screen": "inbox", - "Import": "from pybitmessage.bitmessagekivy.baseclass.inbox import Inbox" - }, - "Login": { - "kv_string": "login", - "Import": "from pybitmessage.bitmessagekivy.baseclass.login import *" - }, - "My addresses": { - "kv_string": "myaddress", - "name_screen": "myaddress", - "Import": "from pybitmessage.bitmessagekivy.baseclass.myaddress import MyAddress" - }, - "Scanner": { - "kv_string": "scan_screen", - "Import": "from pybitmessage.bitmessagekivy.baseclass.scan_screen import ScanScreen" - }, - "Payment": { - "kv_string": "payment", - "name_screen": "payment", - "Import": "from pybitmessage.bitmessagekivy.baseclass.payment import Payment" - }, - "Create": { - "kv_string": "msg_composer", - "name_screen": "create", - "Import": "from pybitmessage.bitmessagekivy.baseclass.msg_composer import Create" - }, - "Network status": { - "kv_string": "network", - "name_screen": "networkstat", - "Import": "from pybitmessage.bitmessagekivy.baseclass.network import NetworkStat" - }, - "Settings": { - "kv_string": "settings", - "name_screen": "set", - "Import": "from pybitmessage.bitmessagekivy.baseclass.settings import Setting" - }, - "Sent": { - "kv_string": "sent", - "name_screen": "sent", - "Import": "from pybitmessage.bitmessagekivy.baseclass.sent import Sent" - }, - "Trash": { - "kv_string": "trash", - "name_screen": "trash", - "Import": "from pybitmessage.bitmessagekivy.baseclass.trash import Trash" - }, - "Address Book": { - "kv_string": "addressbook", - "name_screen": "addressbook", - "Import": "from pybitmessage.bitmessagekivy.baseclass.addressbook import AddressBook" - }, - "Popups": { - "kv_string": "popup", - "Import": "from pybitmessage.bitmessagekivy.baseclass.popup import *" - }, - "All Mails": { - "kv_string": "allmails", - "name_screen": "allmails", - "Import": "from pybitmessage.bitmessagekivy.baseclass.allmail import Allmails" - }, - "MailDetail": { - "kv_string": "maildetail", - "name_screen": "mailDetail", - "Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail" - }, - "Draft": { - "kv_string": "draft", - "name_screen": "draft", - "Import": "from pybitmessage.bitmessagekivy.baseclass.draft import Draft" - }, - "Qrcode": { - "kv_string": "qrcode", - "Import": "from pybitmessage.bitmessagekivy.baseclass.qrcode import ShowQRCode" - }, - "Chat": { - "kv_string": "chat", - "Import": "from pybitmessage.bitmessagekivy.baseclass.chat import Chat" - } - -} diff --git a/src/bitmessagekivy/tests/__init__.py b/src/bitmessagekivy/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/bitmessagekivy/tests/common.py b/src/bitmessagekivy/tests/common.py deleted file mode 100644 index 553fa020..00000000 --- a/src/bitmessagekivy/tests/common.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -This module is used for running test cases ui order. -""" -import unittest - -from pybitmessage import state - - -def make_ordered_test(): - """this method is for comparing and arranging in order""" - order = {} - - def ordered_method(f): - """method for ordering""" - order[f.__name__] = len(order) - return f - - def compare_method(a, b): - """method for comparing order of methods""" - return [1, -1][order[a] < order[b]] - - return ordered_method, compare_method - - -ordered, compare = make_ordered_test() -unittest.defaultTestLoader.sortTestMethodsUsing = compare - - -def skip_screen_checks(x): - """This methos is skipping current screen checks""" - def inner(y): - """Inner function""" - if not state.enableKivy: - return unittest.skip('Kivy not enabled') - else: - x(y) - return inner diff --git a/src/bitmessagekivy/tests/sampleData/keys.dat b/src/bitmessagekivy/tests/sampleData/keys.dat deleted file mode 100644 index 940b3e14..00000000 --- a/src/bitmessagekivy/tests/sampleData/keys.dat +++ /dev/null @@ -1,59 +0,0 @@ -[bitmessagesettings] -settingsversion = 0 -port = 8444 -timeformat = %%c -blackwhitelist = black -startonlogon = false -minimizetotray = false -showtraynotifications = true -startintray = false -socksproxytype = none -sockshostname = localhost -socksport = 9050 -socksauthentication = false -socksusername = -sockspassword = -keysencrypted = false -messagesencrypted = false -defaultnoncetrialsperbyte = 1000 -defaultpayloadlengthextrabytes = 1000 -minimizeonclose = false -replybelow = False -maxdownloadrate = 0 -maxuploadrate = 0 -stopresendingafterxdays = -stopresendingafterxmonths = -sockslisten = false -userlocale = system -sendoutgoingconnections = True -useidenticons = True -identiconsuffix = qcqQGW6sQtZK -maxacceptablenoncetrialsperbyte = 20000000000 -maxacceptablepayloadlengthextrabytes = 20000000000 -onionhostname = -onionport = 8444 -onionbindip = 127.0.0.1 -smtpdeliver = -hidetrayconnectionnotifications = false -ttl = 367200 - -[BM-2cTpgCn57rYUgqm5BrgmykuV9gK1Ak1THF] -label = test1 -enabled = true -decoy = false -noncetrialsperbyte = 1000 -payloadlengthextrabytes = 1000 -privsigningkey = 5KYCPJ4Vp31UD6k5NWmDKtHhfapW25UJ7V2MjctYxcgL3BpWGA3 -privencryptionkey = 5JLER8q2zyj3KDEgGMv682en2SRUkkWWhUrNuqVYfGNNhHJmdkJ -lastpubkeysendtime = 1623160189 - -[BM-2cVrdzQjCQRqUuET6dc3byVyRTjZcgcJXj] -label = test2 -enabled = true -decoy = false -noncetrialsperbyte = 1000 -payloadlengthextrabytes = 1000 -privsigningkey = 5KhryWvNowFWWA9JRjQnLVStYKwhpKpAG4RtWwzyaQqmK2fTMue -privencryptionkey = 5JKQ9NqX2LRzHBCgyxc1GAL3rDvyDTHPifpL22a6UNN7K6y9BmL -lastpubkeysendtime = 1623160221 - diff --git a/src/bitmessagekivy/tests/sampleData/knownnodes.dat b/src/bitmessagekivy/tests/sampleData/knownnodes.dat deleted file mode 100644 index 41ba8a28..00000000 --- a/src/bitmessagekivy/tests/sampleData/knownnodes.dat +++ /dev/null @@ -1,110 +0,0 @@ -[ - { - "stream": 1, - "peer": { - "host": "5.45.99.75", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "75.167.159.54", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "95.165.168.168", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "85.180.139.241", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "158.222.217.190", - "port": 8080 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "178.62.12.187", - "port": 8448 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "24.188.198.204", - "port": 8111 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "109.147.204.113", - "port": 1195 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - }, - { - "stream": 1, - "peer": { - "host": "178.11.46.221", - "port": 8444 - }, - "info": { - "lastseen": 1620741290.255359, - "rating": 0, - "self": false - } - } -] \ No newline at end of file diff --git a/src/bitmessagekivy/tests/sampleData/messages.dat b/src/bitmessagekivy/tests/sampleData/messages.dat deleted file mode 100644 index 7150b42b..00000000 Binary files a/src/bitmessagekivy/tests/sampleData/messages.dat and /dev/null differ diff --git a/src/bitmessagekivy/tests/telenium_process.py b/src/bitmessagekivy/tests/telenium_process.py deleted file mode 100644 index 0a81044d..00000000 --- a/src/bitmessagekivy/tests/telenium_process.py +++ /dev/null @@ -1,121 +0,0 @@ -""" - Base class for telenium test cases which run kivy app as background process -""" - -import os -import shutil -import tempfile -from time import time, sleep - -from telenium.tests import TeleniumTestCase -from telenium.client import TeleniumHttpException - - -_files = ( - 'keys.dat', 'debug.log', 'messages.dat', 'knownnodes.dat', - '.api_started', 'unittest.lock' -) - -tmp_db_file = ( - 'keys.dat', 'messages.dat' -) - - -def cleanup(files=_files): - """Cleanup application files""" - for pfile in files: - try: - os.remove(os.path.join(tempfile.gettempdir(), pfile)) - except OSError: - pass - - -class TeleniumTestProcess(TeleniumTestCase): - """Setting Screen Functionality Testing""" - cmd_entrypoint = [os.path.join(os.path.abspath(os.getcwd()), 'src', 'mockbm', 'kivy_main.py')] - - @classmethod - def setUpClass(cls): - """Setupclass is for setting temp environment""" - os.environ["BITMESSAGE_HOME"] = tempfile.gettempdir() - cls.populate_test_data() - super(TeleniumTestProcess, cls).setUpClass() - - @staticmethod - def populate_test_data(): - """Set temp data in tmp directory""" - for file_name in tmp_db_file: - old_source_file = os.path.join( - os.path.abspath(os.path.dirname(__file__)), 'sampleData', file_name) - new_destination_file = os.path.join(os.environ['BITMESSAGE_HOME'], file_name) - shutil.copyfile(old_source_file, new_destination_file) - - @classmethod - def tearDownClass(cls): - """Ensures that pybitmessage stopped and removes files""" - # pylint: disable=no-member - super(TeleniumTestProcess, cls).tearDownClass() - cleanup() - - def assert_wait_no_except(self, selector, timeout=-1, value='inbox'): - """This method is to check the application is launched.""" - start = time() - deadline = start + timeout - while time() < deadline: - try: - if self.cli.getattr(selector, 'current') == value: - self.assertTrue(selector, value) - return - except TeleniumHttpException: - sleep(0.1) - continue - finally: - # Finally Sleep is used to make the menu button functionally available for the click process. - # (because screen transition is little bit slow) - sleep(0.2) - raise AssertionError("Timeout") - - def drag(self, xpath1, xpath2): - """this method is for dragging""" - self.cli.drag(xpath1, xpath2, 1) - self.cli.sleep(1) - - def assertCheckScrollDown(self, selector, timeout=-1): - """this method is for checking scroll""" - start = time() - while True: - scroll_distance = self.cli.getattr(selector, 'scroll_y') - if scroll_distance > 0.0: - self.assertGreaterEqual(scroll_distance, 0.0) - return True - if timeout == -1: - return False - if timeout > 0 and time() - start > timeout: - raise Exception("Timeout") - sleep(0.5) - - def assertCheckScrollUp(self, selector, timeout=-1): - """this method is for checking scroll UP""" - start = time() - while True: - scroll_distance = self.cli.getattr(selector, 'scroll_y') - if scroll_distance < 1.0: - self.assertGreaterEqual(scroll_distance, 0.0) - return True - if timeout == -1: - return False - if timeout > 0 and time() - start > timeout: - raise Exception("Timeout") - sleep(0.5) - - def open_side_navbar(self): - """Common method for opening Side navbar (Side Drawer)""" - # Checking the drawer is in 'closed' state - self.cli.execute('app.ContentNavigationDrawer.MDNavigationDrawer.opening_time=0') - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # This is for checking the menu button is appeared - self.assertExists('//ActionTopAppBarButton[@icon~=\"menu\"]', timeout=5) - # this is for opening Nav drawer - self.cli.wait_click('//ActionTopAppBarButton[@icon=\"menu\"]', timeout=5) - # checking state of Nav drawer - self.assertExists("//MDNavigationDrawer[@state~=\"open\"]", timeout=5) diff --git a/src/bitmessagekivy/tests/test_addressbook.py b/src/bitmessagekivy/tests/test_addressbook.py deleted file mode 100644 index e61fef2a..00000000 --- a/src/bitmessagekivy/tests/test_addressbook.py +++ /dev/null @@ -1,155 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import skip_screen_checks -from .common import ordered - -test_address = { - 'invalid_address': 'BM-2cWmjntZ47WKEUtocrdvs19y5CivpKoi1', - 'autoresponder_address': 'BM-2cVWtdUzPwF7UNGDrZftWuHWiJ6xxBpiSP', - 'recipient': 'BM-2cVpswZo8rWLXDVtZEUNcDQvnvHJ6TLRYr', - 'sender': 'BM-2cVpswZo8rWLXDVtZEUNcDQvnvHJ6TLRYr' -} - - -class AddressBook(TeleniumTestProcess): - """AddressBook Screen Functionality Testing""" - test_label = 'Auto Responder' - test_subject = 'Test Subject' - test_body = 'Hey,This is draft Message Body from Address Book' - - # @skip_screen_checks - @ordered - def test_save_address(self): - """Saving a new Address On Address Book Screen/Window""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar (written in telenium_process.py) - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) - # this is for opening addressbook screen - self.cli.wait_click('//NavigationItem[@text=\"Address Book\"]', timeout=5) - # This is for checking the Side nav Bar is closed - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=5) - # Check for rendered button - self.assertExists('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) - # Click on "Account-Plus' Icon to open popup to save a new Address - self.cli.wait_click('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) - # Checking the Label Field shows Validation for empty string - self.assertExists('//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Label\"][@text=\"\"]', timeout=5) - # Checking the Address Field shows Validation for empty string - self.assertExists('//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Address\"][@text=\"\"]', timeout=5) - # Add an address Label to label Field - self.cli.setattr('//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Label\"]', 'text', self.test_label) - # Checking the Label Field should not be empty - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format(self.test_label), timeout=5) - # Add Correct Address - self.cli.setattr( - '//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Address\"]', 'text', - test_address['autoresponder_address']) - # Checking the Address Field contains correct address - self.assertEqual( - self.cli.getattr('//AddAddressPopup/BoxLayout[0]/MDTextField[1][@text]', 'text'), - test_address['autoresponder_address']) - # Validating the Label field - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format(self.test_label), timeout=5) - # Validating the Valid Address is entered - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[1][@text=\"{}\"]'.format( - test_address['autoresponder_address']), timeout=5) - # Checking cancel button - self.assertExists('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Click on 'Cancel' Button to dismiss the popup - self.cli.wait_click('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Checking current screen - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_dismiss_addressbook_popup(self): - """This method is to perform Dismiss add Address popup""" - # Checking the "Address saving" Popup is not opened - self.assertNotExists('//AddAddressPopup', timeout=5) - # Checking the "Add account" Button is rendered - self.assertExists('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=6) - # Click on Account-Plus Icon to open popup for add Address - self.cli.wait_click('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) - # Add Label to label Field - self.cli.setattr('//AddAddressPopup/BoxLayout[0]/MDTextField[0]', 'text', 'test_label2') - # Checking the Label Field should not be empty - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format('test_label2'), timeout=5) - # Add Address to Address Field - self.cli.setattr( - '//AddAddressPopup/BoxLayout[0]/MDTextField[1]', 'text', test_address['recipient']) - # Checking the Address Field should not be empty - self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[1][@text=\"{}\"]'.format(test_address['recipient']), - timeout=5) - # Checking for "Cancel" button is rendered - self.assertExists('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Click on 'Cancel' Button to dismiss the popup - self.cli.wait_click('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Check Current Screen (Address Book) - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_send_message_to_saved_address(self): - """This method is to send msg to the saved address from addressbook""" - # Checking the Message detail Dialog box is not opened - self.assertNotExists('//MDDialog', timeout=5) - # Checking the saved address is rendered - self.assertExists('//AddressBook/BoxLayout[0]//SwipeToDeleteItem[0]', timeout=5) - # Click on a Address to open address Details popup - self.cli.wait_click('//AddressBook/BoxLayout[0]//SwipeToDeleteItem[0]', timeout=5) - # Checking the Message detail Dialog is opened - self.assertExists('//MDDialog', timeout=5) - # Checking the buttons are rendered - self.assertExists('//MDRaisedButton', timeout=5) - # Click on the Send to message Button - self.cli.wait_click('//MDRaisedButton[0]', timeout=5) - # Redirected to message composer screen(create) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - # Checking the Address is populated to recipient field when we try to send message to saved address. - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyTextInput[@text="{}"]'.format( - test_address['autoresponder_address']), timeout=5) - # CLICK BACK-BUTTON - self.cli.wait_click('//MDToolbar/BoxLayout[0]/MDActionTopAppBarButton[@icon=\"arrow-left\"]', timeout=5) - # After Back press, redirected to 'inbox' screen - self.assertExists("//ScreenManager[@current=\"inbox\"]", timeout=8) - - @skip_screen_checks - @ordered - def test_delete_address_from_saved_address(self): - """Delete a saved Address from Address Book""" - # Method to open side navbar (written in telenium_process.py) - self.open_side_navbar() - # this is for opening setting screen - self.cli.wait_click('//NavigationItem[@text=\"Address Book\"]', timeout=5) - # checking state of Nav drawer(closed) - self.assertExists("//MDNavigationDrawer[@state~=\"close\"]", timeout=5) - # Checking current screen - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=8) - # Checking the Address is rendered - self.assertExists('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem', timeout=5) - # Waiting for the trash icon to be rendered - self.cli.wait('//MDList[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) - # Enable the trash icon - self.cli.setattr('//MDList[0]//MDIconButton[@disabled]', 'disabled', False) - # Swiping over the Address to delete - self.cli.wait_drag('//MDList[0]//AvatarSampleWidget', '//MDList[0]//TimeTagRightSampleWidget', 2, timeout=5) - # Click on trash icon to delete the Address. - self.cli.wait_click('//MDList[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) - # Checking the deleted Address is disappeared - self.assertNotExists('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem', timeout=5) - # Address count should be zero - self.assertEqual(len(self.cli.select('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]')), 0) - # After Deleting, Screen is redirected to Address Book screen - self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=8) diff --git a/src/bitmessagekivy/tests/test_allmail_message.py b/src/bitmessagekivy/tests/test_allmail_message.py deleted file mode 100644 index b08e0f29..00000000 --- a/src/bitmessagekivy/tests/test_allmail_message.py +++ /dev/null @@ -1,35 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import skip_screen_checks -from .common import ordered - - -class AllMailMessage(TeleniumTestProcess): - """AllMail Screen Functionality Testing""" - - # @skip_screen_checks - @ordered - def test_show_allmail_list(self): - """Show All Messages on Mail Screen/Window""" - # This is for checking Current screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for opening All Mail screen - self.cli.wait_click('//NavigationItem[@text=\"All Mails\"]', timeout=5) - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Assert for checking Current Screen(All mail) - self.assertExists("//ScreenManager[@current=\"allmails\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_delete_message_from_allmail_list(self): - """Delete Message From Message body of Mail Screen/Window""" - # click on a Message to get message details screen - self.cli.wait_click( - '//MDList[0]/CustomSwipeToDeleteItem[0]', timeout=5) - # Assert for checking Current Screen(Mail Detail) - self.assertExists("//ScreenManager[@current=\"mailDetail\"]", timeout=5) - # CLicking on Trash-Can icon to delete Message - self.cli.wait_click('//MDToolbar/BoxLayout[2]/MDActionTopAppBarButton[@icon=\"delete-forever\"]', timeout=5) - # After deleting msg, screen is redirected to All mail screen - self.assertExists("//ScreenManager[@current=\"allmails\"]", timeout=5) diff --git a/src/bitmessagekivy/tests/test_chat_screen.py b/src/bitmessagekivy/tests/test_chat_screen.py deleted file mode 100644 index 98464bf3..00000000 --- a/src/bitmessagekivy/tests/test_chat_screen.py +++ /dev/null @@ -1,26 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class ChatScreen(TeleniumTestProcess): - """Chat Screen Functionality Testing""" - - @ordered - def test_open_chat_screen(self): - """Opening Chat screen""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # Checking Chat screen label on side nav bar - self.assertExists('//NavigationItem[@text=\"Chat\"]', timeout=5) - # this is for opening Chat screen - self.cli.wait_click('//NavigationItem[@text=\"Chat\"]', timeout=5) - # Checking navigation bar state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//Chat[@name~=\"chat\"]", timeout=5) diff --git a/src/bitmessagekivy/tests/test_create_random_address.py b/src/bitmessagekivy/tests/test_create_random_address.py deleted file mode 100644 index 691a9d46..00000000 --- a/src/bitmessagekivy/tests/test_create_random_address.py +++ /dev/null @@ -1,129 +0,0 @@ -""" - Test for creating new identity -""" - -from random import choice -from string import ascii_lowercase -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class CreateRandomAddress(TeleniumTestProcess): - """This is for testing randrom address creation""" - @staticmethod - def populate_test_data(): - pass - - @ordered - # This method tests the landing screen when the app runs first time and - # the landing screen should be "login" where we can create new address - def test_landing_screen(self): - """Click on Proceed Button to Proceed to Next Screen.""" - # Checking current Screen(Login screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='login') - # Dragging from sent to PROS: to NOTE: - self.drag( - '''//Login//Screen//ContentHead[1][@section_name=\"PROS:\"]''', - '''//Login//Screen//ContentHead[0][@section_name=\"NOTE:\"]''' - ) - # Assert the checkbox is rendered - self.assertExists( - '//Login//Screen[@name=\"check_screen\"]//AnchorLayout[1]/Check[@active=false]', timeout=5 - ) - # Clicking on the checkbox - self.cli.wait_click( - '//Login//Screen[@name=\"check_screen\"]//AnchorLayout[1]/Check', timeout=5 - ) - # Checking Status of checkbox after click - self.assertExists( - '//Login//Screen[@name=\"check_screen\"]//AnchorLayout[1]/Check[@active=true]', timeout=5 - ) - # Checking the Proceed Next button is rendered or not - self.assertExists( - '''//Login//Screen[@name=\"check_screen\"]''' - '''//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]''', timeout=5 - ) - # Clicking on Proceed Next Button to redirect to "random" screen - self.cli.wait_click( - '''//Login//Screen[@name=\"check_screen\"]''' - '''//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]''', timeout=5 - ) - self.assertExists("//ScreenManager[@current=\"random\"]", timeout=5) - - @ordered - def test_generate_random_address_label(self): - """Creating New Adress For New User.""" - # Checking the Button is rendered - self.assertExists( - '//Random//RandomBoxlayout//MDTextField[@hint_text=\"Label\"]', timeout=5) - # Click on Label Text Field to give address Label - self.cli.wait_click( - '//Random//RandomBoxlayout//MDTextField[@hint_text=\"Label\"]', timeout=5) - # Enter a Label Randomly - random_label = "" - for _ in range(10): - random_label += choice(ascii_lowercase) - self.cli.setattr('//Random//MDTextField[0]', "text", random_label) - self.cli.sleep(0.1) - # Checking the Button is rendered - self.assertExists( - '//Random//RandomBoxlayout//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]', timeout=5) - # Click on Proceed Next button to generate random Address - self.cli.wait_click( - '//Random//RandomBoxlayout//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]', timeout=5) - # Checking "My Address" Screen after creating a address - self.assertExists("//ScreenManager[@current=\"myaddress\"]", timeout=5) - # Checking the new address is created - self.assertExists('//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem', timeout=10) - - @ordered - def test_set_default_address(self): - """Select First Address From Drawer-Box""" - # Checking current screen - self.assertExists("//ScreenManager[@current=\"myaddress\"]", timeout=5) - # This is for opening side navbar - self.open_side_navbar() - # Click to open Address Dropdown - self.assertExists('//NavigationItem[0][@text=\"dropdown_nav_item\"]', timeout=5) - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=5 - ) - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=false]', timeout=5 - ) - self.cli.wait( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@state=\"normal\"]', timeout=5 - ) - # Click to open Address Dropdown - self.cli.wait_click( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=5 - ) - self.cli.wait( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@state=\"normal\"]', timeout=5 - ) - # Check the state of dropdown. - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=true]', timeout=5 - ) - # List of addresses - addresses_in_dropdown = self.cli.getattr( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]/IdentitySpinner[@values]', 'values' - ) - # Checking the dropdown options are exists - self.assertGreaterEqual(len(self.cli.getattr( - '//MySpinnerOption[@text]', 'text')), len(addresses_in_dropdown) - ) - # Selection of an address to set as a default address. - self.cli.wait_click('//MySpinnerOption[0]', timeout=5) - - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=false]', timeout=5 - ) - self.cli.sleep(0.5) diff --git a/src/bitmessagekivy/tests/test_draft_message.py b/src/bitmessagekivy/tests/test_draft_message.py deleted file mode 100644 index e876f418..00000000 --- a/src/bitmessagekivy/tests/test_draft_message.py +++ /dev/null @@ -1,158 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import skip_screen_checks -from .common import ordered - -test_address = { - 'receiver': 'BM-2cVWtdUzPwF7UNGDrZftWuHWiJ6xxBpiSP' -} - - -class DraftMessage(TeleniumTestProcess): - """Draft Screen Functionality Testing""" - test_subject = 'Test Subject text' - test_body = 'Hey, This is draft Message Body' - - @ordered - def test_draft_screen(self): - """Test draft screen is open""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for opening Draft screen - self.cli.wait_click('//NavigationItem[@text=\"Draft\"]', timeout=5) - # Checking the drawer is in 'closed' state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking Draft Screen - self.assertExists("//ScreenManager[@current=\"draft\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_save_message_to_draft(self): - """ - Saving a message to draft box when click on back button - """ - # Checking current Screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=10, value='inbox') - # Click on Composer Icon(Plus icon) - self.cli.wait_click('//ComposerButton[0]/MDFloatingActionButton[@icon=\"plus\"]', timeout=5) - # Checking Message Composer Screen(Create) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - # ADD SUBJECT - self.cli.setattr('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', 'text', self.test_subject) - # Checking Subject Field is Entered - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"{}\"]'.format(self.test_subject), timeout=5) - # ADD MESSAGE BODY - self.cli.setattr( - '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[@text]', - 'text', self.test_body) - # Checking Message body is Entered - self.assertExists( - '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[@text=\"{}\"]'.format( - self.test_body), timeout=5) - # Click on "Send" Icon - self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) - # Checking validation Pop up is Opened - self.assertExists('//MDDialog[@open]', timeout=5) - # checking the button is rendered - self.assertExists('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Click "OK" button to dismiss the Popup - self.cli.wait_click('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Checking validation Pop up is Closed - self.assertNotExists('//MDDialog[@open]', timeout=5) - # RECEIVER FIELD - # Checking Receiver Address Field - self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=5) - # Entering Receiver Address - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//MyTextInput[0]', "text", test_address['auto_responder']) - # Checking Receiver Address filled or not - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"{}\"]'.format(test_address['auto_responder']), - timeout=5) - # Checking the sender's Field is empty - self.assertExists('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"\"]', timeout=5) - # Assert to check Sender's address dropdown open or not - self.assertEqual(self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open'), False) - # Open Sender's Address DropDown - self.cli.wait_click('//Create//CustomSpinner[0]/ArrowImg[0]', timeout=5) - # Checking the status of dropdown - self.assertEqual(self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open'), False) - # Checking the dropdown option is rendered - self.assertExists('//ComposerSpinnerOption[0]', timeout=5) - # Select Sender's Address from Dropdown options - self.cli.wait_click('//ComposerSpinnerOption[0]', timeout=5) - # Assert to check Sender address dropdown closed - self.assertEqual(self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open'), False) - # Checking sender address is selected - sender_address = self.cli.getattr('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text]', 'text') - self.assertExists( - '//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"{}\"]'.format(sender_address), timeout=5) - # CLICK BACK-BUTTON - self.cli.wait_click('//MDToolbar/BoxLayout[0]/MDActionTopAppBarButton[@icon=\"arrow-left\"]', timeout=5) - # Checking current screen(Login) after "BACK" Press - self.assertExists("//ScreenManager[@current=\"inbox\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_edit_and_resend_draft_messgae(self): - """Click on a Drafted message to send message""" - # OPEN NAVIGATION-DRAWER - # this is for opening Nav drawer - self.open_side_navbar() - # Checking messages in draft box - self.assertEqual(len(self.cli.select('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem')), 1) - # Wait to render the widget - self.cli.wait('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]', timeout=5) - # Click on a Message to view its details (Message Detail screen) - self.cli.wait_click('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]', timeout=5) - # Checking current screen Mail Detail - self.assertExists("//ScreenManager[@current=\"mailDetail\"]", timeout=5) - - # CLICK on EDIT(Pencil) BUTTON - self.cli.wait_click('//MDToolbar/BoxLayout[2]/MDActionTopAppBarButton[@icon=\"pencil\"]', timeout=5) - # Checking Current Screen 'Create'; composer screen. - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=10) - # Checking the recipient is in the receiver field - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"{}\"]'.format(test_address['auto_responder']), - timeout=5) - # Checking the sender address is in the sender field - sender_address = self.cli.getattr('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text]', 'text') - self.assertExists( - '//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"{}\"]'.format(sender_address), timeout=5) - # Checking the subject text is in the subject field - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"{}\"]'.format(self.test_subject), timeout=5) - # Checking the Body text is in the Body field - self.assertExists( - '//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text=\"{}\"]'.format(self.test_body), - timeout=5) - # CLICK BACK-BUTTON to autosave msg in draft screen - self.cli.wait_click('//MDToolbar/BoxLayout[0]/MDActionTopAppBarButton[@icon=\"arrow-left\"]', timeout=5) - # Checking current screen(Login) after BACK Press - self.assertExists("//ScreenManager[@current=\"draft\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_delete_draft_message(self): - """Deleting a Drafted Message""" - # Checking current screen is Draft Screen - self.assertExists("//ScreenManager[@current=\"draft\"]", timeout=5) - # Cheking the Message is rendered - self.assertExists('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]', timeout=5) - # Enable the trash icon - self.cli.setattr('//MDList[0]//MDIconButton[@disabled]', 'disabled', False) - # Waiting for the trash icon to be rendered - self.cli.wait('//MDList[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) - # Swiping over the message to delete - self.cli.wait_drag('//MDList[0]//AvatarSampleWidget', '//MDList[0]//TimeTagRightSampleWidget', 2, timeout=5) - # Click on trash icon to delete the message. - self.cli.wait_click('//MDList[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) - # Checking the deleted message is disappeared - self.assertNotExists('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem', timeout=5) - # Message count should be zero - self.assertEqual(len(self.cli.select('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]')), 0) - # After Deleting, Screen is redirected to Draft screen - self.assertExists("//ScreenManager[@current=\"draft\"]", timeout=8) diff --git a/src/bitmessagekivy/tests/test_filemanager.py b/src/bitmessagekivy/tests/test_filemanager.py deleted file mode 100644 index 6d25553c..00000000 --- a/src/bitmessagekivy/tests/test_filemanager.py +++ /dev/null @@ -1,68 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class FileManagerOpening(TeleniumTestProcess): - """File-manager Opening Functionality Testing""" - @ordered - def test_open_file_manager(self): - """Opening and Closing File-manager""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # Click to open Address Dropdown - self.assertExists('//NavigationItem[0][@text=\"dropdown_nav_item\"]', timeout=5) - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=1 - ) - # Check the state of dropdown - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=false]', timeout=1 - ) - self.cli.wait( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@state=\"normal\"]', timeout=5 - ) - self.cli.wait_click( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=1 - ) - # Check the state of dropdown. - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=true]', timeout=1 - ) - # List of addresses - addresses_in_dropdown = self.cli.getattr( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]/IdentitySpinner[@values]', 'values' - ) - # Checking the dropdown options are exists - self.assertGreaterEqual(len(self.cli.getattr( - '//MySpinnerOption[@text]', 'text')), len(addresses_in_dropdown) - ) - # Selection of an address to set as a default address. - self.cli.wait_click('//MySpinnerOption[0]', timeout=5) - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) - # checking state of Nav drawer - self.assertExists("//MDNavigationDrawer[@state~=\"open\"]", timeout=5) - # Checking File-manager icon - self.assertExists( - '//ContentNavigationDrawer//MDIconButton[1][@icon=\"file-image\"]', - timeout=5 - ) - # Clicking on file manager icon - self.cli.wait_click( - '//ContentNavigationDrawer//MDIconButton[1][@icon=\"file-image\"]', - timeout=5) - # Checking the state of file manager is it open or not - self.assertTrue(self.cli.execute('app.file_manager_open')) - # Closing the filemanager - self.cli.execute('app.exit_manager()') - # Checking the state of file manager is it closed or not - self.assertTrue(self.cli.execute('app.exit_manager()')) diff --git a/src/bitmessagekivy/tests/test_load_screen_data_file.py b/src/bitmessagekivy/tests/test_load_screen_data_file.py deleted file mode 100644 index 619daf25..00000000 --- a/src/bitmessagekivy/tests/test_load_screen_data_file.py +++ /dev/null @@ -1,21 +0,0 @@ - -import unittest -from pybitmessage.bitmessagekivy.load_kivy_screens_data import load_screen_json -from .common import ordered - - -class TestLoadScreenData(unittest.TestCase): - """Screen Data Json test""" - - @ordered - def test_load_json(self): - """Test to load a valid json""" - loaded_screen_names = load_screen_json() - self.assertEqual(loaded_screen_names[3], 'success') - - @ordered - def test_load_invalid_file(self): - """Test to load an invalid json""" - file_name = 'invalid_screens_data.json' - with self.assertRaises(OSError): - load_screen_json(file_name) diff --git a/src/bitmessagekivy/tests/test_myaddress_screen.py b/src/bitmessagekivy/tests/test_myaddress_screen.py deleted file mode 100644 index 78740b79..00000000 --- a/src/bitmessagekivy/tests/test_myaddress_screen.py +++ /dev/null @@ -1,187 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -data = [ - 'BM-2cWmjntZ47WKEUtocrdvs19y5CivpKoi1h', - 'BM-2cVpswZo8rWLXDVtZEUNcDQvnvHJ6TLRYr' -] - - -class MyAddressScreen(TeleniumTestProcess): - """MyAddress Screen Functionality Testing""" - @ordered - def test_myaddress_screen(self): - """Open MyAddress Screen""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # Checking My address label on side nav bar - self.assertExists('//NavigationItem[@text=\"My addresses\"]', timeout=5) - # this is for opening setting screen - self.cli.wait_click('//NavigationItem[@text=\"My addresses\"]', timeout=5) - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//MyAddress[@name~=\"myaddress\"]", timeout=5) - - @ordered - def test_disable_address(self): - """Disable Addresses""" - # Dragging for loading addreses - self.drag( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test2\"]', - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]' - ) - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # Checking list of Addresses - self.assertExists("//MyAddress//CustomTwoLineAvatarIconListItem", timeout=5) - # Checking the Toggle button is rendered on addresses - self.assertExists("//MyAddress//CustomTwoLineAvatarIconListItem//ToggleBtn", timeout=5) - # Clicking on the Toggle button of first address to make it disable - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=true]", - timeout=5 - ) - # Clicking on Toggle button of first address - self.cli.wait_click( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn/Thumb", - timeout=5 - ) - # Checking the address is disabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - # CLICKING ON DISABLE ACCOUNT TO OPEN POPUP - self.cli.wait_click("//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]", timeout=5) - # Checking the popup is Opened - self.assertExists( - '//MDDialog[@text=\"Address is not currently active. Please click on Toggle button to active it.\"]', - timeout=5 - ) - # Clicking on 'Ok' Button To Dismiss the popup - self.cli.wait_click('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Checking the address is disabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - - @ordered - def test_show_Qrcode(self): - """Show the Qr code of selected address""" - # Checking the current screen is MyAddress - self.assertExists("//MyAddress[@name~=\"myaddress\"]", timeout=5) - # Checking first label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[1][@text=\"test1\"]', - timeout=5 - ) - # Checking second label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[0][@text=\"test2\"]', - timeout=5 - ) - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test1\"]//ToggleBtn[@active=true]", - timeout=5 - ) - # Click on Address to open popup - self.cli.wait_click('//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]', timeout=5) - # Check the Popup is opened - self.assertExists('//MyaddDetailPopup//MDLabel[@text=\"Show QR code\"]', timeout=5) - # Cick on 'Show QR code' button to view QR Code - self.cli.wait_click('//MyaddDetailPopup//MDLabel[@text=\"Show QR code\"]', timeout=5) - # Check Current screen is QR Code screen - self.assertExists("//ShowQRCode[@name~=\"showqrcode\"]", timeout=5) - # Check BACK button - self.assertExists('//ActionTopAppBarButton[@icon~=\"arrow-left\"]', timeout=5) - # Click on BACK button - self.cli.wait_click('//ActionTopAppBarButton[@icon~=\"arrow-left\"]', timeout=5) - # Checking current screen(My Address) after BACK press - self.assertExists("//MyAddress[@name~=\"myaddress\"]", timeout=5) - # Checking first label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[1][@text=\"test1\"]', - timeout=5 - ) - # Checking second label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[0][@text=\"test2\"]', - timeout=5 - ) - self.cli.sleep(0.3) - - @ordered - def test_enable_address(self): - """Test to enable the disabled address""" - # Checking list of Addresses - self.assertExists("//MyAddress//CustomTwoLineAvatarIconListItem", timeout=5) - # Check the thumb button on address - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn/Thumb", - timeout=5 - ) - # Checking the address is disabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - self.cli.sleep(0.3) - # Clicking on toggle button to enable the address - self.cli.wait_click( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn/Thumb", - timeout=10 - ) - # Checking the address is enabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=true]", - timeout=10 - ) - - @ordered - def test_send_message_from(self): - """Send Message From Send Message From Button""" - # this is for scrolling Myaddress screen - self.drag( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test2\"]', - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]' - ) - # Checking the addresses - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]', - timeout=5 - ) - # Click on Address to open popup - self.cli.wait_click('//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]', timeout=5) - # Checking Popup Opened - self.assertExists('//MyaddDetailPopup//MDLabel[@text=\"Send message from\"]', timeout=5) - # Click on Send Message Button to redirect Create Screen - self.cli.wait_click('//MyaddDetailPopup//MDRaisedButton[0]/MDLabel[0]', timeout=5) - # Checking Current screen(Create) - self.assertExists("//Create[@name~=\"create\"]", timeout=5) - # Entering Receiver Address - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//MyTextInput[0]', "text", data[1]) - # Checking Receiver Address filled or not - self.assertNotEqual('//DropDownWidget//MyTextInput[0]', '') - # ADD SUBJECT - self.cli.setattr('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', 'text', 'Hey this is Demo Subject') - # Checking Subject Field is Entered - self.assertNotEqual('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', '') - # ADD MESSAGE BODY - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[0]', - 'text', 'Hey,i am sending message directly from MyAddress book' - ) - # Checking Message body is Entered - self.assertNotEqual('//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text]', '') diff --git a/src/bitmessagekivy/tests/test_network_screen.py b/src/bitmessagekivy/tests/test_network_screen.py deleted file mode 100644 index ca398dc1..00000000 --- a/src/bitmessagekivy/tests/test_network_screen.py +++ /dev/null @@ -1,50 +0,0 @@ -# pylint: disable=too-few-public-methods -""" - Kivy Networkstat UI test -""" - -from .telenium_process import TeleniumTestProcess - - -class NetworkStatusScreen(TeleniumTestProcess): - """NetworkStatus Screen Functionality Testing""" - - def test_network_status(self): - """Show NetworkStatus""" - # This is for checking Current screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - # due to rapid transition effect, it doesn't click on menu-bar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) - # Clicking on Network Status tab - self.cli.wait_click('//NavigationItem[@text=\"Network status\"]', timeout=2) - # Checking the drawer is in 'closed' state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking for current screen (Network Status) - self.assertExists("//NetworkStat[@name~=\"networkstat\"]", timeout=2) - # Checking state of Total Connections tab - self.assertExists( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Total connections\"][@state=\"down\"]', timeout=5 - ) - # Getting the value of total connections - total_connection_text = self.cli.getattr('//NetworkStat//MDRaisedButton[@text]', 'text') - # Splitting the string from total connection numbers - number_of_connections = int(total_connection_text.split(' ')[-1]) - # Checking Total connections - self.assertGreaterEqual(number_of_connections, 1) - # Checking the state of Process tab - self.assertExists( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"][@state=\"normal\"]', timeout=5 - ) - # Clicking on Processes Tab - self.cli.wait_click( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"]', timeout=1 - ) - # Checking the state of Process tab - self.assertExists( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"][@state=\"down\"]', timeout=5 - ) diff --git a/src/bitmessagekivy/tests/test_payment_subscription.py b/src/bitmessagekivy/tests/test_payment_subscription.py deleted file mode 100644 index 42309001..00000000 --- a/src/bitmessagekivy/tests/test_payment_subscription.py +++ /dev/null @@ -1,70 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class PaymentScreen(TeleniumTestProcess): - """Payment Plan Screen Functionality Testing""" - - @ordered - def test_select_payment_plan(self): - """Select Payment plan From List of payments""" - # This is for checking Current screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open the side navbar - self.open_side_navbar() - # Dragging from sent to inbox to get Payment tab - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # this is for opening Payment screen - self.cli.wait_click('//NavigationItem[@text=\"Payment plan\"]', timeout=5) - # Checking the navbar is in closed state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Assert for checking Current Screen - self.assertExists("//ScreenManager[@current=\"payment\"]", timeout=5) - # Checking state of Current tab Payment plan - self.assertExists( - '//Payment/MDTabs[0]//MDTabsLabel[@text=\"Payment\"][@state=\"down\"]', timeout=5 - ) - # Scrolling Down Payment plan Cards - self.drag( - '//Payment//MDTabs[0]//MDCard[2]//MDLabel[@text=\"Standard\"]', - '//Payment//MDTabs[0]//MDCard[1]//MDLabel[@text=\"You can get zero encrypted message per month\"]' - ) - # Checking the subscription offer cards - self.assertExists( - '//Payment/MDTabs[0]//MDCard[3]//MDLabel[@text=\"Premium\"]', - timeout=10 - ) - # Checking the get it now button - self.assertExists( - '//Payment/MDTabs[0]//MDCard[3]//MDRaisedButton[@text=\"Get it now\"]', - timeout=10 - ) - # Clicking on the get it now button - self.cli.wait_click( - '//Payment/MDTabs[0]//MDCard[3]//MDRaisedButton[@text=\"Get it now\"]', - timeout=10 - ) - # Checking the Payment method popup - self.assertExists('//PaymentMethodLayout//ScrollView[0]//ListItemWithLabel[0]', timeout=10) - # CLick on the Payment Method - self.cli.wait_click( - '//PaymentMethodLayout//ScrollView[0]//ListItemWithLabel[0]', - timeout=10 - ) - # Check pop up is opened - self.assertExists('//PaymentMethodLayout[@disabled=false]', timeout=10) - # Click out side to dismiss the popup - self.cli.wait_click('//MDRaisedButton[1]', timeout=10) - # Checking state of next tab Payment - self.assertExists( - '//Payment/MDTabs[0]//MDTabsLabel[@text=\"Extra-Messages\"][@state=\"normal\"]', timeout=5 - ) - # Clicking on Payment tab - self.cli.wait_click('//Payment/MDTabs[0]//MDTabsLabel[@text=\"Extra-Messages\"]', timeout=5) - # Checking state of payment tab after click - self.assertExists( - '//Payment/MDTabs[0]//MDTabsLabel[@text=\"Extra-Messages\"][@state=\"down\"]', timeout=5 - ) - self.cli.sleep(1) diff --git a/src/bitmessagekivy/tests/test_sent_message.py b/src/bitmessagekivy/tests/test_sent_message.py deleted file mode 100644 index a7fd576b..00000000 --- a/src/bitmessagekivy/tests/test_sent_message.py +++ /dev/null @@ -1,133 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import skip_screen_checks -from .common import ordered - -test_address = {'autoresponder_address': 'BM-2cVWtdUzPwF7UNGDrZftWuHWiJ6xxBpiSP'} - - -class SendMessage(TeleniumTestProcess): - """Sent Screen Functionality Testing""" - test_subject = 'Test Subject' - test_body = 'Hello, \n Hope your are doing good.\n\t This is test message body' - - @skip_screen_checks - @ordered - def test_validate_empty_form(self): - """ - Sending Message From Inbox Screen - opens a pop-up(screen) which send message from sender to reciever - """ - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=10, value='inbox') - # Click on Composer Icon(Plus icon) - self.cli.wait_click('//ComposerButton[0]/MDFloatingActionButton[@icon=\"plus\"]', timeout=5) - # Checking Message Composer Screen(Create) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - # Checking State of Sender's Address Input Field (should be Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MDTextField[@text=\"\"]', timeout=5) - # Checking State of Receiver's Address Input Field (should be Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=5) - # Checking State of Subject Input Field (shoudl be Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"\"]', timeout=5) - # Click on Send Icon to check validation working - self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) - # Checking validation Pop up is Opened - self.assertExists('//MDDialog[@open]', timeout=5) - # Checking the 'Ok' Button is rendered - self.assertExists('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Click "OK" button to dismiss the Popup - self.cli.wait_click('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Checking the pop is closed - self.assertNotExists('//MDDialog[@open]', timeout=5) - # Checking current screen after dialog dismiss - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=10) - - @skip_screen_checks - @ordered - def test_validate_half_filled_form(self): - """ - Validate the half filled form and press back button to save message in draft box. - """ - # Checking current screen (Msg composer screen) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - # ADD SENDER'S ADDRESS - # Checking State of Sender's Address Input Field (Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MDTextField[@text=\"\"]', timeout=5) - # Assert to check Sender's address dropdown closed - is_open = self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open') - self.assertEqual(is_open, False) - # Open Sender's Address DropDown - self.cli.wait_click('//Create//CustomSpinner[0]/ArrowImg[0]', timeout=5) - # Checking the Address Dropdown is in open State - is_open = self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open') - # Select Sender's Address from Dropdown - self.cli.wait_click('//ComposerSpinnerOption[0]', timeout=5) - # Assert to check Sender's address dropdown closed - is_open = self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open') - self.assertEqual(is_open, False) - # Checking the sender address is selected - sender_address = self.cli.getattr( - '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[@text]', 'text') - self.assertExists( - '//DropDownWidget/ScrollView[0]//MDTextField[@text=\"{}\"]'.format(sender_address), timeout=5) - # Assert check for empty Subject Field - self.assertExists('//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"\"]', timeout=5) - # ADD SUBJECT - self.cli.setattr('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', 'text', self.test_subject) - # Checking Subject Field is Entered - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"{}\"]'.format(self.test_subject), timeout=5) - # Checking BODY Field(EMPTY) - self.assertExists('//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text=\"\"]', timeout=5) - # ADD BODY - self.cli.setattr( - '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[0]', 'text', self.test_body) - # Checking BODY is Entered - self.assertExists( - '//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text=\"{}\"]'.format(self.test_body), - timeout=5) - # click on send icon - self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) - # Checking validation Pop up is Opened - self.assertExists('//MDDialog', timeout=5) - # clicked on 'Ok' button to close popup - self.cli.wait_click('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Checking current screen after dialog dismiss - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) - - @skip_screen_checks - @ordered - def test_sending_msg_fully_filled_form(self): - """ - Sending message when all fields are filled - """ - # ADD RECEIVER ADDRESS - # Checking Receiver Address Field - self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=5) - # Entering Receiver Address - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//MyTextInput[0]', "text", test_address['autoresponder_address']) - # Checking Receiver Address filled or not - self.assertExists( - '//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"{}\"]'.format( - test_address['autoresponder_address']), timeout=5) - # Clicking on send icon - self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) - # Checking the current screen - self.assertExists("//ScreenManager[@current=\"inbox\"]", timeout=10) - - @skip_screen_checks - @ordered - def test_sent_box(self): - """ - Checking Message in Sent Screen after sending a Message. - """ - # this is for opening Nav drawer - self.open_side_navbar() - # Clicking on Sent Tab - self.cli.wait_click('//NavigationItem[@text=\"Sent\"]', timeout=5) - # Checking current screen; Sent - self.assertExists("//ScreenManager[@current=\"sent\"]", timeout=5) - # Checking number of Sent messages - total_sent_msgs = len(self.cli.select("//SwipeToDeleteItem")) - self.assertEqual(total_sent_msgs, 3) diff --git a/src/bitmessagekivy/tests/test_setting_screen.py b/src/bitmessagekivy/tests/test_setting_screen.py deleted file mode 100644 index 4f3a0a59..00000000 --- a/src/bitmessagekivy/tests/test_setting_screen.py +++ /dev/null @@ -1,38 +0,0 @@ -# pylint: disable=too-few-public-methods - -from .telenium_process import TeleniumTestProcess - - -class SettingScreen(TeleniumTestProcess): - """Setting Screen Functionality Testing""" - - def test_setting_screen(self): - """Show Setting Screen""" - # This is for checking Current screen - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # this is for opening setting screen - self.cli.wait_click('//NavigationItem[@text=\"Settings\"]', timeout=5) - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//ScreenManager[@current=\"set\"]", timeout=5) - # Scrolling down currrent screen - self.cli.wait_drag( - '//MDTabs[0]//MDLabel[@text=\"Close to tray\"]', - '//MDTabs[0]//MDLabel[@text=\"Minimize to tray\"]', 1, timeout=5) - # Checking state of 'Network Settings' sub tab should be 'normal'(inactive) - self.assertExists('//MDTabs[0]//MDTabsLabel[@text=\"Network Settings\"][@state=\"normal\"]', timeout=5) - # Click on "Network Settings" subtab - self.cli.wait_click('//MDTabs[0]//MDTabsLabel[@text=\"Network Settings\"]', timeout=5) - # Checking state of 'Network Settings' sub tab should be 'down'(active) - self.assertExists('//MDTabs[0]//MDTabsLabel[@text=\"Network Settings\"][@state=\"down\"]', timeout=5) - # Scrolling down currrent screen - self.cli.wait_drag( - '//MDTabs[0]//MDLabel[@text=\"Username:\"]', '//MDTabs[0]//MDLabel[@text=\"Port:\"]', 1, timeout=5) - # Checking state of 'Resends Expire' sub tab should be 'normal'(inactive) - self.assertExists('//MDTabs[0]//MDTabsLabel[@text=\"Resends Expire\"][@state=\"normal\"]', timeout=5) diff --git a/src/bitmessagekivy/tests/test_trash_message.py b/src/bitmessagekivy/tests/test_trash_message.py deleted file mode 100644 index d7cdb467..00000000 --- a/src/bitmessagekivy/tests/test_trash_message.py +++ /dev/null @@ -1,21 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class TrashMessage(TeleniumTestProcess): - """Trash Screen Functionality Testing""" - - @ordered - def test_delete_trash_message(self): - """Delete Trash message permanently from trash message listing""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for opening Trash screen - self.cli.wait_click('//NavigationItem[@text=\"Trash\"]', timeout=5) - # Checking the drawer is in 'closed' state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking Trash Screen - self.assertExists("//ScreenManager[@current=\"trash\"]", timeout=5) - self.cli.sleep(0.5) diff --git a/src/bitmessagekivy/uikivysignaler.py b/src/bitmessagekivy/uikivysignaler.py deleted file mode 100644 index 6f73247e..00000000 --- a/src/bitmessagekivy/uikivysignaler.py +++ /dev/null @@ -1,38 +0,0 @@ -""" - UI Singnaler for kivy interface -""" - -import logging - -from threading import Thread -from kivy.app import App - -from pybitmessage import queues -from pybitmessage import state - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - -logger = logging.getLogger('default') - - -class UIkivySignaler(Thread): - """Kivy ui signaler""" - - def __init__(self, *args, **kwargs): - super(UIkivySignaler, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - - def run(self): - self.kivy_state.kivyui_ready.wait() - while state.shutdown == 0: - try: - command, data = queues.UISignalQueue.get() - if command == 'writeNewAddressToTable': - address = data[1] - App.get_running_app().identity_list.append(address) - elif command == 'updateSentItemStatusByAckdata': - App.get_running_app().status_dispatching(data) - elif command == 'writeNewpaymentAddressToTable': - pass - except Exception as e: - logger.debug(e) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index 8d73786e..accd5740 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -1,104 +1,217 @@ -#!/usr/bin/env python -""" -The PyBitmessage startup script -""" +#!/usr/bin/python2.7 # Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2022 The Bitmessage developers +# Copyright (c) 2012-2016 The Bitmessage developers # Distributed under the MIT/X11 software license. See the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. # Right now, PyBitmessage only support connecting to stream 1. It doesn't # yet contain logic to expand into further streams. + +# The software version variable is now held in shared.py + import os import sys -try: - import pathmagic -except ImportError: - from pybitmessage import pathmagic -app_dir = pathmagic.setup() +app_dir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(app_dir) +sys.path.insert(0, app_dir) import depends 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 +from time import sleep +from random import randint import getopt -import multiprocessing -# Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully. -import signal -import threading -import time -import traceback + +from api import MySimpleXMLRPCRequestHandler, StoppableXMLRPCServer +from helper_startup import isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections import defaults -# Network subsystem -import network -import shutdown +import shared +import knownnodes import state +import shutdown +import threading + +# Classes +from class_sqlThread import sqlThread +from class_singleCleaner import singleCleaner +from class_objectProcessor import objectProcessor +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 testmode_init import populate_api_test_data -from bmconfigparser import config -from debug import logger # this should go before any threads -from helper_startup import ( - adjustHalfOpenConnectionsLimit, fixSocket, start_proxyconfig) from inventory import Inventory -from singleinstance import singleinstance -# Synchronous threads -from threads import ( - set_thread_name, printLock, - addressGenerator, objectProcessor, singleCleaner, singleWorker, sqlThread) + +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 +import helper_threading -def signal_handler(signum, frame): - """Single handler for any signal sent to pybitmessage""" - process = multiprocessing.current_process() - thread = threading.current_thread() - logger.error( - 'Got signal %i in %s/%s', - signum, process.name, thread.name - ) - if process.name == "RegExParser": - # on Windows this isn't triggered, but it's fine, - # it has its own process termination thing - raise SystemExit - if "PoolWorker" in process.name: - raise SystemExit - if thread.name not in ("PyBitmessage", "MainThread"): - return - logger.error("Got signal %i", signum) - # there are possible non-UI variants to run bitmessage - # which should shutdown especially test-mode - if state.thisapp.daemon or not state.enableGUI: - shutdown.doCleanShutdown() +def connectToStream(streamNumber): + state.streamsInWhichIAmParticipating.append(streamNumber) + selfInitiatedConnections[streamNumber] = {} + + if isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections(): + # Some XP and Vista systems can only have 10 outgoing connections at a time. + state.maximumNumberOfHalfOpenConnections = 9 else: - print('# Thread: %s(%d)' % (thread.name, thread.ident)) - for filename, lineno, name, line in traceback.extract_stack(frame): - print('File: "%s", line %d, in %s' % (filename, lineno, name)) - if line: - print(' %s' % line.strip()) - print('Unfortunately you cannot use Ctrl+C when running the UI' - ' because the UI captures the signal.') + state.maximumNumberOfHalfOpenConnections = 64 + try: + # don't overload Tor + if BMConfigParser().get('bitmessagesettings', 'socksproxytype') != 'none': + state.maximumNumberOfHalfOpenConnections = 4 + except: + pass + + with knownnodes.knownNodesLock: + if streamNumber not in knownnodes.knownNodes: + knownnodes.knownNodes[streamNumber] = {} + if streamNumber*2 not in knownnodes.knownNodes: + knownnodes.knownNodes[streamNumber*2] = {} + if streamNumber*2+1 not in knownnodes.knownNodes: + knownnodes.knownNodes[streamNumber*2+1] = {} + BMConnectionPool().connectToStream(streamNumber) -class Main(object): - """Main PyBitmessage class""" +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 + # socket.inet_ntop but we can make one ourselves using ctypes + if not hasattr(socket, 'inet_ntop'): + addressToString = ctypes.windll.ws2_32.WSAAddressToStringA + def inet_ntop(family, host): + if family == socket.AF_INET: + if len(host) != 4: + raise ValueError("invalid IPv4 host") + host = pack("hH4s8s", socket.AF_INET, 0, host, "\0" * 8) + elif family == socket.AF_INET6: + if len(host) != 16: + raise ValueError("invalid IPv6 host") + host = pack("hHL16sL", socket.AF_INET6, 0, 0, host, 0) + else: + raise ValueError("invalid address family") + buf = "\0" * 64 + lengthBuf = pack("I", len(buf)) + addressToString(host, len(host), None, buf, lengthBuf) + return buf[0:buf.index("\0")] + socket.inet_ntop = inet_ntop + + # Same for inet_pton + if not hasattr(socket, 'inet_pton'): + stringToAddress = ctypes.windll.ws2_32.WSAStringToAddressA + def inet_pton(family, host): + buf = "\0" * 28 + lengthBuf = pack("I", len(buf)) + if stringToAddress(str(host), + int(family), + None, + buf, + lengthBuf) != 0: + raise socket.error("illegal IP address passed to inet_pton") + if family == socket.AF_INET: + return buf[4:8] + elif family == socket.AF_INET6: + return buf[8:24] + else: + raise ValueError("invalid address family") + socket.inet_pton = inet_pton + + # These sockopts are needed on for IPv6 support + if not hasattr(socket, 'IPPROTO_IPV6'): + socket.IPPROTO_IPV6 = 41 + if not hasattr(socket, 'IPV6_V6ONLY'): + socket.IPV6_V6ONLY = 27 + +# This thread, of which there is only one, runs the API. +class singleAPI(threading.Thread, helper_threading.StoppableThread): + def __init__(self): + threading.Thread.__init__(self, name="singleAPI") + self.initStop() + + def stopThread(self): + super(singleAPI, self).stopThread() + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect((BMConfigParser().get('bitmessagesettings', 'apiinterface'), BMConfigParser().getint( + 'bitmessagesettings', 'apiport'))) + s.shutdown(socket.SHUT_RDWR) + s.close() + except: + pass + + def run(self): + 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() + +# This is a list of current connections (the thread pointers at least) +selfInitiatedConnections = {} + +if shared.useVeryEasyProofOfWorkForTesting: + defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte / 100) + defaults.networkDefaultPayloadLengthExtraBytes = int( + defaults.networkDefaultPayloadLengthExtraBytes / 100) + +class Main: def start(self): - """Start main application""" - # pylint: disable=too-many-statements,too-many-branches,too-many-locals - fixSocket() - adjustHalfOpenConnectionsLimit() + _fixSocket() - daemon = config.safeGetBoolean('bitmessagesettings', 'daemon') + daemon = BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon') try: - opts, _ = getopt.getopt( - sys.argv[1:], "hcdt", - ["help", "curses", "daemon", "test"]) + opts, args = getopt.getopt(sys.argv[1:], "hcd", + ["help", "curses", "daemon"]) except getopt.GetoptError: self.usage() sys.exit(2) - for opt, _ in opts: + for opt, arg in opts: if opt in ("-h", "--help"): self.usage() sys.exit() @@ -106,202 +219,148 @@ class Main(object): daemon = True elif opt in ("-c", "--curses"): state.curses = True - elif opt in ("-t", "--test"): - state.testmode = True - if os.path.isfile(os.path.join( - state.appdata, 'unittest.lock')): - daemon = True - state.enableGUI = False # run without a UI - # Fallback: in case when no api command was issued - state.last_api_response = time.time() - # Apply special settings - config.set( - 'bitmessagesettings', 'apienabled', 'true') - config.set( - 'bitmessagesettings', 'apiusername', 'username') - config.set( - 'bitmessagesettings', 'apipassword', 'password') - config.set( - 'bitmessagesettings', 'apivariant', 'legacy') - config.set( - 'bitmessagesettings', 'apinotifypath', - os.path.join(app_dir, 'tests', 'apinotify_handler.py') - ) - if daemon: - state.enableGUI = False # run without a UI - - if state.enableGUI and not state.curses and not depends.check_pyqt(): - sys.exit( - 'PyBitmessage requires PyQt unless you want' - ' to run it as a daemon and interact with it' - ' using the API. You can download PyQt from ' - 'http://www.riverbankcomputing.com/software/pyqt/download' - ' or by searching Google for \'PyQt Download\'.' - ' If you want to run in daemon mode, see ' - 'https://bitmessage.org/wiki/Daemon\n' - 'You can also run PyBitmessage with' - ' the new curses interface by providing' - ' \'-c\' as a commandline argument.' - ) # is the application already running? If yes then exit. - state.thisapp = singleinstance("", daemon) + shared.thisapp = singleinstance("", daemon) if daemon: - with printLock: + with shared.printLock: print('Running as a daemon. Send TERM signal to end.') self.daemonize() self.setSignalHandler() - set_thread_name("PyBitmessage") + helper_threading.set_thread_name("PyBitmessage") - state.dandelion = config.safeGetInt('network', 'dandelion') - # dandelion requires outbound connections, without them, - # stem objects will get stuck forever - if state.dandelion and not config.safeGetBoolean( - 'bitmessagesettings', 'sendoutgoingconnections'): + 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 - if state.testmode or config.safeGetBoolean( - 'bitmessagesettings', 'extralowdifficulty'): - defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte / 100) - defaults.networkDefaultPayloadLengthExtraBytes = int( - defaults.networkDefaultPayloadLengthExtraBytes / 100) + helper_bootstrap.knownNodes() + # Start the address generation thread + addressGeneratorThread = addressGenerator() + addressGeneratorThread.daemon = True # close the main program even if there are threads left + addressGeneratorThread.start() + + # Start the thread that calculates POWs + singleWorkerThread = singleWorker() + singleWorkerThread.daemon = True # close the main program even if there are threads left + singleWorkerThread.start() # Start the SQL thread sqlLookup = sqlThread() - # DON'T close the main program even if there are threads left. - # The closeEvent should command this thread to exit gracefully. - sqlLookup.daemon = False + 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 + Inventory() # init + Dandelion() # init, needs to be early because other thread may access it early - if state.enableObjProc: # Not needed if objproc is disabled - # Start the address generation thread - addressGeneratorThread = addressGenerator() - # close the main program even if there are threads left - addressGeneratorThread.daemon = True - addressGeneratorThread.start() + # SMTP delivery thread + if daemon and BMConfigParser().safeGet("bitmessagesettings", "smtpdeliver", '') != '': + smtpDeliveryThread = smtpDeliver() + smtpDeliveryThread.start() - # Start the thread that calculates POWs - singleWorkerThread = singleWorker() - # close the main program even if there are threads left - singleWorkerThread.daemon = True - singleWorkerThread.start() + # SMTP daemon thread + if daemon and BMConfigParser().safeGetBoolean("bitmessagesettings", "smtpd"): + smtpServerThread = smtpServer() + smtpServerThread.start() - # Start the object processing thread - objectProcessorThread = objectProcessor() - # DON'T close the main program even if the thread remains. - # This thread checks the shutdown variable after processing - # each object. - objectProcessorThread.daemon = False - objectProcessorThread.start() - - # SMTP delivery thread - if daemon and config.safeGet( - 'bitmessagesettings', 'smtpdeliver', '') != '': - from class_smtpDeliver import smtpDeliver - smtpDeliveryThread = smtpDeliver() - smtpDeliveryThread.start() - - # SMTP daemon thread - if daemon and config.safeGetBoolean( - 'bitmessagesettings', 'smtpd'): - from class_smtpServer import smtpServer - smtpServerThread = smtpServer() - smtpServerThread.start() - - # API is also objproc dependent - if config.safeGetBoolean('bitmessagesettings', 'apienabled'): - import api # pylint: disable=relative-import - singleAPIThread = api.singleAPI() - # close the main program even if there are threads left - singleAPIThread.daemon = True - singleAPIThread.start() + # Start the thread that calculates POWs + objectProcessorThread = objectProcessor() + objectProcessorThread.daemon = False # DON'T close the main program even the thread remains. This thread checks the shutdown variable after processing each object. + objectProcessorThread.start() # Start the cleanerThread singleCleanerThread = singleCleaner() - # close the main program even if there are threads left - singleCleanerThread.daemon = True + singleCleanerThread.daemon = True # close the main program even if there are threads left singleCleanerThread.start() - # start network components if networking is enabled - if state.enableNetwork: - start_proxyconfig() - network.start(config, state) + shared.reloadMyAddressHashes() + shared.reloadBroadcastSendersForWhichImWatching() - if config.safeGetBoolean('bitmessagesettings', 'upnp'): - import upnp - upnpThread = upnp.uPnPThread() - upnpThread.start() - else: - # Populate with hardcoded value (same as connectToStream above) - state.streamsInWhichIAmParticipating.append(1) + if BMConfigParser().safeGetBoolean('bitmessagesettings', 'apienabled'): + try: + apiNotifyPath = BMConfigParser().get( + 'bitmessagesettings', 'apinotifypath') + except: + apiNotifyPath = '' + if apiNotifyPath != '': + with shared.printLock: + print('Trying to call', apiNotifyPath) - if not daemon and state.enableGUI: - if state.curses: - if not depends.check_curses(): + call([apiNotifyPath, "startingUp"]) + singleAPIThread = singleAPI() + 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) + + if BMConfigParser().safeGetBoolean('bitmessagesettings','upnp'): + import upnp + upnpThread = upnp.uPnPThread() + upnpThread.start() + + if daemon == False and BMConfigParser().safeGetBoolean('bitmessagesettings', 'daemon') == False: + if state.curses == False: + if not depends.check_pyqt(): + print('PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download PyQt from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\'. If you want to run in daemon mode, see https://bitmessage.org/wiki/Daemon') + print('You can also run PyBitmessage with the new curses interface by providing \'-c\' as a commandline argument.') sys.exit() - print('Running with curses') - import bitmessagecurses - bitmessagecurses.runwrapper() - else: + import bitmessageqt bitmessageqt.run() + else: + if True: +# if depends.check_curses(): + print('Running with curses') + import bitmessagecurses + bitmessagecurses.runwrapper() else: - config.remove_option('bitmessagesettings', 'dontconnect') - - if state.testmode: - populate_api_test_data() + BMConfigParser().remove_option('bitmessagesettings', 'dontconnect') if daemon: while state.shutdown == 0: - time.sleep(1) - if ( - state.testmode - and time.time() - state.last_api_response >= 30 - ): - self.stop() - elif not state.enableGUI: - state.enableGUI = True - try: - # pylint: disable=relative-import - from tests import core as test_core - except ImportError: - try: - from pybitmessage.tests import core as test_core - except ImportError: - self.stop() - return + sleep(1) - test_core_result = test_core.run() - self.stop() - test_core.cleanup() - sys.exit(not test_core_result.wasSuccessful()) - - @staticmethod - def daemonize(): - """Running as a daemon. Send signal in end.""" + def daemonize(self): grandfatherPid = os.getpid() parentPid = None try: if os.fork(): # unlock - state.thisapp.cleanup() + shared.thisapp.cleanup() # wait until grandchild ready while True: - time.sleep(1) - os._exit(0) # pylint: disable=protected-access + sleep(1) + os._exit(0) except AttributeError: # fork not implemented pass else: parentPid = os.getpid() - state.thisapp.lock() # relock - + shared.thisapp.lock() # relock os.umask(0) try: os.setsid() @@ -311,23 +370,23 @@ class Main(object): try: if os.fork(): # unlock - state.thisapp.cleanup() + shared.thisapp.cleanup() # wait until child ready while True: - time.sleep(1) - os._exit(0) # pylint: disable=protected-access + sleep(1) + os._exit(0) except AttributeError: # fork not implemented pass else: - state.thisapp.lock() # relock - state.thisapp.lockPid = None # indicate we're the final child + shared.thisapp.lock() # relock + shared.thisapp.lockPid = None # indicate we're the final child sys.stdout.flush() sys.stderr.flush() if not sys.platform.startswith('win'): - si = open(os.devnull, 'r') - so = open(os.devnull, 'a+') - se = open(os.devnull, 'a+', 0) + 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()) @@ -336,52 +395,41 @@ class Main(object): os.kill(parentPid, signal.SIGTERM) os.kill(grandfatherPid, signal.SIGTERM) - @staticmethod - def setSignalHandler(): - """Setting the Signal Handler""" - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) + 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) - @staticmethod - def usage(): - """Displaying the usages""" - print('Usage: ' + sys.argv[0] + ' [OPTIONS]') - print(''' + 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 - -t, --test dryrun, make testing All parameters are optional. -''') +''' - @staticmethod - def stop(): - """Stop main application""" - with printLock: + def stop(self): + with shared.printLock: print('Stopping Bitmessage Deamon.') shutdown.doCleanShutdown() - # .. todo:: nice function but no one is using this - @staticmethod - def getApiAddress(): - """This function returns API address and port""" - if not config.safeGetBoolean( - 'bitmessagesettings', 'apienabled'): + + #TODO: nice function but no one is using this + def getApiAddress(self): + if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'apienabled'): return None - address = config.get('bitmessagesettings', 'apiinterface') - port = config.getint('bitmessagesettings', 'apiport') - return {'address': address, 'port': port} + address = BMConfigParser().get('bitmessagesettings', 'apiinterface') + port = BMConfigParser().getint('bitmessagesettings', 'apiport') + return {'address':address,'port':port} def main(): - """Triggers main module""" mainprogram = Main() mainprogram.start() - if __name__ == "__main__": main() diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 40113b5a..7bbd7dd9 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -1,60 +1,66 @@ -""" -PyQt based UI for bitmessage, the main module -""" - -import hashlib -import locale -import os -import random -import string -import subprocess -import sys -import textwrap -import threading -import time -from datetime import datetime, timedelta -from sqlite3 import register_adapter - -from PyQt4 import QtCore, QtGui -from PyQt4.QtNetwork import QLocalSocket, QLocalServer - -import shared -import state from debug import logger +import sys + +try: + from PyQt4 import QtCore, QtGui + 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) + sys.exit() + from tr import _translate -from account import ( - accountClass, getSortedSubscriptions, - BMAccount, GatewayAccount, MailchuckAccount, AccountColor) from addresses import decodeAddress, addBMIfNotPresent +import shared from bitmessageui import Ui_MainWindow -from bmconfigparser import config -import namecoin +from bmconfigparser import BMConfigParser +import defaults +from namecoin import namecoinConnection from messageview import MessageView from migrationwizard import Ui_MigrationWizard from foldertree import ( AccountMixin, Ui_FolderWidget, Ui_AddressWidget, Ui_SubscriptionWidget, MessageList_AddressWidget, MessageList_SubjectWidget, - Ui_AddressBookWidgetItemLabel, Ui_AddressBookWidgetItemAddress, - MessageList_TimeWidget) + Ui_AddressBookWidgetItemLabel, Ui_AddressBookWidgetItemAddress) +from settings import Ui_settingsDialog import settingsmixin import support +import locale +import time +import os +import hashlib +from pyelliptic.openssl import OpenSSL +import textwrap +import debug +import random +from sqlite3 import register_adapter +import string +from datetime import datetime, timedelta +from helper_ackPayload import genAckPayload from helper_sql import sqlQuery, sqlExecute, sqlExecuteChunked, sqlStoredProcedure -import helper_addressbook import helper_search import l10n +import openclpow from utils import str_broadcast_subscribers, avatarize +from account import ( + getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount, + GatewayAccount, MailchuckAccount, AccountColor) import dialogs -from network.stats import pendingDownload, pendingUpload +from helper_generic import powQueueSize +from inventory import ( + PendingDownloadQueue, PendingUpload, + PendingUploadDeadlineException) from uisignaler import UISignaler +import knownnodes import paths from proofofwork import getPowType import queues import shutdown +import state from statusbar import BMStatusBar +from network.asyncore_pollchoose import set_rates import sound -# This is needed for tray icon -import bitmessage_icons_rc # noqa:F401 pylint: disable=unused-import -import helper_sent + try: from plugins.plugin import get_plugin, get_plugins @@ -62,90 +68,59 @@ except ImportError: get_plugins = False -# TODO: rewrite -def powQueueSize(): - """Returns the size of queues.workerQueue including current unfinished work""" - queue_len = queues.workerQueue.qsize() - for thread in threading.enumerate(): +def change_translation(newlocale): + global qmytranslator, qsystranslator + try: + if not qmytranslator.isEmpty(): + QtGui.QApplication.removeTranslator(qmytranslator) + except: + pass + try: + if not qsystranslator.isEmpty(): + QtGui.QApplication.removeTranslator(qsystranslator) + except: + pass + + qmytranslator = QtCore.QTranslator() + translationpath = os.path.join (paths.codePath(), 'translations', 'bitmessage_' + newlocale) + qmytranslator.load(translationpath) + QtGui.QApplication.installTranslator(qmytranslator) + + qsystranslator = QtCore.QTranslator() + if paths.frozen: + translationpath = os.path.join (paths.codePath(), 'translations', 'qt_' + newlocale) + else: + translationpath = os.path.join (str(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)), 'qt_' + newlocale) + qsystranslator.load(translationpath) + QtGui.QApplication.installTranslator(qsystranslator) + + lang = locale.normalize(l10n.getTranslationLanguage()) + langs = [lang.split(".")[0] + "." + l10n.encoding, lang.split(".")[0] + "." + 'UTF-8', lang] + if 'win32' in sys.platform or 'win64' in sys.platform: + langs = [l10n.getWindowsLocale(lang)] + for lang in langs: try: - if thread.name == "singleWorker": - queue_len += thread.busy - except Exception as err: - logger.info('Thread error %s', err) - return queue_len - - -def openKeysFile(): - """Open keys file with an external editor""" - keysfile = os.path.join(state.appdata, 'keys.dat') - if 'linux' in sys.platform: - subprocess.call(["xdg-open", keysfile]) - elif sys.platform.startswith('win'): - os.startfile(keysfile) # pylint: disable=no-member + l10n.setlocale(locale.LC_ALL, lang) + if 'win32' not in sys.platform and 'win64' not in sys.platform: + l10n.encoding = locale.nl_langinfo(locale.CODESET) + else: + l10n.encoding = locale.getlocale()[1] + logger.info("Successfully set locale to %s", lang) + break + except: + logger.error("Failed to set locale to %s", lang, exc_info=True) class MyForm(settingsmixin.SMainWindow): + # the last time that a message arrival sound was played + lastSoundTime = datetime.now() - timedelta(days=1) + # the maximum frequency of message sounds in seconds maxSoundFrequencySec = 60 REPLY_TYPE_SENDER = 0 REPLY_TYPE_CHAN = 1 - REPLY_TYPE_UPD = 2 - - def change_translation(self, newlocale=None): - """Change translation language for the application""" - if newlocale is None: - newlocale = l10n.getTranslationLanguage() - try: - if not self.qmytranslator.isEmpty(): - QtGui.QApplication.removeTranslator(self.qmytranslator) - except: - pass - try: - if not self.qsystranslator.isEmpty(): - QtGui.QApplication.removeTranslator(self.qsystranslator) - except: - pass - - self.qmytranslator = QtCore.QTranslator() - translationpath = os.path.join( - paths.codePath(), 'translations', 'bitmessage_' + newlocale) - self.qmytranslator.load(translationpath) - QtGui.QApplication.installTranslator(self.qmytranslator) - - self.qsystranslator = QtCore.QTranslator() - if paths.frozen: - translationpath = os.path.join( - paths.codePath(), 'translations', 'qt_' + newlocale) - else: - translationpath = os.path.join( - str(QtCore.QLibraryInfo.location( - QtCore.QLibraryInfo.TranslationsPath)), 'qt_' + newlocale) - self.qsystranslator.load(translationpath) - QtGui.QApplication.installTranslator(self.qsystranslator) - - # TODO: move this block into l10n - # FIXME: shouldn't newlocale be used here? - lang = locale.normalize(l10n.getTranslationLanguage()) - langs = [ - lang.split(".")[0] + "." + l10n.encoding, - lang.split(".")[0] + "." + 'UTF-8', - lang - ] - if 'win32' in sys.platform or 'win64' in sys.platform: - langs = [l10n.getWindowsLocale(lang)] - for lang in langs: - try: - l10n.setlocale(lang) - if 'win32' not in sys.platform and 'win64' not in sys.platform: - l10n.encoding = locale.nl_langinfo(locale.CODESET) - else: - l10n.encoding = locale.getlocale()[1] - logger.info("Successfully set locale to %s", lang) - break - except: - logger.error("Failed to set locale to %s", lang, exc_info=True) def init_file_menu(self): QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL( @@ -162,10 +137,9 @@ class MyForm(settingsmixin.SMainWindow): QtCore.SIGNAL( "triggered()"), self.click_actionRegenerateDeterministicAddresses) - QtCore.QObject.connect( - self.ui.pushButtonAddChan, - QtCore.SIGNAL("clicked()"), - self.click_actionJoinChan) # also used for creating chans. + QtCore.QObject.connect(self.ui.pushButtonAddChan, QtCore.SIGNAL( + "clicked()"), + self.click_actionJoinChan) # also used for creating chans. QtCore.QObject.connect(self.ui.pushButtonNewAddress, QtCore.SIGNAL( "clicked()"), self.click_NewAddressDialog) QtCore.QObject.connect(self.ui.pushButtonAddAddressBook, QtCore.SIGNAL( @@ -229,19 +203,19 @@ class MyForm(settingsmixin.SMainWindow): if connectSignal: self.connect(self.ui.tableWidgetInbox, QtCore.SIGNAL( 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuInbox) + self.on_context_menuInbox) self.ui.tableWidgetInboxSubscriptions.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) if connectSignal: self.connect(self.ui.tableWidgetInboxSubscriptions, QtCore.SIGNAL( 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuInbox) + self.on_context_menuInbox) self.ui.tableWidgetInboxChans.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) if connectSignal: self.connect(self.ui.tableWidgetInboxChans, QtCore.SIGNAL( 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuInbox) + self.on_context_menuInbox) def init_identities_popup_menu(self, connectSignal=True): # Popup menu for the Your Identities tab @@ -281,23 +255,11 @@ class MyForm(settingsmixin.SMainWindow): if connectSignal: self.connect(self.ui.treeWidgetYourIdentities, QtCore.SIGNAL( 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuYourIdentities) - - # load all gui.menu plugins with prefix 'address' - self.menu_plugins = {'address': []} - if not get_plugins: - return - for plugin in get_plugins('gui.menu', 'address'): - try: - handler, title = plugin(self) - except TypeError: - continue - self.menu_plugins['address'].append( - self.ui.addressContextMenuToolbarYourIdentities.addAction( - title, handler - )) + self.on_context_menuYourIdentities) def init_chan_popup_menu(self, connectSignal=True): + # Popup menu for the Channels tab + self.ui.addressContextMenuToolbar = QtGui.QToolBar() # Actions self.actionNew = self.ui.addressContextMenuToolbar.addAction(_translate( "MainWindow", "New"), self.on_action_YourIdentitiesNew) @@ -318,9 +280,6 @@ class MyForm(settingsmixin.SMainWindow): _translate( "MainWindow", "Copy address to clipboard"), self.on_action_Clipboard) - self.actionSend = self.ui.addressContextMenuToolbar.addAction( - _translate("MainWindow", "Send message to this chan"), - self.on_action_Send) self.actionSpecialAddressBehavior = self.ui.addressContextMenuToolbar.addAction( _translate( "MainWindow", "Special address behavior..."), @@ -331,7 +290,7 @@ class MyForm(settingsmixin.SMainWindow): if connectSignal: self.connect(self.ui.treeWidgetChans, QtCore.SIGNAL( 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuChan) + self.on_context_menuChan) def init_addressbook_popup_menu(self, connectSignal=True): # Popup menu for the Address Book page @@ -368,9 +327,11 @@ class MyForm(settingsmixin.SMainWindow): if connectSignal: self.connect(self.ui.tableWidgetAddressBook, QtCore.SIGNAL( 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuAddressBook) + self.on_context_menuAddressBook) def init_subscriptions_popup_menu(self, connectSignal=True): + # Popup menu for the Subscriptions page + self.ui.subscriptionsContextMenuToolbar = QtGui.QToolBar() # Actions self.actionsubscriptionsNew = self.ui.subscriptionsContextMenuToolbar.addAction( _translate("MainWindow", "New"), self.on_action_SubscriptionsNew) @@ -389,17 +350,16 @@ class MyForm(settingsmixin.SMainWindow): self.actionsubscriptionsSetAvatar = self.ui.subscriptionsContextMenuToolbar.addAction( _translate("MainWindow", "Set avatar..."), self.on_action_TreeWidgetSetAvatar) - self.actionsubscriptionsSend = self.ui.addressContextMenuToolbar.addAction( - _translate("MainWindow", "Send message to this address"), - self.on_action_Send) self.ui.treeWidgetSubscriptions.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) if connectSignal: self.connect(self.ui.treeWidgetSubscriptions, QtCore.SIGNAL( 'customContextMenuRequested(const QPoint&)'), - self.on_context_menuSubscriptions) + self.on_context_menuSubscriptions) def init_sent_popup_menu(self, connectSignal=True): + # Popup menu for the Sent page + self.ui.sentContextMenuToolbar = QtGui.QToolBar() # Actions self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction( _translate( @@ -411,9 +371,6 @@ class MyForm(settingsmixin.SMainWindow): self.actionForceSend = self.ui.sentContextMenuToolbar.addAction( _translate( "MainWindow", "Force send"), self.on_action_ForceSend) - self.actionSentReply = self.ui.sentContextMenuToolbar.addAction( - _translate("MainWindow", "Send update"), - self.on_action_SentReply) # self.popMenuSent = QtGui.QMenu( self ) # self.popMenuSent.addAction( self.actionSentClipboard ) # self.popMenuSent.addAction( self.actionTrashSentMessage ) @@ -428,13 +385,13 @@ class MyForm(settingsmixin.SMainWindow): treeWidget.header().setSortIndicator( 0, QtCore.Qt.AscendingOrder) # init dictionary - + db = getSortedSubscriptions(True) for address in db: for folder in folders: - if folder not in db[address]: + if not folder in db[address]: db[address][folder] = {} - + if treeWidget.isSortingEnabled(): treeWidget.setSortingEnabled(False) @@ -446,8 +403,8 @@ class MyForm(settingsmixin.SMainWindow): toAddress = widget.address else: toAddress = None - - if toAddress not in db: + + if not toAddress in db: treeWidget.takeTopLevelItem(i) # no increment continue @@ -477,16 +434,10 @@ class MyForm(settingsmixin.SMainWindow): widget.setUnreadCount(unread) db.pop(toAddress, None) i += 1 - + i = 0 for toAddress in db: - widget = Ui_SubscriptionWidget( - treeWidget, - i, - toAddress, - db[toAddress]["inbox"]['count'], - db[toAddress]["inbox"]['label'], - db[toAddress]["inbox"]['enabled']) + widget = Ui_SubscriptionWidget(treeWidget, i, toAddress, db[toAddress]["inbox"]['count'], db[toAddress]["inbox"]['label'], db[toAddress]["inbox"]['enabled']) j = 0 unread = 0 for folder in folders: @@ -498,22 +449,23 @@ class MyForm(settingsmixin.SMainWindow): j += 1 widget.setUnreadCount(unread) i += 1 - + treeWidget.setSortingEnabled(True) + def rerenderTabTreeMessages(self): self.rerenderTabTree('messages') def rerenderTabTreeChans(self): self.rerenderTabTree('chan') - + def rerenderTabTree(self, tab): if tab == 'messages': treeWidget = self.ui.treeWidgetYourIdentities elif tab == 'chan': treeWidget = self.ui.treeWidgetChans folders = Ui_FolderWidget.folderWeight.keys() - + # sort ascending when creating if treeWidget.topLevelItemCount() == 0: treeWidget.header().setSortIndicator( @@ -521,13 +473,13 @@ class MyForm(settingsmixin.SMainWindow): # init dictionary db = {} enabled = {} - - for toAddress in config.addresses(True): - isEnabled = config.getboolean( + + for toAddress in getSortedAccounts(): + isEnabled = BMConfigParser().getboolean( toAddress, 'enabled') - isChan = config.safeGetBoolean( + isChan = BMConfigParser().safeGetBoolean( toAddress, 'chan') - isMaillinglist = config.safeGetBoolean( + isMaillinglist = BMConfigParser().safeGetBoolean( toAddress, 'mailinglist') if treeWidget == self.ui.treeWidgetYourIdentities: @@ -540,16 +492,12 @@ class MyForm(settingsmixin.SMainWindow): db[toAddress] = {} for folder in folders: db[toAddress][folder] = 0 - + enabled[toAddress] = isEnabled # get number of (unread) messages total = 0 - queryreturn = sqlQuery( - "SELECT toaddress, folder, count(msgid) as cnt " - "FROM inbox " - "WHERE read = 0 " - "GROUP BY toaddress, folder") + queryreturn = sqlQuery('SELECT toaddress, folder, count(msgid) as cnt FROM inbox WHERE read = 0 GROUP BY toaddress, folder') for row in queryreturn: toaddress, folder, cnt = row total += cnt @@ -562,10 +510,10 @@ class MyForm(settingsmixin.SMainWindow): db[None]["sent"] = 0 db[None]["trash"] = 0 enabled[None] = True - + if treeWidget.isSortingEnabled(): treeWidget.setSortingEnabled(False) - + widgets = {} i = 0 while i < treeWidget.topLevelItemCount(): @@ -574,8 +522,8 @@ class MyForm(settingsmixin.SMainWindow): toAddress = widget.address else: toAddress = None - - if toAddress not in db: + + if not toAddress in db: treeWidget.takeTopLevelItem(i) # no increment continue @@ -584,9 +532,8 @@ class MyForm(settingsmixin.SMainWindow): while j < widget.childCount(): subwidget = widget.child(j) try: - subwidget.setUnreadCount( - db[toAddress][subwidget.folderName]) - if subwidget.folderName not in ("new", "trash", "sent"): + subwidget.setUnreadCount(db[toAddress][subwidget.folderName]) + if subwidget.folderName not in ["new", "trash", "sent"]: unread += db[toAddress][subwidget.folderName] db[toAddress].pop(subwidget.folderName, None) except: @@ -602,13 +549,13 @@ class MyForm(settingsmixin.SMainWindow): if toAddress is not None and tab == 'messages' and folder == "new": continue subwidget = Ui_FolderWidget(widget, j, toAddress, f, c) - if subwidget.folderName not in ("new", "trash", "sent"): + if subwidget.folderName not in ["new", "trash", "sent"]: unread += c j += 1 widget.setUnreadCount(unread) db.pop(toAddress, None) i += 1 - + i = 0 for toAddress in db: widget = Ui_AddressWidget(treeWidget, i, toAddress, db[toAddress]["inbox"], enabled[toAddress]) @@ -618,12 +565,12 @@ class MyForm(settingsmixin.SMainWindow): if toAddress is not None and tab == 'messages' and folder == "new": continue subwidget = Ui_FolderWidget(widget, j, toAddress, folder, db[toAddress][folder]) - if subwidget.folderName not in ("new", "trash", "sent"): + if subwidget.folderName not in ["new", "trash", "sent"]: unread += db[toAddress][folder] j += 1 widget.setUnreadCount(unread) i += 1 - + treeWidget.setSortingEnabled(True) def __init__(self, parent=None): @@ -631,41 +578,47 @@ class MyForm(settingsmixin.SMainWindow): self.ui = Ui_MainWindow() self.ui.setupUi(self) - self.qmytranslator = self.qsystranslator = None - self.indicatorUpdate = None - self.actionStatus = None - - # the last time that a message arrival sound was played - self.lastSoundTime = datetime.now() - timedelta(days=1) - # Ask the user if we may delete their old version 1 addresses if they # have any. - for addressInKeysFile in config.addresses(): + for addressInKeysFile in getSortedAccounts(): status, addressVersionNumber, streamNumber, hash = decodeAddress( addressInKeysFile) if addressVersionNumber == 1: displayMsg = _translate( - "MainWindow", - "One of your addresses, %1, is an old version 1 address. " - "Version 1 addresses are no longer supported. " - "May we delete it now?").arg(addressInKeysFile) + "MainWindow", "One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. " + + "May we delete it now?").arg(addressInKeysFile) reply = QtGui.QMessageBox.question( self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: - config.remove_section(addressInKeysFile) - config.save() + BMConfigParser().remove_section(addressInKeysFile) + BMConfigParser().save() - self.change_translation() + # Configure Bitmessage to start on startup (or remove the + # configuration) based on the setting in the keys.dat file + 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 = 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'): + self.settings.setValue("PyBitmessage", sys.argv[0]) + elif 'darwin' in sys.platform: + # startup for mac + pass + elif 'linux' in sys.platform: + # startup for linux + pass # e.g. for editing labels self.recurDepth = 0 - + # switch back to this when replying self.replyFromTab = None # so that quit won't loop - self.wait = self.quitAccepted = False - + self.quitAccepted = False + self.init_file_menu() self.init_inbox_popup_menu() self.init_identities_popup_menu() @@ -755,18 +708,20 @@ class MyForm(settingsmixin.SMainWindow): QtCore.QObject.connect(self.pushButtonStatusIcon, QtCore.SIGNAL( "clicked()"), self.click_pushButtonStatusIcon) + self.numberOfMessagesProcessed = 0 + self.numberOfBroadcastsProcessed = 0 + self.numberOfPubkeysProcessed = 0 self.unreadCount = 0 # Set the icon sizes for the identicons - identicon_size = 3 * 7 + identicon_size = 3*7 self.ui.tableWidgetInbox.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.treeWidgetChans.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.treeWidgetYourIdentities.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.treeWidgetSubscriptions.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.tableWidgetAddressBook.setIconSize(QtCore.QSize(identicon_size, identicon_size)) - + self.UISignalThread = UISignaler.get() - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( @@ -776,12 +731,9 @@ class MyForm(settingsmixin.SMainWindow): QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByAckdata) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), - self.displayNewInboxMessage) + "displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewInboxMessage) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject," - "PyQt_PyObject,PyQt_PyObject)"), - self.displayNewSentMessage) + "displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewSentMessage) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "setStatusIcon(PyQt_PyObject)"), self.setStatusIcon) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( @@ -807,9 +759,6 @@ class MyForm(settingsmixin.SMainWindow): self.ui.treeWidgetSubscriptions.keyPressEvent = self.treeWidgetKeyPressEvent self.ui.treeWidgetChans.keyPressEvent = self.treeWidgetKeyPressEvent - # Key press in addressbook - self.ui.tableWidgetAddressBook.keyPressEvent = self.addressbookKeyPressEvent - # Key press in messagelist self.ui.tableWidgetInbox.keyPressEvent = self.tableWidgetKeyPressEvent self.ui.tableWidgetInboxSubscriptions.keyPressEvent = self.tableWidgetKeyPressEvent @@ -825,72 +774,42 @@ class MyForm(settingsmixin.SMainWindow): self.rerenderComboBoxSendFrom() self.rerenderComboBoxSendFromBroadcast() - + # Put the TTL slider in the correct spot - TTL = config.getint('bitmessagesettings', 'ttl') + TTL = BMConfigParser().getint('bitmessagesettings', 'ttl') if TTL < 3600: # an hour TTL = 3600 elif TTL > 28*24*60*60: # 28 days TTL = 28*24*60*60 self.ui.horizontalSliderTTL.setSliderPosition((TTL - 3600) ** (1/3.199)) self.updateHumanFriendlyTTLDescription(TTL) - + QtCore.QObject.connect(self.ui.horizontalSliderTTL, QtCore.SIGNAL( "valueChanged(int)"), self.updateTTL) self.initSettings() - self.resetNamecoinConnection() - self.sqlInit() - self.indicatorInit() - self.notifierInit() - self.updateStartOnLogon() - - self.ui.updateNetworkSwitchMenuLabel() - - self._firstrun = config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect') - - self._contact_selected = None - - def getContactSelected(self): - """Returns last selected contact once""" + + # Check to see whether we can connect to namecoin. Hide the 'Fetch Namecoin ID' button if we can't. try: - return self._contact_selected - except AttributeError: - pass - finally: - self._contact_selected = None - - def updateStartOnLogon(self): - """ - Configure Bitmessage to start on startup (or remove the - configuration) based on the setting in the keys.dat file - """ - startonlogon = config.safeGetBoolean( - 'bitmessagesettings', 'startonlogon') - if sys.platform.startswith('win'): # Auto-startup for Windows - RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" - settings = QtCore.QSettings( - RUN_PATH, QtCore.QSettings.NativeFormat) - # In case the user moves the program and the registry entry is - # no longer valid, this will delete the old registry entry. - if startonlogon: - settings.setValue("PyBitmessage", sys.argv[0]) - else: - settings.remove("PyBitmessage") - else: - try: # get desktop plugin if any - self.desktop = get_plugin('desktop')() - self.desktop.adjust_startonlogon(startonlogon) - except (NameError, TypeError): - self.desktop = False - + options = {} + options["type"] = BMConfigParser().get('bitmessagesettings', 'namecoinrpctype') + options["host"] = BMConfigParser().get('bitmessagesettings', 'namecoinrpchost') + options["port"] = BMConfigParser().get('bitmessagesettings', 'namecoinrpcport') + options["user"] = BMConfigParser().get('bitmessagesettings', 'namecoinrpcuser') + options["password"] = BMConfigParser().get('bitmessagesettings', 'namecoinrpcpassword') + nc = namecoinConnection(options) + if nc.test()[0] == 'failed': + self.ui.pushButtonFetchNamecoinID.hide() + except: + logger.error('There was a problem testing for a Namecoin daemon. Hiding the Fetch Namecoin ID button') + self.ui.pushButtonFetchNamecoinID.hide() + def updateTTL(self, sliderPosition): TTL = int(sliderPosition ** 3.199 + 3600) self.updateHumanFriendlyTTLDescription(TTL) - config.set('bitmessagesettings', 'ttl', str(TTL)) - config.save() - + BMConfigParser().set('bitmessagesettings', 'ttl', str(TTL)) + BMConfigParser().save() + def updateHumanFriendlyTTLDescription(self, TTL): numberOfHours = int(round(TTL / (60*60))) font = QtGui.QFont() @@ -906,13 +825,7 @@ class MyForm(settingsmixin.SMainWindow): font.setBold(True) else: numberOfDays = int(round(TTL / (24*60*60))) - self.ui.labelHumanFriendlyTTLDescription.setText( - _translate( - "MainWindow", - "%n day(s)", - None, - QtCore.QCoreApplication.CodecForTr, - numberOfDays)) + self.ui.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n day(s)", None, QtCore.QCoreApplication.CodecForTr, numberOfDays)) font.setBold(False) self.ui.labelHumanFriendlyTTLDescription.setStyleSheet(stylesheet) self.ui.labelHumanFriendlyTTLDescription.setFont(font) @@ -946,7 +859,7 @@ class MyForm(settingsmixin.SMainWindow): self.appIndicatorShowOrHideWindow() def appIndicatorSwitchQuietMode(self): - config.set( + BMConfigParser().set( 'bitmessagesettings', 'showtraynotifications', str(not self.actionQuiet.isChecked()) ) @@ -1006,106 +919,91 @@ class MyForm(settingsmixin.SMainWindow): Switch unread for item of msgid and related items in other STableWidgets "All Accounts" and "Chans" """ - status = widget.item(row, 0).unread - if status != unread: - return - - widgets = [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans] - rrow = None + related = [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans] try: - widgets.remove(widget) - related = widgets.pop() + related.remove(widget) + related = related.pop() except ValueError: - pass + 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 range(related.rowCount()): - if related.item(rrow, 3).data() == msgid: + for rrow in xrange(related.rowCount()): + if msgid == str(related.item(rrow, 3).data( + QtCore.Qt.UserRole).toPyObject()): break + else: + rrow = None - for col in range(widget.columnCount()): - widget.item(row, col).setUnread(not status) - if rrow: - related.item(rrow, col).setUnread(not status) + 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) - # Here we need to update unread count for: - # - all widgets if there is no args - # - All accounts - # - corresponding account if current is "All accounts" - # - current account otherwise - def propagateUnreadCount(self, folder=None, widget=None): - queryReturn = sqlQuery( - 'SELECT toaddress, folder, COUNT(msgid) AS cnt' - ' FROM inbox WHERE read = 0 GROUP BY toaddress, folder') + 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") totalUnread = {} normalUnread = {} + for row in queryReturn: + normalUnread[row[0]] = {} + if row[1] in ["trash"]: + continue + normalUnread[row[0]][row[1]] = row[2] + if row[1] in totalUnread: + totalUnread[row[1]] += row[2] + else: + totalUnread[row[1]] = row[2] + queryReturn = sqlQuery("SELECT fromaddress, folder, COUNT(msgid) AS cnt FROM inbox WHERE read = 0 AND toaddress = ? GROUP BY fromaddress, folder", str_broadcast_subscribers) broadcastsUnread = {} - for addr, fld, count in queryReturn: - try: - normalUnread[addr][fld] = count - except KeyError: - normalUnread[addr] = {fld: count} - try: - totalUnread[fld] += count - except KeyError: - totalUnread[fld] = count - if widget in ( - self.ui.treeWidgetSubscriptions, self.ui.treeWidgetChans): - widgets = (self.ui.treeWidgetYourIdentities,) - else: - widgets = ( - self.ui.treeWidgetYourIdentities, - self.ui.treeWidgetSubscriptions, self.ui.treeWidgetChans - ) - queryReturn = sqlQuery( - 'SELECT fromaddress, folder, COUNT(msgid) AS cnt' - ' FROM inbox WHERE read = 0 AND toaddress = ?' - ' GROUP BY fromaddress, folder', str_broadcast_subscribers) - for addr, fld, count in queryReturn: - try: - broadcastsUnread[addr][fld] = count - except KeyError: - broadcastsUnread[addr] = {fld: count} - + for row in queryReturn: + broadcastsUnread[row[0]] = {} + broadcastsUnread[row[0]][row[1]] = row[2] + for treeWidget in widgets: root = treeWidget.invisibleRootItem() for i in range(root.childCount()): addressItem = root.child(i) + newCount = 0 if addressItem.type == AccountMixin.ALL: newCount = sum(totalUnread.itervalues()) self.drawTrayIcon(self.currentTrayIconFileName, newCount) - else: - try: - newCount = sum(( - broadcastsUnread - if addressItem.type == AccountMixin.SUBSCRIPTION - else normalUnread - )[addressItem.address].itervalues()) - except KeyError: - newCount = 0 + elif addressItem.type == AccountMixin.SUBSCRIPTION: + if addressItem.address in broadcastsUnread: + newCount = sum(broadcastsUnread[addressItem.address].itervalues()) + elif addressItem.address in normalUnread: + newCount = sum(normalUnread[addressItem.address].itervalues()) if newCount != addressItem.unreadCount: addressItem.setUnreadCount(newCount) + if addressItem.childCount == 0: + continue for j in range(addressItem.childCount()): folderItem = addressItem.child(j) + newCount = 0 folderName = folderItem.folderName if folderName == "new": folderName = "inbox" - if folder and folderName != folder: - continue - if addressItem.type == AccountMixin.ALL: - newCount = totalUnread.get(folderName, 0) - else: - try: - newCount = ( - broadcastsUnread - if addressItem.type == AccountMixin.SUBSCRIPTION - else normalUnread - )[addressItem.address][folderName] - except KeyError: - newCount = 0 + if addressItem.type == AccountMixin.ALL and folderName in totalUnread: + newCount = totalUnread[folderName] + elif addressItem.type == AccountMixin.SUBSCRIPTION: + if addressItem.address in broadcastsUnread and folderName in broadcastsUnread[addressItem.address]: + newCount = broadcastsUnread[addressItem.address][folderName] + elif addressItem.address in normalUnread and folderName in normalUnread[addressItem.address]: + newCount = normalUnread[addressItem.address][folderName] if newCount != folderItem.unreadCount: folderItem.setUnreadCount(newCount) @@ -1114,46 +1012,43 @@ class MyForm(settingsmixin.SMainWindow): if sortingEnabled: tableWidget.setSortingEnabled(False) tableWidget.insertRow(0) - for i, item in enumerate(items): - tableWidget.setItem(0, i, item) + for i in range(len(items)): + tableWidget.setItem(0, i, items[i]) if sortingEnabled: tableWidget.setSortingEnabled(True) - def addMessageListItemSent( - self, tableWidget, toAddress, fromAddress, subject, - status, ackdata, lastactiontime - ): - acct = accountClass(fromAddress) or BMAccount(fromAddress) + def addMessageListItemSent(self, tableWidget, toAddress, fromAddress, subject, status, ackdata, lastactiontime): + acct = accountClass(fromAddress) + if acct is None: + acct = BMAccount(fromAddress) acct.parseMessage(toAddress, fromAddress, subject, "") + items = [] + MessageList_AddressWidget(items, str(toAddress), unicode(acct.toLabel, 'utf-8')) + MessageList_AddressWidget(items, str(fromAddress), unicode(acct.fromLabel, 'utf-8')) + MessageList_SubjectWidget(items, str(subject), unicode(acct.subject, 'utf-8', 'replace')) + if status == 'awaitingpubkey': statusText = _translate( - "MainWindow", - "Waiting for their encryption key. Will request it again soon." - ) + "MainWindow", "Waiting for their encryption key. Will request it again soon.") elif status == 'doingpowforpubkey': statusText = _translate( - "MainWindow", "Doing work necessary to request encryption key." - ) + "MainWindow", "Doing work necessary to request encryption key.") elif status == 'msgqueued': - statusText = _translate("MainWindow", "Queued.") + statusText = _translate( + "MainWindow", "Queued.") elif status == 'msgsent': - statusText = _translate( - "MainWindow", - "Message sent. Waiting for acknowledgement. Sent at %1" - ).arg(l10n.formatTimestamp(lastactiontime)) + statusText = _translate("MainWindow", "Message sent. Waiting for acknowledgement. Sent at %1").arg( + l10n.formatTimestamp(lastactiontime)) elif status == 'msgsentnoackexpected': - statusText = _translate( - "MainWindow", "Message sent. Sent at %1" - ).arg(l10n.formatTimestamp(lastactiontime)) + statusText = _translate("MainWindow", "Message sent. Sent at %1").arg( + l10n.formatTimestamp(lastactiontime)) elif status == 'doingmsgpow': statusText = _translate( "MainWindow", "Doing work necessary to send message.") elif status == 'ackreceived': - statusText = _translate( - "MainWindow", - "Acknowledgement of the message received %1" - ).arg(l10n.formatTimestamp(lastactiontime)) + statusText = _translate("MainWindow", "Acknowledgement of the message received %1").arg( + l10n.formatTimestamp(lastactiontime)) elif status == 'broadcastqueued': statusText = _translate( "MainWindow", "Broadcast queued.") @@ -1164,64 +1059,58 @@ class MyForm(settingsmixin.SMainWindow): statusText = _translate("MainWindow", "Broadcast on %1").arg( l10n.formatTimestamp(lastactiontime)) elif status == 'toodifficult': - statusText = _translate( - "MainWindow", - "Problem: The work demanded by the recipient is more" - " difficult than you are willing to do. %1" - ).arg(l10n.formatTimestamp(lastactiontime)) + statusText = _translate("MainWindow", "Problem: The work demanded by the recipient is more difficult than you are willing to do. %1").arg( + l10n.formatTimestamp(lastactiontime)) elif status == 'badkey': - statusText = _translate( - "MainWindow", - "Problem: The recipient\'s encryption key is no good." - " Could not encrypt message. %1" - ).arg(l10n.formatTimestamp(lastactiontime)) + statusText = _translate("MainWindow", "Problem: The recipient\'s encryption key is no good. Could not encrypt message. %1").arg( + l10n.formatTimestamp(lastactiontime)) elif status == 'forcepow': statusText = _translate( - "MainWindow", - "Forced difficulty override. Send should start soon.") + "MainWindow", "Forced difficulty override. Send should start soon.") else: - statusText = _translate( - "MainWindow", "Unknown status: %1 %2").arg(status).arg( + statusText = _translate("MainWindow", "Unknown status: %1 %2").arg(status).arg( l10n.formatTimestamp(lastactiontime)) - - items = [ - MessageList_AddressWidget( - toAddress, unicode(acct.toLabel, 'utf-8')), - MessageList_AddressWidget( - fromAddress, unicode(acct.fromLabel, 'utf-8')), - MessageList_SubjectWidget( - str(subject), unicode(acct.subject, 'utf-8', 'replace')), - MessageList_TimeWidget( - statusText, False, lastactiontime, ackdata)] + newItem = myTableWidgetItem(statusText) + newItem.setToolTip(statusText) + newItem.setData(QtCore.Qt.UserRole, QtCore.QByteArray(ackdata)) + newItem.setData(33, int(lastactiontime)) + newItem.setFlags( + QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + items.append(newItem) self.addMessageListItem(tableWidget, items) - return acct - def addMessageListItemInbox( - self, tableWidget, toAddress, fromAddress, subject, - msgid, received, read - ): + def addMessageListItemInbox(self, tableWidget, msgfolder, msgid, toAddress, fromAddress, subject, received, read): + font = QtGui.QFont() + font.setBold(True) if toAddress == str_broadcast_subscribers: acct = accountClass(fromAddress) else: - acct = accountClass(toAddress) or accountClass(fromAddress) + acct = accountClass(toAddress) + if acct is None: + acct = accountClass(fromAddress) if acct is None: acct = BMAccount(fromAddress) acct.parseMessage(toAddress, fromAddress, subject, "") - - items = [ - MessageList_AddressWidget( - toAddress, unicode(acct.toLabel, 'utf-8'), not read), - MessageList_AddressWidget( - fromAddress, unicode(acct.fromLabel, 'utf-8'), not read), - MessageList_SubjectWidget( - str(subject), unicode(acct.subject, 'utf-8', 'replace'), - not read), - MessageList_TimeWidget( - l10n.formatTimestamp(received), not read, received, msgid) - ] + + items = [] + #to + MessageList_AddressWidget(items, toAddress, unicode(acct.toLabel, 'utf-8'), not read) + # from + MessageList_AddressWidget(items, fromAddress, unicode(acct.fromLabel, 'utf-8'), not read) + # subject + MessageList_SubjectWidget(items, str(subject), unicode(acct.subject, 'utf-8', 'replace'), not read) + # time received + time_item = myTableWidgetItem(l10n.formatTimestamp(received)) + time_item.setToolTip(l10n.formatTimestamp(received)) + time_item.setData(QtCore.Qt.UserRole, QtCore.QByteArray(msgid)) + time_item.setData(33, int(received)) + time_item.setFlags( + QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + if not read: + time_item.setFont(font) + items.append(time_item) self.addMessageListItem(tableWidget, items) - return acct # Load Sent items from database @@ -1236,40 +1125,35 @@ class MyForm(settingsmixin.SMainWindow): xAddress = 'both' else: tableWidget.setColumnHidden(0, False) - tableWidget.setColumnHidden(1, bool(account)) + if account is None: + tableWidget.setColumnHidden(1, False) + else: + tableWidget.setColumnHidden(1, True) xAddress = 'fromaddress' - queryreturn = helper_search.search_sql( - xAddress, account, "sent", where, what, False) + tableWidget.setUpdatesEnabled(False) + tableWidget.setSortingEnabled(False) + tableWidget.setRowCount(0) + queryreturn = helper_search.search_sql(xAddress, account, "sent", where, what, False) for row in queryreturn: - self.addMessageListItemSent(tableWidget, *row) + toAddress, fromAddress, subject, status, ackdata, lastactiontime = row + self.addMessageListItemSent(tableWidget, toAddress, fromAddress, subject, status, ackdata, lastactiontime) tableWidget.horizontalHeader().setSortIndicator( 3, QtCore.Qt.DescendingOrder) tableWidget.setSortingEnabled(True) - tableWidget.horizontalHeaderItem(3).setText( - _translate("MainWindow", "Sent")) + tableWidget.horizontalHeaderItem(3).setText(_translate("MainWindow", "Sent", None)) tableWidget.setUpdatesEnabled(True) # Load messages from database file - def loadMessagelist( - self, tableWidget, account, folder="inbox", where="", what="", - unreadOnly=False - ): - tableWidget.setUpdatesEnabled(False) - tableWidget.setSortingEnabled(False) - tableWidget.setRowCount(0) - + def loadMessagelist(self, tableWidget, account, folder="inbox", where="", what="", unreadOnly = False): if folder == 'sent': self.loadSent(tableWidget, account, where, what) return if tableWidget == self.ui.tableWidgetInboxSubscriptions: xAddress = "fromaddress" - if not what: - where = _translate("MainWindow", "To") - what = str_broadcast_subscribers else: xAddress = "toaddress" if account is not None: @@ -1279,21 +1163,21 @@ class MyForm(settingsmixin.SMainWindow): tableWidget.setColumnHidden(0, False) tableWidget.setColumnHidden(1, False) - queryreturn = helper_search.search_sql( - xAddress, account, folder, where, what, unreadOnly) + tableWidget.setUpdatesEnabled(False) + tableWidget.setSortingEnabled(False) + tableWidget.setRowCount(0) + queryreturn = helper_search.search_sql(xAddress, account, folder, where, what, unreadOnly) + for row in queryreturn: - toAddress, fromAddress, subject, _, msgid, received, read = row - self.addMessageListItemInbox( - tableWidget, toAddress, fromAddress, subject, - msgid, received, read) + msgfolder, msgid, toAddress, fromAddress, subject, received, read = row + self.addMessageListItemInbox(tableWidget, msgfolder, msgid, toAddress, fromAddress, subject, received, read) tableWidget.horizontalHeader().setSortIndicator( 3, QtCore.Qt.DescendingOrder) tableWidget.setSortingEnabled(True) tableWidget.selectRow(0) - tableWidget.horizontalHeaderItem(3).setText( - _translate("MainWindow", "Received")) + tableWidget.horizontalHeaderItem(3).setText(_translate("MainWindow", "Received", None)) tableWidget.setUpdatesEnabled(True) # create application indicator @@ -1317,7 +1201,7 @@ class MyForm(settingsmixin.SMainWindow): # show bitmessage self.actionShow = QtGui.QAction(_translate( "MainWindow", "Show Bitmessage"), m, checkable=True) - self.actionShow.setChecked(not config.getboolean( + self.actionShow.setChecked(not BMConfigParser().getboolean( 'bitmessagesettings', 'startintray')) self.actionShow.triggered.connect(self.appIndicatorShowOrHideWindow) if not sys.platform[0:3] == 'win': @@ -1326,7 +1210,7 @@ class MyForm(settingsmixin.SMainWindow): # quiet mode self.actionQuiet = QtGui.QAction(_translate( "MainWindow", "Quiet Mode"), m, checkable=True) - self.actionQuiet.setChecked(not config.getboolean( + self.actionQuiet.setChecked(not BMConfigParser().getboolean( 'bitmessagesettings', 'showtraynotifications')) self.actionQuiet.triggered.connect(self.appIndicatorSwitchQuietMode) m.addAction(self.actionQuiet) @@ -1449,11 +1333,9 @@ class MyForm(settingsmixin.SMainWindow): def sqlInit(self): register_adapter(QtCore.QByteArray, str) + # Try init the distro specific appindicator, + # for example the Ubuntu MessagingMenu def indicatorInit(self): - """ - Try init the distro specific appindicator, - for example the Ubuntu MessagingMenu - """ def _noop_update(*args, **kwargs): pass @@ -1502,15 +1384,6 @@ class MyForm(settingsmixin.SMainWindow): def treeWidgetKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentTreeWidget()) - # addressbook - def addressbookKeyPressEvent(self, event): - """Handle keypress event in addressbook widget""" - if event.key() == QtCore.Qt.Key_Delete: - self.on_action_AddressBookDelete() - else: - return QtGui.QTableWidget.keyPressEvent( - self.ui.tableWidgetAddressBook, event) - # inbox / sent def tableWidgetKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentMessagelist()) @@ -1519,12 +1392,11 @@ class MyForm(settingsmixin.SMainWindow): def textEditKeyPressEvent(self, event): return self.handleKeyPress(event, self.getCurrentMessageTextedit()) - def handleKeyPress(self, event, focus=None): - """This method handles keypress events for all widgets on MyForm""" + def handleKeyPress(self, event, focus = None): messagelist = self.getCurrentMessagelist() + folder = self.getCurrentFolder() if event.key() == QtCore.Qt.Key_Delete: - if isinstance(focus, (MessageView, QtGui.QTableWidget)): - folder = self.getCurrentFolder() + if isinstance (focus, MessageView) or isinstance(focus, QtGui.QTableWidget): if folder == "sent": self.on_action_SentTrash() else: @@ -1560,18 +1432,17 @@ class MyForm(settingsmixin.SMainWindow): self.ui.lineEditTo.setFocus() event.ignore() elif event.key() == QtCore.Qt.Key_F: - try: - self.getCurrentSearchLine(retObj=True).setFocus() - except AttributeError: - pass + searchline = self.getCurrentSearchLine(retObj = True) + if searchline: + searchline.setFocus() event.ignore() if not event.isAccepted(): return - if isinstance(focus, MessageView): + if isinstance (focus, MessageView): return MessageView.keyPressEvent(focus, event) - if isinstance(focus, QtGui.QTableWidget): + elif isinstance (focus, QtGui.QTableWidget): return QtGui.QTableWidget.keyPressEvent(focus, event) - if isinstance(focus, QtGui.QTreeWidget): + elif isinstance (focus, QtGui.QTreeWidget): return QtGui.QTreeWidget.keyPressEvent(focus, event) # menu button 'manage keys' @@ -1582,81 +1453,36 @@ class MyForm(settingsmixin.SMainWindow): # 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) - 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." - ), - QtGui.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."), 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 \n" - "It is important that you back up this file." - ).arg(state.appdata), - QtGui.QMessageBox.Ok) + 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), 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( - "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. " - "Would you like to open the file now? " - "(Be sure to close Bitmessage before making any changes.)"), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) + reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Open 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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)"), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) else: - reply = QtGui.QMessageBox.question( - self, - _translate("MainWindow", "Open keys.dat?"), - _translate( - "MainWindow", - "You may manage your keys by editing the keys.dat file stored in\n %1 \n" - "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.)").arg(state.appdata), - QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Open 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. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)").arg(state.appdata), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: - openKeysFile() + shared.openKeysFile() # menu button 'delete all treshed messages' def click_actionDeleteAllTrashedMessages(self): - if QtGui.QMessageBox.question( - self, - _translate("MainWindow", "Delete trash?"), - _translate("MainWindow", "Are you sure you want to delete all trashed messages?"), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) == QtGui.QMessageBox.No: + if QtGui.QMessageBox.question(self, _translate("MainWindow", "Delete trash?"), _translate("MainWindow", "Are you sure you want to delete all trashed messages?"), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) == QtGui.QMessageBox.No: return sqlStoredProcedure('deleteandvacuume') self.rerenderTabTreeMessages() self.rerenderTabTreeSubscriptions() self.rerenderTabTreeChans() if self.getCurrentFolder(self.ui.treeWidgetYourIdentities) == "trash": - self.loadMessagelist( - self.ui.tableWidgetInbox, - self.getCurrentAccount(self.ui.treeWidgetYourIdentities), - "trash") + self.loadMessagelist(self.ui.tableWidgetInbox, self.getCurrentAccount(self.ui.treeWidgetYourIdentities), "trash") elif self.getCurrentFolder(self.ui.treeWidgetSubscriptions) == "trash": - self.loadMessagelist( - self.ui.tableWidgetInboxSubscriptions, - self.getCurrentAccount(self.ui.treeWidgetSubscriptions), - "trash") + self.loadMessagelist(self.ui.tableWidgetInboxSubscriptions, self.getCurrentAccount(self.ui.treeWidgetSubscriptions), "trash") elif self.getCurrentFolder(self.ui.treeWidgetChans) == "trash": - self.loadMessagelist( - self.ui.tableWidgetInboxChans, - self.getCurrentAccount(self.ui.treeWidgetChans), - "trash") + self.loadMessagelist(self.ui.tableWidgetInboxChans, self.getCurrentAccount(self.ui.treeWidgetChans), "trash") # menu button 'regenerate deterministic addresses' def click_actionRegenerateDeterministicAddresses(self): @@ -1714,14 +1540,11 @@ class MyForm(settingsmixin.SMainWindow): dialog = dialogs.ConnectDialog(self) if dialog.exec_(): if dialog.radioButtonConnectNow.isChecked(): - self.ui.updateNetworkSwitchMenuLabel(False) - config.remove_option( + BMConfigParser().remove_option( 'bitmessagesettings', 'dontconnect') - config.save() + BMConfigParser().save() elif dialog.radioButtonConfigureNetwork.isChecked(): self.click_actionSettings() - else: - self._firstrun = False def showMigrationWizard(self, level): self.migrationWizardInstance = Ui_MigrationWizard(["a"]) @@ -1742,12 +1565,13 @@ class MyForm(settingsmixin.SMainWindow): self.ui.blackwhitelist.init_blacklist_popup_menu(False) if event.type() == QtCore.QEvent.WindowStateChange: if self.windowState() & QtCore.Qt.WindowMinimized: - if config.getboolean('bitmessagesettings', 'minimizetotray') and not 'darwin' in sys.platform: + if BMConfigParser().getboolean('bitmessagesettings', 'minimizetotray') and not 'darwin' in sys.platform: 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: @@ -1758,65 +1582,68 @@ class MyForm(settingsmixin.SMainWindow): connected = False def setStatusIcon(self, color): - _notifications_enabled = not config.getboolean( + # print 'setting status icon color' + _notifications_enabled = not BMConfigParser().getboolean( 'bitmessagesettings', 'hidetrayconnectionnotifications') - if color not in ('red', 'yellow', 'green'): - return - - self.pushButtonStatusIcon.setIcon( - QtGui.QIcon(":/newPrefix/images/%sicon.png" % color)) - state.statusIconColor = color if color == 'red': + self.pushButtonStatusIcon.setIcon( + QtGui.QIcon(":/newPrefix/images/redicon.png")) + shared.statusIconColor = 'red' # if the connection is lost then show a notification if self.connected and _notifications_enabled: self.notifierShow( 'Bitmessage', _translate("MainWindow", "Connection lost"), sound.SOUND_DISCONNECTED) - proxy = config.safeGet( - 'bitmessagesettings', 'socksproxytype', 'none') - if proxy == 'none' and not config.safeGetBoolean( - 'bitmessagesettings', 'upnp'): + if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp') and \ + BMConfigParser().get('bitmessagesettings', 'socksproxytype') == "none": self.updateStatusBar( _translate( "MainWindow", "Problems connecting? Try enabling UPnP in the Network" " Settings" )) - elif proxy == 'SOCKS5' and config.safeGetBoolean( - 'bitmessagesettings', 'onionservicesonly'): - self.updateStatusBar(( - _translate( - "MainWindow", - "With recent tor you may never connect having" - " 'onionservicesonly' set in your config."), 1 - )) self.connected = False if self.actionStatus is not None: self.actionStatus.setText(_translate( "MainWindow", "Not Connected")) self.setTrayIconFile("can-icon-24px-red.png") - return + 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( + 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 _notifications_enabled: + self.notifierShow( + 'Bitmessage', + _translate("MainWindow", "Connected"), + sound.SOUND_CONNECTED) + self.connected = True - 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 a new connection has been established then show a notification - 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: + self.actionStatus.setText(_translate( + "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() + self.pushButtonStatusIcon.setIcon( + QtGui.QIcon(":/newPrefix/images/greenicon.png")) + shared.statusIconColor = 'green' + 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: - self.actionStatus.setText(_translate( - "MainWindow", "Connected")) - self.setTrayIconFile("can-icon-24px-%s.png" % color) + if self.actionStatus is not None: + self.actionStatus.setText(_translate( + "MainWindow", "Connected")) + self.setTrayIconFile("can-icon-24px-green.png") def initTrayIcon(self, iconFileName, app): self.currentTrayIconFileName = iconFileName @@ -1828,7 +1655,7 @@ class MyForm(settingsmixin.SMainWindow): self.drawTrayIcon(iconFileName, self.findInboxUnreadCount()) def calcTrayIcon(self, iconFileName, inboxUnreadCount): - pixmap = QtGui.QPixmap(":/newPrefix/images/" + iconFileName) + pixmap = QtGui.QPixmap(":/newPrefix/images/"+iconFileName) if inboxUnreadCount > 0: # choose font and calculate font parameters fontName = "Lucida" @@ -1840,8 +1667,7 @@ class MyForm(settingsmixin.SMainWindow): rect = fontMetrics.boundingRect(txt) # margins that we add in the top-right corner marginX = 2 - # it looks like -2 is also ok due to the error of metric - marginY = 0 + marginY = 0 # it looks like -2 is also ok due to the error of metric # if it renders too wide we need to change it to a plus symbol if rect.width() > 20: txt = "+" @@ -1881,18 +1707,11 @@ class MyForm(settingsmixin.SMainWindow): return self.unreadCount def updateSentItemStatusByToAddress(self, toAddress, textToDisplay): - for sent in ( - self.ui.tableWidgetInbox, - self.ui.tableWidgetInboxSubscriptions, - self.ui.tableWidgetInboxChans - ): + for sent in [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxSubscriptions, self.ui.tableWidgetInboxChans]: treeWidget = self.widgetConvert(sent) if self.getCurrentFolder(treeWidget) != "sent": continue - if treeWidget in ( - self.ui.treeWidgetSubscriptions, - self.ui.treeWidgetChans - ) and self.getCurrentAccount(treeWidget) != toAddress: + if treeWidget in [self.ui.treeWidgetSubscriptions, self.ui.treeWidgetChans] and self.getCurrentAccount(treeWidget) != toAddress: continue for i in range(sent.rowCount()): @@ -1901,9 +1720,7 @@ class MyForm(settingsmixin.SMainWindow): sent.item(i, 3).setToolTip(textToDisplay) try: newlinePosition = textToDisplay.indexOf('\n') - except: - # If someone misses adding a "_translate" to a string before passing it to this function, - # this function won't receive a qstring which will cause an exception. + except: # If someone misses adding a "_translate" to a string before passing it to this function, this function won't receive a qstring which will cause an exception. newlinePosition = 0 if newlinePosition > 1: sent.item(i, 3).setText( @@ -1912,28 +1729,22 @@ class MyForm(settingsmixin.SMainWindow): sent.item(i, 3).setText(textToDisplay) def updateSentItemStatusByAckdata(self, ackdata, textToDisplay): - if type(ackdata) is str: - ackdata = QtCore.QByteArray(ackdata) - for sent in ( - self.ui.tableWidgetInbox, - self.ui.tableWidgetInboxSubscriptions, - self.ui.tableWidgetInboxChans - ): + for sent in [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxSubscriptions, self.ui.tableWidgetInboxChans]: treeWidget = self.widgetConvert(sent) if self.getCurrentFolder(treeWidget) != "sent": continue for i in range(sent.rowCount()): - toAddress = sent.item(i, 0).data(QtCore.Qt.UserRole) - tableAckdata = sent.item(i, 3).data() + toAddress = sent.item( + i, 0).data(QtCore.Qt.UserRole) + tableAckdata = sent.item( + i, 3).data(QtCore.Qt.UserRole).toPyObject() status, addressVersionNumber, streamNumber, ripe = decodeAddress( toAddress) if ackdata == tableAckdata: sent.item(i, 3).setToolTip(textToDisplay) try: newlinePosition = textToDisplay.indexOf('\n') - except: - # If someone misses adding a "_translate" to a string before passing it to this function, - # this function won't receive a qstring which will cause an exception. + except: # If someone misses adding a "_translate" to a string before passing it to this function, this function won't receive a qstring which will cause an exception. newlinePosition = 0 if newlinePosition > 1: sent.item(i, 3).setText( @@ -1941,27 +1752,19 @@ class MyForm(settingsmixin.SMainWindow): else: sent.item(i, 3).setText(textToDisplay) - def removeInboxRowByMsgid(self, msgid): - # msgid and inventoryHash are the same thing - for inbox in ( + def removeInboxRowByMsgid(self, msgid): # msgid and inventoryHash are the same thing + for inbox in ([ self.ui.tableWidgetInbox, self.ui.tableWidgetInboxSubscriptions, - self.ui.tableWidgetInboxChans - ): - i = None + self.ui.tableWidgetInboxChans]): for i in range(inbox.rowCount()): - if msgid == inbox.item(i, 3).data(): + 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(QtCore.Qt.UserRole), self.getCurrentFolder(treeWidget), treeWidget, 0) + inbox.removeRow(i) break - else: - continue - self.updateStatusBar(_translate("MainWindow", "Message trashed")) - treeWidget = self.widgetConvert(inbox) - self.propagateUnreadCount( - # wrong assumption about current folder here: - self.getCurrentFolder(treeWidget), treeWidget - ) - if i: - inbox.removeRow(i) def newVersionAvailable(self, version): self.notifiedNewVersion = ".".join(str(n) for n in version) @@ -1979,16 +1782,12 @@ class MyForm(settingsmixin.SMainWindow): os._exit(0) def rerenderMessagelistFromLabels(self): - for messagelist in (self.ui.tableWidgetInbox, - self.ui.tableWidgetInboxChans, - self.ui.tableWidgetInboxSubscriptions): + for messagelist in (self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans, self.ui.tableWidgetInboxSubscriptions): for i in range(messagelist.rowCount()): messagelist.item(i, 1).setLabel() def rerenderMessagelistToLabels(self): - for messagelist in (self.ui.tableWidgetInbox, - self.ui.tableWidgetInboxChans, - self.ui.tableWidgetInboxSubscriptions): + for messagelist in (self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans, self.ui.tableWidgetInboxSubscriptions): for i in range(messagelist.rowCount()): messagelist.item(i, 0).setLabel() @@ -2017,9 +1816,10 @@ class MyForm(settingsmixin.SMainWindow): label, address = row newRows[address] = [label, AccountMixin.SUBSCRIPTION] # chans - for address in config.addresses(True): + addresses = getSortedAccounts() + for address in addresses: account = accountClass(address) - if (account.type == AccountMixin.CHAN and config.safeGetBoolean(address, 'enabled')): + if (account.type == AccountMixin.CHAN and BMConfigParser().safeGetBoolean(address, 'enabled')): newRows[address] = [account.getLabel(), AccountMixin.CHAN] # normal accounts queryreturn = sqlQuery('SELECT * FROM addressbook') @@ -2028,13 +1828,11 @@ class MyForm(settingsmixin.SMainWindow): newRows[address] = [label, AccountMixin.NORMAL] completerList = [] - for address in sorted( - oldRows, key=lambda x: oldRows[x][2], reverse=True - ): - try: - completerList.append( - newRows.pop(address)[0] + " <" + address + ">") - except KeyError: + for address in sorted(oldRows, key = lambda x: oldRows[x][2], reverse = True): + if address in newRows: + completerList.append(unicode(newRows[address][0], encoding="UTF-8") + " <" + address + ">") + newRows.pop(address) + else: self.ui.tableWidgetAddressBook.removeRow(oldRows[address][2]) for address in newRows: addRow(address, newRows[address][0], newRows[address][1]) @@ -2050,21 +1848,16 @@ class MyForm(settingsmixin.SMainWindow): 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."""), - QtGui.QMessageBox.Ok) + 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."""), QtGui.QMessageBox.Ok) def click_pushButtonClear(self): self.ui.lineEditSubject.setText("") self.ui.lineEditTo.setText("") - self.ui.textEditMessage.reset() + self.ui.textEditMessage.setText("") self.ui.comboBoxSendFrom.setCurrentIndex(0) def click_pushButtonSend(self): @@ -2112,14 +1905,11 @@ class MyForm(settingsmixin.SMainWindow): acct = accountClass(fromAddress) - # To send a message to specific people (rather than broadcast) - if sendMessageToPeople: - toAddressesList = set([ - s.strip() for s in toAddresses.replace(',', ';').split(';') - ]) - # remove duplicate addresses. If the user has one address - # with a BM- and the same address without the BM-, this will - # not catch it. They'll send the message to the person twice. + if sendMessageToPeople: # To send a message to specific people (rather than broadcast) + toAddressesList = [s.strip() + for s in toAddresses.replace(',', ';').split(';')] + toAddressesList = list(set( + toAddressesList)) # remove duplicate addresses. If the user has one address with a BM- and the same address without the BM-, this will not catch it. They'll send the message to the person twice. for toAddress in toAddressesList: if toAddress != '': # label plus address @@ -2132,26 +1922,19 @@ class MyForm(settingsmixin.SMainWindow): 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: + 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 + if email[-14:] != "@mailchuck.com": #attempt register # 12 character random email address - email = ''.join( - random.SystemRandom().choice(string.ascii_lowercase) for _ in range(12) - ) + "@mailchuck.com" + email = ''.join(random.SystemRandom().choice(string.ascii_lowercase) for _ in range(12)) + "@mailchuck.com" acct = MailchuckAccount(fromAddress) acct.register(email) - config.set(fromAddress, 'label', email) - config.set(fromAddress, 'gateway', 'mailchuck') - config.save() + BMConfigParser().set(fromAddress, 'label', email) + BMConfigParser().set(fromAddress, 'gateway', 'mailchuck') + BMConfigParser().save() self.updateStatusBar(_translate( "MainWindow", "Error: Your account wasn't registered at" @@ -2161,7 +1944,8 @@ class MyForm(settingsmixin.SMainWindow): ).arg(email) ) return - status, addressVersionNumber, streamNumber = decodeAddress(toAddress)[:3] + status, addressVersionNumber, streamNumber, ripe = decodeAddress( + toAddress) if status != 'success': try: toAddress = unicode(toAddress, 'utf-8', 'ignore') @@ -2236,27 +2020,15 @@ class MyForm(settingsmixin.SMainWindow): toAddress = addBMIfNotPresent(toAddress) if addressVersionNumber > 4 or addressVersionNumber <= 1: - 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))) + 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: - 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))) + 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() - if state.statusIconColor == 'red': + if shared.statusIconColor == 'red': self.updateStatusBar(_translate( "MainWindow", "Warning: You are currently not connected." @@ -2264,9 +2036,29 @@ class MyForm(settingsmixin.SMainWindow): " send the message but it won\'t send until" " you connect.") ) - ackdata = helper_sent.insert( - toAddress=toAddress, fromAddress=fromAddress, - subject=subject, message=message, encoding=encoding) + stealthLevel = BMConfigParser().safeGetInt( + 'bitmessagesettings', 'ackstealthlevel') + ackdata = genAckPayload(streamNumber, stealthLevel) + t = () + sqlExecute( + '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', + '', + toAddress, + ripe, + fromAddress, + subject, + message, + ackdata, + int(time.time()), # sentTime (this will never change) + int(time.time()), # lastActionTime + 0, # sleepTill time. This will get set when the POW gets done. + 'msgqueued', + 0, # retryNumber + 'sent', # folder + encoding, # encodingtype + BMConfigParser().getint('bitmessagesettings', 'ttl') + ) + toLabel = '' queryreturn = sqlQuery('''select label from addressbook where address=?''', toAddress) @@ -2278,7 +2070,10 @@ class MyForm(settingsmixin.SMainWindow): toAddress, toLabel, fromAddress, subject, message, ackdata) queues.workerQueue.put(('sendmessage', toAddress)) - self.click_pushButtonClear() + self.ui.comboBoxSendFrom.setCurrentIndex(0) + self.ui.lineEditTo.setText('') + self.ui.lineEditSubject.setText('') + self.ui.textEditMessage.reset() if self.replyFromTab is not None: self.ui.tabWidget.setCurrentIndex(self.replyFromTab) self.replyFromTab = None @@ -2300,16 +2095,31 @@ class MyForm(settingsmixin.SMainWindow): # 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 = str_broadcast_subscribers - - # msgid. We don't know what this will be until the POW is done. - ackdata = helper_sent.insert( - fromAddress=fromAddress, - subject=subject, message=message, - status='broadcastqueued', encoding=encoding) + ripe = '' + t = ('', # msgid. We don't know what this will be until the POW is done. + toAddress, + ripe, + fromAddress, + subject, + message, + ackdata, + int(time.time()), # sentTime (this will never change) + int(time.time()), # lastActionTime + 0, # sleepTill time. This will get set when the POW gets done. + 'broadcastqueued', + 0, # retryNumber + 'sent', # folder + encoding, # encoding type + BMConfigParser().getint('bitmessagesettings', 'ttl') + ) + sqlExecute( + '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', *t) toLabel = str_broadcast_subscribers - + self.displayNewSentMessage( toAddress, toLabel, fromAddress, subject, message, ackdata) @@ -2338,8 +2148,9 @@ class MyForm(settingsmixin.SMainWindow): )) def click_pushButtonFetchNamecoinID(self): + nc = namecoinConnection() identities = str(self.ui.lineEditTo.text().toUtf8()).split(";") - err, addr = self.namecoin.query(identities[-1].strip()) + err, addr = nc.query(identities[-1].strip()) if err is not None: self.updateStatusBar( _translate("MainWindow", "Error: %1").arg(err)) @@ -2355,20 +2166,18 @@ class MyForm(settingsmixin.SMainWindow): self.ui.tabWidgetSend.setCurrentIndex( self.ui.tabWidgetSend.indexOf( self.ui.sendBroadcast - if config.safeGetBoolean(str(address), 'mailinglist') + if BMConfigParser().safeGetBoolean(str(address), 'mailinglist') else self.ui.sendDirect )) def rerenderComboBoxSendFrom(self): self.ui.comboBoxSendFrom.clear() - for addressInKeysFile in config.addresses(True): - # I realize that this is poor programming practice but I don't care. - # It's easier for others to read. - isEnabled = config.getboolean( - addressInKeysFile, 'enabled') - isMaillinglist = config.safeGetBoolean(addressInKeysFile, 'mailinglist') + for addressInKeysFile in getSortedAccounts(): + isEnabled = BMConfigParser().getboolean( + addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. + isMaillinglist = BMConfigParser().safeGetBoolean(addressInKeysFile, 'mailinglist') if isEnabled and not isMaillinglist: - label = unicode(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() + label = unicode(BMConfigParser().get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() if label == "": label = addressInKeysFile self.ui.comboBoxSendFrom.addItem(avatarize(addressInKeysFile), label, addressInKeysFile) @@ -2387,12 +2196,12 @@ class MyForm(settingsmixin.SMainWindow): def rerenderComboBoxSendFromBroadcast(self): self.ui.comboBoxSendFromBroadcast.clear() - for addressInKeysFile in config.addresses(True): - isEnabled = config.getboolean( - addressInKeysFile, 'enabled') - isChan = config.safeGetBoolean(addressInKeysFile, 'chan') + for addressInKeysFile in getSortedAccounts(): + isEnabled = BMConfigParser().getboolean( + addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. + isChan = BMConfigParser().safeGetBoolean(addressInKeysFile, 'chan') if isEnabled and not isChan: - label = unicode(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() + label = unicode(BMConfigParser().get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() if label == "": label = addressInKeysFile self.ui.comboBoxSendFromBroadcast.addItem(avatarize(addressInKeysFile), label, addressInKeysFile) @@ -2412,88 +2221,53 @@ class MyForm(settingsmixin.SMainWindow): # receives a message to an address that is acting as a # pseudo-mailing-list. The message will be broadcast out. This function # puts the message on the 'Sent' tab. - def displayNewSentMessage( - self, toAddress, toLabel, fromAddress, subject, - message, ackdata): + def displayNewSentMessage(self, toAddress, toLabel, fromAddress, subject, message, ackdata): acct = accountClass(fromAddress) acct.parseMessage(toAddress, fromAddress, subject, message) tab = -1 - for sent in ( - self.ui.tableWidgetInbox, - self.ui.tableWidgetInboxSubscriptions, - self.ui.tableWidgetInboxChans - ): + for sent in [self.ui.tableWidgetInbox, self.ui.tableWidgetInboxSubscriptions, self.ui.tableWidgetInboxChans]: tab += 1 if tab == 1: tab = 2 treeWidget = self.widgetConvert(sent) if self.getCurrentFolder(treeWidget) != "sent": continue - if treeWidget == self.ui.treeWidgetYourIdentities \ - and self.getCurrentAccount(treeWidget) not in ( - fromAddress, None, False): + if treeWidget == self.ui.treeWidgetYourIdentities and self.getCurrentAccount(treeWidget) != fromAddress: continue - elif treeWidget in ( - self.ui.treeWidgetSubscriptions, - self.ui.treeWidgetChans - ) and self.getCurrentAccount(treeWidget) != toAddress: + elif treeWidget in [self.ui.treeWidgetSubscriptions, self.ui.treeWidgetChans] and self.getCurrentAccount(treeWidget) != toAddress: continue - elif not helper_search.check_match( - toAddress, fromAddress, subject, message, - self.getCurrentSearchOption(tab), - self.getCurrentSearchLine(tab) - ): + elif not helper_search.check_match(toAddress, fromAddress, subject, message, self.getCurrentSearchOption(tab), self.getCurrentSearchLine(tab)): continue - - self.addMessageListItemSent( - sent, toAddress, fromAddress, subject, - "msgqueued", ackdata, time.time()) - self.getAccountTextedit(acct).setPlainText(message) + + self.addMessageListItemSent(sent, toAddress, fromAddress, subject, "msgqueued", ackdata, time.time()) + self.getAccountTextedit(acct).setPlainText(unicode(message, 'utf-8)', 'replace')) sent.setCurrentCell(0, 0) - def displayNewInboxMessage( - self, inventoryHash, toAddress, fromAddress, subject, message): - acct = accountClass( - fromAddress if toAddress == str_broadcast_subscribers - else toAddress - ) + def displayNewInboxMessage(self, inventoryHash, toAddress, fromAddress, subject, message): + if toAddress == str_broadcast_subscribers: + acct = accountClass(fromAddress) + else: + acct = accountClass(toAddress) inbox = self.getAccountMessagelist(acct) - ret = treeWidget = None + ret = None tab = -1 - for treeWidget in ( - self.ui.treeWidgetYourIdentities, - self.ui.treeWidgetSubscriptions, - self.ui.treeWidgetChans - ): + for treeWidget in [self.ui.treeWidgetYourIdentities, self.ui.treeWidgetSubscriptions, self.ui.treeWidgetChans]: tab += 1 if tab == 1: tab = 2 - if not helper_search.check_match( - toAddress, fromAddress, subject, message, - self.getCurrentSearchOption(tab), - self.getCurrentSearchLine(tab) - ): - continue tableWidget = self.widgetConvert(treeWidget) - current_account = self.getCurrentAccount(treeWidget) - current_folder = self.getCurrentFolder(treeWidget) - # pylint: disable=too-many-boolean-expressions - if ((tableWidget == inbox - and current_account == acct.address - and current_folder in ("inbox", None)) - or (treeWidget == self.ui.treeWidgetYourIdentities - and current_account is None - and current_folder in ("inbox", "new", None))): - ret = self.addMessageListItemInbox( - tableWidget, toAddress, fromAddress, subject, - inventoryHash, time.time(), False) - + if not helper_search.check_match(toAddress, fromAddress, subject, message, self.getCurrentSearchOption(tab), self.getCurrentSearchLine(tab)): + continue + if tableWidget == inbox and self.getCurrentAccount(treeWidget) == acct.address and self.getCurrentFolder(treeWidget) in ["inbox", None]: + ret = self.addMessageListItemInbox(inbox, "inbox", inventoryHash, toAddress, fromAddress, subject, time.time(), 0) + elif treeWidget == self.ui.treeWidgetYourIdentities and self.getCurrentAccount(treeWidget) is None and self.getCurrentFolder(treeWidget) in ["inbox", "new", None]: + ret = self.addMessageListItemInbox(tableWidget, "inbox", inventoryHash, toAddress, fromAddress, subject, time.time(), 0) if ret is None: acct.parseMessage(toAddress, fromAddress, subject, "") else: acct = ret - self.propagateUnreadCount(widget=treeWidget if ret else None) - if config.safeGetBoolean( + self.propagateUnreadCount(acct.address) + if BMConfigParser().getboolean( 'bitmessagesettings', 'showtraynotifications'): self.notifierShow( _translate("MainWindow", "New Message"), @@ -2501,22 +2275,16 @@ class MyForm(settingsmixin.SMainWindow): 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 irrespective of + 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.indicatorUpdate(True, to_label=acct.toLabel) - - try: - if acct.feedback != GatewayAccount.ALL_OK: - if acct.feedback == GatewayAccount.REGISTRATION_DENIED: - dialogs.EmailGatewayDialog( - self, config, acct).exec_() - # possible other branches? - except AttributeError: - pass + # cannot find item to pass here ): + if hasattr(acct, "feedback") \ + and acct.feedback != GatewayAccount.ALL_OK: + if acct.feedback == GatewayAccount.REGISTRATION_DENIED: + dialogs.EmailGatewayDialog( + self, BMConfigParser(), acct).exec_() def click_pushButtonAddAddressBook(self, dialog=None): if not dialog: @@ -2539,15 +2307,15 @@ class MyForm(settingsmixin.SMainWindow): )) return - if helper_addressbook.insert(label=label, address=address): - self.rerenderMessagelistFromLabels() - self.rerenderMessagelistToLabels() - self.rerenderAddressBook() - else: - self.updateStatusBar(_translate( - "MainWindow", - "Error: You cannot add your own address in the address book." - )) + 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): # This should be handled outside of this function, for error displaying @@ -2595,7 +2363,7 @@ class MyForm(settingsmixin.SMainWindow): )) def click_pushButtonStatusIcon(self): - dialogs.IconGlossaryDialog(self, config=config).exec_() + dialogs.IconGlossaryDialog(self, config=BMConfigParser()).exec_() def click_actionHelp(self): dialogs.HelpDialog(self).exec_() @@ -2607,25 +2375,230 @@ class MyForm(settingsmixin.SMainWindow): dialogs.AboutDialog(self).exec_() def click_actionSettings(self): - dialogs.SettingsDialog(self, firstrun=self._firstrun).exec_() + 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( + self.settingsDialogInstance.ui.checkBoxMinimizeToTray.isChecked())) + BMConfigParser().set('bitmessagesettings', 'trayonclose', str( + self.settingsDialogInstance.ui.checkBoxTrayOnClose.isChecked())) + BMConfigParser().set('bitmessagesettings', 'hidetrayconnectionnotifications', str( + self.settingsDialogInstance.ui.checkBoxHideTrayConnectionNotifications.isChecked())) + BMConfigParser().set('bitmessagesettings', 'showtraynotifications', str( + self.settingsDialogInstance.ui.checkBoxShowTrayNotifications.isChecked())) + BMConfigParser().set('bitmessagesettings', 'startintray', str( + self.settingsDialogInstance.ui.checkBoxStartInTray.isChecked())) + BMConfigParser().set('bitmessagesettings', 'willinglysendtomobile', str( + self.settingsDialogInstance.ui.checkBoxWillinglySendToMobile.isChecked())) + BMConfigParser().set('bitmessagesettings', 'useidenticons', str( + self.settingsDialogInstance.ui.checkBoxUseIdenticons.isChecked())) + BMConfigParser().set('bitmessagesettings', 'replybelow', str( + self.settingsDialogInstance.ui.checkBoxReplyBelow.isChecked())) + + lang = str(self.settingsDialogInstance.ui.languageComboBox.itemData(self.settingsDialogInstance.ui.languageComboBox.currentIndex()).toString()) + BMConfigParser().set('bitmessagesettings', 'userlocale', lang) + change_translation(l10n.getTranslationLanguage()) + + if int(BMConfigParser().get('bitmessagesettings', 'port')) != int(self.settingsDialogInstance.ui.lineEditTCPPort.text()): + if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect'): + 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())) + if self.settingsDialogInstance.ui.checkBoxUPnP.isChecked() != BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp'): + BMConfigParser().set('bitmessagesettings', 'upnp', str(self.settingsDialogInstance.ui.checkBoxUPnP.isChecked())) + if self.settingsDialogInstance.ui.checkBoxUPnP.isChecked(): + import upnp + upnpThread = upnp.uPnPThread() + upnpThread.start() + #print 'self.settingsDialogInstance.ui.comboBoxProxyType.currentText()', self.settingsDialogInstance.ui.comboBoxProxyType.currentText() + #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': + 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() + 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( + self.settingsDialogInstance.ui.comboBoxProxyType.currentText())) + else: + BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'none') + BMConfigParser().set('bitmessagesettings', 'socksauthentication', str( + self.settingsDialogInstance.ui.checkBoxAuthentication.isChecked())) + BMConfigParser().set('bitmessagesettings', 'sockshostname', str( + self.settingsDialogInstance.ui.lineEditSocksHostname.text())) + BMConfigParser().set('bitmessagesettings', 'socksport', str( + self.settingsDialogInstance.ui.lineEditSocksPort.text())) + BMConfigParser().set('bitmessagesettings', 'socksusername', str( + self.settingsDialogInstance.ui.lineEditSocksUsername.text())) + BMConfigParser().set('bitmessagesettings', 'sockspassword', str( + self.settingsDialogInstance.ui.lineEditSocksPassword.text())) + BMConfigParser().set('bitmessagesettings', 'sockslisten', str( + self.settingsDialogInstance.ui.checkBoxSocksListen.isChecked())) + try: + # Rounding to integers just for aesthetics + BMConfigParser().set('bitmessagesettings', 'maxdownloadrate', str( + int(float(self.settingsDialogInstance.ui.lineEditMaxDownloadRate.text())))) + BMConfigParser().set('bitmessagesettings', 'maxuploadrate', str( + int(float(self.settingsDialogInstance.ui.lineEditMaxUploadRate.text())))) + 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")) - def on_action_Send(self): - """Send message to current selected address""" - self.click_pushButtonClear() - account_item = self.getCurrentItem() - if not account_item: - return - self.ui.lineEditTo.setText(account_item.accountString()) - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.send) - ) + BMConfigParser().set('bitmessagesettings', 'maxoutboundconnections', str( + int(float(self.settingsDialogInstance.ui.lineEditMaxOutboundConnections.text())))) + + BMConfigParser().set('bitmessagesettings', 'namecoinrpctype', + self.settingsDialogInstance.getNamecoinType()) + BMConfigParser().set('bitmessagesettings', 'namecoinrpchost', str( + self.settingsDialogInstance.ui.lineEditNamecoinHost.text())) + BMConfigParser().set('bitmessagesettings', 'namecoinrpcport', str( + self.settingsDialogInstance.ui.lineEditNamecoinPort.text())) + BMConfigParser().set('bitmessagesettings', 'namecoinrpcuser', str( + self.settingsDialogInstance.ui.lineEditNamecoinUser.text())) + BMConfigParser().set('bitmessagesettings', 'namecoinrpcpassword', str( + self.settingsDialogInstance.ui.lineEditNamecoinPassword.text())) + + # Demanded difficulty tab + if float(self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) >= 1: + BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str(int(float( + self.settingsDialogInstance.ui.lineEditTotalDifficulty.text()) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) + if float(self.settingsDialogInstance.ui.lineEditSmallMessageDifficulty.text()) >= 1: + BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str(int(float( + self.settingsDialogInstance.ui.lineEditSmallMessageDifficulty.text()) * defaults.networkDefaultPayloadLengthExtraBytes))) + + if self.settingsDialogInstance.ui.comboBoxOpenCL.currentText().toUtf8() != BMConfigParser().safeGet("bitmessagesettings", "opencl"): + BMConfigParser().set('bitmessagesettings', 'opencl', str(self.settingsDialogInstance.ui.comboBoxOpenCL.currentText())) + queues.workerQueue.put(('resetPoW', '')) + + acceptableDifficultyChanged = False + + if float(self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) >= 1 or float(self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) == 0: + if BMConfigParser().get('bitmessagesettings','maxacceptablenoncetrialsperbyte') != str(int(float( + self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)): + # the user changed the max acceptable total difficulty + acceptableDifficultyChanged = True + BMConfigParser().set('bitmessagesettings', 'maxacceptablenoncetrialsperbyte', str(int(float( + self.settingsDialogInstance.ui.lineEditMaxAcceptableTotalDifficulty.text()) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) + if float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) >= 1 or float(self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) == 0: + if BMConfigParser().get('bitmessagesettings','maxacceptablepayloadlengthextrabytes') != str(int(float( + self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) * defaults.networkDefaultPayloadLengthExtraBytes)): + # the user changed the max acceptable small message difficulty + acceptableDifficultyChanged = True + BMConfigParser().set('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', str(int(float( + self.settingsDialogInstance.ui.lineEditMaxAcceptableSmallMessageDifficulty.text()) * defaults.networkDefaultPayloadLengthExtraBytes))) + if acceptableDifficultyChanged: + # It might now be possible to send msgs which were previously marked as toodifficult. + # Let us change them to 'msgqueued'. The singleWorker will try to send them and will again + # mark them as toodifficult if the receiver's required difficulty is still higher than + # we are willing to do. + sqlExecute('''UPDATE sent SET status='msgqueued' WHERE status='toodifficult' ''') + queues.workerQueue.put(('sendmessage', '')) + + #start:UI setting to stop trying to send messages after X days/months + # I'm open to changing this UI to something else if someone has a better idea. + if ((self.settingsDialogInstance.ui.lineEditDays.text()=='') and (self.settingsDialogInstance.ui.lineEditMonths.text()=='')):#We need to handle this special case. Bitmessage has its default behavior. The input is blank/blank + BMConfigParser().set('bitmessagesettings', 'stopresendingafterxdays', '') + BMConfigParser().set('bitmessagesettings', 'stopresendingafterxmonths', '') + shared.maximumLengthOfTimeToBotherResendingMessages = float('inf') + try: + float(self.settingsDialogInstance.ui.lineEditDays.text()) + lineEditDaysIsValidFloat = True + except: + lineEditDaysIsValidFloat = False + try: + float(self.settingsDialogInstance.ui.lineEditMonths.text()) + lineEditMonthsIsValidFloat = True + except: + lineEditMonthsIsValidFloat = False + if lineEditDaysIsValidFloat and not lineEditMonthsIsValidFloat: + self.settingsDialogInstance.ui.lineEditMonths.setText("0") + if lineEditMonthsIsValidFloat and not lineEditDaysIsValidFloat: + self.settingsDialogInstance.ui.lineEditDays.setText("0") + if lineEditDaysIsValidFloat or lineEditMonthsIsValidFloat: + 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. + 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') + shared.maximumLengthOfTimeToBotherResendingMessages = 0 + else: + BMConfigParser().set('bitmessagesettings', 'stopresendingafterxdays', str(float( + self.settingsDialogInstance.ui.lineEditDays.text()))) + BMConfigParser().set('bitmessagesettings', 'stopresendingafterxmonths', str(float( + self.settingsDialogInstance.ui.lineEditMonths.text()))) + + BMConfigParser().save() + + 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 = QtCore.QSettings(RUN_PATH, QtCore.QSettings.NativeFormat) + if BMConfigParser().getboolean('bitmessagesettings', 'startonlogon'): + self.settings.setValue("PyBitmessage", sys.argv[0]) + else: + self.settings.remove("PyBitmessage") + elif 'darwin' in sys.platform: + # startup for mac + pass + elif 'linux' in sys.platform: + # startup for linux + pass + + if state.appdata != paths.lookupExeFolder() and self.settingsDialogInstance.ui.checkBoxPortableMode.isChecked(): # If we are NOT using portable mode now but the user selected that we should... + # Write the keys.dat file to disk in the new location + sqlStoredProcedure('movemessagstoprog') + with open(paths.lookupExeFolder() + 'keys.dat', 'wb') as configfile: + BMConfigParser().write(configfile) + # Write the knownnodes.dat file to disk in the new location + knownnodes.saveKnownNodes(paths.lookupExeFolder()) + os.remove(state.appdata + 'keys.dat') + os.remove(state.appdata + 'knownnodes.dat') + previousAppdataLocation = state.appdata + state.appdata = paths.lookupExeFolder() + debug.restartLoggingInUpdatedAppdataLocation() + try: + os.remove(previousAppdataLocation + 'debug.log') + os.remove(previousAppdataLocation + 'debug.log.1') + except: + pass + + if state.appdata == paths.lookupExeFolder() and not self.settingsDialogInstance.ui.checkBoxPortableMode.isChecked(): # If we ARE using portable mode now but the user selected that we shouldn't... + state.appdata = paths.lookupAppdataFolder() + if not os.path.exists(state.appdata): + os.makedirs(state.appdata) + sqlStoredProcedure('movemessagstoappdata') + # Write the keys.dat file to disk in the new location + BMConfigParser().save() + # Write the knownnodes.dat file to disk in the new location + knownnodes.saveKnownNodes(state.appdata) + os.remove(paths.lookupExeFolder() + 'keys.dat') + os.remove(paths.lookupExeFolder() + 'knownnodes.dat') + debug.restartLoggingInUpdatedAppdataLocation() + try: + os.remove(paths.lookupExeFolder() + 'debug.log') + os.remove(paths.lookupExeFolder() + 'debug.log.1') + except: + pass def on_action_SpecialAddressBehaviorDialog(self): - """Show SpecialAddressBehaviorDialog""" - dialogs.SpecialAddressBehaviorDialog(self, config) + dialogs.SpecialAddressBehaviorDialog(self, BMConfigParser()) def on_action_EmailGatewayDialog(self): - dialog = dialogs.EmailGatewayDialog(self, config=config) + dialog = dialogs.EmailGatewayDialog(self, config=BMConfigParser()) # For Modal dialogs dialog.exec_() try: @@ -2663,58 +2636,58 @@ class MyForm(settingsmixin.SMainWindow): ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No ) != QtGui.QMessageBox.Yes: return + # addressAtCurrentRow = self.getCurrentAccount() tableWidget = self.getCurrentMessagelist() idCount = tableWidget.rowCount() if idCount == 0: return + font = QtGui.QFont() + font.setBold(False) + msgids = [] for i in range(0, idCount): - msgids.append(tableWidget.item(i, 3).data()) - for col in xrange(tableWidget.columnCount()): - tableWidget.item(i, col).setUnread(False) + msgids.append(str(tableWidget.item( + 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) markread = sqlExecuteChunked( - "UPDATE inbox SET read = 1 WHERE msgid IN({0}) AND read=0", - idCount, *msgids + "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) def click_NewAddressDialog(self): dialogs.NewAddressDialog(self) def network_switch(self): - dontconnect_option = not config.safeGetBoolean( + dontconnect_option = not BMConfigParser().safeGetBoolean( 'bitmessagesettings', 'dontconnect') - reply = QtGui.QMessageBox.question( - self, _translate("MainWindow", "Disconnecting") - if dontconnect_option else _translate("MainWindow", "Connecting"), - _translate( - "MainWindow", - "Bitmessage will now drop all connections. Are you sure?" - ) if dontconnect_option else _translate( - "MainWindow", - "Bitmessage will now start connecting to network. Are you sure?" - ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.Cancel, - QtGui.QMessageBox.Cancel) - if reply != QtGui.QMessageBox.Yes: - return - config.set( + BMConfigParser().set( 'bitmessagesettings', 'dontconnect', str(dontconnect_option)) - config.save() + BMConfigParser().save() self.ui.updateNetworkSwitchMenuLabel(dontconnect_option) - self.ui.pushButtonFetchNamecoinID.setHidden( - dontconnect_option or self.namecoin.test()[0] == 'failed' - ) - # Quit selected from menu or application indicator def quit(self): - """Quit the bitmessageqt application""" - if self.quitAccepted and not self.wait: + '''quit_msg = "Are you sure you want to exit Bitmessage?" + reply = QtGui.QMessageBox.question(self, 'Message', + quit_msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + + if reply is QtGui.QMessageBox.No: + return + ''' + + if self.quitAccepted: return self.show() @@ -2726,61 +2699,36 @@ class MyForm(settingsmixin.SMainWindow): waitForSync = False # C PoW currently doesn't support interrupting and OpenCL is untested - if getPowType() == "python" and (powQueueSize() > 0 or pendingUpload() > 0): - reply = QtGui.QMessageBox.question( - self, _translate("MainWindow", "Proof of work pending"), - _translate( - "MainWindow", - "%n object(s) pending proof of work", None, - QtCore.QCoreApplication.CodecForTr, powQueueSize() - ) + ", " + - _translate( - "MainWindow", - "%n object(s) waiting to be distributed", None, - QtCore.QCoreApplication.CodecForTr, pendingUpload() - ) + "\n\n" + - _translate( - "MainWindow", "Wait until these tasks finish?"), - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No - | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) + if getPowType() == "python" and (powQueueSize() > 0 or PendingUpload().len() > 0): + reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Proof of work pending"), + _translate("MainWindow", "%n object(s) pending proof of work", None, QtCore.QCoreApplication.CodecForTr, powQueueSize()) + ", " + + _translate("MainWindow", "%n object(s) waiting to be distributed", None, QtCore.QCoreApplication.CodecForTr, PendingUpload().len()) + "\n\n" + + _translate("MainWindow", "Wait until these tasks finish?"), + QtGui.QMessageBox.Yes|QtGui.QMessageBox.No|QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) if reply == QtGui.QMessageBox.No: waitForPow = False elif reply == QtGui.QMessageBox.Cancel: return - if pendingDownload() > 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() - ), - QtGui.QMessageBox.Yes | QtGui.QMessageBox.No - | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) + 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, PendingDownloadQueue.totalSize()), + QtGui.QMessageBox.Yes|QtGui.QMessageBox.No|QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) if reply == QtGui.QMessageBox.Yes: - self.wait = waitForSync = True + waitForSync = True elif reply == QtGui.QMessageBox.Cancel: return + else: + PendingDownloadQueue.stop() - if state.statusIconColor == 'red' and not config.safeGetBoolean( + 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) + 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) if reply == QtGui.QMessageBox.Yes: waitForConnection = True - self.wait = waitForSync = True + waitForSync = True elif reply == QtGui.QMessageBox.Cancel: return @@ -2792,19 +2740,17 @@ class MyForm(settingsmixin.SMainWindow): if waitForConnection: self.updateStatusBar(_translate( "MainWindow", "Waiting for network connection...")) - while state.statusIconColor == 'red': + while shared.statusIconColor == 'red': time.sleep(0.5) 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. + # 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.updateStatusBar(_translate( "MainWindow", "Waiting for finishing synchronisation...")) - while pendingDownload() > 0: + while PendingDownloadQueue.totalSize() > 0: time.sleep(0.5) QtCore.QCoreApplication.processEvents( QtCore.QEventLoop.AllEvents, 1000 @@ -2822,8 +2768,9 @@ class MyForm(settingsmixin.SMainWindow): if curWorkerQueue > 0: self.updateStatusBar(_translate( "MainWindow", "Waiting for PoW to finish... %1%" - ).arg(50 * (maxWorkerQueue - curWorkerQueue) / - maxWorkerQueue)) + ).arg(50 * (maxWorkerQueue - curWorkerQueue) + / maxWorkerQueue) + ) time.sleep(0.5) QtCore.QCoreApplication.processEvents( QtCore.QEventLoop.AllEvents, 1000 @@ -2845,17 +2792,19 @@ class MyForm(settingsmixin.SMainWindow): # check if upload (of objects created locally) pending self.updateStatusBar(_translate( "MainWindow", "Waiting for objects to be sent... %1%").arg(50)) - maxPendingUpload = max(1, pendingUpload()) - - while pendingUpload() > 1: - self.updateStatusBar(_translate( - "MainWindow", - "Waiting for objects to be sent... %1%" - ).arg(int(50 + 20 * (pendingUpload() / maxPendingUpload)))) - time.sleep(0.5) - QtCore.QCoreApplication.processEvents( - QtCore.QEventLoop.AllEvents, 1000 - ) + try: + while PendingUpload().progress() < 1: + 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 + ) + except PendingUploadDeadlineException: + pass QtCore.QCoreApplication.processEvents( QtCore.QEventLoop.AllEvents, 1000 @@ -2884,30 +2833,37 @@ class MyForm(settingsmixin.SMainWindow): QtCore.QEventLoop.AllEvents, 1000 ) shutdown.doCleanShutdown() - self.updateStatusBar(_translate( "MainWindow", "Stopping notifications... %1%").arg(90)) self.tray.hide() self.updateStatusBar(_translate( "MainWindow", "Shutdown imminent... %1%").arg(100)) - + shared.thisapp.cleanup() logger.info("Shutdown complete") - self.close() - # FIXME: rewrite loops with timer instead - if self.wait: - self.destroy() - app.quit() + super(MyForm, myapp).close() + # return + os._exit(0) + # window close event def closeEvent(self, event): - """window close event""" + self.appIndicatorHide() + trayonclose = False + + try: + trayonclose = BMConfigParser().getboolean( + 'bitmessagesettings', 'trayonclose') + except Exception: + pass + + # always ignore, it shuts down by itself + if self.quitAccepted: + event.accept() + return + event.ignore() - trayonclose = config.safeGetBoolean( - 'bitmessagesettings', 'trayonclose') - if trayonclose: - self.appIndicatorHide() - else: - # custom quit method + if not trayonclose: + # quit the application self.quit() def on_action_InboxMessageForceHtml(self): @@ -2946,7 +2902,8 @@ class MyForm(settingsmixin.SMainWindow): # modified = 0 for row in tableWidget.selectedIndexes(): currentRow = row.row() - msgid = tableWidget.item(currentRow, 3).data() + msgid = str(tableWidget.item( + currentRow, 3).data(QtCore.Qt.UserRole).toPyObject()) msgids.add(msgid) # if not tableWidget.item(currentRow, 0).unread: # modified += 1 @@ -2961,24 +2918,26 @@ class MyForm(settingsmixin.SMainWindow): ) self.propagateUnreadCount() - # 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. - # tableWidget.clearSelection() manages to mark the message - # as read again. + # 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. + # tableWidget.clearSelection() manages to mark the message as read again. # Format predefined text on message reply. def quoted_text(self, message): - if not config.safeGetBoolean('bitmessagesettings', 'replybelow'): - return '\n\n------------------------------------------------------\n' + message - - quoteWrapper = textwrap.TextWrapper( - replace_whitespace=False, initial_indent='> ', - subsequent_indent='> ', break_long_words=False, - break_on_hyphens=False) + if not BMConfigParser().safeGetBoolean('bitmessagesettings', 'replybelow'): + return '\n\n------------------------------------------------------\n' + message + quoteWrapper = textwrap.TextWrapper(replace_whitespace = False, + initial_indent = '> ', + subsequent_indent = '> ', + break_long_words = False, + break_on_hyphens = False) def quote_line(line): # Do quote empty lines. if line == '' or line.isspace(): @@ -2991,91 +2950,65 @@ class MyForm(settingsmixin.SMainWindow): return quoteWrapper.fill(line) return '\n'.join([quote_line(l) for l in message.splitlines()]) + '\n\n' - def setSendFromComboBox(self, address=None): + def setSendFromComboBox(self, address = None): if address is None: messagelist = self.getCurrentMessagelist() - if not messagelist: - return - currentInboxRow = messagelist.currentRow() - address = messagelist.item(currentInboxRow, 0).address - for box in ( - self.ui.comboBoxSendFrom, self.ui.comboBoxSendFromBroadcast - ): - for i in range(box.count()): - if str(box.itemData(i).toPyObject()) == address: - box.setCurrentIndex(i) - break + if messagelist: + currentInboxRow = messagelist.currentRow() + address = messagelist.item( + currentInboxRow, 0).address + for box in [self.ui.comboBoxSendFrom, self.ui.comboBoxSendFromBroadcast]: + listOfAddressesInComboBoxSendFrom = [str(box.itemData(i).toPyObject()) for i in range(box.count())] + if address in listOfAddressesInComboBoxSendFrom: + currentIndex = listOfAddressesInComboBoxSendFrom.index(address) + box.setCurrentIndex(currentIndex) else: box.setCurrentIndex(0) def on_action_InboxReplyChan(self): self.on_action_InboxReply(self.REPLY_TYPE_CHAN) - - def on_action_SentReply(self): - self.on_action_InboxReply(self.REPLY_TYPE_UPD) - - def on_action_InboxReply(self, reply_type=None): - """Handle any reply action depending on reply_type""" - # pylint: disable=too-many-locals + + def on_action_InboxReply(self, replyType = None): tableWidget = self.getCurrentMessagelist() if not tableWidget: return - - if reply_type is None: - reply_type = self.REPLY_TYPE_SENDER - + + if replyType is None: + replyType = self.REPLY_TYPE_SENDER + # save this to return back after reply is done self.replyFromTab = self.ui.tabWidget.currentIndex() - - column_to = 1 if reply_type == self.REPLY_TYPE_UPD else 0 - column_from = 0 if reply_type == self.REPLY_TYPE_UPD else 1 - + currentInboxRow = tableWidget.currentRow() toAddressAtCurrentInboxRow = tableWidget.item( - currentInboxRow, column_to).address + currentInboxRow, 0).address acct = accountClass(toAddressAtCurrentInboxRow) fromAddressAtCurrentInboxRow = tableWidget.item( - currentInboxRow, column_from).address - msgid = tableWidget.item(currentInboxRow, 3).data() + currentInboxRow, 1).address + msgid = str(tableWidget.item( + currentInboxRow, 3).data(QtCore.Qt.UserRole).toPyObject()) queryreturn = sqlQuery( - "SELECT message FROM inbox WHERE msgid=?", msgid - ) or sqlQuery("SELECT message FROM sent WHERE ackdata=?", msgid) + '''select message from inbox where msgid=?''', msgid) if queryreturn != []: for row in queryreturn: messageAtCurrentInboxRow, = row - acct.parseMessage( - toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow, - tableWidget.item(currentInboxRow, 2).subject, - messageAtCurrentInboxRow) + acct.parseMessage(toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow, tableWidget.item(currentInboxRow, 2).subject, messageAtCurrentInboxRow) widget = { 'subject': self.ui.lineEditSubject, 'from': self.ui.comboBoxSendFrom, 'message': self.ui.textEditMessage } - if toAddressAtCurrentInboxRow == str_broadcast_subscribers: self.ui.tabWidgetSend.setCurrentIndex( self.ui.tabWidgetSend.indexOf(self.ui.sendDirect) ) # toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow - elif not config.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), QtGui.QMessageBox.Ok) - elif not config.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." - ), QtGui.QMessageBox.Ok) + 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), 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."), QtGui.QMessageBox.Ok) else: self.setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(toAddressAtCurrentInboxRow) broadcast_tab_index = self.ui.tabWidgetSend.indexOf( @@ -3089,42 +3022,28 @@ class MyForm(settingsmixin.SMainWindow): } self.ui.tabWidgetSend.setCurrentIndex(broadcast_tab_index) toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow - if fromAddressAtCurrentInboxRow == \ - tableWidget.item(currentInboxRow, column_from).label or ( - isinstance(acct, GatewayAccount) and - fromAddressAtCurrentInboxRow == acct.relayAddress): + if fromAddressAtCurrentInboxRow == tableWidget.item(currentInboxRow, 1).label or ( + isinstance(acct, GatewayAccount) and fromAddressAtCurrentInboxRow == acct.relayAddress): self.ui.lineEditTo.setText(str(acct.fromAddress)) else: - self.ui.lineEditTo.setText( - tableWidget.item(currentInboxRow, column_from).accountString() - ) - - # If the previous message was to a chan then we should send our - # reply to the chan rather than to the particular person who sent - # the message. - if acct.type == AccountMixin.CHAN and reply_type == self.REPLY_TYPE_CHAN: - logger.debug( - 'Original sent to a chan. Setting the to address in the' - ' reply to the chan address.') - if toAddressAtCurrentInboxRow == \ - tableWidget.item(currentInboxRow, column_to).label: + self.ui.lineEditTo.setText(tableWidget.item(currentInboxRow, 1).label + " <" + str(acct.fromAddress) + ">") + + # If the previous message was to a chan then we should send our reply to the chan rather than to the particular person who sent the message. + if acct.type == AccountMixin.CHAN and replyType == self.REPLY_TYPE_CHAN: + logger.debug('original sent to a chan. Setting the to address in the reply to the chan address.') + if toAddressAtCurrentInboxRow == tableWidget.item(currentInboxRow, 0).label: self.ui.lineEditTo.setText(str(toAddressAtCurrentInboxRow)) else: - self.ui.lineEditTo.setText( - tableWidget.item(currentInboxRow, column_to).accountString() - ) - + self.ui.lineEditTo.setText(tableWidget.item(currentInboxRow, 0).label + " <" + str(acct.toAddress) + ">") + self.setSendFromComboBox(toAddressAtCurrentInboxRow) - - quotedText = self.quoted_text( - unicode(messageAtCurrentInboxRow, 'utf-8', 'replace')) + + quotedText = self.quoted_text(unicode(messageAtCurrentInboxRow, 'utf-8', 'replace')) widget['message'].setPlainText(quotedText) - if acct.subject[0:3] in ('Re:', 'RE:'): - widget['subject'].setText( - tableWidget.item(currentInboxRow, 2).label) + if acct.subject[0:3] in ['Re:', 'RE:']: + widget['subject'].setText(tableWidget.item(currentInboxRow, 2).label) else: - widget['subject'].setText( - 'Re: ' + tableWidget.item(currentInboxRow, 2).label) + widget['subject'].setText('Re: ' + tableWidget.item(currentInboxRow, 2).label) self.ui.tabWidget.setCurrentIndex( self.ui.tabWidget.indexOf(self.ui.send) ) @@ -3135,6 +3054,7 @@ class MyForm(settingsmixin.SMainWindow): if not tableWidget: return currentInboxRow = tableWidget.currentRow() + # tableWidget.item(currentRow,1).data(Qt.UserRole).toPyObject() addressAtCurrentInboxRow = tableWidget.item( currentInboxRow, 1).data(QtCore.Qt.UserRole) self.ui.tabWidget.setCurrentIndex( @@ -3148,6 +3068,7 @@ class MyForm(settingsmixin.SMainWindow): if not tableWidget: return currentInboxRow = tableWidget.currentRow() + # tableWidget.item(currentRow,1).data(Qt.UserRole).toPyObject() addressAtCurrentInboxRow = tableWidget.item( currentInboxRow, 1).data(QtCore.Qt.UserRole) recipientAddress = tableWidget.item( @@ -3156,8 +3077,7 @@ class MyForm(settingsmixin.SMainWindow): queryreturn = sqlQuery('''select * from blacklist where address=?''', addressAtCurrentInboxRow) if queryreturn == []: - label = "\"" + tableWidget.item(currentInboxRow, 2).subject + "\" in " + config.get( - recipientAddress, "label") + label = "\"" + tableWidget.item(currentInboxRow, 2).subject + "\" in " + BMConfigParser().get(recipientAddress, "label") sqlExecute('''INSERT INTO blacklist VALUES (?,?, ?)''', label, addressAtCurrentInboxRow, True) @@ -3172,28 +3092,23 @@ class MyForm(settingsmixin.SMainWindow): "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 - ): + def deleteRowFromMessagelist(self, row = None, inventoryHash = None, ackData = None, messageLists = None): if messageLists is None: - messageLists = ( - self.ui.tableWidgetInbox, - self.ui.tableWidgetInboxChans, - self.ui.tableWidgetInboxSubscriptions - ) + messageLists = (self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans, self.ui.tableWidgetInboxSubscriptions) elif type(messageLists) not in (list, tuple): - messageLists = (messageLists,) + messageLists = (messageLists) for messageList in messageLists: if row is not None: - inventoryHash = messageList.item(row, 3).data() + 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() == 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() == ackData: + if messageList.item(i, 3).data(QtCore.Qt.UserRole).toPyObject() == ackData: messageList.removeRow(i) # Send item on the Inbox tab to trash @@ -3203,30 +3118,32 @@ class MyForm(settingsmixin.SMainWindow): return currentRow = 0 folder = self.getCurrentFolder() - shifted = QtGui.QApplication.queryKeyboardModifiers() \ - & QtCore.Qt.ShiftModifier - tableWidget.setUpdatesEnabled(False) - inventoryHashesToTrash = set() + shifted = QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier + 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): - inventoryHashesToTrash.add( - tableWidget.item(r.topRow() + i, 3).data()) + 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.model().removeRows( - r.topRow(), r.bottomRow() - r.topRow() + 1) + tableWidget.model().removeRows(r.topRow(), r.bottomRow()-r.topRow()+1) idCount = len(inventoryHashesToTrash) - sqlExecuteChunked( - ("DELETE FROM inbox" if folder == "trash" or shifted else - "UPDATE inbox SET folder='trash', read=1") + - " WHERE msgid IN ({0})", idCount, *inventoryHashesToTrash) + if folder == "trash" or shifted: + sqlExecuteChunked('''DELETE FROM inbox WHERE msgid IN ({0})''', + idCount, *inventoryHashesToTrash) + else: + 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(folder) - self.updateStatusBar(_translate("MainWindow", "Moved items to trash.")) + self.propagateUnreadCount(self.getCurrentAccount, folder) + self.updateStatusBar(_translate( + "MainWindow", "Moved items to trash.")) def on_action_TrashUndelete(self): tableWidget = self.getCurrentMessagelist() @@ -3234,26 +3151,28 @@ class MyForm(settingsmixin.SMainWindow): return currentRow = 0 tableWidget.setUpdatesEnabled(False) - inventoryHashesToTrash = set() + 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): - inventoryHashesToTrash.add( - tableWidget.item(r.topRow() + i, 3).data()) + 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.model().removeRows( - r.topRow(), r.bottomRow() - r.topRow() + 1) - tableWidget.selectRow(0 if currentRow == 0 else currentRow - 1) + 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) + 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.propagateUnreadCount(self.getCurrentAccount) self.updateStatusBar(_translate("MainWindow", "Undeleted item.")) def on_action_InboxSaveMessageAs(self): @@ -3268,7 +3187,8 @@ class MyForm(settingsmixin.SMainWindow): subjectAtCurrentInboxRow = '' # Retrieve the message data out of the SQL database - msgid = tableWidget.item(currentInboxRow, 3).data() + msgid = str(tableWidget.item( + currentInboxRow, 3).data(QtCore.Qt.UserRole).toPyObject()) queryreturn = sqlQuery( '''select message from inbox where msgid=?''', msgid) if queryreturn != []: @@ -3276,11 +3196,7 @@ class MyForm(settingsmixin.SMainWindow): message, = row defaultFilename = "".join(x for x in subjectAtCurrentInboxRow if x.isalnum()) + '.txt' - filename = QtGui.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: @@ -3293,19 +3209,23 @@ class MyForm(settingsmixin.SMainWindow): # Send item on the Sent tab to trash def on_action_SentTrash(self): + currentRow = 0 + unread = False tableWidget = self.getCurrentMessagelist() if not tableWidget: return folder = self.getCurrentFolder() - shifted = QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier + shifted = (QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier) > 0 while tableWidget.selectedIndexes() != []: currentRow = tableWidget.selectedIndexes()[0].row() - ackdataToTrash = tableWidget.item(currentRow, 3).data() - sqlExecute( - "DELETE FROM sent" if folder == "trash" or shifted else - "UPDATE sent SET folder='trash'" - " WHERE ackdata = ?", ackdataToTrash - ) + ackdataToTrash = str(tableWidget.item( + 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(QtCore.Qt.UserRole), folder, self.getCurrentTreeWidget(), -1) self.getCurrentMessageTextedit().setPlainText("") tableWidget.removeRow(currentRow) self.updateStatusBar(_translate( @@ -3344,57 +3264,73 @@ class MyForm(settingsmixin.SMainWindow): while self.ui.tableWidgetAddressBook.selectedIndexes() != []: currentRow = self.ui.tableWidgetAddressBook.selectedIndexes()[ 0].row() - item = self.ui.tableWidgetAddressBook.item(currentRow, 0) - sqlExecute( - 'DELETE FROM addressbook WHERE address=?', item.address) + labelAtCurrentRow = self.ui.tableWidgetAddressBook.item( + currentRow, 0).text().toUtf8() + addressAtCurrentRow = self.ui.tableWidgetAddressBook.item( + currentRow, 1).text() + sqlExecute('''DELETE FROM addressbook WHERE label=? AND address=?''', + str(labelAtCurrentRow), str(addressAtCurrentRow)) self.ui.tableWidgetAddressBook.removeRow(currentRow) - self.rerenderMessagelistFromLabels() - self.rerenderMessagelistToLabels() + self.rerenderMessagelistFromLabels() + self.rerenderMessagelistToLabels() def on_action_AddressBookClipboard(self): - addresses_string = '' - for item in self.getAddressbookSelectedItems(): - if addresses_string == '': - addresses_string = item.address + fullStringOfAddresses = '' + listOfSelectedRows = {} + for i in range(len(self.ui.tableWidgetAddressBook.selectedIndexes())): + listOfSelectedRows[ + self.ui.tableWidgetAddressBook.selectedIndexes()[i].row()] = 0 + for currentRow in listOfSelectedRows: + addressAtCurrentRow = self.ui.tableWidgetAddressBook.item( + currentRow, 1).text() + if fullStringOfAddresses == '': + fullStringOfAddresses = addressAtCurrentRow else: - addresses_string += ', ' + item.address + fullStringOfAddresses += ', ' + str(addressAtCurrentRow) clipboard = QtGui.QApplication.clipboard() - clipboard.setText(addresses_string) + clipboard.setText(fullStringOfAddresses) def on_action_AddressBookSend(self): - selected_items = self.getAddressbookSelectedItems() - - if not selected_items: # FIXME: impossible - return self.updateStatusBar(_translate( - "MainWindow", "No addresses selected.")) - - addresses_string = unicode( - self.ui.lineEditTo.text().toUtf8(), 'utf-8') - for item in selected_items: - address_string = item.accountString() - if not addresses_string: - addresses_string = address_string + listOfSelectedRows = {} + for i in range(len(self.ui.tableWidgetAddressBook.selectedIndexes())): + listOfSelectedRows[ + self.ui.tableWidgetAddressBook.selectedIndexes()[i].row()] = 0 + for currentRow in listOfSelectedRows: + addressAtCurrentRow = self.ui.tableWidgetAddressBook.item( + currentRow, 0).address + labelAtCurrentRow = self.ui.tableWidgetAddressBook.item( + currentRow, 0).label + stringToAdd = labelAtCurrentRow + " <" + addressAtCurrentRow + ">" + if self.ui.lineEditTo.text() == '': + self.ui.lineEditTo.setText(stringToAdd) else: - addresses_string += '; ' + address_string - - self.ui.lineEditTo.setText(addresses_string) - self.statusbar.clearMessage() - self.ui.tabWidget.setCurrentIndex( - self.ui.tabWidget.indexOf(self.ui.send) - ) + self.ui.lineEditTo.setText(unicode( + self.ui.lineEditTo.text().toUtf8(), encoding="UTF-8") + '; ' + stringToAdd) + if listOfSelectedRows == {}: + self.updateStatusBar(_translate( + "MainWindow", "No addresses selected.")) + else: + self.statusbar.clearMessage() + self.ui.tabWidget.setCurrentIndex( + self.ui.tabWidget.indexOf(self.ui.send) + ) def on_action_AddressBookSubscribe(self): - for item in self.getAddressbookSelectedItems(): - # Then subscribe to it... - # provided it's not already in the address book - if shared.isAddressInMySubscriptionsList(item.address): + listOfSelectedRows = {} + for i in range(len(self.ui.tableWidgetAddressBook.selectedIndexes())): + listOfSelectedRows[self.ui.tableWidgetAddressBook.selectedIndexes()[i].row()] = 0 + for currentRow in listOfSelectedRows: + 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.updateStatusBar(_translate( "MainWindow", "Error: You cannot add the same address to your" " subscriptions twice. Perhaps rename the existing" " one if you want.")) continue - self.addSubscription(item.address, item.label) + labelAtCurrentRow = self.ui.tableWidgetAddressBook.item(currentRow,0).text().toUtf8() + self.addSubscription(addressAtCurrentRow, labelAtCurrentRow) self.ui.tabWidget.setCurrentIndex( self.ui.tabWidget.indexOf(self.ui.subscriptions) ) @@ -3409,19 +3345,15 @@ class MyForm(settingsmixin.SMainWindow): self.popMenuAddressBook.addSeparator() self.popMenuAddressBook.addAction(self.actionAddressBookNew) normal = True - selected_items = self.getAddressbookSelectedItems() - for item in selected_items: - if item.type != AccountMixin.NORMAL: + for row in self.ui.tableWidgetAddressBook.selectedIndexes(): + currentRow = row.row() + type = self.ui.tableWidgetAddressBook.item( + currentRow, 0).type + if type != AccountMixin.NORMAL: normal = False - break if normal: # only if all selected addressbook items are normal, allow delete self.popMenuAddressBook.addAction(self.actionAddressBookDelete) - if len(selected_items) == 1: - self._contact_selected = selected_items.pop() - self.popMenuAddressBook.addSeparator() - for plugin in self.menu_plugins['address']: - self.popMenuAddressBook.addAction(plugin) self.popMenuAddressBook.exec_( self.ui.tableWidgetAddressBook.mapToGlobal(point)) @@ -3491,22 +3423,12 @@ class MyForm(settingsmixin.SMainWindow): self.popMenuSubscriptions.addAction(self.actionsubscriptionsSetAvatar) self.popMenuSubscriptions.addSeparator() self.popMenuSubscriptions.addAction(self.actionsubscriptionsClipboard) - self.popMenuSubscriptions.addAction(self.actionsubscriptionsSend) self.popMenuSubscriptions.addSeparator() - - self._contact_selected = currentItem - # preloaded gui.menu plugins with prefix 'address' - for plugin in self.menu_plugins['address']: - self.popMenuSubscriptions.addAction(plugin) - self.popMenuSubscriptions.addSeparator() - if self.getCurrentFolder() != 'sent': - self.popMenuSubscriptions.addAction(self.actionMarkAllRead) - if self.popMenuSubscriptions.isEmpty(): - return + self.popMenuSubscriptions.addAction(self.actionMarkAllRead) self.popMenuSubscriptions.exec_( self.ui.treeWidgetSubscriptions.mapToGlobal(point)) - def widgetConvert(self, widget): + def widgetConvert (self, widget): if widget == self.ui.tableWidgetInbox: return self.ui.treeWidgetYourIdentities elif widget == self.ui.tableWidgetInboxSubscriptions: @@ -3523,13 +3445,13 @@ class MyForm(settingsmixin.SMainWindow): return None def getCurrentTreeWidget(self): - currentIndex = self.ui.tabWidget.currentIndex() - treeWidgetList = ( + currentIndex = self.ui.tabWidget.currentIndex(); + treeWidgetList = [ self.ui.treeWidgetYourIdentities, False, self.ui.treeWidgetSubscriptions, self.ui.treeWidgetChans - ) + ] if currentIndex >= 0 and currentIndex < len(treeWidgetList): return treeWidgetList[currentIndex] else: @@ -3547,16 +3469,18 @@ class MyForm(settingsmixin.SMainWindow): return self.ui.treeWidgetYourIdentities def getCurrentMessagelist(self): - currentIndex = self.ui.tabWidget.currentIndex() - messagelistList = ( + currentIndex = self.ui.tabWidget.currentIndex(); + messagelistList = [ self.ui.tableWidgetInbox, False, self.ui.tableWidgetInboxSubscriptions, self.ui.tableWidgetInboxChans, - ) + ] if currentIndex >= 0 and currentIndex < len(messagelistList): return messagelistList[currentIndex] - + else: + return False + def getAccountMessagelist(self, account): try: if account.type == AccountMixin.CHAN: @@ -3573,18 +3497,24 @@ class MyForm(settingsmixin.SMainWindow): if messagelist: currentRow = messagelist.currentRow() if currentRow >= 0: - return messagelist.item(currentRow, 3).data() + msgid = str(messagelist.item( + 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() - messagelistList = ( + messagelistList = [ self.ui.textEditInboxMessage, False, self.ui.textEditInboxMessageSubscriptions, self.ui.textEditInboxMessageChans, - ) + ] if currentIndex >= 0 and currentIndex < len(messagelistList): return messagelistList[currentIndex] + else: + return False def getAccountTextedit(self, account): try: @@ -3600,63 +3530,73 @@ class MyForm(settingsmixin.SMainWindow): def getCurrentSearchLine(self, currentIndex=None, retObj=False): if currentIndex is None: currentIndex = self.ui.tabWidget.currentIndex() - messagelistList = ( + messagelistList = [ self.ui.inboxSearchLineEdit, False, self.ui.inboxSearchLineEditSubscriptions, self.ui.inboxSearchLineEditChans, - ) + ] if currentIndex >= 0 and currentIndex < len(messagelistList): - return ( - messagelistList[currentIndex] if retObj - else messagelistList[currentIndex].text().toUtf8().data()) + if retObj: + return messagelistList[currentIndex] + else: + return messagelistList[currentIndex].text().toUtf8().data() + else: + return None def getCurrentSearchOption(self, currentIndex=None): if currentIndex is None: currentIndex = self.ui.tabWidget.currentIndex() - messagelistList = ( + messagelistList = [ self.ui.inboxSearchOption, False, self.ui.inboxSearchOptionSubscriptions, self.ui.inboxSearchOptionChans, - ) + ] if currentIndex >= 0 and currentIndex < len(messagelistList): - return messagelistList[currentIndex].currentText() + return messagelistList[currentIndex].currentText().toUtf8().data() + else: + return None # Group of functions for the Your Identities dialog box def getCurrentItem(self, treeWidget=None): if treeWidget is None: treeWidget = self.getCurrentTreeWidget() if treeWidget: - return treeWidget.currentItem() - + currentItem = treeWidget.currentItem() + if currentItem: + return currentItem + return False + def getCurrentAccount(self, treeWidget=None): currentItem = self.getCurrentItem(treeWidget) if currentItem: - return currentItem.address + account = currentItem.address + return account + else: + # TODO need debug msg? + return False def getCurrentFolder(self, treeWidget=None): - currentItem = self.getCurrentItem(treeWidget) - try: - return currentItem.folderName - except AttributeError: - pass + if treeWidget is None: + treeWidget = self.getCurrentTreeWidget() + #treeWidget = self.ui.treeWidgetYourIdentities + if treeWidget: + currentItem = treeWidget.currentItem() + if currentItem and hasattr(currentItem, 'folderName'): + return currentItem.folderName + else: + return None def setCurrentItemColor(self, color): - currentItem = self.getCurrentItem() - if currentItem: + treeWidget = self.getCurrentTreeWidget() + if treeWidget: brush = QtGui.QBrush() brush.setStyle(QtCore.Qt.NoBrush) brush.setColor(color) + currentItem = treeWidget.currentItem() currentItem.setForeground(0, brush) - def getAddressbookSelectedItems(self): - return [ - self.ui.tableWidgetAddressBook.item(i.row(), 0) - for i in self.ui.tableWidgetAddressBook.selectedIndexes() - if i.column() == 0 - ] - def on_action_YourIdentitiesNew(self): self.click_NewAddressDialog() @@ -3678,12 +3618,12 @@ class MyForm(settingsmixin.SMainWindow): " delete the channel?" ), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No ) == QtGui.QMessageBox.Yes: - config.remove_section(str(account.address)) + BMConfigParser().remove_section(str(account.address)) else: return else: return - config.save() + BMConfigParser().save() shared.reloadMyAddressHashes() self.rerenderAddressBook() self.rerenderComboBoxSendFrom() @@ -3699,8 +3639,8 @@ class MyForm(settingsmixin.SMainWindow): account.setEnabled(True) def enableIdentity(self, address): - config.set(address, 'enabled', 'true') - config.save() + BMConfigParser().set(address, 'enabled', 'true') + BMConfigParser().save() shared.reloadMyAddressHashes() self.rerenderAddressBook() @@ -3711,8 +3651,8 @@ class MyForm(settingsmixin.SMainWindow): account.setEnabled(False) def disableIdentity(self, address): - config.set(str(address), 'enabled', 'false') - config.save() + BMConfigParser().set(str(address), 'enabled', 'false') + BMConfigParser().save() shared.reloadMyAddressHashes() self.rerenderAddressBook() @@ -3725,11 +3665,12 @@ class MyForm(settingsmixin.SMainWindow): tableWidget = self.getCurrentMessagelist() currentColumn = tableWidget.currentColumn() currentRow = tableWidget.currentRow() - currentFolder = self.getCurrentFolder() - if currentColumn not in (0, 1, 2): # to, from, subject - currentColumn = 0 if currentFolder == "sent" else 1 - - if currentFolder == "sent": + if currentColumn not in [0, 1, 2]: # to, from, subject + if self.getCurrentFolder() == "sent": + currentColumn = 0 + else: + currentColumn = 1 + if self.getCurrentFolder() == "sent": myAddress = tableWidget.item(currentRow, 1).data(QtCore.Qt.UserRole) otherAddress = tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole) else: @@ -3737,23 +3678,23 @@ class MyForm(settingsmixin.SMainWindow): 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")): + (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(QtCore.Qt.UserRole) - + text = unicode(str(text), 'utf-8', 'ignore') clipboard = QtGui.QApplication.clipboard() clipboard.setText(text) - # set avatar functions + #set avatar functions def on_action_TreeWidgetSetAvatar(self): address = self.getCurrentAccount() self.setAvatar(address) def on_action_AddressBookSetAvatar(self): self.on_action_SetAvatar(self.ui.tableWidgetAddressBook) - + def on_action_SetAvatar(self, thisTableWidget): currentRow = thisTableWidget.currentRow() addressAtCurrentRow = thisTableWidget.item( @@ -3763,36 +3704,19 @@ class MyForm(settingsmixin.SMainWindow): thisTableWidget.item( currentRow, 0).setIcon(avatarize(addressAtCurrentRow)) - # TODO: reuse utils def setAvatar(self, addressAtCurrentRow): if not os.path.exists(state.appdata + 'avatars/'): os.makedirs(state.appdata + 'avatars/') hash = hashlib.md5(addBMIfNotPresent(addressAtCurrentRow)).hexdigest() - extensions = [ - 'PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', - 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] - - names = { - 'BMP': 'Windows Bitmap', - 'GIF': 'Graphic Interchange Format', - 'JPG': 'Joint Photographic Experts Group', - 'JPEG': 'Joint Photographic Experts Group', - 'MNG': 'Multiple-image Network Graphics', - 'PNG': 'Portable Network Graphics', - 'PBM': 'Portable Bitmap', - 'PGM': 'Portable Graymap', - 'PPM': 'Portable Pixmap', - 'TIFF': 'Tagged Image File Format', - 'XBM': 'X11 Bitmap', - 'XPM': 'X11 Pixmap', - 'SVG': 'Scalable Vector Graphics', - 'TGA': 'Targa Image Format'} + extensions = ['PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] + # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats + names = {'BMP':'Windows Bitmap', 'GIF':'Graphic Interchange Format', 'JPG':'Joint Photographic Experts Group', 'JPEG':'Joint Photographic Experts Group', 'MNG':'Multiple-image Network Graphics', 'PNG':'Portable Network Graphics', 'PBM':'Portable Bitmap', 'PGM':'Portable Graymap', 'PPM':'Portable Pixmap', 'TIFF':'Tagged Image File Format', 'XBM':'X11 Bitmap', 'XPM':'X11 Pixmap', 'SVG':'Scalable Vector Graphics', 'TGA':'Targa Image Format'} filters = [] all_images_filter = [] current_files = [] for ext in extensions: - filters += [names[ext] + ' (*.' + ext.lower() + ')'] - all_images_filter += ['*.' + ext.lower()] + filters += [ names[ext] + ' (*.' + ext.lower() + ')' ] + all_images_filter += [ '*.' + ext.lower() ] upper = state.appdata + 'avatars/' + hash + '.' + ext.upper() lower = state.appdata + 'avatars/' + hash + '.' + ext.lower() if os.path.isfile(lower): @@ -3803,34 +3727,28 @@ class MyForm(settingsmixin.SMainWindow): filters[1:1] = ['All files (*.*)'] sourcefile = QtGui.QFileDialog.getOpenFileName( self, _translate("MainWindow", "Set avatar..."), - filter=';;'.join(filters) + 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) if sourcefile == '': # ask for removal of avatar - if exists | (len(current_files) > 0): - displayMsg = _translate( - "MainWindow", "Do you really want to remove this avatar?") + if exists | (len(current_files)>0): + displayMsg = _translate("MainWindow", "Do you really want to remove this avatar?") overwrite = QtGui.QMessageBox.question( - self, 'Message', displayMsg, - QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) else: overwrite = QtGui.QMessageBox.No else: # ask whether to overwrite old avatar - if exists | (len(current_files) > 0): - displayMsg = _translate( - "MainWindow", - "You have already set an avatar for this address." - " Do you really want to overwrite it?") + if exists | (len(current_files)>0): + displayMsg = _translate("MainWindow", "You have already set an avatar for this address. Do you really want to overwrite it?") overwrite = QtGui.QMessageBox.question( - self, 'Message', displayMsg, - QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) else: overwrite = QtGui.QMessageBox.No - + # copy the image file to the appdata folder if (not exists) | (overwrite == QtGui.QMessageBox.Yes): if overwrite == QtGui.QMessageBox.Yes: @@ -3916,16 +3834,13 @@ class MyForm(settingsmixin.SMainWindow): self.popMenuYourIdentities.addAction(self.actionSpecialAddressBehaviorYourIdentities) self.popMenuYourIdentities.addAction(self.actionEmailGateway) self.popMenuYourIdentities.addSeparator() - if currentItem.type != AccountMixin.ALL: - self._contact_selected = currentItem - # preloaded gui.menu plugins with prefix 'address' - for plugin in self.menu_plugins['address']: - self.popMenuYourIdentities.addAction(plugin) - self.popMenuYourIdentities.addSeparator() - if self.getCurrentFolder() != 'sent': - self.popMenuYourIdentities.addAction(self.actionMarkAllRead) - if self.popMenuYourIdentities.isEmpty(): - return + 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)) @@ -3937,86 +3852,65 @@ class MyForm(settingsmixin.SMainWindow): self.popMenu.addAction(self.actionNew) self.popMenu.addAction(self.actionDelete) self.popMenu.addSeparator() + self.popMenu.addAction(self.actionClipboard) + self.popMenu.addSeparator() if currentItem.isEnabled: self.popMenu.addAction(self.actionDisable) else: self.popMenu.addAction(self.actionEnable) self.popMenu.addAction(self.actionSetAvatar) self.popMenu.addSeparator() - self.popMenu.addAction(self.actionClipboard) - self.popMenu.addAction(self.actionSend) - self.popMenu.addSeparator() - self._contact_selected = currentItem - # preloaded gui.menu plugins with prefix 'address' - for plugin in self.menu_plugins['address']: - self.popMenu.addAction(plugin) - self.popMenu.addSeparator() - if self.getCurrentFolder() != 'sent': - self.popMenu.addAction(self.actionMarkAllRead) - if self.popMenu.isEmpty(): - return + self.popMenu.addAction(self.actionMarkAllRead) self.popMenu.exec_( self.ui.treeWidgetChans.mapToGlobal(point)) def on_context_menuInbox(self, point): tableWidget = self.getCurrentMessagelist() - if not tableWidget: - return - - currentFolder = self.getCurrentFolder() - if currentFolder == 'sent': - self.on_context_menuSent(point) - return - - self.popMenuInbox = QtGui.QMenu(self) - self.popMenuInbox.addAction(self.actionForceHtml) - self.popMenuInbox.addAction(self.actionMarkUnread) - self.popMenuInbox.addSeparator() - currentRow = tableWidget.currentRow() - account = accountClass( - tableWidget.item(currentRow, 0).data(QtCore.Qt.UserRole)) - - if account.type == AccountMixin.CHAN: - self.popMenuInbox.addAction(self.actionReplyChan) - self.popMenuInbox.addAction(self.actionReply) - self.popMenuInbox.addAction(self.actionAddSenderToAddressBook) - self.actionClipboardMessagelist = self.ui.inboxContextMenuToolbar.addAction( - _translate("MainWindow", "Copy subject to clipboard") - if tableWidget.currentColumn() == 2 else - _translate("MainWindow", "Copy address to clipboard"), - self.on_action_ClipboardMessagelist) - self.popMenuInbox.addAction(self.actionClipboardMessagelist) - # pylint: disable=no-member - self._contact_selected = tableWidget.item(currentRow, 1) - # preloaded gui.menu plugins with prefix 'address' - for plugin in self.menu_plugins['address']: - self.popMenuInbox.addAction(plugin) - self.popMenuInbox.addSeparator() - self.popMenuInbox.addAction(self.actionAddSenderToBlackList) - self.popMenuInbox.addSeparator() - self.popMenuInbox.addAction(self.actionSaveMessageAs) - if currentFolder == "trash": - self.popMenuInbox.addAction(self.actionUndeleteTrashedMessage) - else: - self.popMenuInbox.addAction(self.actionTrashInboxMessage) - self.popMenuInbox.exec_(tableWidget.mapToGlobal(point)) + if tableWidget: + currentFolder = self.getCurrentFolder() + if currentFolder is None: + pass + if currentFolder == 'sent': + self.on_context_menuSent(point) + else: + self.popMenuInbox = QtGui.QMenu(self) + self.popMenuInbox.addAction(self.actionForceHtml) + self.popMenuInbox.addAction(self.actionMarkUnread) + self.popMenuInbox.addSeparator() + address = tableWidget.item( + tableWidget.currentRow(), 0).data(QtCore.Qt.UserRole) + account = accountClass(address) + if account.type == AccountMixin.CHAN: + self.popMenuInbox.addAction(self.actionReplyChan) + self.popMenuInbox.addAction(self.actionReply) + self.popMenuInbox.addAction(self.actionAddSenderToAddressBook) + self.actionClipboardMessagelist = self.ui.inboxContextMenuToolbar.addAction( + _translate("MainWindow", + "Copy subject to clipboard" if tableWidget.currentColumn() == 2 else "Copy address to clipboard" + ), + self.on_action_ClipboardMessagelist) + self.popMenuInbox.addAction(self.actionClipboardMessagelist) + self.popMenuInbox.addSeparator() + self.popMenuInbox.addAction(self.actionAddSenderToBlackList) + self.popMenuInbox.addSeparator() + self.popMenuInbox.addAction(self.actionSaveMessageAs) + if currentFolder == "trash": + self.popMenuInbox.addAction(self.actionUndeleteTrashedMessage) + else: + self.popMenuInbox.addAction(self.actionTrashInboxMessage) + self.popMenuInbox.exec_(tableWidget.mapToGlobal(point)) def on_context_menuSent(self, point): - currentRow = self.ui.tableWidgetInbox.currentRow() self.popMenuSent = QtGui.QMenu(self) self.popMenuSent.addAction(self.actionSentClipboard) - self._contact_selected = self.ui.tableWidgetInbox.item(currentRow, 0) - # preloaded gui.menu plugins with prefix 'address' - for plugin in self.menu_plugins['address']: - self.popMenuSent.addAction(plugin) - self.popMenuSent.addSeparator() self.popMenuSent.addAction(self.actionTrashSentMessage) - self.popMenuSent.addAction(self.actionSentReply) # Check to see if this item is toodifficult and display an additional # menu option (Force Send) if it is. + currentRow = self.ui.tableWidgetInbox.currentRow() if currentRow >= 0: - ackData = self.ui.tableWidgetInbox.item(currentRow, 3).data() + ackData = str(self.ui.tableWidgetInbox.item( + currentRow, 3).data(QtCore.Qt.UserRole).toPyObject()) queryreturn = sqlQuery('''SELECT status FROM sent where ackdata=?''', ackData) for row in queryreturn: status, = row @@ -4027,55 +3921,51 @@ class MyForm(settingsmixin.SMainWindow): def inboxSearchLineEditUpdated(self, text): # dynamic search for too short text is slow - text = text.toUtf8() - if 0 < len(text) < 3: + if len(str(text)) < 3: return messagelist = self.getCurrentMessagelist() + searchOption = self.getCurrentSearchOption() if messagelist: - searchOption = self.getCurrentSearchOption() account = self.getCurrentAccount() folder = self.getCurrentFolder() - self.loadMessagelist( - messagelist, account, folder, searchOption, text) + self.loadMessagelist(messagelist, account, folder, searchOption, str(text)) def inboxSearchLineEditReturnPressed(self): logger.debug("Search return pressed") searchLine = self.getCurrentSearchLine() messagelist = self.getCurrentMessagelist() - if messagelist and len(str(searchLine)) < 3: + if len(str(searchLine)) < 3: searchOption = self.getCurrentSearchOption() account = self.getCurrentAccount() folder = self.getCurrentFolder() - self.loadMessagelist( - messagelist, account, folder, searchOption, searchLine) + self.loadMessagelist(messagelist, account, folder, searchOption, searchLine) + if messagelist: messagelist.setFocus() def treeWidgetItemClicked(self): - messagelist = self.getCurrentMessagelist() - if not messagelist: - return + searchLine = self.getCurrentSearchLine() + searchOption = self.getCurrentSearchOption() messageTextedit = self.getCurrentMessageTextedit() if messageTextedit: - messageTextedit.setPlainText("") - account = self.getCurrentAccount() - folder = self.getCurrentFolder() - # refresh count indicator - self.propagateUnreadCount(folder) - self.loadMessagelist( - messagelist, account, folder, - self.getCurrentSearchOption(), self.getCurrentSearchLine()) + messageTextedit.setPlainText(QtCore.QString("")) + messagelist = self.getCurrentMessagelist() + if messagelist: + account = self.getCurrentAccount() + folder = self.getCurrentFolder() + treeWidget = self.getCurrentTreeWidget() + # refresh count indicator + self.propagateUnreadCount(account.address if hasattr(account, 'address') else None, folder, treeWidget, 0) + self.loadMessagelist(messagelist, account, folder, searchOption, searchLine) def treeWidgetItemChanged(self, item, column): # only for manual edits. automatic edits (setText) are ignored if column != 0: return # only account names of normal addresses (no chans/mailinglists) - if (not isinstance(item, Ui_AddressWidget)) or \ - (not self.getCurrentTreeWidget()) or \ - self.getCurrentTreeWidget().currentItem() is None: + if (not isinstance(item, Ui_AddressWidget)) or (not self.getCurrentTreeWidget()) or self.getCurrentTreeWidget().currentItem() is None: return # not visible - if (not self.getCurrentItem()) or (not isinstance(self.getCurrentItem(), Ui_AddressWidget)): + if (not self.getCurrentItem()) or (not isinstance (self.getCurrentItem(), Ui_AddressWidget)): return # only currently selected item if item.address != self.getCurrentAccount(): @@ -4083,7 +3973,7 @@ class MyForm(settingsmixin.SMainWindow): # "All accounts" can't be renamed if item.type == AccountMixin.ALL: return - + newLabel = unicode(item.text(0), 'utf-8', 'ignore') oldLabel = item.defaultLabel() @@ -4108,12 +3998,12 @@ class MyForm(settingsmixin.SMainWindow): self.recurDepth -= 1 def tableWidgetInboxItemClicked(self): + folder = self.getCurrentFolder() messageTextedit = self.getCurrentMessageTextedit() if not messageTextedit: return msgid = self.getCurrentMessageId() - folder = self.getCurrentFolder() if msgid: queryreturn = sqlQuery( '''SELECT message FROM %s WHERE %s=?''' % ( @@ -4174,15 +4064,12 @@ class MyForm(settingsmixin.SMainWindow): self.rerenderAddressBook() def updateStatusBar(self, data): - try: - message, option = data - except ValueError: + if type(data) is tuple or type(data) is list: + option = data[1] + message = data[0] + else: option = 0 message = data - except TypeError: - logger.debug( - 'Invalid argument for updateStatusBar!', exc_info=True) - if message != "": logger.info('Status bar: ' + message) @@ -4191,23 +4078,10 @@ class MyForm(settingsmixin.SMainWindow): else: self.statusbar.showMessage(message, 10000) - def resetNamecoinConnection(self): - namecoin.ensureNamecoinOptions() - self.namecoin = namecoin.namecoinConnection() - - # Check to see whether we can connect to namecoin. - # Hide the 'Fetch Namecoin ID' button if we can't. - if config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect' - ) or self.namecoin.test()[0] == 'failed': - logger.warning( - 'There was a problem testing for a Namecoin daemon.' - ' Hiding the Fetch Namecoin ID button') - self.ui.pushButtonFetchNamecoinID.hide() - else: - self.ui.pushButtonFetchNamecoinID.show() - def initSettings(self): + QtCore.QCoreApplication.setOrganizationName("PyBitmessage") + QtCore.QCoreApplication.setOrganizationDomain("bitmessage.org") + QtCore.QCoreApplication.setApplicationName("pybitmessageqt") self.loadSettings() for attr, obj in self.ui.__dict__.iteritems(): if hasattr(obj, "__class__") and \ @@ -4217,11 +4091,253 @@ class MyForm(settingsmixin.SMainWindow): obj.loadSettings() +class settingsDialog(QtGui.QDialog): + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_settingsDialog() + self.ui.setupUi(self) + self.parent = parent + self.ui.checkBoxStartOnLogon.setChecked( + BMConfigParser().getboolean('bitmessagesettings', 'startonlogon')) + self.ui.checkBoxMinimizeToTray.setChecked( + BMConfigParser().getboolean('bitmessagesettings', 'minimizetotray')) + self.ui.checkBoxTrayOnClose.setChecked( + BMConfigParser().safeGetBoolean('bitmessagesettings', 'trayonclose')) + self.ui.checkBoxHideTrayConnectionNotifications.setChecked( + BMConfigParser().getboolean("bitmessagesettings", "hidetrayconnectionnotifications")) + self.ui.checkBoxShowTrayNotifications.setChecked( + BMConfigParser().getboolean('bitmessagesettings', 'showtraynotifications')) + self.ui.checkBoxStartInTray.setChecked( + BMConfigParser().getboolean('bitmessagesettings', 'startintray')) + self.ui.checkBoxWillinglySendToMobile.setChecked( + BMConfigParser().safeGetBoolean('bitmessagesettings', 'willinglysendtomobile')) + self.ui.checkBoxUseIdenticons.setChecked( + BMConfigParser().safeGetBoolean('bitmessagesettings', 'useidenticons')) + self.ui.checkBoxReplyBelow.setChecked( + BMConfigParser().safeGetBoolean('bitmessagesettings', 'replybelow')) + + if state.appdata == paths.lookupExeFolder(): + self.ui.checkBoxPortableMode.setChecked(True) + else: + try: + import tempfile + tempfile.NamedTemporaryFile( + dir=paths.lookupExeFolder(), delete=True + ).close() # should autodelete + except: + self.ui.checkBoxPortableMode.setDisabled(True) + + if 'darwin' in sys.platform: + self.ui.checkBoxStartOnLogon.setDisabled(True) + self.ui.checkBoxStartOnLogon.setText(_translate( + "MainWindow", "Start-on-login not yet supported on your OS.")) + self.ui.checkBoxMinimizeToTray.setDisabled(True) + self.ui.checkBoxMinimizeToTray.setText(_translate( + "MainWindow", "Minimize-to-tray not yet supported on your OS.")) + self.ui.checkBoxShowTrayNotifications.setDisabled(True) + self.ui.checkBoxShowTrayNotifications.setText(_translate( + "MainWindow", "Tray notifications not yet supported on your OS.")) + elif 'linux' in sys.platform: + self.ui.checkBoxStartOnLogon.setDisabled(True) + self.ui.checkBoxStartOnLogon.setText(_translate( + "MainWindow", "Start-on-login not yet supported on your OS.")) + # On the Network settings tab: + self.ui.lineEditTCPPort.setText(str( + BMConfigParser().get('bitmessagesettings', 'port'))) + self.ui.checkBoxUPnP.setChecked( + BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp')) + self.ui.checkBoxAuthentication.setChecked(BMConfigParser().getboolean( + 'bitmessagesettings', 'socksauthentication')) + self.ui.checkBoxSocksListen.setChecked(BMConfigParser().getboolean( + 'bitmessagesettings', 'sockslisten')) + if str(BMConfigParser().get('bitmessagesettings', 'socksproxytype')) == 'none': + self.ui.comboBoxProxyType.setCurrentIndex(0) + self.ui.lineEditSocksHostname.setEnabled(False) + self.ui.lineEditSocksPort.setEnabled(False) + self.ui.lineEditSocksUsername.setEnabled(False) + self.ui.lineEditSocksPassword.setEnabled(False) + self.ui.checkBoxAuthentication.setEnabled(False) + self.ui.checkBoxSocksListen.setEnabled(False) + elif str(BMConfigParser().get('bitmessagesettings', 'socksproxytype')) == 'SOCKS4a': + self.ui.comboBoxProxyType.setCurrentIndex(1) + elif str(BMConfigParser().get('bitmessagesettings', 'socksproxytype')) == 'SOCKS5': + self.ui.comboBoxProxyType.setCurrentIndex(2) + + self.ui.lineEditSocksHostname.setText(str( + BMConfigParser().get('bitmessagesettings', 'sockshostname'))) + self.ui.lineEditSocksPort.setText(str( + BMConfigParser().get('bitmessagesettings', 'socksport'))) + self.ui.lineEditSocksUsername.setText(str( + BMConfigParser().get('bitmessagesettings', 'socksusername'))) + self.ui.lineEditSocksPassword.setText(str( + BMConfigParser().get('bitmessagesettings', 'sockspassword'))) + QtCore.QObject.connect(self.ui.comboBoxProxyType, QtCore.SIGNAL( + "currentIndexChanged(int)"), self.comboBoxProxyTypeChanged) + self.ui.lineEditMaxDownloadRate.setText(str( + BMConfigParser().get('bitmessagesettings', 'maxdownloadrate'))) + self.ui.lineEditMaxUploadRate.setText(str( + BMConfigParser().get('bitmessagesettings', 'maxuploadrate'))) + self.ui.lineEditMaxOutboundConnections.setText(str( + BMConfigParser().get('bitmessagesettings', 'maxoutboundconnections'))) + + # Demanded difficulty tab + self.ui.lineEditTotalDifficulty.setText(str((float(BMConfigParser().getint( + 'bitmessagesettings', 'defaultnoncetrialsperbyte')) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) + self.ui.lineEditSmallMessageDifficulty.setText(str((float(BMConfigParser().getint( + 'bitmessagesettings', 'defaultpayloadlengthextrabytes')) / defaults.networkDefaultPayloadLengthExtraBytes))) + + # Max acceptable difficulty tab + self.ui.lineEditMaxAcceptableTotalDifficulty.setText(str((float(BMConfigParser().getint( + 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte')) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) + self.ui.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float(BMConfigParser().getint( + 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes')) / defaults.networkDefaultPayloadLengthExtraBytes))) + + # OpenCL + if openclpow.openclAvailable(): + self.ui.comboBoxOpenCL.setEnabled(True) + else: + self.ui.comboBoxOpenCL.setEnabled(False) + self.ui.comboBoxOpenCL.clear() + self.ui.comboBoxOpenCL.addItem("None") + self.ui.comboBoxOpenCL.addItems(openclpow.vendors) + self.ui.comboBoxOpenCL.setCurrentIndex(0) + for i in range(self.ui.comboBoxOpenCL.count()): + if self.ui.comboBoxOpenCL.itemText(i) == BMConfigParser().safeGet('bitmessagesettings', 'opencl'): + self.ui.comboBoxOpenCL.setCurrentIndex(i) + break + + # Namecoin integration tab + nmctype = BMConfigParser().get('bitmessagesettings', 'namecoinrpctype') + self.ui.lineEditNamecoinHost.setText(str( + BMConfigParser().get('bitmessagesettings', 'namecoinrpchost'))) + self.ui.lineEditNamecoinPort.setText(str( + BMConfigParser().get('bitmessagesettings', 'namecoinrpcport'))) + self.ui.lineEditNamecoinUser.setText(str( + BMConfigParser().get('bitmessagesettings', 'namecoinrpcuser'))) + self.ui.lineEditNamecoinPassword.setText(str( + BMConfigParser().get('bitmessagesettings', 'namecoinrpcpassword'))) + + if nmctype == "namecoind": + self.ui.radioButtonNamecoinNamecoind.setChecked(True) + elif nmctype == "nmcontrol": + self.ui.radioButtonNamecoinNmcontrol.setChecked(True) + self.ui.lineEditNamecoinUser.setEnabled(False) + self.ui.labelNamecoinUser.setEnabled(False) + self.ui.lineEditNamecoinPassword.setEnabled(False) + self.ui.labelNamecoinPassword.setEnabled(False) + else: + assert False + + QtCore.QObject.connect(self.ui.radioButtonNamecoinNamecoind, QtCore.SIGNAL( + "toggled(bool)"), self.namecoinTypeChanged) + QtCore.QObject.connect(self.ui.radioButtonNamecoinNmcontrol, QtCore.SIGNAL( + "toggled(bool)"), self.namecoinTypeChanged) + QtCore.QObject.connect(self.ui.pushButtonNamecoinTest, QtCore.SIGNAL( + "clicked()"), self.click_pushButtonNamecoinTest) + + #Message Resend tab + self.ui.lineEditDays.setText(str( + BMConfigParser().get('bitmessagesettings', 'stopresendingafterxdays'))) + self.ui.lineEditMonths.setText(str( + BMConfigParser().get('bitmessagesettings', 'stopresendingafterxmonths'))) + + + #'System' tab removed for now. + """try: + maxCores = BMConfigParser().getint('bitmessagesettings', 'maxcores') + except: + maxCores = 99999 + if maxCores <= 1: + self.ui.comboBoxMaxCores.setCurrentIndex(0) + elif maxCores == 2: + self.ui.comboBoxMaxCores.setCurrentIndex(1) + elif maxCores <= 4: + self.ui.comboBoxMaxCores.setCurrentIndex(2) + elif maxCores <= 8: + self.ui.comboBoxMaxCores.setCurrentIndex(3) + elif maxCores <= 16: + self.ui.comboBoxMaxCores.setCurrentIndex(4) + else: + self.ui.comboBoxMaxCores.setCurrentIndex(5)""" + + QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + + def comboBoxProxyTypeChanged(self, comboBoxIndex): + if comboBoxIndex == 0: + self.ui.lineEditSocksHostname.setEnabled(False) + self.ui.lineEditSocksPort.setEnabled(False) + self.ui.lineEditSocksUsername.setEnabled(False) + self.ui.lineEditSocksPassword.setEnabled(False) + self.ui.checkBoxAuthentication.setEnabled(False) + self.ui.checkBoxSocksListen.setEnabled(False) + elif comboBoxIndex == 1 or comboBoxIndex == 2: + self.ui.lineEditSocksHostname.setEnabled(True) + self.ui.lineEditSocksPort.setEnabled(True) + self.ui.checkBoxAuthentication.setEnabled(True) + self.ui.checkBoxSocksListen.setEnabled(True) + if self.ui.checkBoxAuthentication.isChecked(): + self.ui.lineEditSocksUsername.setEnabled(True) + self.ui.lineEditSocksPassword.setEnabled(True) + + # Check status of namecoin integration radio buttons and translate + # it to a string as in the options. + def getNamecoinType(self): + if self.ui.radioButtonNamecoinNamecoind.isChecked(): + return "namecoind" + if self.ui.radioButtonNamecoinNmcontrol.isChecked(): + return "nmcontrol" + assert False + + # Namecoin connection type was changed. + def namecoinTypeChanged(self, checked): + nmctype = self.getNamecoinType() + assert nmctype == "namecoind" or nmctype == "nmcontrol" + + isNamecoind = (nmctype == "namecoind") + self.ui.lineEditNamecoinUser.setEnabled(isNamecoind) + self.ui.labelNamecoinUser.setEnabled(isNamecoind) + self.ui.lineEditNamecoinPassword.setEnabled(isNamecoind) + self.ui.labelNamecoinPassword.setEnabled(isNamecoind) + + if isNamecoind: + self.ui.lineEditNamecoinPort.setText(defaults.namecoinDefaultRpcPort) + else: + self.ui.lineEditNamecoinPort.setText("9000") + + # Test the namecoin settings specified in the settings dialog. + def click_pushButtonNamecoinTest(self): + self.ui.labelNamecoinTestResult.setText(_translate( + "MainWindow", "Testing...")) + options = {} + options["type"] = self.getNamecoinType() + options["host"] = str(self.ui.lineEditNamecoinHost.text().toUtf8()) + options["port"] = str(self.ui.lineEditNamecoinPort.text().toUtf8()) + options["user"] = str(self.ui.lineEditNamecoinUser.text().toUtf8()) + options["password"] = str(self.ui.lineEditNamecoinPassword.text().toUtf8()) + nc = namecoinConnection(options) + response = nc.test() + responseStatus = response[0] + responseText = response[1] + self.ui.labelNamecoinTestResult.setText(responseText) + if responseStatus== 'success': + self.parent.ui.pushButtonFetchNamecoinID.show() + + +# 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(QtGui.QTableWidgetItem): + + def __lt__(self, other): + return int(self.data(33).toPyObject()) < int(other.data(33).toPyObject()) + + app = None myapp = None -class BitmessageQtApplication(QtGui.QApplication): +class MySingleApplication(QtGui.QApplication): """ Listener to allow our Qt form to get focus when another instance of the application is open. @@ -4234,12 +4350,8 @@ class BitmessageQtApplication(QtGui.QApplication): uuid = '6ec0149b-96e1-4be1-93ab-1465fb3ebf7c' def __init__(self, *argv): - super(BitmessageQtApplication, self).__init__(*argv) - id = BitmessageQtApplication.uuid - - QtCore.QCoreApplication.setOrganizationName("PyBitmessage") - QtCore.QCoreApplication.setOrganizationDomain("bitmessage.org") - QtCore.QCoreApplication.setApplicationName("pybitmessageqt") + super(MySingleApplication, self).__init__(*argv) + id = MySingleApplication.uuid self.server = None self.is_running = False @@ -4267,8 +4379,6 @@ class BitmessageQtApplication(QtGui.QApplication): self.server.listen(id) self.server.newConnection.connect(self.on_new_connection) - self.setStyleSheet("QStatusBar::item { border: 0px solid black }") - def __del__(self): if self.server: self.server.close() @@ -4281,30 +4391,35 @@ class BitmessageQtApplication(QtGui.QApplication): def init(): global app if not app: - app = BitmessageQtApplication(sys.argv) + app = MySingleApplication(sys.argv) return app def run(): global myapp app = init() + change_translation(l10n.getTranslationLanguage()) + app.setStyleSheet("QStatusBar::item { border: 0px solid black }") myapp = MyForm() + myapp.sqlInit() myapp.appIndicatorInit(app) - + myapp.indicatorInit() + myapp.notifierInit() + myapp._firstrun = BMConfigParser().safeGetBoolean( + 'bitmessagesettings', 'dontconnect') if myapp._firstrun: myapp.showConnectDialog() # ask the user if we may connect + myapp.ui.updateNetworkSwitchMenuLabel() # try: -# if config.get('bitmessagesettings', 'mailchuck') < 1: -# myapp.showMigrationWizard(config.get('bitmessagesettings', 'mailchuck')) +# if BMConfigParser().get('bitmessagesettings', 'mailchuck') < 1: +# myapp.showMigrationWizard(BMConfigParser().get('bitmessagesettings', 'mailchuck')) # except: # myapp.showMigrationWizard(0) - + # only show after wizards and connect dialogs have completed - if not config.getboolean('bitmessagesettings', 'startintray'): + if not BMConfigParser().getboolean('bitmessagesettings', 'startintray'): myapp.show() - QtCore.QTimer.singleShot( - 30000, lambda: myapp.setStatusIcon(state.statusIconColor)) - app.exec_() + sys.exit(app.exec_()) diff --git a/src/bitmessageqt/about.ui b/src/bitmessageqt/about.ui index 49bd4eca..8ec7159c 100644 --- a/src/bitmessageqt/about.ui +++ b/src/bitmessageqt/about.ui @@ -46,7 +46,7 @@ - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2012-2022 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> Qt::AlignLeft @@ -66,7 +66,7 @@ - <html><head/><body><p>Distributed under the MIT/X11 software license; see <a href="https://www.opensource.org/licenses/mit-license.php"><span style=" text-decoration: underline; color:#0000ff;">https://www.opensource.org/licenses/mit-license.php</span></a></p></body></html> + <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 diff --git a/src/bitmessageqt/account.py b/src/bitmessageqt/account.py index 8c82c6f6..92d497f8 100644 --- a/src/bitmessageqt/account.py +++ b/src/bitmessageqt/account.py @@ -1,39 +1,26 @@ -# pylint: disable=too-many-instance-attributes,attribute-defined-outside-init -""" -account.py -========== - -Account related functions. - -""" - -from __future__ import absolute_import - -import inspect -import re -import sys -import time - -from PyQt4 import QtGui +from PyQt4 import QtCore, QtGui import queues -from addresses import decodeAddress -from bmconfigparser import config +import re +import sys +import inspect +from helper_sql import * from helper_ackPayload import genAckPayload -from helper_sql import sqlQuery, sqlExecute -from .foldertree import AccountMixin -from .utils import str_broadcast_subscribers +from addresses import decodeAddress +from bmconfigparser import BMConfigParser +from foldertree import AccountMixin +from pyelliptic.openssl import OpenSSL +from utils import str_broadcast_subscribers +import time +def getSortedAccounts(): + 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()) + ) + return configSections -def getSortedSubscriptions(count=False): - """ - Actually return a grouped dictionary rather than a sorted list - - :param count: Whether to count messages for each fromaddress in the inbox - :type count: bool, default False - :retuns: dict keys are addresses, values are dicts containing settings - :rtype: dict, default {} - """ +def getSortedSubscriptions(count = False): queryreturn = sqlQuery('SELECT label, address, enabled FROM subscriptions ORDER BY label COLLATE NOCASE ASC') ret = {} for row in queryreturn: @@ -50,7 +37,7 @@ def getSortedSubscriptions(count=False): GROUP BY inbox.fromaddress, folder''', str_broadcast_subscribers) for row in queryreturn: address, folder, cnt = row - if folder not in ret[address]: + if not folder in ret[address]: ret[address][folder] = { 'label': ret[address]['inbox']['label'], 'enabled': ret[address]['inbox']['enabled'] @@ -58,11 +45,9 @@ def getSortedSubscriptions(count=False): ret[address][folder]['count'] = cnt return ret - def accountClass(address): - """Return a BMAccount for the address""" - if not config.has_section(address): - # .. todo:: This BROADCAST section makes no sense + if not BMConfigParser().has_section(address): + # FIXME: This BROADCAST section makes no sense if address == str_broadcast_subscribers: subscription = BroadcastAccount(address) if subscription.type != AccountMixin.BROADCAST: @@ -74,8 +59,9 @@ def accountClass(address): return NoAccount(address) return subscription try: - gateway = config.get(address, "gateway") - for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): + gateway = BMConfigParser().get(address, "gateway") + for name, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): +# obj = g(address) if issubclass(cls, GatewayAccount) and cls.gatewayName == gateway: return cls(address) # general gateway @@ -84,40 +70,35 @@ def accountClass(address): pass # no gateway return BMAccount(address) - - -class AccountColor(AccountMixin): # pylint: disable=too-few-public-methods - """Set the type of account""" - - def __init__(self, address, address_type=None): + +class AccountColor(AccountMixin): + def __init__(self, address, type = None): self.isEnabled = True self.address = address - if address_type is None: + if type is None: if address is None: self.type = AccountMixin.ALL - elif config.safeGetBoolean(self.address, 'mailinglist'): + elif BMConfigParser().safeGetBoolean(self.address, 'mailinglist'): self.type = AccountMixin.MAILINGLIST - elif config.safeGetBoolean(self.address, 'chan'): + elif BMConfigParser().safeGetBoolean(self.address, 'chan'): self.type = AccountMixin.CHAN elif sqlQuery( - '''select label from subscriptions where address=?''', self.address): + '''select label from subscriptions where address=?''', self.address): self.type = AccountMixin.SUBSCRIPTION else: self.type = AccountMixin.NORMAL else: - self.type = address_type - + self.type = type + class BMAccount(object): - """Encapsulate a Bitmessage account""" - - def __init__(self, address=None): + def __init__(self, address = None): self.address = address self.type = AccountMixin.NORMAL - if config.has_section(address): - if config.safeGetBoolean(self.address, 'chan'): + if BMConfigParser().has_section(address): + if BMConfigParser().safeGetBoolean(self.address, 'chan'): self.type = AccountMixin.CHAN - elif config.safeGetBoolean(self.address, 'mailinglist'): + elif BMConfigParser().safeGetBoolean(self.address, 'mailinglist'): self.type = AccountMixin.MAILINGLIST elif self.address == str_broadcast_subscribers: self.type = AccountMixin.BROADCAST @@ -127,11 +108,12 @@ class BMAccount(object): if queryreturn: self.type = AccountMixin.SUBSCRIPTION - def getLabel(self, address=None): - """Get a label for this bitmessage account""" + def getLabel(self, address = None): if address is None: address = self.address - label = config.safeGet(address, 'label', address) + label = address + if BMConfigParser().has_section(address): + label = BMConfigParser().get(address, 'label') queryreturn = sqlQuery( '''select label from addressbook where address=?''', address) if queryreturn != []: @@ -144,10 +126,8 @@ class BMAccount(object): for row in queryreturn: label, = row return label - + def parseMessage(self, toAddress, fromAddress, subject, message): - """Set metadata and address labels on self""" - self.toAddress = toAddress self.fromAddress = fromAddress if isinstance(subject, unicode): @@ -160,45 +140,36 @@ class BMAccount(object): class NoAccount(BMAccount): - """Override the __init__ method on a BMAccount""" - - def __init__(self, address=None): # pylint: disable=super-init-not-called + def __init__(self, address = None): self.address = address self.type = AccountMixin.NORMAL - def getLabel(self, address=None): + def getLabel(self, address = None): if address is None: address = self.address return address - + class SubscriptionAccount(BMAccount): - """Encapsulate a subscription account""" pass - + class BroadcastAccount(BMAccount): - """Encapsulate a broadcast account""" pass - - + + class GatewayAccount(BMAccount): - """Encapsulate a gateway account""" - gatewayName = None ALL_OK = 0 REGISTRATION_DENIED = 1 - def __init__(self, address): super(GatewayAccount, self).__init__(address) - + def send(self): - """Override the send method for gateway accounts""" - - # pylint: disable=unused-variable status, addressVersionNumber, streamNumber, ripe = decodeAddress(self.toAddress) - stealthLevel = config.safeGetInt('bitmessagesettings', 'ackstealthlevel') + stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') ackdata = genAckPayload(streamNumber, stealthLevel) + t = () sqlExecute( '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', '', @@ -208,52 +179,47 @@ class GatewayAccount(BMAccount): self.subject, self.message, ackdata, - int(time.time()), # sentTime (this will never change) - int(time.time()), # lastActionTime - 0, # sleepTill time. This will get set when the POW gets done. + int(time.time()), # sentTime (this will never change) + int(time.time()), # lastActionTime + 0, # sleepTill time. This will get set when the POW gets done. 'msgqueued', - 0, # retryNumber - 'sent', # folder - 2, # encodingtype - # not necessary to have a TTL higher than 2 days - min(config.getint('bitmessagesettings', 'ttl'), 86400 * 2) + 0, # retryNumber + 'sent', # folder + 2, # encodingtype + min(BMConfigParser().getint('bitmessagesettings', 'ttl'), 86400 * 2) # not necessary to have a TTL higher than 2 days ) queues.workerQueue.put(('sendmessage', self.toAddress)) - + + def parseMessage(self, toAddress, fromAddress, subject, message): + super(GatewayAccount, self).parseMessage(toAddress, fromAddress, subject, message) class MailchuckAccount(GatewayAccount): - """Encapsulate a particular kind of gateway account""" - # set "gateway" in keys.dat to this gatewayName = "mailchuck" registrationAddress = "BM-2cVYYrhaY5Gbi3KqrX9Eae2NRNrkfrhCSA" unregistrationAddress = "BM-2cVMAHTRjZHCTPMue75XBK5Tco175DtJ9J" relayAddress = "BM-2cWim8aZwUNqxzjMxstnUMtVEUQJeezstf" - regExpIncoming = re.compile(r"(.*)MAILCHUCK-FROM::(\S+) \| (.*)") - regExpOutgoing = re.compile(r"(\S+) (.*)") - + regExpIncoming = re.compile("(.*)MAILCHUCK-FROM::(\S+) \| (.*)") + regExpOutgoing = re.compile("(\S+) (.*)") def __init__(self, address): super(MailchuckAccount, self).__init__(address) self.feedback = self.ALL_OK - + def createMessage(self, toAddress, fromAddress, subject, message): - """createMessage specific to a MailchuckAccount""" self.subject = toAddress + " " + subject self.toAddress = self.relayAddress self.fromAddress = fromAddress self.message = message - + def register(self, email): - """register specific to a MailchuckAccount""" self.toAddress = self.registrationAddress self.subject = email self.message = "" self.fromAddress = self.address self.send() - + def unregister(self): - """unregister specific to a MailchuckAccount""" self.toAddress = self.unregistrationAddress self.subject = "" self.message = "" @@ -261,7 +227,6 @@ class MailchuckAccount(GatewayAccount): self.send() def status(self): - """status specific to a MailchuckAccount""" self.toAddress = self.registrationAddress self.subject = "status" self.message = "" @@ -269,16 +234,12 @@ class MailchuckAccount(GatewayAccount): self.send() def settings(self): - """settings specific to a MailchuckAccount""" - self.toAddress = self.registrationAddress self.subject = "config" - self.message = QtGui.QApplication.translate( - "Mailchuck", - """# You can use this to configure your email gateway account + self.message = QtGui.QApplication.translate("Mailchuck", """# You can use this to configure your email gateway account # Uncomment the setting you want to use # Here are the options: -# +# # pgp: server # The email gateway will create and maintain PGP keys for you and sign, verify, # encrypt and decrypt on your behalf. When you want to use PGP but are lazy, @@ -294,7 +255,7 @@ class MailchuckAccount(GatewayAccount): # # attachments: no # Attachments will be ignored. -# +# # archive: yes # Your incoming emails will be archived on the server. Use this if you need # help with debugging problems or you need a third party proof of emails. This @@ -318,12 +279,10 @@ class MailchuckAccount(GatewayAccount): self.fromAddress = self.address def parseMessage(self, toAddress, fromAddress, subject, message): - """parseMessage specific to a MailchuckAccount""" - super(MailchuckAccount, self).parseMessage(toAddress, fromAddress, subject, message) if fromAddress == self.relayAddress: matches = self.regExpIncoming.search(subject) - if matches is not None: + if not matches is None: self.subject = "" if not matches.group(1) is None: self.subject += matches.group(1) @@ -334,7 +293,7 @@ class MailchuckAccount(GatewayAccount): self.fromAddress = matches.group(2) if toAddress == self.relayAddress: matches = self.regExpOutgoing.search(subject) - if matches is not None: + if not matches is None: if not matches.group(2) is None: self.subject = matches.group(2) if not matches.group(1) is None: diff --git a/src/bitmessageqt/address_dialogs.py b/src/bitmessageqt/address_dialogs.py index 3d3e5e37..2ea5cef8 100644 --- a/src/bitmessageqt/address_dialogs.py +++ b/src/bitmessageqt/address_dialogs.py @@ -1,38 +1,29 @@ -""" -Dialogs that work with BM address. -""" -# pylint: disable=attribute-defined-outside-init,too-few-public-methods,relative-import - -import hashlib - 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 widgets -from account import AccountMixin, GatewayAccount, MailchuckAccount, accountClass -from addresses import addBMIfNotPresent, decodeAddress, encodeVarint -from bmconfigparser import config as global_config +import hashlib from inventory import Inventory -from tr import _translate class AddressCheckMixin(object): - """Base address validation class for QT UI""" def __init__(self): self.valid = False - QtCore.QObject.connect( # pylint: disable=no-member - self.lineEditAddress, - QtCore.SIGNAL("textChanged(QString)"), - self.addressChanged) + QtCore.QObject.connect(self.lineEditAddress, QtCore.SIGNAL( + "textChanged(QString)"), self.addressChanged) def _onSuccess(self, addressVersion, streamNumber, ripe): pass def addressChanged(self, QString): - """ - Address validation callback, performs validation and gives feedback - """ status, addressVersion, streamNumber, ripe = decodeAddress( str(QString)) self.valid = status == 'success' @@ -80,14 +71,11 @@ class AddressCheckMixin(object): class AddressDataDialog(QtGui.QDialog, AddressCheckMixin): - """QDialog with Bitmessage address validation""" - def __init__(self, parent): super(AddressDataDialog, self).__init__(parent) self.parent = parent def accept(self): - """Callback for QDIalog accepting value""" if self.valid: self.data = ( addBMIfNotPresent(str(self.lineEditAddress.text())), @@ -101,8 +89,7 @@ class AddressDataDialog(QtGui.QDialog, AddressCheckMixin): super(AddressDataDialog, self).accept() -class AddAddressDialog(AddressDataDialog): - """QDialog for adding a new address""" +class AddAddressDialog(AddressDataDialog, RetranslateMixin): def __init__(self, parent=None, address=None): super(AddAddressDialog, self).__init__(parent) @@ -112,8 +99,7 @@ class AddAddressDialog(AddressDataDialog): self.lineEditAddress.setText(address) -class NewAddressDialog(QtGui.QDialog): - """QDialog for generating a new address""" +class NewAddressDialog(QtGui.QDialog, RetranslateMixin): def __init__(self, parent=None): super(NewAddressDialog, self).__init__(parent) @@ -121,7 +107,7 @@ class NewAddressDialog(QtGui.QDialog): # Let's fill out the 'existing address' combo box with addresses # from the 'Your Identities' tab. - for address in global_config.addresses(True): + for address in getSortedAccounts(): self.radioButtonExisting.click() self.comboBoxExisting.addItem(address) self.groupBoxDeterministic.setHidden(True) @@ -129,7 +115,6 @@ class NewAddressDialog(QtGui.QDialog): self.show() def accept(self): - """accept callback""" self.hide() # self.buttonBox.enabled = False if self.radioButtonRandomAddress.isChecked(): @@ -174,8 +159,7 @@ class NewAddressDialog(QtGui.QDialog): )) -class NewSubscriptionDialog(AddressDataDialog): - """QDialog for subscribing to an address""" +class NewSubscriptionDialog(AddressDataDialog, RetranslateMixin): def __init__(self, parent=None): super(NewSubscriptionDialog, self).__init__(parent) @@ -192,8 +176,8 @@ class NewSubscriptionDialog(AddressDataDialog): else: Inventory().flush() doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersion) - + encodeVarint(streamNumber) + ripe + encodeVarint(addressVersion) + + encodeVarint(streamNumber) + ripe ).digest()).digest() tag = doubleHashOfAddressData[32:] self.recent = Inventory().by_type_and_tag(3, tag) @@ -217,8 +201,7 @@ class NewSubscriptionDialog(AddressDataDialog): )) -class RegenerateAddressesDialog(QtGui.QDialog): - """QDialog for regenerating deterministic addresses""" +class RegenerateAddressesDialog(QtGui.QDialog, RetranslateMixin): def __init__(self, parent=None): super(RegenerateAddressesDialog, self).__init__(parent) widgets.load('regenerateaddresses.ui', self) @@ -226,12 +209,9 @@ class RegenerateAddressesDialog(QtGui.QDialog): QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) -class SpecialAddressBehaviorDialog(QtGui.QDialog): - """ - QDialog for special address behaviour (e.g. mailing list functionality) - """ +class SpecialAddressBehaviorDialog(QtGui.QDialog, RetranslateMixin): - def __init__(self, parent=None, config=global_config): + def __init__(self, parent=None, config=None): super(SpecialAddressBehaviorDialog, self).__init__(parent) widgets.load('specialaddressbehavior.ui', self) self.address = parent.getCurrentAccount() @@ -257,7 +237,11 @@ class SpecialAddressBehaviorDialog(QtGui.QDialog): self.radioButtonBehaviorMailingList.click() else: self.radioButtonBehaveNormalAddress.click() - mailingListName = config.safeGet(self.address, 'mailinglistname', '') + try: + mailingListName = config.get( + self.address, 'mailinglistname') + except: + mailingListName = '' self.lineEditMailingListName.setText( unicode(mailingListName, 'utf-8') ) @@ -266,7 +250,6 @@ class SpecialAddressBehaviorDialog(QtGui.QDialog): self.show() def accept(self): - """Accept callback""" self.hide() if self.address_is_chan: return @@ -284,16 +267,15 @@ class SpecialAddressBehaviorDialog(QtGui.QDialog): self.config.set(str(self.address), 'mailinglistname', str( self.lineEditMailingListName.text().toUtf8())) self.parent.setCurrentItemColor( - QtGui.QColor(137, 4, 177)) # magenta + QtGui.QColor(137, 04, 177)) # magenta self.parent.rerenderComboBoxSendFrom() self.parent.rerenderComboBoxSendFromBroadcast() self.config.save() self.parent.rerenderMessagelistToLabels() -class EmailGatewayDialog(QtGui.QDialog): - """QDialog for email gateway control""" - def __init__(self, parent, config=global_config, account=None): +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 @@ -333,7 +315,6 @@ class EmailGatewayDialog(QtGui.QDialog): QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) def accept(self): - """Accept callback""" self.hide() # no chans / mailinglists if self.acct.type != AccountMixin.NORMAL: diff --git a/src/bitmessageqt/addressvalidator.py b/src/bitmessageqt/addressvalidator.py index dc61b41c..f9de70a2 100644 --- a/src/bitmessageqt/addressvalidator.py +++ b/src/bitmessageqt/addressvalidator.py @@ -1,30 +1,14 @@ -""" -Address validator module. -""" -# pylint: disable=too-many-branches,too-many-arguments - +from PyQt4 import QtGui from Queue import Empty -from PyQt4 import QtGui - from addresses import decodeAddress, addBMIfNotPresent -from bmconfigparser import config +from account import getSortedAccounts from queues import apiAddressGeneratorReturnQueue, addressGeneratorQueue from tr import _translate from utils import str_chan - -class AddressPassPhraseValidatorMixin(object): - """Bitmessage address or passphrase validator class for Qt UI""" - def setParams( - self, - passPhraseObject=None, - addressObject=None, - feedBackObject=None, - buttonBox=None, - addressMandatory=True, - ): - """Initialisation""" +class AddressPassPhraseValidatorMixin(): + def setParams(self, passPhraseObject=None, addressObject=None, feedBackObject=None, buttonBox=None, addressMandatory=True): self.addressObject = addressObject self.passPhraseObject = passPhraseObject self.feedBackObject = feedBackObject @@ -35,7 +19,6 @@ class AddressPassPhraseValidatorMixin(object): self.okButtonLabel = self.buttonBox.button(QtGui.QDialogButtonBox.Ok).text() def setError(self, string): - """Indicate that the validation is pending or failed""" if string is not None and self.feedBackObject is not None: font = QtGui.QFont() font.setBold(True) @@ -46,14 +29,11 @@ class AddressPassPhraseValidatorMixin(object): 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")) + self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText(_translate("AddressValidator", "Invalid")) else: - self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText( - _translate("AddressValidator", "Validating...")) + self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText(_translate("AddressValidator", "Validating...")) def setOK(self, string): - """Indicate that the validation succeeded""" if string is not None and self.feedBackObject is not None: font = QtGui.QFont() font.setBold(False) @@ -66,13 +46,12 @@ class AddressPassPhraseValidatorMixin(object): self.buttonBox.button(QtGui.QDialogButtonBox.Ok).setText(self.okButtonLabel) def checkQueue(self): - """Validator queue loop""" gotOne = False # wait until processing is done if not addressGeneratorQueue.empty(): self.setError(None) - return None + return while True: try: @@ -81,30 +60,25 @@ class AddressPassPhraseValidatorMixin(object): if gotOne: break else: - return None + return else: gotOne = True - if not addressGeneratorReturnValue: + if len(addressGeneratorReturnValue) == 0: self.setError(_translate("AddressValidator", "Address already present as one of your identities.")) return (QtGui.QValidator.Intermediate, 0) if addressGeneratorReturnValue[0] == 'chan name does not match address': - self.setError( - _translate( - "AddressValidator", - "Although the Bitmessage address you " - "entered was valid, it doesn't match the chan name.")) + self.setError(_translate("AddressValidator", "Although the Bitmessage address you entered was valid, it doesn\'t match the chan name.")) return (QtGui.QValidator.Intermediate, 0) self.setOK(_translate("MainWindow", "Passphrase and address appear to be valid.")) def returnValid(self): - """Return the value of whether the validation was successful""" if self.isValid: return QtGui.QValidator.Acceptable - return QtGui.QValidator.Intermediate + else: + return QtGui.QValidator.Intermediate def validate(self, s, pos): - """Top level validator method""" if self.addressObject is None: address = None else: @@ -125,21 +99,15 @@ class AddressPassPhraseValidatorMixin(object): if self.addressMandatory or address is not None: # check if address already exists: - if address in config.addresses(): + if address in getSortedAccounts(): self.setError(_translate("AddressValidator", "Address already present as one of your identities.")) return (QtGui.QValidator.Intermediate, pos) # version too high if decodeAddress(address)[0] == 'versiontoohigh': - self.setError( - _translate( - "AddressValidator", - "Address too new. Although that Bitmessage" - " address might be valid, its version number" - " is too new for us to handle. Perhaps you need" - " to upgrade Bitmessage.")) + self.setError(_translate("AddressValidator", "Address too new. Although that Bitmessage address might be valid, its version number is too new for us to handle. Perhaps you need to upgrade Bitmessage.")) return (QtGui.QValidator.Intermediate, pos) - + # invalid if decodeAddress(address)[0] != 'success': self.setError(_translate("AddressValidator", "The Bitmessage address is not valid.")) @@ -154,28 +122,23 @@ class AddressPassPhraseValidatorMixin(object): if address is None: addressGeneratorQueue.put(('createChan', 4, 1, str_chan + ' ' + str(passPhrase), passPhrase, False)) else: - addressGeneratorQueue.put( - ('joinChan', addBMIfNotPresent(address), - "{} {}".format(str_chan, passPhrase), passPhrase, False)) + addressGeneratorQueue.put(('joinChan', addBMIfNotPresent(address), str_chan + ' ' + str(passPhrase), passPhrase, False)) if self.buttonBox.button(QtGui.QDialogButtonBox.Ok).hasFocus(): return (self.returnValid(), pos) - return (QtGui.QValidator.Intermediate, pos) + else: + return (QtGui.QValidator.Intermediate, pos) def checkData(self): - """Validator Qt signal interface""" return self.validate("", 0) - class AddressValidator(QtGui.QValidator, AddressPassPhraseValidatorMixin): - """AddressValidator class for Qt UI""" def __init__(self, parent=None, passPhraseObject=None, feedBackObject=None, buttonBox=None, addressMandatory=True): super(AddressValidator, self).__init__(parent) self.setParams(passPhraseObject, parent, feedBackObject, buttonBox, addressMandatory) class PassPhraseValidator(QtGui.QValidator, AddressPassPhraseValidatorMixin): - """PassPhraseValidator class for Qt UI""" def __init__(self, parent=None, addressObject=None, feedBackObject=None, buttonBox=None, addressMandatory=False): super(PassPhraseValidator, self).__init__(parent) self.setParams(parent, addressObject, feedBackObject, buttonBox, addressMandatory) diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index 961fc093..cb3578c0 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -8,7 +8,7 @@ # WARNING! All changes made in this file will be lost! from PyQt4 import QtCore, QtGui -from bmconfigparser import config +from bmconfigparser import BMConfigParser from foldertree import AddressBookCompleter from messageview import MessageView from messagecompose import MessageCompose @@ -24,28 +24,24 @@ except AttributeError: try: _encoding = QtGui.QApplication.UnicodeUTF8 - - def _translate(context, text, disambig, encoding=QtCore.QCoreApplication.CodecForTr, n=None): + def _translate(context, text, disambig, encoding = QtCore.QCoreApplication.CodecForTr, n = None): if n is None: return QtGui.QApplication.translate(context, text, disambig, _encoding) else: return QtGui.QApplication.translate(context, text, disambig, _encoding, n) except AttributeError: - def _translate(context, text, disambig, encoding=QtCore.QCoreApplication.CodecForTr, n=None): + def _translate(context, text, disambig, encoding = QtCore.QCoreApplication.CodecForTr, n = None): if n is None: return QtGui.QApplication.translate(context, text, disambig) else: return QtGui.QApplication.translate(context, text, disambig, QtCore.QCoreApplication.CodecForTr, n) - class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.resize(885, 580) icon = QtGui.QIcon() - icon.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-24px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off - ) + icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-24px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) MainWindow.setTabShape(QtGui.QTabWidget.Rounded) self.centralwidget = QtGui.QWidget(MainWindow) @@ -79,9 +75,7 @@ class Ui_MainWindow(object): self.treeWidgetYourIdentities.setObjectName(_fromUtf8("treeWidgetYourIdentities")) self.treeWidgetYourIdentities.resize(200, self.treeWidgetYourIdentities.height()) icon1 = QtGui.QIcon() - icon1.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/identities.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off - ) + icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/identities.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) self.treeWidgetYourIdentities.headerItem().setIcon(0, icon1) self.verticalSplitter_12.addWidget(self.treeWidgetYourIdentities) self.pushButtonNewAddress = QtGui.QPushButton(self.inbox) @@ -110,7 +104,6 @@ class Ui_MainWindow(object): self.inboxSearchOption.addItem(_fromUtf8("")) self.inboxSearchOption.addItem(_fromUtf8("")) self.inboxSearchOption.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.inboxSearchOption.setCurrentIndex(3) self.horizontalSplitterSearch.addWidget(self.inboxSearchOption) self.horizontalSplitterSearch.handle(1).setEnabled(False) self.horizontalSplitterSearch.setStretchFactor(0, 1) @@ -182,9 +175,7 @@ class Ui_MainWindow(object): self.tableWidgetAddressBook.resize(200, self.tableWidgetAddressBook.height()) item = QtGui.QTableWidgetItem() icon3 = QtGui.QIcon() - icon3.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/addressbook.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off - ) + icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/addressbook.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) item.setIcon(icon3) self.tableWidgetAddressBook.setHorizontalHeaderItem(0, item) item = QtGui.QTableWidgetItem() @@ -385,9 +376,7 @@ class Ui_MainWindow(object): self.treeWidgetSubscriptions.setObjectName(_fromUtf8("treeWidgetSubscriptions")) self.treeWidgetSubscriptions.resize(200, self.treeWidgetSubscriptions.height()) icon5 = QtGui.QIcon() - icon5.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off - ) + icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) self.treeWidgetSubscriptions.headerItem().setIcon(0, icon5) self.verticalSplitter_3.addWidget(self.treeWidgetSubscriptions) self.pushButtonAddSubscription = QtGui.QPushButton(self.subscriptions) @@ -414,8 +403,8 @@ class Ui_MainWindow(object): self.inboxSearchOptionSubscriptions.addItem(_fromUtf8("")) self.inboxSearchOptionSubscriptions.addItem(_fromUtf8("")) self.inboxSearchOptionSubscriptions.addItem(_fromUtf8("")) + self.inboxSearchOptionSubscriptions.addItem(_fromUtf8("")) self.inboxSearchOptionSubscriptions.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.inboxSearchOptionSubscriptions.setCurrentIndex(2) self.horizontalSplitter_2.addWidget(self.inboxSearchOptionSubscriptions) self.horizontalSplitter_2.handle(1).setEnabled(False) self.horizontalSplitter_2.setStretchFactor(0, 1) @@ -466,9 +455,7 @@ class Ui_MainWindow(object): self.horizontalSplitter_4.setCollapsible(1, False) self.gridLayout_3.addWidget(self.horizontalSplitter_4, 0, 0, 1, 1) icon6 = QtGui.QIcon() - icon6.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off - ) + icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.tabWidget.addTab(self.subscriptions, icon6, _fromUtf8("")) self.chans = QtGui.QWidget() self.chans.setObjectName(_fromUtf8("chans")) @@ -488,9 +475,7 @@ class Ui_MainWindow(object): self.treeWidgetChans.setObjectName(_fromUtf8("treeWidgetChans")) self.treeWidgetChans.resize(200, self.treeWidgetChans.height()) icon7 = QtGui.QIcon() - icon7.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off - ) + icon7.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) self.treeWidgetChans.headerItem().setIcon(0, icon7) self.verticalSplitter_17.addWidget(self.treeWidgetChans) self.pushButtonAddChan = QtGui.QPushButton(self.chans) @@ -519,7 +504,6 @@ class Ui_MainWindow(object): self.inboxSearchOptionChans.addItem(_fromUtf8("")) self.inboxSearchOptionChans.addItem(_fromUtf8("")) self.inboxSearchOptionChans.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.inboxSearchOptionChans.setCurrentIndex(3) self.horizontalSplitter_6.addWidget(self.inboxSearchOptionChans) self.horizontalSplitter_6.handle(1).setEnabled(False) self.horizontalSplitter_6.setStretchFactor(0, 1) @@ -570,14 +554,12 @@ class Ui_MainWindow(object): self.horizontalSplitter_7.setCollapsible(1, False) self.gridLayout_4.addWidget(self.horizontalSplitter_7, 0, 0, 1, 1) icon8 = QtGui.QIcon() - icon8.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off - ) + icon8.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.tabWidget.addTab(self.chans, icon8, _fromUtf8("")) self.blackwhitelist = Blacklist() self.tabWidget.addTab(self.blackwhitelist, QtGui.QIcon(":/newPrefix/images/blacklist.png"), "") # Initialize the Blacklist or Whitelist - if config.get('bitmessagesettings', 'blackwhitelist') == 'white': + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'white': self.blackwhitelist.radioButtonWhitelist.click() self.blackwhitelist.rerenderBlackWhiteList() @@ -669,17 +651,9 @@ class Ui_MainWindow(object): MainWindow.setTabOrder(self.lineEditSubject, self.textEditMessage) MainWindow.setTabOrder(self.textEditMessage, self.pushButtonAddSubscription) - # Popup menu actions container for the Sent page - # pylint: disable=attribute-defined-outside-init - self.sentContextMenuToolbar = QtGui.QToolBar() - # Popup menu actions container for chans tree - self.addressContextMenuToolbar = QtGui.QToolBar() - # Popup menu actions container for subscriptions tree - self.subscriptionsContextMenuToolbar = QtGui.QToolBar() - def updateNetworkSwitchMenuLabel(self, dontconnect=None): if dontconnect is None: - dontconnect = config.safeGetBoolean( + dontconnect = BMConfigParser().safeGetBoolean( 'bitmessagesettings', 'dontconnect') self.actionNetworkSwitch.setText( _translate("MainWindow", "Go online", None) @@ -717,24 +691,19 @@ class Ui_MainWindow(object): self.label_3.setText(_translate("MainWindow", "Subject:", None)) self.label_2.setText(_translate("MainWindow", "From:", None)) self.label.setText(_translate("MainWindow", "To:", None)) - self.tabWidgetSend.setTabText( - self.tabWidgetSend.indexOf(self.sendDirect), _translate("MainWindow", "Send ordinary Message", None) - ) + #self.textEditMessage.setHtml("") + self.tabWidgetSend.setTabText(self.tabWidgetSend.indexOf(self.sendDirect), _translate("MainWindow", "Send ordinary Message", None)) self.label_8.setText(_translate("MainWindow", "From:", None)) self.label_7.setText(_translate("MainWindow", "Subject:", None)) - self.tabWidgetSend.setTabText( - self.tabWidgetSend.indexOf(self.sendBroadcast), - _translate("MainWindow", "Send Message to your Subscribers", None) - ) + #self.textEditMessageBroadcast.setHtml("") + self.tabWidgetSend.setTabText(self.tabWidgetSend.indexOf(self.sendBroadcast), _translate("MainWindow", "Send Message to your Subscribers", None)) self.pushButtonTTL.setText(_translate("MainWindow", "TTL:", None)) hours = 48 try: - hours = int(config.getint('bitmessagesettings', 'ttl') / 60 / 60) + hours = int(BMConfigParser().getint('bitmessagesettings', 'ttl')/60/60) except: pass - self.labelHumanFriendlyTTLDescription.setText( - _translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours) - ) + 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)) @@ -742,9 +711,10 @@ class Ui_MainWindow(object): self.pushButtonAddSubscription.setText(_translate("MainWindow", "Add new Subscription", None)) self.inboxSearchLineEditSubscriptions.setPlaceholderText(_translate("MainWindow", "Search", None)) self.inboxSearchOptionSubscriptions.setItemText(0, _translate("MainWindow", "All", None)) - self.inboxSearchOptionSubscriptions.setItemText(1, _translate("MainWindow", "From", None)) - self.inboxSearchOptionSubscriptions.setItemText(2, _translate("MainWindow", "Subject", None)) - self.inboxSearchOptionSubscriptions.setItemText(3, _translate("MainWindow", "Message", None)) + self.inboxSearchOptionSubscriptions.setItemText(1, _translate("MainWindow", "To", None)) + self.inboxSearchOptionSubscriptions.setItemText(2, _translate("MainWindow", "From", None)) + self.inboxSearchOptionSubscriptions.setItemText(3, _translate("MainWindow", "Subject", None)) + self.inboxSearchOptionSubscriptions.setItemText(4, _translate("MainWindow", "Message", None)) self.tableWidgetInboxSubscriptions.setSortingEnabled(True) item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(0) item.setText(_translate("MainWindow", "To", None)) @@ -754,10 +724,7 @@ class Ui_MainWindow(object): item.setText(_translate("MainWindow", "Subject", None)) item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(3) item.setText(_translate("MainWindow", "Received", None)) - self.tabWidget.setTabText( - self.tabWidget.indexOf(self.subscriptions), - _translate("MainWindow", "Subscriptions", None) - ) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.subscriptions), _translate("MainWindow", "Subscriptions", None)) self.treeWidgetChans.headerItem().setText(0, _translate("MainWindow", "Chans", None)) self.pushButtonAddChan.setText(_translate("MainWindow", "Add Chan", None)) self.inboxSearchLineEditChans.setPlaceholderText(_translate("MainWindow", "Search", None)) @@ -777,15 +744,9 @@ class Ui_MainWindow(object): item.setText(_translate("MainWindow", "Received", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.chans), _translate("MainWindow", "Chans", None)) self.blackwhitelist.retranslateUi() - self.tabWidget.setTabText( - self.tabWidget.indexOf(self.blackwhitelist), - _translate("blacklist", "Blacklist", None) - ) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.blackwhitelist), _translate("blacklist", "Blacklist", None)) self.networkstatus.retranslateUi() - self.tabWidget.setTabText( - self.tabWidget.indexOf(self.networkstatus), - _translate("networkstatus", "Network Status", None) - ) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.networkstatus), _translate("networkstatus", "Network Status", None)) self.menuFile.setTitle(_translate("MainWindow", "File", None)) self.menuSettings.setTitle(_translate("MainWindow", "Settings", None)) self.menuHelp.setTitle(_translate("MainWindow", "Help", None)) @@ -798,20 +759,19 @@ class Ui_MainWindow(object): self.actionSupport.setText(_translate("MainWindow", "Contact support", None)) self.actionAbout.setText(_translate("MainWindow", "About", None)) self.actionSettings.setText(_translate("MainWindow", "Settings", None)) - self.actionRegenerateDeterministicAddresses.setText( - _translate("MainWindow", "Regenerate deterministic addresses", None) - ) + self.actionRegenerateDeterministicAddresses.setText(_translate("MainWindow", "Regenerate deterministic addresses", None)) self.actionDeleteAllTrashedMessages.setText(_translate("MainWindow", "Delete all trashed messages", None)) self.actionJoinChan.setText(_translate("MainWindow", "Join / Create chan", None)) - self.updateNetworkSwitchMenuLabel() +import bitmessage_icons_rc if __name__ == "__main__": import sys - + app = QtGui.QApplication(sys.argv) MainWindow = settingsmixin.SMainWindow() ui = Ui_MainWindow() ui.setupUi(MainWindow) MainWindow.show() sys.exit(app.exec_()) + diff --git a/src/bitmessageqt/blacklist.py b/src/bitmessageqt/blacklist.py index 093f23d8..e07f9469 100644 --- a/src/bitmessageqt/blacklist.py +++ b/src/bitmessageqt/blacklist.py @@ -1,15 +1,14 @@ from PyQt4 import QtCore, QtGui - +from tr import _translate +import l10n import widgets from addresses import addBMIfNotPresent -from bmconfigparser import config +from bmconfigparser import BMConfigParser from dialogs import AddAddressDialog from helper_sql import sqlExecute, sqlQuery -from queues import UISignalQueue from retranslateui import RetranslateMixin -from tr import _translate -from uisignaler import UISignaler from utils import avatarize +from uisignaler import UISignaler class Blacklist(QtGui.QWidget, RetranslateMixin): @@ -39,17 +38,17 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): "rerenderBlackWhiteList()"), self.rerenderBlackWhiteList) def click_radioButtonBlacklist(self): - if config.get('bitmessagesettings', 'blackwhitelist') == 'white': - config.set('bitmessagesettings', 'blackwhitelist', 'black') - config.save() + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'white': + BMConfigParser().set('bitmessagesettings', 'blackwhitelist', 'black') + BMConfigParser().save() # self.tableWidgetBlacklist.clearContents() self.tableWidgetBlacklist.setRowCount(0) self.rerenderBlackWhiteList() def click_radioButtonWhitelist(self): - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': - config.set('bitmessagesettings', 'blackwhitelist', 'white') - config.save() + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': + BMConfigParser().set('bitmessagesettings', 'blackwhitelist', 'white') + BMConfigParser().save() # self.tableWidgetBlacklist.clearContents() self.tableWidgetBlacklist.setRowCount(0) self.rerenderBlackWhiteList() @@ -57,15 +56,14 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): def click_pushButtonAddBlacklist(self): self.NewBlacklistDialogInstance = AddAddressDialog(self) if self.NewBlacklistDialogInstance.exec_(): - if self.NewBlacklistDialogInstance.labelAddressCheck.text() == \ - _translate("MainWindow", "Address is valid."): + if self.NewBlacklistDialogInstance.ui.labelAddressCheck.text() == _translate("MainWindow", "Address is valid."): address = addBMIfNotPresent(str( - self.NewBlacklistDialogInstance.lineEditAddress.text())) + self.NewBlacklistDialogInstance.ui.lineEditAddress.text())) # 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. t = (address,) - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': sql = '''select * from blacklist where address=?''' else: sql = '''select * from whitelist where address=?''' @@ -74,7 +72,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): self.tableWidgetBlacklist.setSortingEnabled(False) self.tableWidgetBlacklist.insertRow(0) newItem = QtGui.QTableWidgetItem(unicode( - self.NewBlacklistDialogInstance.lineEditLabel.text().toUtf8(), 'utf-8')) + self.NewBlacklistDialogInstance.ui.newAddressLabel.text().toUtf8(), 'utf-8')) newItem.setIcon(avatarize(address)) self.tableWidgetBlacklist.setItem(0, 0, newItem) newItem = QtGui.QTableWidgetItem(address) @@ -82,28 +80,18 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) self.tableWidgetBlacklist.setItem(0, 1, newItem) self.tableWidgetBlacklist.setSortingEnabled(True) - t = (str(self.NewBlacklistDialogInstance.lineEditLabel.text().toUtf8()), address, True) - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': + t = (str(self.NewBlacklistDialogInstance.ui.newAddressLabel.text().toUtf8()), address, True) + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': sql = '''INSERT INTO blacklist VALUES (?,?,?)''' else: sql = '''INSERT INTO whitelist VALUES (?,?,?)''' sqlExecute(sql, *t) else: - UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", - "Error: You cannot add the same address to your" - " list twice. Perhaps rename the existing one" - " if you want.") - )) + self.statusBar().showMessage(_translate( + "MainWindow", "Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want.")) else: - UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", - "The address you entered was invalid. Ignoring it.") - )) + self.statusBar().showMessage(_translate( + "MainWindow", "The address you entered was invalid. Ignoring it.")) def tableWidgetBlacklistItemChanged(self, item): if item.column() == 0: @@ -158,12 +146,12 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): def rerenderBlackWhiteList(self): tabs = self.parent().parent() - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': tabs.setTabText(tabs.indexOf(self), _translate('blacklist', 'Blacklist')) else: tabs.setTabText(tabs.indexOf(self), _translate('blacklist', 'Whitelist')) self.tableWidgetBlacklist.setRowCount(0) - listType = config.get('bitmessagesettings', 'blackwhitelist') + listType = BMConfigParser().get('bitmessagesettings', 'blackwhitelist') if listType == 'black': queryreturn = sqlQuery('''SELECT label, address, enabled FROM blacklist''') else: @@ -195,7 +183,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): currentRow, 0).text().toUtf8() addressAtCurrentRow = self.tableWidgetBlacklist.item( currentRow, 1).text() - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': sqlExecute( '''DELETE FROM blacklist WHERE label=? AND address=?''', str(labelAtCurrentRow), str(addressAtCurrentRow)) @@ -224,7 +212,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): currentRow, 0).setTextColor(QtGui.QApplication.palette().text().color()) self.tableWidgetBlacklist.item( currentRow, 1).setTextColor(QtGui.QApplication.palette().text().color()) - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': sqlExecute( '''UPDATE blacklist SET enabled=1 WHERE address=?''', str(addressAtCurrentRow)) @@ -241,7 +229,7 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): currentRow, 0).setTextColor(QtGui.QColor(128, 128, 128)) self.tableWidgetBlacklist.item( currentRow, 1).setTextColor(QtGui.QColor(128, 128, 128)) - if config.get('bitmessagesettings', 'blackwhitelist') == 'black': + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': sqlExecute( '''UPDATE blacklist SET enabled=0 WHERE address=?''', str(addressAtCurrentRow)) else: @@ -250,3 +238,4 @@ class Blacklist(QtGui.QWidget, RetranslateMixin): def on_action_BlacklistSetAvatar(self): self.window().on_action_SetAvatar(self.tableWidgetBlacklist) + diff --git a/src/bitmessageqt/dialogs.py b/src/bitmessageqt/dialogs.py index dc31e266..cb82f348 100644 --- a/src/bitmessageqt/dialogs.py +++ b/src/bitmessageqt/dialogs.py @@ -1,32 +1,26 @@ -""" -Custom dialog classes -""" -# pylint: disable=too-few-public-methods 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 -import widgets -from address_dialogs import ( - AddAddressDialog, EmailGatewayDialog, NewAddressDialog, - NewSubscriptionDialog, RegenerateAddressesDialog, - SpecialAddressBehaviorDialog -) -from newchandialog import NewChanDialog -from settings import SettingsDialog -from tr import _translate from version import softwareVersion __all__ = [ "NewChanDialog", "AddAddressDialog", "NewAddressDialog", "NewSubscriptionDialog", "RegenerateAddressesDialog", - "SpecialAddressBehaviorDialog", "EmailGatewayDialog", - "SettingsDialog" + "SpecialAddressBehaviorDialog", "EmailGatewayDialog" ] -class AboutDialog(QtGui.QDialog): - """The `About` dialog""" +class AboutDialog(QtGui.QDialog, RetranslateMixin): def __init__(self, parent=None): super(AboutDialog, self).__init__(parent) widgets.load('about.ui', self) @@ -38,14 +32,14 @@ class AboutDialog(QtGui.QDialog): self.labelVersion.setText( self.labelVersion.text().replace( ':version:', version - ).replace(':branch:', commit or 'v%s' % version) + ).replace(':branch:', commit or 'v%s' % version) ) self.labelVersion.setOpenExternalLinks(True) try: self.label_2.setText( self.label_2.text().replace( - '2022', str(last_commit.get('time').year) + '2017', str(last_commit.get('time').year) )) except AttributeError: pass @@ -53,32 +47,29 @@ class AboutDialog(QtGui.QDialog): self.setFixedSize(QtGui.QWidget.sizeHint(self)) -class IconGlossaryDialog(QtGui.QDialog): - """The `Icon Glossary` dialog, explaining the status icon colors""" +class IconGlossaryDialog(QtGui.QDialog, RetranslateMixin): def __init__(self, parent=None, config=None): super(IconGlossaryDialog, self).__init__(parent) widgets.load('iconglossary.ui', self) - # .. todo:: FIXME: check the window title visibility here + # 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'))) + ).arg(config.getint('bitmessagesettings', 'port'))) self.setFixedSize(QtGui.QWidget.sizeHint(self)) -class HelpDialog(QtGui.QDialog): - """The `Help` dialog""" +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): - """The `Connect` dialog""" +class ConnectDialog(QtGui.QDialog, RetranslateMixin): def __init__(self, parent=None): super(ConnectDialog, self).__init__(parent) widgets.load('connect.ui', self) diff --git a/src/bitmessageqt/foldertree.py b/src/bitmessageqt/foldertree.py index c50b7d3d..11227fca 100644 --- a/src/bitmessageqt/foldertree.py +++ b/src/bitmessageqt/foldertree.py @@ -1,18 +1,11 @@ -""" -Folder tree and messagelist widgets definitions. -""" -# pylint: disable=too-many-arguments,bad-super-call -# pylint: disable=attribute-defined-outside-init - -from cgi import escape - from PyQt4 import QtCore, QtGui +from string import find, rfind, rstrip, lstrip -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -from settingsmixin import SettingsMixin from tr import _translate -from utils import avatarize +from bmconfigparser import BMConfigParser +from helper_sql import * +from utils import * +from settingsmixin import SettingsMixin # for pylupdate _translate("MainWindow", "inbox") @@ -20,11 +13,8 @@ _translate("MainWindow", "new") _translate("MainWindow", "sent") _translate("MainWindow", "trash") -TimestampRole = QtCore.Qt.UserRole + 1 - class AccountMixin(object): - """UI-related functionality for accounts""" ALL = 0 NORMAL = 1 CHAN = 2 @@ -32,67 +22,49 @@ class AccountMixin(object): SUBSCRIPTION = 4 BROADCAST = 5 - def accountColor(self): - """QT UI color for an account""" + def accountColor (self): if not self.isEnabled: return QtGui.QColor(128, 128, 128) elif self.type == self.CHAN: return QtGui.QColor(216, 119, 0) elif self.type in [self.MAILINGLIST, self.SUBSCRIPTION]: - return QtGui.QColor(137, 4, 177) - return QtGui.QApplication.palette().text().color() - - def folderColor(self): - """QT UI color for a folder""" + return QtGui.QColor(137, 04, 177) + else: + return QtGui.QApplication.palette().text().color() + + def folderColor (self): if not self.parent().isEnabled: return QtGui.QColor(128, 128, 128) - return QtGui.QApplication.palette().text().color() - + else: + return QtGui.QApplication.palette().text().color() + def accountBrush(self): - """Account brush (for QT UI)""" brush = QtGui.QBrush(self.accountColor()) brush.setStyle(QtCore.Qt.NoBrush) return brush - + def folderBrush(self): - """Folder brush (for QT UI)""" brush = QtGui.QBrush(self.folderColor()) brush.setStyle(QtCore.Qt.NoBrush) return brush - def accountString(self): - """Account string suitable for use in To: field: label
""" - label = self._getLabel() - return ( - self.address if label == self.address - else '%s <%s>' % (label, self.address) - ) - def setAddress(self, address): - """Set bitmessage address of the object""" if address is None: self.address = None else: self.address = str(address) - + def setUnreadCount(self, cnt): - """Set number of unread messages""" - try: - if self.unreadCount == int(cnt): - return - except AttributeError: - pass + if hasattr(self, "unreadCount") and self.unreadCount == int(cnt): + return self.unreadCount = int(cnt) if isinstance(self, QtGui.QTreeWidgetItem): self.emitDataChanged() def setEnabled(self, enabled): - """Set account enabled (QT UI)""" self.isEnabled = enabled - try: + if hasattr(self, "setExpanded"): self.setExpanded(enabled) - except AttributeError: - pass if isinstance(self, Ui_AddressWidget): for i in range(self.childCount()): if isinstance(self.child(i), Ui_FolderWidget): @@ -101,32 +73,27 @@ class AccountMixin(object): self.emitDataChanged() def setType(self): - """Set account type (QT UI)""" self.setFlags(self.flags() | QtCore.Qt.ItemIsEditable) if self.address is None: self.type = self.ALL self.setFlags(self.flags() & ~QtCore.Qt.ItemIsEditable) - elif config.safeGetBoolean(self.address, 'chan'): + elif BMConfigParser().safeGetBoolean(self.address, 'chan'): self.type = self.CHAN - elif config.safeGetBoolean(self.address, 'mailinglist'): + elif BMConfigParser().safeGetBoolean(self.address, 'mailinglist'): self.type = self.MAILINGLIST elif sqlQuery( - '''select label from subscriptions where address=?''', self.address): + '''select label from subscriptions where address=?''', self.address): self.type = AccountMixin.SUBSCRIPTION else: self.type = self.NORMAL - + def defaultLabel(self): - """Default label (in case no label is set manually)""" queryreturn = None retval = None - if self.type in ( - AccountMixin.NORMAL, - AccountMixin.CHAN, AccountMixin.MAILINGLIST): + if self.type in (AccountMixin.NORMAL, AccountMixin.CHAN, AccountMixin.MAILINGLIST): try: - retval = unicode( - config.get(self.address, 'label'), 'utf-8') - except Exception: + retval = unicode(BMConfigParser().get(self.address, 'label'), 'utf-8') + except Exception as e: queryreturn = sqlQuery( '''select label from addressbook where address=?''', self.address) elif self.type == AccountMixin.SUBSCRIPTION: @@ -140,68 +107,44 @@ class AccountMixin(object): elif self.address is None or self.type == AccountMixin.ALL: return unicode( str(_translate("MainWindow", "All accounts")), 'utf-8') - - return retval or unicode(self.address, 'utf-8') + if retval is None: + return unicode(self.address, 'utf-8') + else: + return retval -class BMTreeWidgetItem(QtGui.QTreeWidgetItem, AccountMixin): - """A common abstract class for Tree widget item""" - - def __init__(self, parent, pos, address, unreadCount): +class Ui_FolderWidget(QtGui.QTreeWidgetItem, AccountMixin): + folderWeight = {"inbox": 1, "new": 2, "sent": 3, "trash": 4} + def __init__(self, parent, pos = 0, address = "", folderName = "", unreadCount = 0): super(QtGui.QTreeWidgetItem, self).__init__() self.setAddress(address) + self.setFolderName(folderName) self.setUnreadCount(unreadCount) - self._setup(parent, pos) + parent.insertChild(pos, self) - def _getAddressBracket(self, unreadCount=False): - return " (" + str(self.unreadCount) + ")" if unreadCount else "" + def setFolderName(self, fname): + self.folderName = str(fname) def data(self, column, role): - """Override internal QT method for returning object data""" if column == 0: if role == QtCore.Qt.DisplayRole: - return self._getLabel() + self._getAddressBracket( - self.unreadCount > 0) - elif role == QtCore.Qt.EditRole: - return self._getLabel() - elif role == QtCore.Qt.ToolTipRole: - return self._getLabel() + self._getAddressBracket(False) + 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) return font - return super(BMTreeWidgetItem, self).data(column, role) - - -class Ui_FolderWidget(BMTreeWidgetItem): - """Item in the account/folder tree representing a folder""" - folderWeight = {"inbox": 1, "new": 2, "sent": 3, "trash": 4} - - def __init__( - self, parent, pos=0, address="", folderName="", unreadCount=0): - self.setFolderName(folderName) - super(Ui_FolderWidget, self).__init__( - parent, pos, address, unreadCount) - - def _setup(self, parent, pos): - parent.insertChild(pos, self) - - def _getLabel(self): - return _translate("MainWindow", self.folderName) - - def setFolderName(self, fname): - """Set folder name (for QT UI)""" - self.folderName = str(fname) - - def data(self, column, role): - """Override internal QT method for returning object data""" - if column == 0 and role == QtCore.Qt.ForegroundRole: - return self.folderBrush() + elif role == QtCore.Qt.ForegroundRole: + return self.folderBrush() return super(Ui_FolderWidget, self).data(column, role) # inbox, sent, thrash first, rest alphabetically def __lt__(self, other): - if isinstance(other, Ui_FolderWidget): + if (isinstance(other, Ui_FolderWidget)): if self.folderName in self.folderWeight: x = self.folderWeight[self.folderName] else: @@ -210,25 +153,27 @@ class Ui_FolderWidget(BMTreeWidgetItem): y = self.folderWeight[other.folderName] else: y = 99 - reverse = QtCore.Qt.DescendingOrder == \ - self.treeWidget().header().sortIndicatorOrder() + reverse = False + if self.treeWidget().header().sortIndicatorOrder() == QtCore.Qt.DescendingOrder: + reverse = True if x == y: return self.folderName < other.folderName - return x >= y if reverse else x < y + else: + return (x >= y if reverse else x < y) return super(QtGui.QTreeWidgetItem, self).__lt__(other) + - -class Ui_AddressWidget(BMTreeWidgetItem, SettingsMixin): - """Item in the account/folder tree representing an account""" - def __init__(self, parent, pos=0, address=None, unreadCount=0, enabled=True): - super(Ui_AddressWidget, self).__init__( - parent, pos, address, unreadCount) - self.setEnabled(enabled) - - def _setup(self, parent, pos): - self.setType() +class Ui_AddressWidget(QtGui.QTreeWidgetItem, AccountMixin, SettingsMixin): + def __init__(self, parent, pos = 0, address = None, unreadCount = 0, enabled = True): + super(QtGui.QTreeWidgetItem, self).__init__() parent.insertTopLevelItem(pos, self) + # only set default when creating + #super(QtGui.QTreeWidgetItem, self).setExpanded(BMConfigParser().getboolean(self.address, 'enabled')) + self.setAddress(address) + self.setEnabled(enabled) + self.setUnreadCount(unreadCount) + self.setType() def _getLabel(self): if self.address is None: @@ -237,74 +182,91 @@ class Ui_AddressWidget(BMTreeWidgetItem, SettingsMixin): else: try: return unicode( - config.get(self.address, 'label'), + BMConfigParser().get(self.address, 'label'), 'utf-8', 'ignore') except: return unicode(self.address, 'utf-8') - def _getAddressBracket(self, unreadCount=False): - ret = "" if self.isExpanded() \ - else super(Ui_AddressWidget, self)._getAddressBracket(unreadCount) + def _getAddressBracket(self, unreadCount = False): + ret = "" + if unreadCount: + ret += " (" + str(self.unreadCount) + ")" if self.address is not None: ret += " (" + self.address + ")" return ret - + def data(self, column, role): - """Override internal QT method for returning object data""" if column == 0: - if role == QtCore.Qt.DecorationRole: - return avatarize( - self.address or self._getLabel().encode('utf8')) + if role == QtCore.Qt.DisplayRole: + if self.unreadCount > 0 and not self.isExpanded(): + return self._getLabel() + self._getAddressBracket(True) + else: + return self._getLabel() + self._getAddressBracket(False) + elif role == QtCore.Qt.EditRole: + return self._getLabel() + elif role == QtCore.Qt.ToolTipRole: + return self._getLabel() + self._getAddressBracket(False) + elif role == QtCore.Qt.DecorationRole: + if self.address is None: + return avatarize(self._getLabel().encode('utf8')) + else: + return avatarize(self.address) + elif role == QtCore.Qt.FontRole: + font = QtGui.QFont() + font.setBold(self.unreadCount > 0) + return font elif role == QtCore.Qt.ForegroundRole: return self.accountBrush() return super(Ui_AddressWidget, self).data(column, role) - + def setData(self, column, role, value): - """Save account label (if you edit in the the UI, this will be triggered and will save it to keys.dat)""" - if role == QtCore.Qt.EditRole \ - and self.type != AccountMixin.SUBSCRIPTION: - config.set( - str(self.address), 'label', - str(value.toString().toUtf8()) - if isinstance(value, QtCore.QVariant) - else value.encode('utf-8') - ) - config.save() + if role == QtCore.Qt.EditRole and self.type != AccountMixin.SUBSCRIPTION: + if isinstance(value, QtCore.QVariant): + BMConfigParser().set(str(self.address), 'label', str(value.toString().toUtf8())) + else: + BMConfigParser().set(str(self.address), 'label', str(value)) + BMConfigParser().save() return super(Ui_AddressWidget, self).setData(column, role, value) - + def setAddress(self, address): - """Set address to object (for QT UI)""" super(Ui_AddressWidget, self).setAddress(address) self.setData(0, QtCore.Qt.UserRole, self.address) - + + def setExpanded(self, expand): + super(Ui_AddressWidget, self).setExpanded(expand) + def _getSortRank(self): - return self.type if self.isEnabled else (self.type + 100) + ret = self.type + if not self.isEnabled: + ret += 100 + return ret # label (or address) alphabetically, disabled at the end def __lt__(self, other): - # pylint: disable=protected-access - if isinstance(other, Ui_AddressWidget): - reverse = QtCore.Qt.DescendingOrder == \ - self.treeWidget().header().sortIndicatorOrder() + if (isinstance(other, Ui_AddressWidget)): + reverse = False + if self.treeWidget().header().sortIndicatorOrder() == QtCore.Qt.DescendingOrder: + reverse = True if self._getSortRank() == other._getSortRank(): x = self._getLabel().lower() y = other._getLabel().lower() return x < y - return ( - not reverse - if self._getSortRank() < other._getSortRank() else reverse - ) + return (not reverse if self._getSortRank() < other._getSortRank() else reverse) return super(QtGui.QTreeWidgetItem, self).__lt__(other) - -class Ui_SubscriptionWidget(Ui_AddressWidget): - """Special treating of subscription addresses""" - # pylint: disable=unused-argument - def __init__(self, parent, pos=0, address="", unreadCount=0, label="", enabled=True): - super(Ui_SubscriptionWidget, self).__init__( - parent, pos, address, unreadCount, enabled) - + +class Ui_SubscriptionWidget(Ui_AddressWidget, AccountMixin): + def __init__(self, parent, pos = 0, address = "", unreadCount = 0, label = "", enabled = True): + super(QtGui.QTreeWidgetItem, self).__init__() + parent.insertTopLevelItem(pos, self) + # only set default when creating + #super(QtGui.QTreeWidgetItem, self).setExpanded(BMConfigParser().getboolean(self.address, 'enabled')) + self.setAddress(address) + self.setEnabled(enabled) + self.setType() + self.setUnreadCount(unreadCount) + def _getLabel(self): queryreturn = sqlQuery( '''select label from subscriptions where address=?''', self.address) @@ -313,18 +275,16 @@ class Ui_SubscriptionWidget(Ui_AddressWidget): retval, = row return unicode(retval, 'utf-8', 'ignore') return unicode(self.address, 'utf-8') - + def setType(self): - """Set account type""" - super(Ui_SubscriptionWidget, self).setType() # sets it editable - self.type = AccountMixin.SUBSCRIPTION # overrides type - + super(Ui_SubscriptionWidget, self).setType() # sets it editable + self.type = AccountMixin.SUBSCRIPTION # overrides type + def setData(self, column, role, value): - """Save subscription label to database""" if role == QtCore.Qt.EditRole: + from debug import logger if isinstance(value, QtCore.QVariant): - label = str( - value.toString().toUtf8()).decode('utf-8', 'ignore') + label = str(value.toString().toUtf8()).decode('utf-8', 'ignore') else: label = unicode(value, 'utf-8', 'ignore') sqlExecute( @@ -333,266 +293,227 @@ class Ui_SubscriptionWidget(Ui_AddressWidget): return super(Ui_SubscriptionWidget, self).setData(column, role, value) -class BMTableWidgetItem(QtGui.QTableWidgetItem, SettingsMixin): - """A common abstract class for Table widget item""" - - def __init__(self, label=None, unread=False): +class MessageList_AddressWidget(QtGui.QTableWidgetItem, AccountMixin, SettingsMixin): + def __init__(self, parent, address = None, label = None, unread = False): super(QtGui.QTableWidgetItem, self).__init__() + #parent.insertTopLevelItem(pos, self) + # only set default when creating + #super(QtGui.QTreeWidgetItem, self).setExpanded(BMConfigParser().getboolean(self.address, 'enabled')) + self.isEnabled = True + self.setAddress(address) self.setLabel(label) self.setUnread(unread) - self._setup() - - def _setup(self): self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + self.setType() + parent.append(self) - def setLabel(self, label): - """Set object label""" - self.label = label + def setLabel(self, label = None): + newLabel = self.address + if label is None: + queryreturn = None + if self.type in (AccountMixin.NORMAL, AccountMixin.CHAN, AccountMixin.MAILINGLIST): + try: + newLabel = unicode(BMConfigParser().get(self.address, 'label'), 'utf-8', 'ignore') + except: + queryreturn = sqlQuery( + '''select label from addressbook where address=?''', self.address) + elif self.type == AccountMixin.SUBSCRIPTION: + queryreturn = sqlQuery( + '''select label from subscriptions where address=?''', self.address) + if queryreturn is not None: + if queryreturn != []: + for row in queryreturn: + newLabel = unicode(row[0], 'utf-8', 'ignore') + else: + newLabel = label + if hasattr(self, 'label') and newLabel == self.label: + return + self.label = newLabel def setUnread(self, unread): - """Set/unset read state of an item""" self.unread = unread def data(self, role): - """Return object data (QT UI)""" - if role in ( - QtCore.Qt.DisplayRole, QtCore.Qt.EditRole, QtCore.Qt.ToolTipRole - ): + if role == QtCore.Qt.DisplayRole: return self.label + elif role == QtCore.Qt.EditRole: + return self.label + elif role == QtCore.Qt.ToolTipRole: + return self.label + " (" + self.address + ")" + elif role == QtCore.Qt.DecorationRole: + if BMConfigParser().safeGetBoolean('bitmessagesettings', 'useidenticons'): + if self.address is None: + return avatarize(self.label) + else: + return avatarize(self.address) elif role == QtCore.Qt.FontRole: font = QtGui.QFont() font.setBold(self.unread) return font - return super(BMTableWidgetItem, self).data(role) - - -class BMAddressWidget(BMTableWidgetItem, AccountMixin): - """A common class for Table widget item with account""" - - def _setup(self): - super(BMAddressWidget, self)._setup() - self.setEnabled(True) - self.setType() - - def _getLabel(self): - return self.label - - def data(self, role): - """Return object data (QT UI)""" - if role == QtCore.Qt.ToolTipRole: - return self.label + " (" + self.address + ")" - elif role == QtCore.Qt.DecorationRole: - if config.safeGetBoolean( - 'bitmessagesettings', 'useidenticons'): - return avatarize(self.address or self.label) elif role == QtCore.Qt.ForegroundRole: return self.accountBrush() - return super(BMAddressWidget, self).data(role) - - -class MessageList_AddressWidget(BMAddressWidget): - """Address item in a messagelist""" - def __init__(self, address=None, label=None, unread=False): - self.setAddress(address) - super(MessageList_AddressWidget, self).__init__(label, unread) - - def setLabel(self, label=None): - """Set label""" - super(MessageList_AddressWidget, self).setLabel(label) - if label is not None: - return - newLabel = self.address - queryreturn = None - if self.type in ( - AccountMixin.NORMAL, - AccountMixin.CHAN, AccountMixin.MAILINGLIST): - try: - newLabel = unicode( - config.get(self.address, 'label'), - 'utf-8', 'ignore') - except: - queryreturn = sqlQuery( - '''select label from addressbook where address=?''', self.address) - elif self.type == AccountMixin.SUBSCRIPTION: - queryreturn = sqlQuery( - '''select label from subscriptions where address=?''', self.address) - if queryreturn: - for row in queryreturn: - newLabel = unicode(row[0], 'utf-8', 'ignore') - - self.label = newLabel - - def data(self, role): - """Return object data (QT UI)""" - if role == QtCore.Qt.UserRole: + elif role == QtCore.Qt.UserRole: return self.address return super(MessageList_AddressWidget, self).data(role) - + def setData(self, role, value): - """Set object data""" if role == QtCore.Qt.EditRole: self.setLabel() return super(MessageList_AddressWidget, self).setData(role, value) # label (or address) alphabetically, disabled at the end def __lt__(self, other): - if isinstance(other, MessageList_AddressWidget): + if (isinstance(other, MessageList_AddressWidget)): return self.label.lower() < other.label.lower() return super(QtGui.QTableWidgetItem, self).__lt__(other) -class MessageList_SubjectWidget(BMTableWidgetItem): - """Message list subject item""" - def __init__(self, subject=None, label=None, unread=False): +class MessageList_SubjectWidget(QtGui.QTableWidgetItem, SettingsMixin): + def __init__(self, parent, subject = None, label = None, unread = False): + super(QtGui.QTableWidgetItem, self).__init__() + #parent.insertTopLevelItem(pos, self) + # only set default when creating + #super(QtGui.QTreeWidgetItem, self).setExpanded(BMConfigParser().getboolean(self.address, 'enabled')) self.setSubject(subject) - super(MessageList_SubjectWidget, self).__init__(label, unread) + self.setLabel(label) + self.setUnread(unread) + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + parent.append(self) + def setLabel(self, label): + self.label = label + def setSubject(self, subject): - """Set subject""" self.subject = subject + def setUnread(self, unread): + self.unread = unread + def data(self, role): - """Return object data (QT UI)""" - if role == QtCore.Qt.UserRole: + if role == QtCore.Qt.DisplayRole: + return self.label + elif role == QtCore.Qt.EditRole: + return self.label + elif role == QtCore.Qt.ToolTipRole: + return self.label + elif role == QtCore.Qt.FontRole: + font = QtGui.QFont() + font.setBold(self.unread) + return font + elif role == QtCore.Qt.UserRole: return self.subject - if role == QtCore.Qt.ToolTipRole: - return escape(unicode(self.subject, 'utf-8')) return super(MessageList_SubjectWidget, self).data(role) + + def setData(self, role, value): + return super(MessageList_SubjectWidget, self).setData(role, value) # label (or address) alphabetically, disabled at the end def __lt__(self, other): - if isinstance(other, MessageList_SubjectWidget): + if (isinstance(other, MessageList_SubjectWidget)): return self.label.lower() < other.label.lower() return super(QtGui.QTableWidgetItem, self).__lt__(other) -# 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 MessageList_TimeWidget(BMTableWidgetItem): - """ - A subclass of QTableWidgetItem for received (lastactiontime) field. - '<' operator is overloaded to sort by TimestampRole == 33 - msgid is available by QtCore.Qt.UserRole - """ - - def __init__(self, label=None, unread=False, timestamp=None, msgid=''): - super(MessageList_TimeWidget, self).__init__(label, unread) - self.setData(QtCore.Qt.UserRole, QtCore.QByteArray(msgid)) - self.setData(TimestampRole, int(timestamp)) - - def __lt__(self, other): - return self.data(TimestampRole) < other.data(TimestampRole) - - def data(self, role=QtCore.Qt.UserRole): - """ - Returns expected python types for QtCore.Qt.UserRole and TimestampRole - custom roles and super for any Qt role - """ - data = super(MessageList_TimeWidget, self).data(role) - if role == TimestampRole: - return int(data.toPyObject()) - if role == QtCore.Qt.UserRole: - return str(data.toPyObject()) - return data - - -class Ui_AddressBookWidgetItem(BMAddressWidget): - """Addressbook item""" - # pylint: disable=unused-argument - def __init__(self, label=None, acc_type=AccountMixin.NORMAL): - self.type = acc_type - super(Ui_AddressBookWidgetItem, self).__init__(label=label) +class Ui_AddressBookWidgetItem(QtGui.QTableWidgetItem, AccountMixin): + def __init__ (self, text, type = AccountMixin.NORMAL): + super(QtGui.QTableWidgetItem, self).__init__(text) + self.label = text + self.type = type + self.setEnabled(True) def data(self, role): - """Return object data""" - if role == QtCore.Qt.UserRole: + if role == QtCore.Qt.DisplayRole: + return self.label + elif role == QtCore.Qt.EditRole: + return self.label + elif role == QtCore.Qt.ToolTipRole: + return self.label + " (" + self.address + ")" + elif role == QtCore.Qt.DecorationRole: + if BMConfigParser().safeGetBoolean('bitmessagesettings', 'useidenticons'): + if self.address is None: + return avatarize(self.label) + else: + return avatarize(self.address) + elif role == QtCore.Qt.FontRole: + font = QtGui.QFont() + return font + elif role == QtCore.Qt.ForegroundRole: + return self.accountBrush() + elif role == QtCore.Qt.UserRole: return self.type return super(Ui_AddressBookWidgetItem, self).data(role) def setData(self, role, value): - """Set data""" if role == QtCore.Qt.EditRole: - self.label = str( - value.toString().toUtf8() - if isinstance(value, QtCore.QVariant) else value - ) - if self.type in ( - AccountMixin.NORMAL, - AccountMixin.MAILINGLIST, AccountMixin.CHAN): + if isinstance(value, QtCore.QVariant): + self.label = str(value.toString().toUtf8()) + else: + self.label = str(value) + if self.type in (AccountMixin.NORMAL, AccountMixin.MAILINGLIST, AccountMixin.CHAN): try: - config.get(self.address, 'label') - config.set(self.address, 'label', self.label) - config.save() + a = BMConfigParser().get(self.address, 'label') + BMConfigParser().set(self.address, 'label', self.label) + BMConfigParser().save() except: sqlExecute('''UPDATE addressbook set label=? WHERE address=?''', self.label, self.address) elif self.type == AccountMixin.SUBSCRIPTION: + from debug import logger sqlExecute('''UPDATE subscriptions set label=? WHERE address=?''', self.label, self.address) else: pass - return super(Ui_AddressBookWidgetItem, self).setData(role, value) - - def __lt__(self, other): - if isinstance(other, Ui_AddressBookWidgetItem): - reverse = QtCore.Qt.DescendingOrder == \ - self.tableWidget().horizontalHeader().sortIndicatorOrder() + return super(Ui_AddressBookWidgetItem, self).setData(role, value) + def __lt__ (self, other): + if (isinstance(other, Ui_AddressBookWidgetItem)): + reverse = False + if self.tableWidget().horizontalHeader().sortIndicatorOrder() == QtCore.Qt.DescendingOrder: + reverse = True if self.type == other.type: return self.label.lower() < other.label.lower() - return not reverse if self.type < other.type else reverse + else: + return (not reverse if self.type < other.type else reverse) return super(QtGui.QTableWidgetItem, self).__lt__(other) class Ui_AddressBookWidgetItemLabel(Ui_AddressBookWidgetItem): - """Addressbook label item""" - def __init__(self, address, label, acc_type): + def __init__ (self, address, label, type): + Ui_AddressBookWidgetItem.__init__(self, label, type) self.address = address - super(Ui_AddressBookWidgetItemLabel, self).__init__(label, acc_type) + self.label = label def data(self, role): - """Return object data""" self.label = self.defaultLabel() return super(Ui_AddressBookWidgetItemLabel, self).data(role) class Ui_AddressBookWidgetItemAddress(Ui_AddressBookWidgetItem): - """Addressbook address item""" - def __init__(self, address, label, acc_type): + def __init__ (self, address, label, type): + Ui_AddressBookWidgetItem.__init__(self, address, type) self.address = address - super(Ui_AddressBookWidgetItemAddress, self).__init__(address, acc_type) - - def data(self, role): - """Return object data""" - if role == QtCore.Qt.ToolTipRole: - return self.address - if role == QtCore.Qt.DecorationRole: - return None - return super(Ui_AddressBookWidgetItemAddress, self).data(role) - - + self.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) + class AddressBookCompleter(QtGui.QCompleter): - """Addressbook completer""" - def __init__(self): - super(AddressBookCompleter, self).__init__() + super(QtGui.QCompleter, self).__init__() self.cursorPos = -1 - - def onCursorPositionChanged(self, oldPos, newPos): # pylint: disable=unused-argument - """Callback for cursor position change""" + + def onCursorPositionChanged(self, oldPos, newPos): if oldPos != self.cursorPos: self.cursorPos = -1 - + def splitPath(self, path): - """Split on semicolon""" - text = unicode(path.toUtf8(), 'utf-8') - return [text[:self.widget().cursorPosition()].split(';')[-1].strip()] - + stringList = [] + text = unicode(path.toUtf8(), encoding="UTF-8") + splitIndex = rfind(text[0:self.widget().cursorPosition()], ";") + 1 + str = text[splitIndex:self.widget().cursorPosition()] + str = rstrip(lstrip(str)) + stringList.append(str) + return stringList + def pathFromIndex(self, index): - """Perform autocompletion (reimplemented QCompleter method)""" - autoString = unicode( - index.data(QtCore.Qt.EditRole).toString().toUtf8(), 'utf-8') - text = unicode(self.widget().text().toUtf8(), 'utf-8') - + autoString = unicode(index.data(QtCore.Qt.EditRole).toString().toUtf8(), encoding="UTF-8") + text = unicode(self.widget().text().toUtf8(), encoding="UTF-8") + # If cursor position was saved, restore it, else save it if self.cursorPos != -1: self.widget().setCursorPosition(self.cursorPos) @@ -601,17 +522,15 @@ class AddressBookCompleter(QtGui.QCompleter): # Get current prosition curIndex = self.widget().cursorPosition() - - # prev_delimiter_index should actually point at final white space - # AFTER the delimiter + + # prev_delimiter_index should actually point at final white space AFTER the delimiter # Get index of last delimiter before current position - prevDelimiterIndex = text[0:curIndex].rfind(";") + prevDelimiterIndex = rfind(text[0:curIndex], ";") while text[prevDelimiterIndex + 1] == " ": prevDelimiterIndex += 1 - - # Get index of first delimiter after current position - # (or EOL if no delimiter after cursor) - nextDelimiterIndex = text.find(";", curIndex) + + # Get index of first delimiter after current position (or EOL if no delimiter after cursor) + nextDelimiterIndex = find(text, ";", curIndex) if nextDelimiterIndex == -1: nextDelimiterIndex = len(text) @@ -619,9 +538,9 @@ class AddressBookCompleter(QtGui.QCompleter): part1 = text[0:prevDelimiterIndex + 1] # Get string value from before auto finished string is selected - # pre = text[prevDelimiterIndex + 1:curIndex - 1] + pre = text[prevDelimiterIndex + 1:curIndex - 1]; # Get part of string that occurs AFTER cursor part2 = text[nextDelimiterIndex:] - return part1 + autoString + part2 + return part1 + autoString + part2; diff --git a/src/bitmessageqt/languagebox.py b/src/bitmessageqt/languagebox.py index 34f96b02..552e0350 100644 --- a/src/bitmessageqt/languagebox.py +++ b/src/bitmessageqt/languagebox.py @@ -1,48 +1,37 @@ -"""Language Box Module for Locale Settings""" -# pylint: disable=too-few-public-methods,bad-continuation import glob import os - from PyQt4 import QtCore, QtGui +from bmconfigparser import BMConfigParser import paths -from bmconfigparser import config - class LanguageBox(QtGui.QComboBox): - """LanguageBox class for Qt UI""" - languageName = { - "system": "System Settings", "eo": "Esperanto", - "en_pirate": "Pirate English" - } - - def __init__(self, parent=None): + languageName = {"system": "System Settings", "eo": "Esperanto", "en_pirate": "Pirate English"} + def __init__(self, parent = None): super(QtGui.QComboBox, self).__init__(parent) self.populate() def populate(self): - """Populates drop down list with all available languages.""" + self.languages = [] self.clear() - localesPath = os.path.join(paths.codePath(), 'translations') - self.addItem(QtGui.QApplication.translate( - "settingsDialog", "System Settings", "system"), "system") + localesPath = os.path.join (paths.codePath(), 'translations') + configuredLocale = "system" + try: + configuredLocale = BMConfigParser().get('bitmessagesettings', 'userlocale', "system") + except: + pass + self.addItem(QtGui.QApplication.translate("settingsDialog", "System Settings", "system"), "system") self.setCurrentIndex(0) self.setInsertPolicy(QtGui.QComboBox.InsertAlphabetically) - for translationFile in sorted( - glob.glob(os.path.join(localesPath, "bitmessage_*.qm")) - ): - localeShort = \ - os.path.split(translationFile)[1].split("_", 1)[1][:-3] + for translationFile in sorted(glob.glob(os.path.join(localesPath, "bitmessage_*.qm"))): + localeShort = os.path.split(translationFile)[1].split("_", 1)[1][:-3] + locale = QtCore.QLocale(QtCore.QString(localeShort)) if localeShort in LanguageBox.languageName: - self.addItem( - LanguageBox.languageName[localeShort], localeShort) + self.addItem(LanguageBox.languageName[localeShort], localeShort) + elif locale.nativeLanguageName() == "": + self.addItem(localeShort, localeShort) else: - locale = QtCore.QLocale(localeShort) - self.addItem( - locale.nativeLanguageName() or localeShort, localeShort) - - configuredLocale = config.safeGet( - 'bitmessagesettings', 'userlocale', "system") + self.addItem(locale.nativeLanguageName(), localeShort) for i in range(self.count()): if self.itemData(i) == configuredLocale: self.setCurrentIndex(i) diff --git a/src/bitmessageqt/messagecompose.py b/src/bitmessageqt/messagecompose.py index c51282f8..f7d5dac3 100644 --- a/src/bitmessageqt/messagecompose.py +++ b/src/bitmessageqt/messagecompose.py @@ -1,37 +1,23 @@ -""" -Message editor with a wheel zoom functionality -""" -# pylint: disable=bad-continuation - from PyQt4 import QtCore, QtGui - class MessageCompose(QtGui.QTextEdit): - """Editor class with wheel zoom functionality""" - def __init__(self, parent=0): + + def __init__(self, parent = 0): super(MessageCompose, self).__init__(parent) - self.setAcceptRichText(False) + self.setAcceptRichText(False) # we'll deal with this later when we have a new message format self.defaultFontPointSize = self.currentFont().pointSize() - + def wheelEvent(self, event): - """Mouse wheel scroll event handler""" - if ( - QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ControlModifier - ) == QtCore.Qt.ControlModifier and event.orientation() == QtCore.Qt.Vertical: + if (QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ControlModifier) == QtCore.Qt.ControlModifier and event.orientation() == QtCore.Qt.Vertical: if event.delta() > 0: self.zoomIn(1) else: self.zoomOut(1) zoom = self.currentFont().pointSize() * 100 / self.defaultFontPointSize - QtGui.QApplication.activeWindow().statusBar().showMessage( - QtGui.QApplication.translate("MainWindow", "Zoom level %1%").arg( - str(zoom) - ) - ) + QtGui.QApplication.activeWindow().statusBar().showMessage(QtGui.QApplication.translate("MainWindow", "Zoom level %1%").arg(str(zoom))) else: # in QTextEdit, super does not zoom, only scroll super(MessageCompose, self).wheelEvent(event) def reset(self): - """Clear the edit content""" self.setText('') diff --git a/src/bitmessageqt/messageview.py b/src/bitmessageqt/messageview.py index 13ea16f9..4d2e768d 100644 --- a/src/bitmessageqt/messageview.py +++ b/src/bitmessageqt/messageview.py @@ -1,24 +1,17 @@ -""" -Custom message viewer with support for switching between HTML and plain -text rendering, HTML sanitization, lazy rendering (as you scroll down), -zoom and URL click warning popup - -""" - from PyQt4 import QtCore, QtGui -from safehtmlparser import SafeHTMLParser -from tr import _translate - +import multiprocessing +import Queue +from urlparse import urlparse +from safehtmlparser import * class MessageView(QtGui.QTextBrowser): - """Message content viewer class, can switch between plaintext and HTML""" MODE_PLAIN = 0 MODE_HTML = 1 - - def __init__(self, parent=0): + + def __init__(self, parent = 0): super(MessageView, self).__init__(parent) - self.mode = MessageView.MODE_PLAIN + self.mode = MessageView.MODE_PLAIN self.html = None self.setOpenExternalLinks(False) self.setOpenLinks(False) @@ -32,14 +25,12 @@ class MessageView(QtGui.QTextBrowser): self.setWrappingWidth() def resizeEvent(self, event): - """View resize event handler""" super(MessageView, self).resizeEvent(event) self.setWrappingWidth(event.size().width()) - + def mousePressEvent(self, event): - """Mouse press button event handler""" - if event.button() == QtCore.Qt.LeftButton and self.html and self.html.has_html and self.cursorForPosition( - event.pos()).block().blockNumber() == 0: + #text = textCursor.block().text() + if event.button() == QtCore.Qt.LeftButton and self.html and self.html.has_html and self.cursorForPosition(event.pos()).block().blockNumber() == 0: if self.mode == MessageView.MODE_PLAIN: self.showHTML() else: @@ -48,25 +39,19 @@ class MessageView(QtGui.QTextBrowser): super(MessageView, self).mousePressEvent(event) def wheelEvent(self, event): - """Mouse wheel scroll event handler""" # super will actually automatically take care of zooming super(MessageView, self).wheelEvent(event) - if ( - QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ControlModifier - ) == QtCore.Qt.ControlModifier and event.orientation() == QtCore.Qt.Vertical: + if (QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ControlModifier) == QtCore.Qt.ControlModifier and event.orientation() == QtCore.Qt.Vertical: zoom = self.currentFont().pointSize() * 100 / self.defaultFontPointSize - QtGui.QApplication.activeWindow().statusBar().showMessage(_translate( - "MainWindow", "Zoom level %1%").arg(str(zoom))) + QtGui.QApplication.activeWindow().statusBar().showMessage(QtGui.QApplication.translate("MainWindow", "Zoom level %1%").arg(str(zoom))) def setWrappingWidth(self, width=None): - """Set word-wrapping width""" self.setLineWrapMode(QtGui.QTextEdit.FixedPixelWidth) if width is None: width = self.width() self.setLineWrapColumnOrWidth(width) def confirmURL(self, link): - """Show a dialog requesting URL opening confirmation""" if link.scheme() == "mailto": window = QtGui.QApplication.activeWindow() window.ui.lineEditTo.setText(link.path()) @@ -83,39 +68,35 @@ class MessageView(QtGui.QTextBrowser): ) 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(unicode(link.toString())), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) + 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(unicode(link.toString())), + QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: QtGui.QDesktopServices.openUrl(link) - def loadResource(self, restype, name): - """ - Callback for loading referenced objects, such as an image. For security reasons at the moment doesn't do - anything) - """ - pass + def loadResource (self, restype, name): + if restype == QtGui.QTextDocument.ImageResource and name.scheme() == "bmmsg": + pass +# QImage correctImage; +# lookup the correct QImage from a cache +# return QVariant::fromValue(correctImage); +# elif restype == QtGui.QTextDocument.HtmlResource: +# elif restype == QtGui.QTextDocument.ImageResource: +# elif restype == QtGui.QTextDocument.StyleSheetResource: +# elif restype == QtGui.QTextDocument.UserResource: + else: + pass +# by default, this will interpret it as a local file +# QtGui.QTextBrowser.loadResource(restype, name) def lazyRender(self): - """ - Partially render a message. This is to avoid UI freezing when loading huge messages. It continues loading as - you scroll down. - """ if self.rendering: return self.rendering = True position = self.verticalScrollBar().value() cursor = QtGui.QTextCursor(self.document()) - while self.outpos < len(self.out) and self.verticalScrollBar().value( - ) >= self.document().size().height() - 2 * self.size().height(): + while self.outpos < len(self.out) and self.verticalScrollBar().value() >= self.document().size().height() - 2 * self.size().height(): startpos = self.outpos self.outpos += 10240 # find next end of tag @@ -127,33 +108,27 @@ class MessageView(QtGui.QTextBrowser): cursor.insertHtml(QtCore.QString(self.out[startpos:self.outpos])) self.verticalScrollBar().setValue(position) self.rendering = False - + def showPlain(self): - """Render message as plain text.""" self.mode = MessageView.MODE_PLAIN out = self.html.raw if self.html.has_html: - out = "
" + unicode( - QtGui.QApplication.translate( - "MessageView", "HTML detected, click here to display")) + "

" + out + out = "
" + unicode(QtGui.QApplication.translate("MessageView", "HTML detected, click here to display")) + "

" + out self.out = out self.outpos = 0 self.setHtml("") self.lazyRender() def showHTML(self): - """Render message as HTML""" self.mode = MessageView.MODE_HTML out = self.html.sanitised - out = "
" + unicode( - QtGui.QApplication.translate("MessageView", "Click here to disable HTML")) + "

" + out + out = "
" + unicode(QtGui.QApplication.translate("MessageView", "Click here to disable HTML")) + "

" + out self.out = out self.outpos = 0 self.setHtml("") self.lazyRender() def setContent(self, data): - """Set message content from argument""" self.html = SafeHTMLParser() self.html.reset() self.html.reset_safe() diff --git a/src/bitmessageqt/networkstatus.py b/src/bitmessageqt/networkstatus.py index e7fd9e94..06b1e0ce 100644 --- a/src/bitmessageqt/networkstatus.py +++ b/src/bitmessageqt/networkstatus.py @@ -1,24 +1,20 @@ -""" -Network status tab widget definition. -""" - -import time - from PyQt4 import QtCore, QtGui +import time +import shared +from tr import _translate +from inventory import Inventory, PendingDownloadQueue, PendingUpload +import knownnodes import l10n import network.stats -import state -import widgets -from inventory import Inventory -from network import BMConnectionPool, knownnodes from retranslateui import RetranslateMixin -from tr import _translate from uisignaler import UISignaler +import widgets + +from network.connectionpool import BMConnectionPool class NetworkStatus(QtGui.QWidget, RetranslateMixin): - """Network status tab""" def __init__(self, parent=None): super(NetworkStatus, self).__init__(parent) widgets.load('networkstatus.ui', self) @@ -31,9 +27,10 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin): header.setSortIndicator(0, QtCore.Qt.AscendingOrder) self.startup = time.localtime() - + self.labelStartupTime.setText(_translate("networkstatus", "Since startup on %1").arg( + l10n.formatTimestamp(self.startup))) + self.UISignalThread = UISignaler.get() - # pylint: disable=no-member QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "updateNumberOfMessagesProcessed()"), self.updateNumberOfMessagesProcessed) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( @@ -45,108 +42,57 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin): self.timer = QtCore.QTimer() - QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.runEveryTwoSeconds) - # pylint: enable=no-member + QtCore.QObject.connect( + self.timer, QtCore.SIGNAL("timeout()"), self.runEveryTwoSeconds) def startUpdate(self): - """Start a timer to update counters every 2 seconds""" Inventory().numberOfInventoryLookupsPerformed = 0 self.runEveryTwoSeconds() self.timer.start(2000) # milliseconds def stopUpdate(self): - """Stop counter update timer""" self.timer.stop() def formatBytes(self, num): - """Format bytes nicely (SI prefixes)""" - # pylint: disable=no-self-use - for x in [ - _translate( - "networkstatus", - "byte(s)", - None, - QtCore.QCoreApplication.CodecForTr, - num), - "kB", - "MB", - "GB", - ]: + for x in [_translate("networkstatus", "byte(s)", None, QtCore.QCoreApplication.CodecForTr, num), "kB", "MB", "GB"]: if num < 1000.0: return "%3.0f %s" % (num, x) num /= 1000.0 return "%3.0f %s" % (num, 'TB') def formatByteRate(self, num): - """Format transfer speed in kB/s""" - # pylint: disable=no-self-use num /= 1000 return "%4.0f kB" % num - + def updateNumberOfObjectsToBeSynced(self): - """Update the counter for number of objects to be synced""" - self.labelSyncStatus.setText( - _translate( - "networkstatus", - "Object(s) to be synced: %n", - None, - QtCore.QCoreApplication.CodecForTr, - network.stats.pendingDownload() - + network.stats.pendingUpload())) + self.labelSyncStatus.setText(_translate("networkstatus", "Object(s) to be synced: %n", None, QtCore.QCoreApplication.CodecForTr, network.stats.pendingDownload() + network.stats.pendingUpload())) def updateNumberOfMessagesProcessed(self): - """Update the counter for number of processed messages""" self.updateNumberOfObjectsToBeSynced() - self.labelMessageCount.setText( - _translate( - "networkstatus", - "Processed %n person-to-person message(s).", - None, - QtCore.QCoreApplication.CodecForTr, - state.numberOfMessagesProcessed)) + self.labelMessageCount.setText(_translate( + "networkstatus", "Processed %n person-to-person message(s).", None, QtCore.QCoreApplication.CodecForTr, shared.numberOfMessagesProcessed)) def updateNumberOfBroadcastsProcessed(self): - """Update the counter for the number of processed broadcasts""" self.updateNumberOfObjectsToBeSynced() - self.labelBroadcastCount.setText( - _translate( - "networkstatus", - "Processed %n broadcast message(s).", - None, - QtCore.QCoreApplication.CodecForTr, - state.numberOfBroadcastsProcessed)) + self.labelBroadcastCount.setText(_translate( + "networkstatus", "Processed %n broadcast message(s).", None, QtCore.QCoreApplication.CodecForTr, shared.numberOfBroadcastsProcessed)) def updateNumberOfPubkeysProcessed(self): - """Update the counter for the number of processed pubkeys""" self.updateNumberOfObjectsToBeSynced() - self.labelPubkeyCount.setText( - _translate( - "networkstatus", - "Processed %n public key(s).", - None, - QtCore.QCoreApplication.CodecForTr, - state.numberOfPubkeysProcessed)) + self.labelPubkeyCount.setText(_translate( + "networkstatus", "Processed %n public key(s).", None, QtCore.QCoreApplication.CodecForTr, shared.numberOfPubkeysProcessed)) def updateNumberOfBytes(self): """ This function is run every two seconds, so we divide the rate of bytes sent and received by 2. """ - self.labelBytesRecvCount.setText( - _translate( - "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(network.stats.uploadSpeed()), - self.formatBytes(network.stats.sentBytes()))) + self.labelBytesRecvCount.setText(_translate( + "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(network.stats.uploadSpeed()), self.formatBytes(network.stats.sentBytes()))) def updateNetworkStatusTab(self, outbound, add, destination): - """Add or remove an entry to the list of connected peers""" - # pylint: disable=too-many-branches,undefined-variable if outbound: try: c = BMConnectionPool().outboundConnections[destination] @@ -165,85 +111,62 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin): self.tableWidgetConnectionCount.setUpdatesEnabled(False) self.tableWidgetConnectionCount.setSortingEnabled(False) - if add: self.tableWidgetConnectionCount.insertRow(0) - self.tableWidgetConnectionCount.setItem( - 0, 0, + self.tableWidgetConnectionCount.setItem(0, 0, QtGui.QTableWidgetItem("%s:%i" % (destination.host, destination.port)) - ) - self.tableWidgetConnectionCount.setItem( - 0, 2, + ) + self.tableWidgetConnectionCount.setItem(0, 2, QtGui.QTableWidgetItem("%s" % (c.userAgent)) - ) - self.tableWidgetConnectionCount.setItem( - 0, 3, + ) + self.tableWidgetConnectionCount.setItem(0, 3, QtGui.QTableWidgetItem("%s" % (c.tlsVersion)) - ) - self.tableWidgetConnectionCount.setItem( - 0, 4, - QtGui.QTableWidgetItem("%s" % (",".join(map(str, c.streams)))) - ) + ) + self.tableWidgetConnectionCount.setItem(0, 4, + QtGui.QTableWidgetItem("%s" % (",".join(map(str,c.streams)))) + ) try: - # .. todo:: FIXME: hard coded stream no + # FIXME hard coded stream no rating = "%.1f" % (knownnodes.knownNodes[1][destination]['rating']) except KeyError: rating = "-" - self.tableWidgetConnectionCount.setItem( - 0, 1, + self.tableWidgetConnectionCount.setItem(0, 1, QtGui.QTableWidgetItem("%s" % (rating)) - ) + ) if outbound: brush = QtGui.QBrush(QtGui.QColor("yellow"), QtCore.Qt.SolidPattern) else: brush = QtGui.QBrush(QtGui.QColor("green"), QtCore.Qt.SolidPattern) - for j in range(1): + 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: - if not BMConnectionPool().inboundConnections: - self.window().setStatusIcon('yellow') 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(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 state.statusIconColor == 'red': + self.labelTotalConnections.setText(_translate( + "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 self.tableWidgetConnectionCount.rowCount() == 0 and state.statusIconColor != "red": + elif self.tableWidgetConnectionCount.rowCount() == 0 and shared.statusIconColor != "red": self.window().setStatusIcon('red') # timer driven def runEveryTwoSeconds(self): - """Updates counters, runs every 2 seconds if the timer is running""" - self.labelLookupsPerSecond.setText(_translate("networkstatus", "Inventory lookups per second: %1").arg( - str(Inventory().numberOfInventoryLookupsPerformed / 2))) + self.labelLookupsPerSecond.setText(_translate( + "networkstatus", "Inventory lookups per second: %1").arg(str(Inventory().numberOfInventoryLookupsPerformed/2))) Inventory().numberOfInventoryLookupsPerformed = 0 self.updateNumberOfBytes() self.updateNumberOfObjectsToBeSynced() def retranslateUi(self): - """Conventional Qt Designer method for dynamic l10n""" super(NetworkStatus, self).retranslateUi() - self.labelTotalConnections.setText( - _translate( - "networkstatus", "Total Connections: %1").arg( - str(self.tableWidgetConnectionCount.rowCount()))) - self.labelStartupTime.setText(_translate( - "networkstatus", "Since startup on %1" - ).arg(l10n.formatTimestamp(self.startup))) - self.updateNumberOfMessagesProcessed() - self.updateNumberOfBroadcastsProcessed() - self.updateNumberOfPubkeysProcessed() + self.labelStartupTime.setText(_translate("networkstatus", "Since startup on %1").arg( + l10n.formatTimestamp(self.startup))) diff --git a/src/bitmessageqt/newaddressdialog.ui b/src/bitmessageqt/newaddressdialog.ui index 8b5276cc..a9eda5c3 100644 --- a/src/bitmessageqt/newaddressdialog.ui +++ b/src/bitmessageqt/newaddressdialog.ui @@ -309,10 +309,9 @@ The 'Random Number' option is selected by default but deterministic addresses ha - newaddresslabel - buttonBox - radioButtonDeterministicAddress radioButtonRandomAddress + radioButtonDeterministicAddress + newaddresslabel radioButtonMostAvailable radioButtonExisting comboBoxExisting @@ -320,6 +319,7 @@ The 'Random Number' option is selected by default but deterministic addresses ha lineEditPassphraseAgain spinBoxNumberOfAddressesToMake checkBoxEighteenByteRipe + buttonBox diff --git a/src/bitmessageqt/newaddresswizard.py b/src/bitmessageqt/newaddresswizard.py new file mode 100644 index 00000000..2311239c --- /dev/null +++ b/src/bitmessageqt/newaddresswizard.py @@ -0,0 +1,354 @@ +#!/usr/bin/env python2.7 +from PyQt4 import QtCore, QtGui + +class NewAddressWizardIntroPage(QtGui.QWizardPage): + def __init__(self): + super(QtGui.QWizardPage, self).__init__() + self.setTitle("Creating a new address") + + label = QtGui.QLabel("This wizard will help you create as many addresses as you like. Indeed, creating and abandoning addresses is encouraged.\n\n" + "What type of address would you like? Would you like to send emails or not?\n" + "You can still change your mind later, and register/unregister with an email service provider.\n\n") + label.setWordWrap(True) + + self.emailAsWell = QtGui.QRadioButton("Combined email and bitmessage address") + self.onlyBM = QtGui.QRadioButton("Bitmessage-only address (no email)") + self.emailAsWell.setChecked(True) + self.registerField("emailAsWell", self.emailAsWell) + self.registerField("onlyBM", self.onlyBM) + + layout = QtGui.QVBoxLayout() + layout.addWidget(label) + layout.addWidget(self.emailAsWell) + layout.addWidget(self.onlyBM) + self.setLayout(layout) + + def nextId(self): + if self.emailAsWell.isChecked(): + return 4 + else: + return 1 + + +class NewAddressWizardRngPassphrasePage(QtGui.QWizardPage): + def __init__(self): + super(QtGui.QWizardPage, self).__init__() + self.setTitle("Random or Passphrase") + + label = QtGui.QLabel("

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:

" + "" + "" + "
Pros:Cons:
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.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." + "

") + label.setWordWrap(True) + + self.randomAddress = QtGui.QRadioButton("Use a random number generator to make an address") + self.deterministicAddress = QtGui.QRadioButton("Use a passphrase to make an address") + self.randomAddress.setChecked(True) + + layout = QtGui.QVBoxLayout() + layout.addWidget(label) + layout.addWidget(self.randomAddress) + layout.addWidget(self.deterministicAddress) + self.setLayout(layout) + + def nextId(self): + if self.randomAddress.isChecked(): + return 2 + else: + return 3 + +class NewAddressWizardRandomPage(QtGui.QWizardPage): + def __init__(self, addresses): + super(QtGui.QWizardPage, self).__init__() + self.setTitle("Random") + + label = QtGui.QLabel("Random address.") + label.setWordWrap(True) + + labelLabel = QtGui.QLabel("Label (not shown to anyone except you):") + self.labelLineEdit = QtGui.QLineEdit() + + self.radioButtonMostAvailable = QtGui.QRadioButton("Use the most available stream\n" + "(best if this is the first of many addresses you will create)") + self.radioButtonExisting = QtGui.QRadioButton("Use the same stream as an existing address\n" + "(saves you some bandwidth and processing power)") + self.radioButtonMostAvailable.setChecked(True) + self.comboBoxExisting = QtGui.QComboBox() + self.comboBoxExisting.setEnabled(False) + self.comboBoxExisting.setEditable(True) + + for address in addresses: + self.comboBoxExisting.addItem(address) + +# self.comboBoxExisting.setObjectName(_fromUtf8("comboBoxExisting")) + self.checkBoxEighteenByteRipe = QtGui.QCheckBox("Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter") + + layout = QtGui.QGridLayout() + layout.addWidget(label, 0, 0) + layout.addWidget(labelLabel, 1, 0) + layout.addWidget(self.labelLineEdit, 2, 0) + layout.addWidget(self.radioButtonMostAvailable, 3, 0) + layout.addWidget(self.radioButtonExisting, 4, 0) + layout.addWidget(self.comboBoxExisting, 5, 0) + layout.addWidget(self.checkBoxEighteenByteRipe, 6, 0) + self.setLayout(layout) + + QtCore.QObject.connect(self.radioButtonExisting, QtCore.SIGNAL("toggled(bool)"), self.comboBoxExisting.setEnabled) + + self.registerField("label", self.labelLineEdit) + self.registerField("radioButtonMostAvailable", self.radioButtonMostAvailable) + self.registerField("radioButtonExisting", self.radioButtonExisting) + self.registerField("comboBoxExisting", self.comboBoxExisting) + +# self.emailAsWell = QtGui.QRadioButton("Combined email and bitmessage account") +# self.onlyBM = QtGui.QRadioButton("Bitmessage-only account (no email)") +# self.emailAsWell.setChecked(True) + + def nextId(self): + return 6 + + +class NewAddressWizardPassphrasePage(QtGui.QWizardPage): + def __init__(self): + super(QtGui.QWizardPage, self).__init__() + self.setTitle("Passphrase") + + label = QtGui.QLabel("Deterministric address.") + label.setWordWrap(True) + + passphraseLabel = QtGui.QLabel("Passphrase") + self.lineEditPassphrase = QtGui.QLineEdit() + self.lineEditPassphrase.setEchoMode(QtGui.QLineEdit.Password) + self.lineEditPassphrase.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText) + retypePassphraseLabel = QtGui.QLabel("Retype passphrase") + self.lineEditPassphraseAgain = QtGui.QLineEdit() + self.lineEditPassphraseAgain.setEchoMode(QtGui.QLineEdit.Password) + + numberLabel = QtGui.QLabel("Number of addresses to make based on your passphrase:") + self.spinBoxNumberOfAddressesToMake = QtGui.QSpinBox() + self.spinBoxNumberOfAddressesToMake.setMinimum(1) + self.spinBoxNumberOfAddressesToMake.setProperty("value", 8) +# self.spinBoxNumberOfAddressesToMake.setObjectName(_fromUtf8("spinBoxNumberOfAddressesToMake")) + label2 = QtGui.QLabel("In addition to your passphrase, you must remember these numbers:") + label3 = QtGui.QLabel("Address version number: 4") + label4 = QtGui.QLabel("Stream number: 1") + + layout = QtGui.QGridLayout() + layout.addWidget(label, 0, 0, 1, 4) + layout.addWidget(passphraseLabel, 1, 0, 1, 4) + layout.addWidget(self.lineEditPassphrase, 2, 0, 1, 4) + layout.addWidget(retypePassphraseLabel, 3, 0, 1, 4) + layout.addWidget(self.lineEditPassphraseAgain, 4, 0, 1, 4) + layout.addWidget(numberLabel, 5, 0, 1, 3) + layout.addWidget(self.spinBoxNumberOfAddressesToMake, 5, 3) + layout.setColumnMinimumWidth(3, 1) + layout.addWidget(label2, 6, 0, 1, 4) + layout.addWidget(label3, 7, 0, 1, 2) + layout.addWidget(label4, 7, 2, 1, 2) + self.setLayout(layout) + + def nextId(self): + return 6 + + +class NewAddressWizardEmailProviderPage(QtGui.QWizardPage): + def __init__(self): + super(QtGui.QWizardPage, self).__init__() + self.setTitle("Choose email provider") + + label = QtGui.QLabel("Currently only Mailchuck email gateway is available " + "(@mailchuck.com email address). In the future, maybe other gateways will be available. " + "Press Next.") + label.setWordWrap(True) + +# self.mailchuck = QtGui.QRadioButton("Mailchuck email gateway (@mailchuck.com)") +# self.mailchuck.setChecked(True) + + layout = QtGui.QVBoxLayout() + layout.addWidget(label) +# layout.addWidget(self.mailchuck) + self.setLayout(layout) + + def nextId(self): + return 5 + + +class NewAddressWizardEmailAddressPage(QtGui.QWizardPage): + def __init__(self): + super(QtGui.QWizardPage, self).__init__() + self.setTitle("Email address") + + label = QtGui.QLabel("Choosing an email address. Address must end with @mailchuck.com") + label.setWordWrap(True) + + self.specificEmail = QtGui.QRadioButton("Pick your own email address:") + self.specificEmail.setChecked(True) + self.emailLineEdit = QtGui.QLineEdit() + self.randomEmail = QtGui.QRadioButton("Generate a random email address") + + QtCore.QObject.connect(self.specificEmail, QtCore.SIGNAL("toggled(bool)"), self.emailLineEdit.setEnabled) + + layout = QtGui.QVBoxLayout() + layout.addWidget(label) + layout.addWidget(self.specificEmail) + layout.addWidget(self.emailLineEdit) + layout.addWidget(self.randomEmail) + self.setLayout(layout) + + def nextId(self): + return 6 + + +class NewAddressWizardWaitPage(QtGui.QWizardPage): + def __init__(self): + super(QtGui.QWizardPage, self).__init__() + self.setTitle("Wait") + + self.label = QtGui.QLabel("Wait!") + self.label.setWordWrap(True) + self.progressBar = QtGui.QProgressBar() + self.progressBar.setMinimum(0) + self.progressBar.setMaximum(100) + self.progressBar.setValue(0) + +# self.emailAsWell = QtGui.QRadioButton("Combined email and bitmessage account") +# self.onlyBM = QtGui.QRadioButton("Bitmessage-only account (no email)") +# self.emailAsWell.setChecked(True) + + layout = QtGui.QVBoxLayout() + layout.addWidget(self.label) + layout.addWidget(self.progressBar) +# layout.addWidget(self.emailAsWell) +# layout.addWidget(self.onlyBM) + self.setLayout(layout) + + def update(self, i): + if i == 101 and self.wizard().currentId() == 6: + self.wizard().button(QtGui.QWizard.NextButton).click() + return + elif i == 101: + print "haha" + return + self.progressBar.setValue(i) + if i == 50: + self.emit(QtCore.SIGNAL('completeChanged()')) + + def isComplete(self): +# print "val = " + str(self.progressBar.value()) + if self.progressBar.value() >= 50: + return True + else: + return False + + def initializePage(self): + if self.field("emailAsWell").toBool(): + val = "yes/" + else: + val = "no/" + if self.field("onlyBM").toBool(): + val += "yes" + else: + val += "no" + + self.label.setText("Wait! " + val) +# self.wizard().button(QtGui.QWizard.NextButton).setEnabled(False) + self.progressBar.setValue(0) + self.thread = NewAddressThread() + self.connect(self.thread, self.thread.signal, self.update) + self.thread.start() + + def nextId(self): + return 10 + + +class NewAddressWizardConclusionPage(QtGui.QWizardPage): + def __init__(self): + super(QtGui.QWizardPage, self).__init__() + self.setTitle("All done!") + + label = QtGui.QLabel("You successfully created a new address.") + label.setWordWrap(True) + + layout = QtGui.QVBoxLayout() + layout.addWidget(label) + self.setLayout(layout) + +class Ui_NewAddressWizard(QtGui.QWizard): + def __init__(self, addresses): + super(QtGui.QWizard, self).__init__() + + self.pages = {} + + page = NewAddressWizardIntroPage() + self.setPage(0, page) + self.setStartId(0) + page = NewAddressWizardRngPassphrasePage() + self.setPage(1, page) + page = NewAddressWizardRandomPage(addresses) + self.setPage(2, page) + page = NewAddressWizardPassphrasePage() + self.setPage(3, page) + page = NewAddressWizardEmailProviderPage() + self.setPage(4, page) + page = NewAddressWizardEmailAddressPage() + self.setPage(5, page) + page = NewAddressWizardWaitPage() + self.setPage(6, page) + page = NewAddressWizardConclusionPage() + self.setPage(10, page) + + self.setWindowTitle("New address wizard") + self.adjustSize() + self.show() + +class NewAddressThread(QtCore.QThread): + def __init__(self): + QtCore.QThread.__init__(self) + self.signal = QtCore.SIGNAL("signal") + + def __del__(self): + self.wait() + + def createDeterministic(self): + pass + + def createPassphrase(self): + pass + + def broadcastAddress(self): + pass + + def registerMailchuck(self): + pass + + def waitRegistration(self): + pass + + def run(self): + import time + for i in range(1, 101): + time.sleep(0.1) # artificial time delay + self.emit(self.signal, i) + self.emit(self.signal, 101) +# self.terminate() + +if __name__ == '__main__': + + import sys + + app = QtGui.QApplication(sys.argv) + + wizard = Ui_NewAddressWizard(["a", "b", "c", "d"]) + if (wizard.exec_()): + print "Email: " + ("yes" if wizard.field("emailAsWell").toBool() else "no") + print "BM: " + ("yes" if wizard.field("onlyBM").toBool() else "no") + else: + print "Wizard cancelled" + sys.exit() diff --git a/src/bitmessageqt/newchandialog.py b/src/bitmessageqt/newchandialog.py index c0629cd7..ed683b13 100644 --- a/src/bitmessageqt/newchandialog.py +++ b/src/bitmessageqt/newchandialog.py @@ -1,72 +1,41 @@ -""" -src/bitmessageqt/newchandialog.py -================================= - -""" - from PyQt4 import QtCore, QtGui -import widgets from addresses import addBMIfNotPresent from addressvalidator import AddressValidator, PassPhraseValidator -from queues import ( - addressGeneratorQueue, apiAddressGeneratorReturnQueue, UISignalQueue) +from queues import apiAddressGeneratorReturnQueue, addressGeneratorQueue, UISignalQueue +from retranslateui import RetranslateMixin from tr import _translate from utils import str_chan +import widgets - -class NewChanDialog(QtGui.QDialog): - """The `New Chan` dialog""" +class NewChanDialog(QtGui.QDialog, RetranslateMixin): def __init__(self, parent=None): super(NewChanDialog, self).__init__(parent) widgets.load('newchandialog.ui', self) 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)) + 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)) self.timer = QtCore.QTimer() - QtCore.QObject.connect( # pylint: disable=no-member - self.timer, QtCore.SIGNAL("timeout()"), self.delayedUpdateStatus) - self.timer.start(500) # milliseconds + QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.delayedUpdateStatus) + self.timer.start(500) # milliseconds self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.show() def delayedUpdateStatus(self): - """Related to updating the UI for the chan passphrase validity""" self.chanPassPhrase.validator().checkQueue() def accept(self): - """Proceed in joining the chan""" self.timer.stop() self.hide() apiAddressGeneratorReturnQueue.queue.clear() if self.chanAddress.text().toUtf8() == "": - addressGeneratorQueue.put( - ('createChan', 4, 1, str_chan + ' ' + str(self.chanPassPhrase.text().toUtf8()), - self.chanPassPhrase.text().toUtf8(), - True)) + addressGeneratorQueue.put(('createChan', 4, 1, str_chan + ' ' + str(self.chanPassPhrase.text().toUtf8()), self.chanPassPhrase.text().toUtf8(), True)) else: - addressGeneratorQueue.put( - ('joinChan', addBMIfNotPresent(self.chanAddress.text().toUtf8()), - str_chan + ' ' + str(self.chanPassPhrase.text().toUtf8()), - self.chanPassPhrase.text().toUtf8(), - True)) + 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 addressGeneratorReturnValue and addressGeneratorReturnValue[0] != 'chan name does not match address': - UISignalQueue.put(('updateStatusBar', _translate( - "newchandialog", "Successfully created / joined chan %1").arg(unicode(self.chanPassPhrase.text())))) + if len(addressGeneratorReturnValue) > 0 and addressGeneratorReturnValue[0] != 'chan name does not match address': + 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) ) @@ -76,7 +45,6 @@ class NewChanDialog(QtGui.QDialog): self.done(QtGui.QDialog.Rejected) def reject(self): - """Cancel joining the chan""" self.timer.stop() self.hide() UISignalQueue.put(('updateStatusBar', _translate("newchandialog", "Chan creation / joining cancelled"))) diff --git a/src/bitmessageqt/retranslateui.py b/src/bitmessageqt/retranslateui.py index c7676f77..e9d5bb3a 100644 --- a/src/bitmessageqt/retranslateui.py +++ b/src/bitmessageqt/retranslateui.py @@ -13,8 +13,6 @@ class RetranslateMixin(object): getattr(self, attr).setText(getattr(defaults, attr).text()) elif isinstance(value, QtGui.QTableWidget): for i in range (value.columnCount()): - getattr(self, attr).horizontalHeaderItem(i).setText( - getattr(defaults, attr).horizontalHeaderItem(i).text()) + getattr(self, attr).horizontalHeaderItem(i).setText(getattr(defaults, attr).horizontalHeaderItem(i).text()) for i in range (value.rowCount()): - getattr(self, attr).verticalHeaderItem(i).setText( - getattr(defaults, attr).verticalHeaderItem(i).text()) + getattr(self, attr).verticalHeaderItem(i).setText(getattr(defaults, attr).verticalHeaderItem(i).text()) diff --git a/src/bitmessageqt/safehtmlparser.py b/src/bitmessageqt/safehtmlparser.py index d408d2c7..d1d7910c 100644 --- a/src/bitmessageqt/safehtmlparser.py +++ b/src/bitmessageqt/safehtmlparser.py @@ -1,75 +1,51 @@ -"""Subclass of HTMLParser.HTMLParser for MessageView widget""" - +from HTMLParser import HTMLParser import inspect import re -from HTMLParser import HTMLParser - -from urllib import quote_plus +from urllib import quote, quote_plus from urlparse import urlparse - class SafeHTMLParser(HTMLParser): - """HTML parser with sanitisation""" # from html5lib.sanitiser - acceptable_elements = ( - 'a', 'abbr', 'acronym', 'address', 'area', - 'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button', - 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', - 'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn', - 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset', - 'figcaption', 'figure', 'footer', 'font', 'header', 'h1', - 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', - 'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter', - 'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option', - 'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select', - 'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong', - 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot', - 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video' - ) - replaces_pre = ( - ("&", "&"), ("\"", """), ("<", "<"), (">", ">")) - replaces_post = ( - ("\n", "
"), ("\t", "    "), - (" ", "  "), (" ", "  "), ("
", "
 ")) - src_schemes = ["data"] - # uriregex1 = re.compile( - # r'(?i)\b((?:(https?|ftp|bitcoin):(?:/{1,3}|[a-z0-9%])' - # r'|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)' - # r'(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))' - # r'+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?]))') - uriregex1 = re.compile( - r'((https?|ftp|bitcoin):(?:/{1,3}|[a-z0-9%])' - r'(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)' - ) + acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area', + 'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button', + 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', + 'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn', + 'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset', + 'figcaption', 'figure', 'footer', 'font', 'header', 'h1', + 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', + 'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter', + 'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option', + 'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select', + 'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong', + 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot', + 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video'] + 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'((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' 1 and text[0] == " ": text = " " + text[1:] return text def __init__(self, *args, **kwargs): HTMLParser.__init__(self, *args, **kwargs) - self.reset() self.reset_safe() - self.has_html = None - self.allow_picture = None - + def reset_safe(self): - """Reset runtime variables specific to this class""" self.elements = set() self.raw = u"" self.sanitised = u"" @@ -77,9 +53,8 @@ class SafeHTMLParser(HTMLParser): self.allow_picture = False self.allow_external_src = False - def add_if_acceptable(self, tag, attrs=None): - """Add tag if it passes sanitisation""" - if tag not in self.acceptable_elements: + def add_if_acceptable(self, tag, attrs = None): + if tag not in SafeHTMLParser.acceptable_elements: return self.sanitised += "<" if inspect.stack()[1][3] == "handle_endtag": @@ -91,34 +66,34 @@ class SafeHTMLParser(HTMLParser): val = "" elif attr == "src" and not self.allow_external_src: url = urlparse(val) - if url.scheme not in self.src_schemes: + if url.scheme not in SafeHTMLParser.src_schemes: val = "" self.sanitised += " " + quote_plus(attr) - if val is not None: + if not (val is None): self.sanitised += "=\"" + val + "\"" if inspect.stack()[1][3] == "handle_startendtag": self.sanitised += "/" self.sanitised += ">" - + def handle_starttag(self, tag, attrs): - if tag in self.acceptable_elements: + if tag in SafeHTMLParser.acceptable_elements: self.has_html = True self.add_if_acceptable(tag, attrs) def handle_endtag(self, tag): self.add_if_acceptable(tag) - + def handle_startendtag(self, tag, attrs): - if tag in self.acceptable_elements: + if tag in SafeHTMLParser.acceptable_elements: self.has_html = True self.add_if_acceptable(tag, attrs) - + def handle_data(self, data): self.sanitised += data - + def handle_charref(self, name): self.sanitised += "&#" + name + ";" - + def handle_entityref(self, name): self.sanitised += "&" + name + ";" @@ -129,14 +104,15 @@ class SafeHTMLParser(HTMLParser): data = unicode(data, 'utf-8', errors='replace') HTMLParser.feed(self, data) tmp = SafeHTMLParser.replace_pre(data) - tmp = self.uriregex1.sub(r'\1', tmp) - tmp = self.uriregex2.sub(r'\1', tmp) + tmp = SafeHTMLParser.uriregex1.sub( + r'\1', + tmp) + tmp = SafeHTMLParser.uriregex2.sub(r'\1', tmp) tmp = SafeHTMLParser.replace_post(tmp) self.raw += tmp - def is_html(self, text=None, allow_picture=False): - """Detect if string contains HTML tags""" + def is_html(self, text = None, allow_picture = False): if text: self.reset() self.reset_safe() diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index 6e0d4792..4342fd09 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -1,636 +1,516 @@ -""" -This module setting file is for settings -""" -import ConfigParser -import os -import sys -import tempfile +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'settings.ui' +# +# Created: Thu Dec 25 23:21:20 2014 +# by: PyQt4 UI code generator 4.10.3 +# +# WARNING! All changes made in this file will be lost! -import six from PyQt4 import QtCore, QtGui +from languagebox import LanguageBox +from sys import platform -import debug -import defaults -import namecoin -import openclpow -import paths -import queues -import state -import widgets -from bmconfigparser import config as config_obj -from helper_sql import sqlExecute, sqlStoredProcedure -from helper_startup import start_proxyconfig -from network import knownnodes, AnnounceThread -from network.asyncore_pollchoose import set_rates -from tr import _translate +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) -def getSOCKSProxyType(config): - """Get user socksproxytype setting from *config*""" - try: - result = ConfigParser.SafeConfigParser.get( - config, 'bitmessagesettings', 'socksproxytype') - except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): - return None - else: - if result.lower() in ('', 'none', 'false'): - result = None - return result - - -class SettingsDialog(QtGui.QDialog): - """The "Settings" dialog""" - def __init__(self, parent=None, firstrun=False): - super(SettingsDialog, self).__init__(parent) - widgets.load('settings.ui', self) - - self.parent = parent - self.firstrun = firstrun - self.config = config_obj - self.net_restart_needed = False - self.timer = QtCore.QTimer() - - if self.config.safeGetBoolean('bitmessagesettings', 'dontconnect'): - self.firstrun = False - try: - import pkg_resources - except ImportError: - pass +class Ui_settingsDialog(object): + def setupUi(self, settingsDialog): + settingsDialog.setObjectName(_fromUtf8("settingsDialog")) + settingsDialog.resize(521, 413) + self.gridLayout = QtGui.QGridLayout(settingsDialog) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.buttonBox = QtGui.QDialogButtonBox(settingsDialog) + 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, 1, 0, 1, 1) + self.tabWidgetSettings = QtGui.QTabWidget(settingsDialog) + self.tabWidgetSettings.setObjectName(_fromUtf8("tabWidgetSettings")) + self.tabUserInterface = QtGui.QWidget() + self.tabUserInterface.setEnabled(True) + self.tabUserInterface.setObjectName(_fromUtf8("tabUserInterface")) + self.formLayout = QtGui.QFormLayout(self.tabUserInterface) + self.formLayout.setObjectName(_fromUtf8("formLayout")) + self.checkBoxStartOnLogon = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxStartOnLogon.setObjectName(_fromUtf8("checkBoxStartOnLogon")) + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.checkBoxStartOnLogon) + self.groupBoxTray = QtGui.QGroupBox(self.tabUserInterface) + self.groupBoxTray.setObjectName(_fromUtf8("groupBoxTray")) + self.formLayoutTray = QtGui.QFormLayout(self.groupBoxTray) + self.formLayoutTray.setObjectName(_fromUtf8("formLayoutTray")) + self.checkBoxStartInTray = QtGui.QCheckBox(self.groupBoxTray) + self.checkBoxStartInTray.setObjectName(_fromUtf8("checkBoxStartInTray")) + self.formLayoutTray.setWidget(0, QtGui.QFormLayout.SpanningRole, self.checkBoxStartInTray) + self.checkBoxMinimizeToTray = QtGui.QCheckBox(self.groupBoxTray) + self.checkBoxMinimizeToTray.setChecked(True) + self.checkBoxMinimizeToTray.setObjectName(_fromUtf8("checkBoxMinimizeToTray")) + self.formLayoutTray.setWidget(1, QtGui.QFormLayout.LabelRole, self.checkBoxMinimizeToTray) + self.checkBoxTrayOnClose = QtGui.QCheckBox(self.groupBoxTray) + self.checkBoxTrayOnClose.setChecked(True) + self.checkBoxTrayOnClose.setObjectName(_fromUtf8("checkBoxTrayOnClose")) + self.formLayoutTray.setWidget(2, QtGui.QFormLayout.LabelRole, self.checkBoxTrayOnClose) + self.formLayout.setWidget(1, QtGui.QFormLayout.SpanningRole, self.groupBoxTray) + self.checkBoxHideTrayConnectionNotifications = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxHideTrayConnectionNotifications.setChecked(False) + self.checkBoxHideTrayConnectionNotifications.setObjectName(_fromUtf8("checkBoxHideTrayConnectionNotifications")) + self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.checkBoxHideTrayConnectionNotifications) + self.checkBoxShowTrayNotifications = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxShowTrayNotifications.setObjectName(_fromUtf8("checkBoxShowTrayNotifications")) + self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.checkBoxShowTrayNotifications) + self.checkBoxPortableMode = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxPortableMode.setObjectName(_fromUtf8("checkBoxPortableMode")) + self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.checkBoxPortableMode) + self.PortableModeDescription = QtGui.QLabel(self.tabUserInterface) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.PortableModeDescription.sizePolicy().hasHeightForWidth()) + self.PortableModeDescription.setSizePolicy(sizePolicy) + self.PortableModeDescription.setWordWrap(True) + self.PortableModeDescription.setObjectName(_fromUtf8("PortableModeDescription")) + self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.PortableModeDescription) + self.checkBoxWillinglySendToMobile = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxWillinglySendToMobile.setObjectName(_fromUtf8("checkBoxWillinglySendToMobile")) + self.formLayout.setWidget(6, QtGui.QFormLayout.SpanningRole, self.checkBoxWillinglySendToMobile) + self.checkBoxUseIdenticons = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxUseIdenticons.setObjectName(_fromUtf8("checkBoxUseIdenticons")) + self.formLayout.setWidget(7, QtGui.QFormLayout.LabelRole, self.checkBoxUseIdenticons) + self.checkBoxReplyBelow = QtGui.QCheckBox(self.tabUserInterface) + self.checkBoxReplyBelow.setObjectName(_fromUtf8("checkBoxReplyBelow")) + self.formLayout.setWidget(8, QtGui.QFormLayout.LabelRole, self.checkBoxReplyBelow) + self.groupBox = QtGui.QGroupBox(self.tabUserInterface) + self.groupBox.setObjectName(_fromUtf8("groupBox")) + self.formLayout_2 = QtGui.QFormLayout(self.groupBox) + self.formLayout_2.setObjectName(_fromUtf8("formLayout_2")) + self.languageComboBox = LanguageBox(self.groupBox) + self.languageComboBox.setMinimumSize(QtCore.QSize(100, 0)) + self.languageComboBox.setObjectName(_fromUtf8("languageComboBox")) + self.formLayout_2.setWidget(0, QtGui.QFormLayout.LabelRole, self.languageComboBox) + self.formLayout.setWidget(9, QtGui.QFormLayout.FieldRole, self.groupBox) + self.tabWidgetSettings.addTab(self.tabUserInterface, _fromUtf8("")) + self.tabNetworkSettings = QtGui.QWidget() + self.tabNetworkSettings.setObjectName(_fromUtf8("tabNetworkSettings")) + self.gridLayout_4 = QtGui.QGridLayout(self.tabNetworkSettings) + self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) + self.groupBox1 = QtGui.QGroupBox(self.tabNetworkSettings) + self.groupBox1.setObjectName(_fromUtf8("groupBox1")) + self.gridLayout_3 = QtGui.QGridLayout(self.groupBox1) + self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) + #spacerItem = QtGui.QSpacerItem(125, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + #self.gridLayout_3.addItem(spacerItem, 0, 0, 1, 1) + self.label = QtGui.QLabel(self.groupBox1) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout_3.addWidget(self.label, 0, 0, 1, 1, QtCore.Qt.AlignRight) + self.lineEditTCPPort = QtGui.QLineEdit(self.groupBox1) + self.lineEditTCPPort.setMaximumSize(QtCore.QSize(70, 16777215)) + self.lineEditTCPPort.setObjectName(_fromUtf8("lineEditTCPPort")) + self.gridLayout_3.addWidget(self.lineEditTCPPort, 0, 1, 1, 1, QtCore.Qt.AlignLeft) + self.labelUPnP = QtGui.QLabel(self.groupBox1) + self.labelUPnP.setObjectName(_fromUtf8("labelUPnP")) + self.gridLayout_3.addWidget(self.labelUPnP, 0, 2, 1, 1, QtCore.Qt.AlignRight) + self.checkBoxUPnP = QtGui.QCheckBox(self.groupBox1) + self.checkBoxUPnP.setObjectName(_fromUtf8("checkBoxUPnP")) + self.gridLayout_3.addWidget(self.checkBoxUPnP, 0, 3, 1, 1, QtCore.Qt.AlignLeft) + self.gridLayout_4.addWidget(self.groupBox1, 0, 0, 1, 1) + self.groupBox_3 = QtGui.QGroupBox(self.tabNetworkSettings) + self.groupBox_3.setObjectName(_fromUtf8("groupBox_3")) + self.gridLayout_9 = QtGui.QGridLayout(self.groupBox_3) + self.gridLayout_9.setObjectName(_fromUtf8("gridLayout_9")) + spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_9.addItem(spacerItem1, 0, 0, 2, 1) + self.label_24 = QtGui.QLabel(self.groupBox_3) + self.label_24.setObjectName(_fromUtf8("label_24")) + self.gridLayout_9.addWidget(self.label_24, 0, 1, 1, 1) + self.lineEditMaxDownloadRate = QtGui.QLineEdit(self.groupBox_3) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditMaxDownloadRate.sizePolicy().hasHeightForWidth()) + self.lineEditMaxDownloadRate.setSizePolicy(sizePolicy) + self.lineEditMaxDownloadRate.setMaximumSize(QtCore.QSize(60, 16777215)) + self.lineEditMaxDownloadRate.setObjectName(_fromUtf8("lineEditMaxDownloadRate")) + self.gridLayout_9.addWidget(self.lineEditMaxDownloadRate, 0, 2, 1, 1) + self.label_25 = QtGui.QLabel(self.groupBox_3) + self.label_25.setObjectName(_fromUtf8("label_25")) + self.gridLayout_9.addWidget(self.label_25, 1, 1, 1, 1) + self.lineEditMaxUploadRate = QtGui.QLineEdit(self.groupBox_3) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditMaxUploadRate.sizePolicy().hasHeightForWidth()) + self.lineEditMaxUploadRate.setSizePolicy(sizePolicy) + self.lineEditMaxUploadRate.setMaximumSize(QtCore.QSize(60, 16777215)) + self.lineEditMaxUploadRate.setObjectName(_fromUtf8("lineEditMaxUploadRate")) + self.gridLayout_9.addWidget(self.lineEditMaxUploadRate, 1, 2, 1, 1) + self.label_26 = QtGui.QLabel(self.groupBox_3) + self.label_26.setObjectName(_fromUtf8("label_26")) + self.gridLayout_9.addWidget(self.label_26, 2, 1, 1, 1) + self.lineEditMaxOutboundConnections = QtGui.QLineEdit(self.groupBox_3) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditMaxOutboundConnections.sizePolicy().hasHeightForWidth()) + self.lineEditMaxOutboundConnections.setSizePolicy(sizePolicy) + self.lineEditMaxOutboundConnections.setMaximumSize(QtCore.QSize(60, 16777215)) + self.lineEditMaxOutboundConnections.setObjectName(_fromUtf8("lineEditMaxOutboundConnections")) + self.lineEditMaxOutboundConnections.setValidator(QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections)) + self.gridLayout_9.addWidget(self.lineEditMaxOutboundConnections, 2, 2, 1, 1) + self.gridLayout_4.addWidget(self.groupBox_3, 2, 0, 1, 1) + self.groupBox_2 = QtGui.QGroupBox(self.tabNetworkSettings) + self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) + self.gridLayout_2 = QtGui.QGridLayout(self.groupBox_2) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.label_2 = QtGui.QLabel(self.groupBox_2) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1) + self.label_3 = QtGui.QLabel(self.groupBox_2) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout_2.addWidget(self.label_3, 1, 1, 1, 1) + self.lineEditSocksHostname = QtGui.QLineEdit(self.groupBox_2) + self.lineEditSocksHostname.setObjectName(_fromUtf8("lineEditSocksHostname")) + self.lineEditSocksHostname.setPlaceholderText(_fromUtf8("127.0.0.1")) + self.gridLayout_2.addWidget(self.lineEditSocksHostname, 1, 2, 1, 2) + self.label_4 = QtGui.QLabel(self.groupBox_2) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.gridLayout_2.addWidget(self.label_4, 1, 4, 1, 1) + self.lineEditSocksPort = QtGui.QLineEdit(self.groupBox_2) + self.lineEditSocksPort.setObjectName(_fromUtf8("lineEditSocksPort")) + if platform in ['darwin', 'win32', 'win64']: + self.lineEditSocksPort.setPlaceholderText(_fromUtf8("9150")) else: - # Append proxy types defined in plugins - # FIXME: this should be a function in mod:`plugin` - for ep in pkg_resources.iter_entry_points( - 'bitmessage.proxyconfig'): - try: - ep.load() - except Exception: # it should add only functional plugins - # many possible exceptions, which are don't matter - pass - else: - self.comboBoxProxyType.addItem(ep.name) + self.lineEditSocksPort.setPlaceholderText(_fromUtf8("9050")) + self.gridLayout_2.addWidget(self.lineEditSocksPort, 1, 5, 1, 1) + self.checkBoxAuthentication = QtGui.QCheckBox(self.groupBox_2) + self.checkBoxAuthentication.setObjectName(_fromUtf8("checkBoxAuthentication")) + self.gridLayout_2.addWidget(self.checkBoxAuthentication, 2, 1, 1, 1) + self.label_5 = QtGui.QLabel(self.groupBox_2) + self.label_5.setObjectName(_fromUtf8("label_5")) + self.gridLayout_2.addWidget(self.label_5, 2, 2, 1, 1) + self.lineEditSocksUsername = QtGui.QLineEdit(self.groupBox_2) + self.lineEditSocksUsername.setEnabled(False) + self.lineEditSocksUsername.setObjectName(_fromUtf8("lineEditSocksUsername")) + self.gridLayout_2.addWidget(self.lineEditSocksUsername, 2, 3, 1, 1) + self.label_6 = QtGui.QLabel(self.groupBox_2) + self.label_6.setObjectName(_fromUtf8("label_6")) + self.gridLayout_2.addWidget(self.label_6, 2, 4, 1, 1) + self.lineEditSocksPassword = QtGui.QLineEdit(self.groupBox_2) + self.lineEditSocksPassword.setEnabled(False) + self.lineEditSocksPassword.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText) + self.lineEditSocksPassword.setEchoMode(QtGui.QLineEdit.Password) + self.lineEditSocksPassword.setObjectName(_fromUtf8("lineEditSocksPassword")) + self.gridLayout_2.addWidget(self.lineEditSocksPassword, 2, 5, 1, 1) + self.checkBoxSocksListen = QtGui.QCheckBox(self.groupBox_2) + self.checkBoxSocksListen.setObjectName(_fromUtf8("checkBoxSocksListen")) + self.gridLayout_2.addWidget(self.checkBoxSocksListen, 3, 1, 1, 4) + self.comboBoxProxyType = QtGui.QComboBox(self.groupBox_2) + self.comboBoxProxyType.setObjectName(_fromUtf8("comboBoxProxyType")) + self.comboBoxProxyType.addItem(_fromUtf8("")) + self.comboBoxProxyType.addItem(_fromUtf8("")) + self.comboBoxProxyType.addItem(_fromUtf8("")) + self.gridLayout_2.addWidget(self.comboBoxProxyType, 0, 1, 1, 1) + self.gridLayout_4.addWidget(self.groupBox_2, 1, 0, 1, 1) + spacerItem2 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_4.addItem(spacerItem2, 3, 0, 1, 1) + self.tabWidgetSettings.addTab(self.tabNetworkSettings, _fromUtf8("")) + self.tabDemandedDifficulty = QtGui.QWidget() + self.tabDemandedDifficulty.setObjectName(_fromUtf8("tabDemandedDifficulty")) + self.gridLayout_6 = QtGui.QGridLayout(self.tabDemandedDifficulty) + self.gridLayout_6.setObjectName(_fromUtf8("gridLayout_6")) + self.label_9 = QtGui.QLabel(self.tabDemandedDifficulty) + self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_9.setObjectName(_fromUtf8("label_9")) + self.gridLayout_6.addWidget(self.label_9, 1, 1, 1, 1) + self.label_10 = QtGui.QLabel(self.tabDemandedDifficulty) + self.label_10.setWordWrap(True) + self.label_10.setObjectName(_fromUtf8("label_10")) + self.gridLayout_6.addWidget(self.label_10, 2, 0, 1, 3) + self.label_11 = QtGui.QLabel(self.tabDemandedDifficulty) + self.label_11.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_11.setObjectName(_fromUtf8("label_11")) + self.gridLayout_6.addWidget(self.label_11, 3, 1, 1, 1) + self.label_8 = QtGui.QLabel(self.tabDemandedDifficulty) + self.label_8.setWordWrap(True) + self.label_8.setObjectName(_fromUtf8("label_8")) + self.gridLayout_6.addWidget(self.label_8, 0, 0, 1, 3) + spacerItem3 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_6.addItem(spacerItem3, 1, 0, 1, 1) + self.label_12 = QtGui.QLabel(self.tabDemandedDifficulty) + self.label_12.setWordWrap(True) + self.label_12.setObjectName(_fromUtf8("label_12")) + self.gridLayout_6.addWidget(self.label_12, 4, 0, 1, 3) + self.lineEditSmallMessageDifficulty = QtGui.QLineEdit(self.tabDemandedDifficulty) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditSmallMessageDifficulty.sizePolicy().hasHeightForWidth()) + self.lineEditSmallMessageDifficulty.setSizePolicy(sizePolicy) + self.lineEditSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) + self.lineEditSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditSmallMessageDifficulty")) + self.gridLayout_6.addWidget(self.lineEditSmallMessageDifficulty, 3, 2, 1, 1) + self.lineEditTotalDifficulty = QtGui.QLineEdit(self.tabDemandedDifficulty) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditTotalDifficulty.sizePolicy().hasHeightForWidth()) + self.lineEditTotalDifficulty.setSizePolicy(sizePolicy) + self.lineEditTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) + self.lineEditTotalDifficulty.setObjectName(_fromUtf8("lineEditTotalDifficulty")) + self.gridLayout_6.addWidget(self.lineEditTotalDifficulty, 1, 2, 1, 1) + spacerItem4 = QtGui.QSpacerItem(203, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_6.addItem(spacerItem4, 3, 0, 1, 1) + spacerItem5 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_6.addItem(spacerItem5, 5, 0, 1, 1) + self.tabWidgetSettings.addTab(self.tabDemandedDifficulty, _fromUtf8("")) + self.tabMaxAcceptableDifficulty = QtGui.QWidget() + self.tabMaxAcceptableDifficulty.setObjectName(_fromUtf8("tabMaxAcceptableDifficulty")) + self.gridLayout_7 = QtGui.QGridLayout(self.tabMaxAcceptableDifficulty) + self.gridLayout_7.setObjectName(_fromUtf8("gridLayout_7")) + self.label_15 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) + self.label_15.setWordWrap(True) + self.label_15.setObjectName(_fromUtf8("label_15")) + self.gridLayout_7.addWidget(self.label_15, 0, 0, 1, 3) + spacerItem6 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem6, 1, 0, 1, 1) + self.label_13 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) + self.label_13.setLayoutDirection(QtCore.Qt.LeftToRight) + self.label_13.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_13.setObjectName(_fromUtf8("label_13")) + self.gridLayout_7.addWidget(self.label_13, 1, 1, 1, 1) + self.lineEditMaxAcceptableTotalDifficulty = QtGui.QLineEdit(self.tabMaxAcceptableDifficulty) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditMaxAcceptableTotalDifficulty.sizePolicy().hasHeightForWidth()) + self.lineEditMaxAcceptableTotalDifficulty.setSizePolicy(sizePolicy) + self.lineEditMaxAcceptableTotalDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) + self.lineEditMaxAcceptableTotalDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableTotalDifficulty")) + self.gridLayout_7.addWidget(self.lineEditMaxAcceptableTotalDifficulty, 1, 2, 1, 1) + spacerItem7 = QtGui.QSpacerItem(102, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_7.addItem(spacerItem7, 2, 0, 1, 1) + self.label_14 = QtGui.QLabel(self.tabMaxAcceptableDifficulty) + self.label_14.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_14.setObjectName(_fromUtf8("label_14")) + self.gridLayout_7.addWidget(self.label_14, 2, 1, 1, 1) + self.lineEditMaxAcceptableSmallMessageDifficulty = QtGui.QLineEdit(self.tabMaxAcceptableDifficulty) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.lineEditMaxAcceptableSmallMessageDifficulty.sizePolicy().hasHeightForWidth()) + self.lineEditMaxAcceptableSmallMessageDifficulty.setSizePolicy(sizePolicy) + self.lineEditMaxAcceptableSmallMessageDifficulty.setMaximumSize(QtCore.QSize(70, 16777215)) + self.lineEditMaxAcceptableSmallMessageDifficulty.setObjectName(_fromUtf8("lineEditMaxAcceptableSmallMessageDifficulty")) + self.gridLayout_7.addWidget(self.lineEditMaxAcceptableSmallMessageDifficulty, 2, 2, 1, 1) + spacerItem8 = QtGui.QSpacerItem(20, 147, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_7.addItem(spacerItem8, 3, 1, 1, 1) + self.labelOpenCL = QtGui.QLabel(self.tabMaxAcceptableDifficulty) + self.labelOpenCL.setObjectName(_fromUtf8("labelOpenCL")) + self.gridLayout_7.addWidget(self.labelOpenCL, 4, 0, 1, 1) + self.comboBoxOpenCL = QtGui.QComboBox(self.tabMaxAcceptableDifficulty) + self.comboBoxOpenCL.setObjectName = (_fromUtf8("comboBoxOpenCL")) + self.gridLayout_7.addWidget(self.comboBoxOpenCL, 4, 1, 1, 1) + self.tabWidgetSettings.addTab(self.tabMaxAcceptableDifficulty, _fromUtf8("")) + self.tabNamecoin = QtGui.QWidget() + self.tabNamecoin.setObjectName(_fromUtf8("tabNamecoin")) + self.gridLayout_8 = QtGui.QGridLayout(self.tabNamecoin) + self.gridLayout_8.setObjectName(_fromUtf8("gridLayout_8")) + spacerItem9 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem9, 2, 0, 1, 1) + self.label_16 = QtGui.QLabel(self.tabNamecoin) + self.label_16.setWordWrap(True) + self.label_16.setObjectName(_fromUtf8("label_16")) + self.gridLayout_8.addWidget(self.label_16, 0, 0, 1, 3) + self.label_17 = QtGui.QLabel(self.tabNamecoin) + self.label_17.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_17.setObjectName(_fromUtf8("label_17")) + self.gridLayout_8.addWidget(self.label_17, 2, 1, 1, 1) + self.lineEditNamecoinHost = QtGui.QLineEdit(self.tabNamecoin) + self.lineEditNamecoinHost.setObjectName(_fromUtf8("lineEditNamecoinHost")) + self.gridLayout_8.addWidget(self.lineEditNamecoinHost, 2, 2, 1, 1) + spacerItem10 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem10, 3, 0, 1, 1) + spacerItem11 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem11, 4, 0, 1, 1) + self.label_18 = QtGui.QLabel(self.tabNamecoin) + self.label_18.setEnabled(True) + self.label_18.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_18.setObjectName(_fromUtf8("label_18")) + self.gridLayout_8.addWidget(self.label_18, 3, 1, 1, 1) + self.lineEditNamecoinPort = QtGui.QLineEdit(self.tabNamecoin) + self.lineEditNamecoinPort.setObjectName(_fromUtf8("lineEditNamecoinPort")) + self.gridLayout_8.addWidget(self.lineEditNamecoinPort, 3, 2, 1, 1) + spacerItem12 = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_8.addItem(spacerItem12, 8, 1, 1, 1) + self.labelNamecoinUser = QtGui.QLabel(self.tabNamecoin) + self.labelNamecoinUser.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.labelNamecoinUser.setObjectName(_fromUtf8("labelNamecoinUser")) + self.gridLayout_8.addWidget(self.labelNamecoinUser, 4, 1, 1, 1) + self.lineEditNamecoinUser = QtGui.QLineEdit(self.tabNamecoin) + self.lineEditNamecoinUser.setObjectName(_fromUtf8("lineEditNamecoinUser")) + self.gridLayout_8.addWidget(self.lineEditNamecoinUser, 4, 2, 1, 1) + spacerItem13 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_8.addItem(spacerItem13, 5, 0, 1, 1) + self.labelNamecoinPassword = QtGui.QLabel(self.tabNamecoin) + self.labelNamecoinPassword.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.labelNamecoinPassword.setObjectName(_fromUtf8("labelNamecoinPassword")) + self.gridLayout_8.addWidget(self.labelNamecoinPassword, 5, 1, 1, 1) + self.lineEditNamecoinPassword = QtGui.QLineEdit(self.tabNamecoin) + self.lineEditNamecoinPassword.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText) + self.lineEditNamecoinPassword.setEchoMode(QtGui.QLineEdit.Password) + self.lineEditNamecoinPassword.setObjectName(_fromUtf8("lineEditNamecoinPassword")) + self.gridLayout_8.addWidget(self.lineEditNamecoinPassword, 5, 2, 1, 1) + self.labelNamecoinTestResult = QtGui.QLabel(self.tabNamecoin) + self.labelNamecoinTestResult.setText(_fromUtf8("")) + self.labelNamecoinTestResult.setObjectName(_fromUtf8("labelNamecoinTestResult")) + self.gridLayout_8.addWidget(self.labelNamecoinTestResult, 7, 0, 1, 2) + self.pushButtonNamecoinTest = QtGui.QPushButton(self.tabNamecoin) + self.pushButtonNamecoinTest.setObjectName(_fromUtf8("pushButtonNamecoinTest")) + self.gridLayout_8.addWidget(self.pushButtonNamecoinTest, 7, 2, 1, 1) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.label_21 = QtGui.QLabel(self.tabNamecoin) + self.label_21.setObjectName(_fromUtf8("label_21")) + self.horizontalLayout.addWidget(self.label_21) + self.radioButtonNamecoinNamecoind = QtGui.QRadioButton(self.tabNamecoin) + self.radioButtonNamecoinNamecoind.setObjectName(_fromUtf8("radioButtonNamecoinNamecoind")) + self.horizontalLayout.addWidget(self.radioButtonNamecoinNamecoind) + self.radioButtonNamecoinNmcontrol = QtGui.QRadioButton(self.tabNamecoin) + self.radioButtonNamecoinNmcontrol.setObjectName(_fromUtf8("radioButtonNamecoinNmcontrol")) + self.horizontalLayout.addWidget(self.radioButtonNamecoinNmcontrol) + self.gridLayout_8.addLayout(self.horizontalLayout, 1, 0, 1, 3) + self.tabWidgetSettings.addTab(self.tabNamecoin, _fromUtf8("")) + self.tabResendsExpire = QtGui.QWidget() + self.tabResendsExpire.setObjectName(_fromUtf8("tabResendsExpire")) + self.gridLayout_5 = QtGui.QGridLayout(self.tabResendsExpire) + self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) + self.label_7 = QtGui.QLabel(self.tabResendsExpire) + self.label_7.setWordWrap(True) + self.label_7.setObjectName(_fromUtf8("label_7")) + self.gridLayout_5.addWidget(self.label_7, 0, 0, 1, 3) + spacerItem14 = QtGui.QSpacerItem(212, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_5.addItem(spacerItem14, 1, 0, 1, 1) + self.widget = QtGui.QWidget(self.tabResendsExpire) + self.widget.setMinimumSize(QtCore.QSize(231, 75)) + self.widget.setObjectName(_fromUtf8("widget")) + self.label_19 = QtGui.QLabel(self.widget) + self.label_19.setGeometry(QtCore.QRect(10, 20, 101, 20)) + self.label_19.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_19.setObjectName(_fromUtf8("label_19")) + self.label_20 = QtGui.QLabel(self.widget) + self.label_20.setGeometry(QtCore.QRect(30, 40, 80, 16)) + self.label_20.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_20.setObjectName(_fromUtf8("label_20")) + self.lineEditDays = QtGui.QLineEdit(self.widget) + self.lineEditDays.setGeometry(QtCore.QRect(113, 20, 51, 20)) + self.lineEditDays.setObjectName(_fromUtf8("lineEditDays")) + self.lineEditMonths = QtGui.QLineEdit(self.widget) + self.lineEditMonths.setGeometry(QtCore.QRect(113, 40, 51, 20)) + self.lineEditMonths.setObjectName(_fromUtf8("lineEditMonths")) + self.label_22 = QtGui.QLabel(self.widget) + self.label_22.setGeometry(QtCore.QRect(169, 23, 61, 16)) + self.label_22.setObjectName(_fromUtf8("label_22")) + self.label_23 = QtGui.QLabel(self.widget) + self.label_23.setGeometry(QtCore.QRect(170, 41, 71, 16)) + self.label_23.setObjectName(_fromUtf8("label_23")) + self.gridLayout_5.addWidget(self.widget, 1, 2, 1, 1) + spacerItem15 = QtGui.QSpacerItem(20, 129, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.gridLayout_5.addItem(spacerItem15, 2, 1, 1, 1) + self.tabWidgetSettings.addTab(self.tabResendsExpire, _fromUtf8("")) + self.gridLayout.addWidget(self.tabWidgetSettings, 0, 0, 1, 1) - self.lineEditMaxOutboundConnections.setValidator( - QtGui.QIntValidator(0, 8, self.lineEditMaxOutboundConnections)) + self.retranslateUi(settingsDialog) + self.tabWidgetSettings.setCurrentIndex(0) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), settingsDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), settingsDialog.reject) + QtCore.QObject.connect(self.checkBoxAuthentication, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.lineEditSocksUsername.setEnabled) + QtCore.QObject.connect(self.checkBoxAuthentication, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.lineEditSocksPassword.setEnabled) + QtCore.QMetaObject.connectSlotsByName(settingsDialog) + settingsDialog.setTabOrder(self.tabWidgetSettings, self.checkBoxStartOnLogon) + settingsDialog.setTabOrder(self.checkBoxStartOnLogon, self.checkBoxStartInTray) + settingsDialog.setTabOrder(self.checkBoxStartInTray, self.checkBoxMinimizeToTray) + settingsDialog.setTabOrder(self.checkBoxMinimizeToTray, self.lineEditTCPPort) + settingsDialog.setTabOrder(self.lineEditTCPPort, self.comboBoxProxyType) + settingsDialog.setTabOrder(self.comboBoxProxyType, self.lineEditSocksHostname) + settingsDialog.setTabOrder(self.lineEditSocksHostname, self.lineEditSocksPort) + settingsDialog.setTabOrder(self.lineEditSocksPort, self.checkBoxAuthentication) + settingsDialog.setTabOrder(self.checkBoxAuthentication, self.lineEditSocksUsername) + settingsDialog.setTabOrder(self.lineEditSocksUsername, self.lineEditSocksPassword) + settingsDialog.setTabOrder(self.lineEditSocksPassword, self.checkBoxSocksListen) + settingsDialog.setTabOrder(self.checkBoxSocksListen, self.buttonBox) - self.adjust_from_config(self.config) - if firstrun: - # switch to "Network Settings" tab if user selected - # "Let me configure special network settings first" on first run - self.tabWidgetSettings.setCurrentIndex( - self.tabWidgetSettings.indexOf(self.tabNetworkSettings) - ) - QtGui.QWidget.resize(self, QtGui.QWidget.sizeHint(self)) + def retranslateUi(self, settingsDialog): + settingsDialog.setWindowTitle(_translate("settingsDialog", "Settings", None)) + self.checkBoxStartOnLogon.setText(_translate("settingsDialog", "Start Bitmessage on user login", None)) + self.groupBoxTray.setTitle(_translate("settingsDialog", "Tray", None)) + self.checkBoxStartInTray.setText(_translate("settingsDialog", "Start Bitmessage in the tray (don\'t show main window)", None)) + self.checkBoxMinimizeToTray.setText(_translate("settingsDialog", "Minimize to tray", None)) + self.checkBoxTrayOnClose.setText(_translate("settingsDialog", "Close to tray", None)) + self.checkBoxHideTrayConnectionNotifications.setText(_translate("settingsDialog", "Hide connection notifications", None)) + self.checkBoxShowTrayNotifications.setText(_translate("settingsDialog", "Show notification when message received", None)) + self.checkBoxPortableMode.setText(_translate("settingsDialog", "Run in Portable Mode", None)) + self.PortableModeDescription.setText(_translate("settingsDialog", "In Portable Mode, messages and config files are stored in the same directory as the program rather than the normal application-data folder. This makes it convenient to run Bitmessage from a USB thumb drive.", None)) + self.checkBoxWillinglySendToMobile.setText(_translate("settingsDialog", "Willingly include unencrypted destination address when sending to a mobile device", None)) + self.checkBoxUseIdenticons.setText(_translate("settingsDialog", "Use Identicons", None)) + self.checkBoxReplyBelow.setText(_translate("settingsDialog", "Reply below Quote", None)) + self.groupBox.setTitle(_translate("settingsDialog", "Interface Language", None)) + self.languageComboBox.setItemText(0, _translate("settingsDialog", "System Settings", "system")) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabUserInterface), _translate("settingsDialog", "User Interface", None)) + self.groupBox1.setTitle(_translate("settingsDialog", "Listening port", None)) + self.label.setText(_translate("settingsDialog", "Listen for connections on port:", None)) + self.labelUPnP.setText(_translate("settingsDialog", "UPnP:", None)) + self.groupBox_3.setTitle(_translate("settingsDialog", "Bandwidth limit", None)) + self.label_24.setText(_translate("settingsDialog", "Maximum download rate (kB/s): [0: unlimited]", None)) + self.label_25.setText(_translate("settingsDialog", "Maximum upload rate (kB/s): [0: unlimited]", None)) + self.label_26.setText(_translate("settingsDialog", "Maximum outbound connections: [0: none]", None)) + self.groupBox_2.setTitle(_translate("settingsDialog", "Proxy server / Tor", None)) + self.label_2.setText(_translate("settingsDialog", "Type:", None)) + self.label_3.setText(_translate("settingsDialog", "Server hostname:", None)) + self.label_4.setText(_translate("settingsDialog", "Port:", None)) + self.checkBoxAuthentication.setText(_translate("settingsDialog", "Authentication", None)) + self.label_5.setText(_translate("settingsDialog", "Username:", None)) + self.label_6.setText(_translate("settingsDialog", "Pass:", None)) + self.checkBoxSocksListen.setText(_translate("settingsDialog", "Listen for incoming connections when using proxy", None)) + self.comboBoxProxyType.setItemText(0, _translate("settingsDialog", "none", None)) + self.comboBoxProxyType.setItemText(1, _translate("settingsDialog", "SOCKS4a", None)) + self.comboBoxProxyType.setItemText(2, _translate("settingsDialog", "SOCKS5", None)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabNetworkSettings), _translate("settingsDialog", "Network Settings", None)) + self.label_9.setText(_translate("settingsDialog", "Total difficulty:", None)) + self.label_10.setText(_translate("settingsDialog", "The \'Total difficulty\' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work.", None)) + self.label_11.setText(_translate("settingsDialog", "Small message difficulty:", None)) + self.label_8.setText(_translate("settingsDialog", "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. ", None)) + self.label_12.setText(_translate("settingsDialog", "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.", None)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabDemandedDifficulty), _translate("settingsDialog", "Demanded difficulty", None)) + self.label_15.setText(_translate("settingsDialog", "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.", None)) + self.label_13.setText(_translate("settingsDialog", "Maximum acceptable total difficulty:", None)) + self.label_14.setText(_translate("settingsDialog", "Maximum acceptable small message difficulty:", None)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabMaxAcceptableDifficulty), _translate("settingsDialog", "Max acceptable difficulty", None)) + self.labelOpenCL.setText(_translate("settingsDialog", "Hardware GPU acceleration (OpenCL):", None)) + self.label_16.setText(_translate("settingsDialog", "

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

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

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

", None)) + self.label_17.setText(_translate("settingsDialog", "Host:", None)) + self.label_18.setText(_translate("settingsDialog", "Port:", None)) + self.labelNamecoinUser.setText(_translate("settingsDialog", "Username:", None)) + self.labelNamecoinPassword.setText(_translate("settingsDialog", "Password:", None)) + self.pushButtonNamecoinTest.setText(_translate("settingsDialog", "Test", None)) + self.label_21.setText(_translate("settingsDialog", "Connect to:", None)) + self.radioButtonNamecoinNamecoind.setText(_translate("settingsDialog", "Namecoind", None)) + self.radioButtonNamecoinNmcontrol.setText(_translate("settingsDialog", "NMControl", None)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabNamecoin), _translate("settingsDialog", "Namecoin integration", None)) + self.label_7.setText(_translate("settingsDialog", "

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.

Leave these input fields blank for the default behavior.

", None)) + self.label_19.setText(_translate("settingsDialog", "Give up after", None)) + self.label_20.setText(_translate("settingsDialog", "and", None)) + self.label_22.setText(_translate("settingsDialog", "days", None)) + self.label_23.setText(_translate("settingsDialog", "months.", None)) + self.tabWidgetSettings.setTabText(self.tabWidgetSettings.indexOf(self.tabResendsExpire), _translate("settingsDialog", "Resends Expire", None)) - def adjust_from_config(self, config): - """Adjust all widgets state according to config settings""" - # pylint: disable=too-many-branches,too-many-statements - if not self.parent.tray.isSystemTrayAvailable(): - self.groupBoxTray.setEnabled(False) - self.groupBoxTray.setTitle(_translate( - "MainWindow", "Tray (not available in your system)")) - for setting in ( - 'minimizetotray', 'trayonclose', 'startintray'): - config.set('bitmessagesettings', setting, 'false') - else: - self.checkBoxMinimizeToTray.setChecked( - config.getboolean('bitmessagesettings', 'minimizetotray')) - self.checkBoxTrayOnClose.setChecked( - config.safeGetBoolean('bitmessagesettings', 'trayonclose')) - self.checkBoxStartInTray.setChecked( - config.getboolean('bitmessagesettings', 'startintray')) - - self.checkBoxHideTrayConnectionNotifications.setChecked( - config.getboolean( - 'bitmessagesettings', 'hidetrayconnectionnotifications')) - self.checkBoxShowTrayNotifications.setChecked( - config.getboolean('bitmessagesettings', 'showtraynotifications')) - - self.checkBoxStartOnLogon.setChecked( - config.getboolean('bitmessagesettings', 'startonlogon')) - - self.checkBoxWillinglySendToMobile.setChecked( - config.safeGetBoolean( - 'bitmessagesettings', 'willinglysendtomobile')) - self.checkBoxUseIdenticons.setChecked( - config.safeGetBoolean('bitmessagesettings', 'useidenticons')) - self.checkBoxReplyBelow.setChecked( - config.safeGetBoolean('bitmessagesettings', 'replybelow')) - - if state.appdata == paths.lookupExeFolder(): - self.checkBoxPortableMode.setChecked(True) - else: - try: - tempfile.NamedTemporaryFile( - dir=paths.lookupExeFolder(), delete=True - ).close() # should autodelete - except Exception: - self.checkBoxPortableMode.setDisabled(True) - - if 'darwin' in sys.platform: - self.checkBoxMinimizeToTray.setDisabled(True) - self.checkBoxMinimizeToTray.setText(_translate( - "MainWindow", - "Minimize-to-tray not yet supported on your OS.")) - self.checkBoxShowTrayNotifications.setDisabled(True) - self.checkBoxShowTrayNotifications.setText(_translate( - "MainWindow", - "Tray notifications not yet supported on your OS.")) - - if 'win' not in sys.platform and not self.parent.desktop: - self.checkBoxStartOnLogon.setDisabled(True) - self.checkBoxStartOnLogon.setText(_translate( - "MainWindow", "Start-on-login not yet supported on your OS.")) - - # On the Network settings tab: - self.lineEditTCPPort.setText(str( - config.get('bitmessagesettings', 'port'))) - self.checkBoxUPnP.setChecked( - config.safeGetBoolean('bitmessagesettings', 'upnp')) - self.checkBoxUDP.setChecked( - config.safeGetBoolean('bitmessagesettings', 'udp')) - self.checkBoxAuthentication.setChecked( - config.getboolean('bitmessagesettings', 'socksauthentication')) - self.checkBoxSocksListen.setChecked( - config.getboolean('bitmessagesettings', 'sockslisten')) - self.checkBoxOnionOnly.setChecked( - config.safeGetBoolean('bitmessagesettings', 'onionservicesonly')) - - self._proxy_type = getSOCKSProxyType(config) - self.comboBoxProxyType.setCurrentIndex( - 0 if not self._proxy_type - else self.comboBoxProxyType.findText(self._proxy_type)) - self.comboBoxProxyTypeChanged(self.comboBoxProxyType.currentIndex()) - - if self._proxy_type: - for node, info in six.iteritems( - knownnodes.knownNodes.get( - min(state.streamsInWhichIAmParticipating), []) - ): - if ( - node.host.endswith('.onion') and len(node.host) > 22 - and not info.get('self') - ): - break - else: - if self.checkBoxOnionOnly.isChecked(): - self.checkBoxOnionOnly.setText( - self.checkBoxOnionOnly.text() + ", " + _translate( - "MainWindow", "may cause connection problems!")) - self.checkBoxOnionOnly.setStyleSheet( - "QCheckBox { color : red; }") - else: - self.checkBoxOnionOnly.setEnabled(False) - - self.lineEditSocksHostname.setText( - config.get('bitmessagesettings', 'sockshostname')) - self.lineEditSocksPort.setText(str( - config.get('bitmessagesettings', 'socksport'))) - self.lineEditSocksUsername.setText( - config.get('bitmessagesettings', 'socksusername')) - self.lineEditSocksPassword.setText( - config.get('bitmessagesettings', 'sockspassword')) - - self.lineEditMaxDownloadRate.setText(str( - config.get('bitmessagesettings', 'maxdownloadrate'))) - self.lineEditMaxUploadRate.setText(str( - config.get('bitmessagesettings', 'maxuploadrate'))) - self.lineEditMaxOutboundConnections.setText(str( - config.get('bitmessagesettings', 'maxoutboundconnections'))) - - # Demanded difficulty tab - self.lineEditTotalDifficulty.setText(str((float( - config.getint( - 'bitmessagesettings', 'defaultnoncetrialsperbyte') - ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - self.lineEditSmallMessageDifficulty.setText(str((float( - config.getint( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes') - ) / defaults.networkDefaultPayloadLengthExtraBytes))) - - # Max acceptable difficulty tab - self.lineEditMaxAcceptableTotalDifficulty.setText(str((float( - config.getint( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte') - ) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - self.lineEditMaxAcceptableSmallMessageDifficulty.setText(str((float( - config.getint( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') - ) / defaults.networkDefaultPayloadLengthExtraBytes))) - - # OpenCL - self.comboBoxOpenCL.setEnabled(openclpow.openclAvailable()) - self.comboBoxOpenCL.clear() - self.comboBoxOpenCL.addItem("None") - self.comboBoxOpenCL.addItems(openclpow.vendors) - self.comboBoxOpenCL.setCurrentIndex(0) - for i in range(self.comboBoxOpenCL.count()): - if self.comboBoxOpenCL.itemText(i) == config.safeGet( - 'bitmessagesettings', 'opencl'): - self.comboBoxOpenCL.setCurrentIndex(i) - break - - # Namecoin integration tab - nmctype = config.get('bitmessagesettings', 'namecoinrpctype') - self.lineEditNamecoinHost.setText( - config.get('bitmessagesettings', 'namecoinrpchost')) - self.lineEditNamecoinPort.setText(str( - config.get('bitmessagesettings', 'namecoinrpcport'))) - self.lineEditNamecoinUser.setText( - config.get('bitmessagesettings', 'namecoinrpcuser')) - self.lineEditNamecoinPassword.setText( - config.get('bitmessagesettings', 'namecoinrpcpassword')) - - if nmctype == "namecoind": - self.radioButtonNamecoinNamecoind.setChecked(True) - elif nmctype == "nmcontrol": - self.radioButtonNamecoinNmcontrol.setChecked(True) - self.lineEditNamecoinUser.setEnabled(False) - self.labelNamecoinUser.setEnabled(False) - self.lineEditNamecoinPassword.setEnabled(False) - self.labelNamecoinPassword.setEnabled(False) - else: - assert False - - # Message Resend tab - self.lineEditDays.setText(str( - config.get('bitmessagesettings', 'stopresendingafterxdays'))) - self.lineEditMonths.setText(str( - config.get('bitmessagesettings', 'stopresendingafterxmonths'))) - - def comboBoxProxyTypeChanged(self, comboBoxIndex): - """A callback for currentIndexChanged event of comboBoxProxyType""" - if comboBoxIndex == 0: - self.lineEditSocksHostname.setEnabled(False) - self.lineEditSocksPort.setEnabled(False) - self.lineEditSocksUsername.setEnabled(False) - self.lineEditSocksPassword.setEnabled(False) - self.checkBoxAuthentication.setEnabled(False) - self.checkBoxSocksListen.setEnabled(False) - self.checkBoxOnionOnly.setEnabled(False) - else: - self.lineEditSocksHostname.setEnabled(True) - self.lineEditSocksPort.setEnabled(True) - self.checkBoxAuthentication.setEnabled(True) - self.checkBoxSocksListen.setEnabled(True) - self.checkBoxOnionOnly.setEnabled(True) - if self.checkBoxAuthentication.isChecked(): - self.lineEditSocksUsername.setEnabled(True) - self.lineEditSocksPassword.setEnabled(True) - - def getNamecoinType(self): - """ - Check status of namecoin integration radio buttons - and translate it to a string as in the options. - """ - if self.radioButtonNamecoinNamecoind.isChecked(): - return "namecoind" - if self.radioButtonNamecoinNmcontrol.isChecked(): - return "nmcontrol" - assert False - - # Namecoin connection type was changed. - def namecoinTypeChanged(self, checked): # pylint: disable=unused-argument - """A callback for toggled event of radioButtonNamecoinNamecoind""" - nmctype = self.getNamecoinType() - assert nmctype == "namecoind" or nmctype == "nmcontrol" - - isNamecoind = (nmctype == "namecoind") - self.lineEditNamecoinUser.setEnabled(isNamecoind) - self.labelNamecoinUser.setEnabled(isNamecoind) - self.lineEditNamecoinPassword.setEnabled(isNamecoind) - self.labelNamecoinPassword.setEnabled(isNamecoind) - - if isNamecoind: - self.lineEditNamecoinPort.setText(defaults.namecoinDefaultRpcPort) - else: - self.lineEditNamecoinPort.setText("9000") - - def click_pushButtonNamecoinTest(self): - """Test the namecoin settings specified in the settings dialog.""" - self.labelNamecoinTestResult.setText( - _translate("MainWindow", "Testing...")) - nc = namecoin.namecoinConnection({ - 'type': self.getNamecoinType(), - 'host': str(self.lineEditNamecoinHost.text().toUtf8()), - 'port': str(self.lineEditNamecoinPort.text().toUtf8()), - 'user': str(self.lineEditNamecoinUser.text().toUtf8()), - 'password': str(self.lineEditNamecoinPassword.text().toUtf8()) - }) - status, text = nc.test() - self.labelNamecoinTestResult.setText(text) - if status == 'success': - self.parent.namecoin = nc - - def accept(self): - """A callback for accepted event of buttonBox (OK button pressed)""" - # pylint: disable=too-many-branches,too-many-statements - super(SettingsDialog, self).accept() - if self.firstrun: - self.config.remove_option('bitmessagesettings', 'dontconnect') - self.config.set('bitmessagesettings', 'startonlogon', str( - self.checkBoxStartOnLogon.isChecked())) - self.config.set('bitmessagesettings', 'minimizetotray', str( - self.checkBoxMinimizeToTray.isChecked())) - self.config.set('bitmessagesettings', 'trayonclose', str( - self.checkBoxTrayOnClose.isChecked())) - self.config.set( - 'bitmessagesettings', 'hidetrayconnectionnotifications', - str(self.checkBoxHideTrayConnectionNotifications.isChecked())) - self.config.set('bitmessagesettings', 'showtraynotifications', str( - self.checkBoxShowTrayNotifications.isChecked())) - self.config.set('bitmessagesettings', 'startintray', str( - self.checkBoxStartInTray.isChecked())) - self.config.set('bitmessagesettings', 'willinglysendtomobile', str( - self.checkBoxWillinglySendToMobile.isChecked())) - self.config.set('bitmessagesettings', 'useidenticons', str( - self.checkBoxUseIdenticons.isChecked())) - self.config.set('bitmessagesettings', 'replybelow', str( - self.checkBoxReplyBelow.isChecked())) - - lang = str(self.languageComboBox.itemData( - self.languageComboBox.currentIndex()).toString()) - self.config.set('bitmessagesettings', 'userlocale', lang) - self.parent.change_translation() - - if int(self.config.get('bitmessagesettings', 'port')) != int( - self.lineEditTCPPort.text()): - self.config.set( - 'bitmessagesettings', 'port', str(self.lineEditTCPPort.text())) - if not self.config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect'): - self.net_restart_needed = True - - if self.checkBoxUPnP.isChecked() != self.config.safeGetBoolean( - 'bitmessagesettings', 'upnp'): - self.config.set( - 'bitmessagesettings', 'upnp', - str(self.checkBoxUPnP.isChecked())) - if self.checkBoxUPnP.isChecked(): - import upnp - upnpThread = upnp.uPnPThread() - upnpThread.start() - - udp_enabled = self.checkBoxUDP.isChecked() - if udp_enabled != self.config.safeGetBoolean( - 'bitmessagesettings', 'udp'): - self.config.set('bitmessagesettings', 'udp', str(udp_enabled)) - if udp_enabled: - announceThread = AnnounceThread() - announceThread.daemon = True - announceThread.start() - else: - try: - state.announceThread.stopThread() - except AttributeError: - pass - - proxytype_index = self.comboBoxProxyType.currentIndex() - if proxytype_index == 0: - if self._proxy_type and state.statusIconColor != 'red': - self.net_restart_needed = True - elif state.statusIconColor == 'red' and self.config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect'): - self.net_restart_needed = False - elif self.comboBoxProxyType.currentText() != self._proxy_type: - self.net_restart_needed = True - self.parent.statusbar.clearMessage() - - self.config.set( - 'bitmessagesettings', 'socksproxytype', - 'none' if self.comboBoxProxyType.currentIndex() == 0 - else str(self.comboBoxProxyType.currentText()) - ) - if proxytype_index > 2: # last literal proxytype in ui - start_proxyconfig() - - self.config.set('bitmessagesettings', 'socksauthentication', str( - self.checkBoxAuthentication.isChecked())) - self.config.set('bitmessagesettings', 'sockshostname', str( - self.lineEditSocksHostname.text())) - self.config.set('bitmessagesettings', 'socksport', str( - self.lineEditSocksPort.text())) - self.config.set('bitmessagesettings', 'socksusername', str( - self.lineEditSocksUsername.text())) - self.config.set('bitmessagesettings', 'sockspassword', str( - self.lineEditSocksPassword.text())) - self.config.set('bitmessagesettings', 'sockslisten', str( - self.checkBoxSocksListen.isChecked())) - if ( - self.checkBoxOnionOnly.isChecked() - and not self.config.safeGetBoolean( - 'bitmessagesettings', 'onionservicesonly') - ): - self.net_restart_needed = True - self.config.set('bitmessagesettings', 'onionservicesonly', str( - self.checkBoxOnionOnly.isChecked())) - try: - # Rounding to integers just for aesthetics - self.config.set('bitmessagesettings', 'maxdownloadrate', str( - int(float(self.lineEditMaxDownloadRate.text())))) - self.config.set('bitmessagesettings', 'maxuploadrate', str( - int(float(self.lineEditMaxUploadRate.text())))) - 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( - self.config.safeGetInt('bitmessagesettings', 'maxdownloadrate'), - self.config.safeGetInt('bitmessagesettings', 'maxuploadrate')) - - self.config.set('bitmessagesettings', 'maxoutboundconnections', str( - int(float(self.lineEditMaxOutboundConnections.text())))) - - self.config.set( - 'bitmessagesettings', 'namecoinrpctype', self.getNamecoinType()) - self.config.set('bitmessagesettings', 'namecoinrpchost', str( - self.lineEditNamecoinHost.text())) - self.config.set('bitmessagesettings', 'namecoinrpcport', str( - self.lineEditNamecoinPort.text())) - self.config.set('bitmessagesettings', 'namecoinrpcuser', str( - self.lineEditNamecoinUser.text())) - self.config.set('bitmessagesettings', 'namecoinrpcpassword', str( - self.lineEditNamecoinPassword.text())) - self.parent.resetNamecoinConnection() - - # Demanded difficulty tab - if float(self.lineEditTotalDifficulty.text()) >= 1: - self.config.set( - 'bitmessagesettings', 'defaultnoncetrialsperbyte', - str(int( - float(self.lineEditTotalDifficulty.text()) - * defaults.networkDefaultProofOfWorkNonceTrialsPerByte))) - if float(self.lineEditSmallMessageDifficulty.text()) >= 1: - self.config.set( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes', - str(int( - float(self.lineEditSmallMessageDifficulty.text()) - * defaults.networkDefaultPayloadLengthExtraBytes))) - - if self.comboBoxOpenCL.currentText().toUtf8() != self.config.safeGet( - 'bitmessagesettings', 'opencl'): - self.config.set( - 'bitmessagesettings', 'opencl', - str(self.comboBoxOpenCL.currentText())) - queues.workerQueue.put(('resetPoW', '')) - - acceptableDifficultyChanged = False - - if ( - float(self.lineEditMaxAcceptableTotalDifficulty.text()) >= 1 - or float(self.lineEditMaxAcceptableTotalDifficulty.text()) == 0 - ): - if self.config.get( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte' - ) != str(int( - float(self.lineEditMaxAcceptableTotalDifficulty.text()) - * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)): - # the user changed the max acceptable total difficulty - acceptableDifficultyChanged = True - self.config.set( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', - str(int( - float(self.lineEditMaxAcceptableTotalDifficulty.text()) - * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) - ) - if ( - float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) >= 1 - or float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) == 0 - ): - if self.config.get( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes' - ) != str(int( - float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) - * defaults.networkDefaultPayloadLengthExtraBytes)): - # the user changed the max acceptable small message difficulty - acceptableDifficultyChanged = True - self.config.set( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', - str(int( - float(self.lineEditMaxAcceptableSmallMessageDifficulty.text()) - * defaults.networkDefaultPayloadLengthExtraBytes)) - ) - if acceptableDifficultyChanged: - # It might now be possible to send msgs which were previously - # marked as toodifficult. Let us change them to 'msgqueued'. - # The singleWorker will try to send them and will again mark - # them as toodifficult if the receiver's required difficulty - # is still higher than we are willing to do. - sqlExecute( - "UPDATE sent SET status='msgqueued'" - " WHERE status='toodifficult'") - queues.workerQueue.put(('sendmessage', '')) - - stopResendingDefaults = False - - # UI setting to stop trying to send messages after X days/months - # I'm open to changing this UI to something else if someone has a better idea. - if self.lineEditDays.text() == '' and self.lineEditMonths.text() == '': - # We need to handle this special case. Bitmessage has its - # default behavior. The input is blank/blank - self.config.set('bitmessagesettings', 'stopresendingafterxdays', '') - self.config.set('bitmessagesettings', 'stopresendingafterxmonths', '') - state.maximumLengthOfTimeToBotherResendingMessages = float('inf') - stopResendingDefaults = True - - try: - days = float(self.lineEditDays.text()) - except ValueError: - self.lineEditDays.setText("0") - days = 0.0 - try: - months = float(self.lineEditMonths.text()) - except ValueError: - self.lineEditMonths.setText("0") - months = 0.0 - - if days >= 0 and months >= 0 and not stopResendingDefaults: - state.maximumLengthOfTimeToBotherResendingMessages = \ - days * 24 * 60 * 60 + months * 60 * 60 * 24 * 365 / 12 - if state.maximumLengthOfTimeToBotherResendingMessages < 432000: - # If the time period is less than 5 hours, we give - # zero values to all fields. No message will be sent again. - 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.") - ) - self.config.set( - 'bitmessagesettings', 'stopresendingafterxdays', '0') - self.config.set( - 'bitmessagesettings', 'stopresendingafterxmonths', '0') - state.maximumLengthOfTimeToBotherResendingMessages = 0.0 - else: - self.config.set( - 'bitmessagesettings', 'stopresendingafterxdays', str(days)) - self.config.set( - 'bitmessagesettings', 'stopresendingafterxmonths', - str(months)) - - self.config.save() - - if self.net_restart_needed: - self.net_restart_needed = False - self.config.setTemp('bitmessagesettings', 'dontconnect', 'true') - self.timer.singleShot( - 5000, lambda: - self.config.setTemp( - 'bitmessagesettings', 'dontconnect', 'false') - ) - - self.parent.updateStartOnLogon() - - if ( - state.appdata != paths.lookupExeFolder() - and self.checkBoxPortableMode.isChecked() - ): - # If we are NOT using portable mode now but the user selected - # that we should... - # Write the keys.dat file to disk in the new location - sqlStoredProcedure('movemessagstoprog') - with open(paths.lookupExeFolder() + 'keys.dat', 'wb') as configfile: - self.config.write(configfile) - # Write the knownnodes.dat file to disk in the new location - knownnodes.saveKnownNodes(paths.lookupExeFolder()) - os.remove(state.appdata + 'keys.dat') - os.remove(state.appdata + 'knownnodes.dat') - previousAppdataLocation = state.appdata - state.appdata = paths.lookupExeFolder() - debug.resetLogging() - try: - os.remove(previousAppdataLocation + 'debug.log') - os.remove(previousAppdataLocation + 'debug.log.1') - except Exception: - pass - - if ( - state.appdata == paths.lookupExeFolder() - and not self.checkBoxPortableMode.isChecked() - ): - # If we ARE using portable mode now but the user selected - # that we shouldn't... - state.appdata = paths.lookupAppdataFolder() - if not os.path.exists(state.appdata): - os.makedirs(state.appdata) - sqlStoredProcedure('movemessagstoappdata') - # Write the keys.dat file to disk in the new location - self.config.save() - # Write the knownnodes.dat file to disk in the new location - knownnodes.saveKnownNodes(state.appdata) - os.remove(paths.lookupExeFolder() + 'keys.dat') - os.remove(paths.lookupExeFolder() + 'knownnodes.dat') - debug.resetLogging() - try: - os.remove(paths.lookupExeFolder() + 'debug.log') - os.remove(paths.lookupExeFolder() + 'debug.log.1') - except Exception: - pass +import bitmessage_icons_rc diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index 1e9a6f09..4aeba3ce 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -37,18 +37,6 @@ User Interface - - 8 - - - 8 - - - 8 - - - 8 - @@ -56,43 +44,20 @@ - - - - Tray + + + + Start Bitmessage in the tray (don't show main window) - - - - - Start Bitmessage in the tray (don't show main window) - - - - - - - Minimize to tray - - - false - - - - - - - Close to tray - - - - - + - Hide connection notifications + Minimize to tray + + + true @@ -152,15 +117,90 @@ Interface Language - - - + + + 100 0 + + + System Settings + + + + + English + + + + + Esperanto + + + + + Français + + + + + Deutsch + + + + + Español + + + + + русский + + + + + Norsk + + + + + العربية + + + + + 简体中文 + + + + + 日本語 + + + + + Nederlands + + + + + Česky + + + + + Pirate English + + + + + Other (set in keys.dat) + + @@ -173,18 +213,6 @@ Network Settings - - 8 - - - 8 - - - 8 - - - 8 - @@ -192,13 +220,26 @@ + + + Qt::Horizontal + + + + 125 + 20 + + + + + Listen for connections on port: - + @@ -208,30 +249,10 @@ - - - - UPnP - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + Bandwidth limit @@ -322,7 +343,7 @@ - + Proxy server / Tor @@ -403,13 +424,6 @@ - - - - Only connect to onion services (*.onion) - - - @@ -419,12 +433,12 @@ - SOCKS4a + SOCKS4a - SOCKS5 + SOCKS5 @@ -432,14 +446,7 @@ - - - - Announce self by UDP - - - - + Qt::Vertical @@ -459,18 +466,6 @@ Demanded difficulty - - 8 - - - 8 - - - 8 - - - 8 - @@ -599,18 +594,6 @@ Max acceptable difficulty - - 8 - - - 8 - - - 8 - - - 8 - @@ -715,33 +698,6 @@ - - - - - - Hardware GPU acceleration (OpenCL): - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - @@ -749,18 +705,6 @@ Namecoin integration - - 8 - - - 8 - - - 8 - - - 8 - @@ -944,18 +888,6 @@ Resends Expire - - 8 - - - 8 - - - 8 - - - 8 - @@ -980,69 +912,91 @@ - + 231 75 - - + + + 10 + 20 + 101 + 20 + + Give up after - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + - - + + + 30 + 40 + 80 + 16 + + and - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - 55 - 100 - - + + + + 113 + 20 + 51 + 20 + + - - - - - - 55 - 100 - - + + + + 113 + 40 + 51 + 20 + + - - - + + + + 169 + 23 + 61 + 16 + + days - - - + + + + 170 + 41 + 71 + 16 + + months. - - @@ -1063,14 +1017,7 @@ - - - - LanguageBox - QComboBox -
bitmessageqt.languagebox
-
-
+ tabWidgetSettings checkBoxStartOnLogon @@ -1154,53 +1101,5 @@ - - comboBoxProxyType - currentIndexChanged(int) - settingsDialog - comboBoxProxyTypeChanged - - - 20 - 20 - - - 20 - 20 - - - - - radioButtonNamecoinNamecoind - toggled(bool) - settingsDialog - namecoinTypeChanged - - - 20 - 20 - - - 20 - 20 - - - - - pushButtonNamecoinTest - clicked() - settingsDialog - click_pushButtonNamecoinTest - - - 20 - 20 - - - 20 - 20 - - - diff --git a/src/bitmessageqt/settingsmixin.py b/src/bitmessageqt/settingsmixin.py index 3d5999e2..c534d1b5 100644 --- a/src/bitmessageqt/settingsmixin.py +++ b/src/bitmessageqt/settingsmixin.py @@ -1,108 +1,79 @@ #!/usr/bin/python2.7 -""" -src/settingsmixin.py -==================== - -""" from PyQt4 import QtCore, QtGui - class SettingsMixin(object): - """Mixin for adding geometry and state saving between restarts.""" def warnIfNoObjectName(self): - """ - Handle objects which don't have a name. Currently it ignores them. Objects without a name can't have their - state/geometry saved as they don't have an identifier. - """ if self.objectName() == "": - # .. todo:: logger + # TODO: logger pass - + def writeState(self, source): - """Save object state (e.g. relative position of a splitter)""" self.warnIfNoObjectName() settings = QtCore.QSettings() settings.beginGroup(self.objectName()) settings.setValue("state", source.saveState()) settings.endGroup() - + def writeGeometry(self, source): - """Save object geometry (e.g. window size and position)""" self.warnIfNoObjectName() settings = QtCore.QSettings() settings.beginGroup(self.objectName()) settings.setValue("geometry", source.saveGeometry()) settings.endGroup() - + def readGeometry(self, target): - """Load object geometry""" self.warnIfNoObjectName() settings = QtCore.QSettings() try: geom = settings.value("/".join([str(self.objectName()), "geometry"])) target.restoreGeometry(geom.toByteArray() if hasattr(geom, 'toByteArray') else geom) - except Exception: + except Exception as e: pass def readState(self, target): - """Load object state""" self.warnIfNoObjectName() settings = QtCore.QSettings() try: state = settings.value("/".join([str(self.objectName()), "state"])) target.restoreState(state.toByteArray() if hasattr(state, 'toByteArray') else state) - except Exception: + except Exception as e: pass - + class SMainWindow(QtGui.QMainWindow, SettingsMixin): - """Main window with Settings functionality.""" def loadSettings(self): - """Load main window settings.""" self.readGeometry(self) self.readState(self) - + def saveSettings(self): - """Save main window settings""" self.writeState(self) self.writeGeometry(self) class STableWidget(QtGui.QTableWidget, SettingsMixin): - """Table widget with Settings functionality""" - # pylint: disable=too-many-ancestors def loadSettings(self): - """Load table settings.""" self.readState(self.horizontalHeader()) def saveSettings(self): - """Save table settings.""" self.writeState(self.horizontalHeader()) - + class SSplitter(QtGui.QSplitter, SettingsMixin): - """Splitter with Settings functionality.""" def loadSettings(self): - """Load splitter settings""" self.readState(self) def saveSettings(self): - """Save splitter settings.""" self.writeState(self) - + class STreeWidget(QtGui.QTreeWidget, SettingsMixin): - """Tree widget with settings functionality.""" - # pylint: disable=too-many-ancestors def loadSettings(self): - """Load tree settings.""" - # recurse children - # self.readState(self) + #recurse children + #self.readState(self) pass def saveSettings(self): - """Save tree settings""" - # recurse children - # self.writeState(self) + #recurse children + #self.writeState(self) pass diff --git a/src/bitmessageqt/sound.py b/src/bitmessageqt/sound.py index 33b4c500..9c86a9a4 100644 --- a/src/bitmessageqt/sound.py +++ b/src/bitmessageqt/sound.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -"""Sound Module""" # sound type constants SOUND_NONE = 0 @@ -13,12 +12,10 @@ SOUND_CONNECTION_GREEN = 5 # returns true if the given sound category is a connection sound # rather than a received message sound def is_connection_sound(category): - """Check if sound type is related to connectivity""" return category in ( SOUND_CONNECTED, SOUND_DISCONNECTED, SOUND_CONNECTION_GREEN ) - extensions = ('wav', 'mp3', 'oga') diff --git a/src/bitmessageqt/statusbar.py b/src/bitmessageqt/statusbar.py index 2add604d..65a5acfb 100644 --- a/src/bitmessageqt/statusbar.py +++ b/src/bitmessageqt/statusbar.py @@ -1,12 +1,8 @@ -# pylint: disable=unused-argument -"""Status bar Module""" - +from PyQt4 import QtCore, QtGui +from Queue import Queue from time import time -from PyQt4 import QtGui - class BMStatusBar(QtGui.QStatusBar): - """Status bar with queue and priorities""" duration = 10000 deleteAfter = 60 @@ -17,9 +13,6 @@ class BMStatusBar(QtGui.QStatusBar): self.iterator = 0 def timerEvent(self, event): - """an event handler which allows to queue and prioritise messages to - show in the status bar, for example if many messages come very quickly - after one another, it adds delays and so on""" while len(self.important) > 0: self.iterator += 1 try: @@ -37,3 +30,9 @@ class BMStatusBar(QtGui.QStatusBar): self.important.append([message, time()]) self.iterator = len(self.important) - 2 self.timerEvent(None) + + def showMessage(self, message, timeout=0): + super(BMStatusBar, self).showMessage(message, timeout) + + def clearMessage(self): + super(BMStatusBar, self).clearMessage() diff --git a/src/bitmessageqt/support.py b/src/bitmessageqt/support.py index a84affa4..25c6113d 100644 --- a/src/bitmessageqt/support.py +++ b/src/bitmessageqt/support.py @@ -1,43 +1,32 @@ -"""Composing support request message functions.""" -# pylint: disable=no-member - import ctypes +from PyQt4 import QtCore, QtGui import ssl import sys import time -from PyQt4 import QtCore - import account +from bmconfigparser import BMConfigParser +from debug import logger import defaults -import network.stats +from foldertree import AccountMixin +from helper_sql import * +from l10n import getTranslationLanguage +from openclpow import openclAvailable, openclEnabled import paths import proofofwork -import queues -import state -from bmconfigparser import config -from foldertree import AccountMixin -from helper_sql import sqlExecute, sqlQuery -from l10n import getTranslationLanguage -from openclpow import openclEnabled from pyelliptic.openssl import OpenSSL -from settings import getSOCKSProxyType +import queues +import network.stats +import state from version import softwareVersion -from tr import _translate - # this is BM support address going to Peter Surda OLD_SUPPORT_ADDRESS = 'BM-2cTkCtMYkrSPwFTpgcBrMrf5d8oZwvMZWK' SUPPORT_ADDRESS = 'BM-2cUdgkDDAahwPAU6oD2A7DnjqZz3hgY832' -SUPPORT_LABEL = _translate("Support", "PyBitmessage support") -SUPPORT_MY_LABEL = _translate("Support", "My new address") +SUPPORT_LABEL = 'PyBitmessage support' +SUPPORT_MY_LABEL = 'My new address' SUPPORT_SUBJECT = 'Support request' -SUPPORT_MESSAGE = _translate("Support", ''' -You can use this message to send a report to one of the PyBitmessage core \ -developers regarding PyBitmessage or the mailchuck.com email service. \ -If you are using PyBitmessage involuntarily, for example because \ -your computer was infected with ransomware, this is not an appropriate venue \ -for resolving such issues. +SUPPORT_MESSAGE = '''You can use this message to send a report to one of the PyBitmessage core developers regarding PyBitmessage or the mailchuck.com email service. If you are using PyBitmessage involuntarily, for example because your computer was infected with ransomware, this is not an appropriate venue for resolving such issues. Please describe what you are trying to do: @@ -47,8 +36,7 @@ Please describe what happens instead: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Please write above this line and if possible, keep the information about your \ -environment below intact. +Please write above this line and if possible, keep the information about your environment below intact. PyBitmessage version: {} Operating system: {} @@ -63,53 +51,39 @@ Locale: {} SOCKS: {} UPnP: {} Connected hosts: {} -''') - +''' def checkAddressBook(myapp): - sqlExecute('DELETE from addressbook WHERE address=?', OLD_SUPPORT_ADDRESS) - queryreturn = sqlQuery('SELECT * FROM addressbook WHERE address=?', SUPPORT_ADDRESS) + sqlExecute('''DELETE from addressbook WHERE address=?''', OLD_SUPPORT_ADDRESS) + queryreturn = sqlQuery('''SELECT * FROM addressbook WHERE address=?''', SUPPORT_ADDRESS) if queryreturn == []: - sqlExecute( - 'INSERT INTO addressbook VALUES (?,?)', - SUPPORT_LABEL.toUtf8(), SUPPORT_ADDRESS) + sqlExecute('''INSERT INTO addressbook VALUES (?,?)''', str(QtGui.QApplication.translate("Support", SUPPORT_LABEL)), SUPPORT_ADDRESS) myapp.rerenderAddressBook() - def checkHasNormalAddress(): - for address in config.addresses(): + for address in account.getSortedAccounts(): acct = account.accountClass(address) - if acct.type == AccountMixin.NORMAL and config.safeGetBoolean(address, 'enabled'): + if acct.type == AccountMixin.NORMAL and BMConfigParser().safeGetBoolean(address, 'enabled'): return address return False - def createAddressIfNeeded(myapp): if not checkHasNormalAddress(): - queues.addressGeneratorQueue.put(( - 'createRandomAddress', 4, 1, - str(SUPPORT_MY_LABEL.toUtf8()), - 1, "", False, - defaults.networkDefaultProofOfWorkNonceTrialsPerByte, - defaults.networkDefaultPayloadLengthExtraBytes - )) + queues.addressGeneratorQueue.put(('createRandomAddress', 4, 1, str(QtGui.QApplication.translate("Support", SUPPORT_MY_LABEL)), 1, "", False, defaults.networkDefaultProofOfWorkNonceTrialsPerByte, defaults.networkDefaultPayloadLengthExtraBytes)) while state.shutdown == 0 and not checkHasNormalAddress(): time.sleep(.2) myapp.rerenderComboBoxSendFrom() return checkHasNormalAddress() - def createSupportMessage(myapp): checkAddressBook(myapp) address = createAddressIfNeeded(myapp) if state.shutdown: return - myapp.ui.lineEditSubject.setText(SUPPORT_SUBJECT) - addrIndex = myapp.ui.comboBoxSendFrom.findData( - address, QtCore.Qt.UserRole, - QtCore.Qt.MatchFixedString | QtCore.Qt.MatchCaseSensitive) - if addrIndex == -1: # something is very wrong + myapp.ui.lineEditSubject.setText(str(QtGui.QApplication.translate("Support", SUPPORT_SUBJECT))) + addrIndex = myapp.ui.comboBoxSendFrom.findData(address, QtCore.Qt.UserRole, QtCore.Qt.MatchFixedString | QtCore.Qt.MatchCaseSensitive) + if addrIndex == -1: # something is very wrong return myapp.ui.comboBoxSendFrom.setCurrentIndex(addrIndex) myapp.ui.lineEditTo.setText(SUPPORT_ADDRESS) @@ -132,26 +106,29 @@ def createSupportMessage(myapp): pass architecture = "32" if ctypes.sizeof(ctypes.c_voidp) == 4 else "64" pythonversion = sys.version - - opensslversion = "%s (Python internal), %s (external for PyElliptic)" % ( - ssl.OPENSSL_VERSION, OpenSSL._version) + + opensslversion = "%s (Python internal), %s (external for PyElliptic)" % (ssl.OPENSSL_VERSION, OpenSSL._version) frozen = "N/A" if paths.frozen: frozen = paths.frozen portablemode = "True" if state.appdata == paths.lookupExeFolder() else "False" cpow = "True" if proofofwork.bmpow else "False" - openclpow = str( - config.safeGet('bitmessagesettings', 'opencl') - ) if openclEnabled() else "None" + #cpow = QtGui.QApplication.translate("Support", cpow) + openclpow = str(BMConfigParser().safeGet('bitmessagesettings', 'opencl')) if openclEnabled() else "None" + #openclpow = QtGui.QApplication.translate("Support", openclpow) locale = getTranslationLanguage() - socks = getSOCKSProxyType(config) or "N/A" - upnp = config.safeGet('bitmessagesettings', 'upnp', "N/A") + try: + socks = BMConfigParser().get('bitmessagesettings', 'socksproxytype') + except: + socks = "N/A" + try: + upnp = BMConfigParser().get('bitmessagesettings', 'upnp') + except: + upnp = "N/A" connectedhosts = len(network.stats.connectedHostsList()) - myapp.ui.textEditMessage.setText(unicode(SUPPORT_MESSAGE, 'utf-8').format( - version, os, architecture, pythonversion, opensslversion, frozen, - portablemode, cpow, openclpow, locale, socks, upnp, connectedhosts)) + myapp.ui.textEditMessage.setText(str(QtGui.QApplication.translate("Support", SUPPORT_MESSAGE)).format(version, os, architecture, pythonversion, opensslversion, frozen, portablemode, cpow, openclpow, locale, socks, upnp, connectedhosts)) # single msg tab myapp.ui.tabWidgetSend.setCurrentIndex( diff --git a/src/bitmessageqt/tests/__init__.py b/src/bitmessageqt/tests/__init__.py deleted file mode 100644 index a542abdc..00000000 --- a/src/bitmessageqt/tests/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""bitmessageqt tests""" - -from addressbook import TestAddressbook -from main import TestMain, TestUISignaler -from settings import TestSettings -from support import TestSupport - -__all__ = [ - "TestAddressbook", "TestMain", "TestSettings", "TestSupport", - "TestUISignaler" -] diff --git a/src/bitmessageqt/tests/addressbook.py b/src/bitmessageqt/tests/addressbook.py deleted file mode 100644 index cd86c5d6..00000000 --- a/src/bitmessageqt/tests/addressbook.py +++ /dev/null @@ -1,17 +0,0 @@ -import helper_addressbook -from bitmessageqt.support import createAddressIfNeeded - -from main import TestBase - - -class TestAddressbook(TestBase): - """Test case for addressbook""" - - def test_add_own_address_to_addressbook(self): - """Checking own address adding in addressbook""" - try: - address = createAddressIfNeeded(self.window) - self.assertFalse( - helper_addressbook.insert(label='test', address=address)) - except IndexError: - self.fail("Can't generate addresses") diff --git a/src/bitmessageqt/tests/main.py b/src/bitmessageqt/tests/main.py deleted file mode 100644 index b3aa67fa..00000000 --- a/src/bitmessageqt/tests/main.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Common definitions for bitmessageqt tests""" - -import Queue -import sys -import unittest - -from PyQt4 import QtCore, QtGui - -import bitmessageqt -import queues -from tr import _translate - - -class TestBase(unittest.TestCase): - """Base class for bitmessageqt test case""" - - def setUp(self): - self.app = ( - QtGui.QApplication.instance() - or bitmessageqt.BitmessageQtApplication(sys.argv)) - self.window = self.app.activeWindow() - if not self.window: - self.window = bitmessageqt.MyForm() - self.window.appIndicatorInit(self.app) - - def tearDown(self): - # self.app.deleteLater() - while True: - try: - thread, exc = queues.excQueue.get(block=False) - except Queue.Empty: - return - if thread == 'tests': - self.fail('Exception in the main thread: %s' % exc) - - -class TestMain(unittest.TestCase): - """Test case for main window - basic features""" - - def test_translate(self): - """Check the results of _translate() with various args""" - self.assertIsInstance( - _translate("MainWindow", "Test"), - QtCore.QString - ) - - -class TestUISignaler(TestBase): - """Test case for UISignalQueue""" - - def test_updateStatusBar(self): - """Check arguments order of updateStatusBar command""" - queues.UISignalQueue.put(( - 'updateStatusBar', ( - _translate("test", "Testing updateStatusBar..."), 1) - )) - - QtCore.QTimer.singleShot(60, self.app.quit) - self.app.exec_() - # self.app.processEvents(QtCore.QEventLoop.AllEvents, 60) diff --git a/src/bitmessageqt/tests/settings.py b/src/bitmessageqt/tests/settings.py deleted file mode 100644 index 0dcf8cf3..00000000 --- a/src/bitmessageqt/tests/settings.py +++ /dev/null @@ -1,34 +0,0 @@ -import threading -import time - -from main import TestBase -from bmconfigparser import config -from bitmessageqt import settings - - -class TestSettings(TestBase): - """A test case for the "Settings" dialog""" - def setUp(self): - super(TestSettings, self).setUp() - self.dialog = settings.SettingsDialog(self.window) - - def test_udp(self): - """Test the effect of checkBoxUDP""" - udp_setting = config.safeGetBoolean( - 'bitmessagesettings', 'udp') - self.assertEqual(udp_setting, self.dialog.checkBoxUDP.isChecked()) - self.dialog.checkBoxUDP.setChecked(not udp_setting) - self.dialog.accept() - self.assertEqual( - not udp_setting, - config.safeGetBoolean('bitmessagesettings', 'udp')) - time.sleep(5) - for thread in threading.enumerate(): - if thread.name == 'Announcer': # find Announcer thread - if udp_setting: - self.fail( - 'Announcer thread is running while udp set to False') - break - else: - if not udp_setting: - self.fail('No Announcer thread found while udp set to True') diff --git a/src/bitmessageqt/tests/support.py b/src/bitmessageqt/tests/support.py deleted file mode 100644 index ba28b73a..00000000 --- a/src/bitmessageqt/tests/support.py +++ /dev/null @@ -1,33 +0,0 @@ -# from PyQt4 import QtTest - -import sys - -from shared import isAddressInMyAddressBook - -from main import TestBase - - -class TestSupport(TestBase): - """A test case for support module""" - SUPPORT_ADDRESS = 'BM-2cUdgkDDAahwPAU6oD2A7DnjqZz3hgY832' - SUPPORT_SUBJECT = 'Support request' - - def test(self): - """trigger menu action "Contact Support" and check the result""" - ui = self.window.ui - self.assertEqual(ui.lineEditTo.text(), '') - self.assertEqual(ui.lineEditSubject.text(), '') - - ui.actionSupport.trigger() - - self.assertTrue( - isAddressInMyAddressBook(self.SUPPORT_ADDRESS)) - - self.assertEqual( - ui.tabWidget.currentIndex(), ui.tabWidget.indexOf(ui.send)) - self.assertEqual( - ui.lineEditTo.text(), self.SUPPORT_ADDRESS) - self.assertEqual( - ui.lineEditSubject.text(), self.SUPPORT_SUBJECT) - self.assertIn( - sys.version, ui.textEditMessage.toPlainText()) diff --git a/src/bitmessageqt/uisignaler.py b/src/bitmessageqt/uisignaler.py index c23ec3bc..055f9097 100644 --- a/src/bitmessageqt/uisignaler.py +++ b/src/bitmessageqt/uisignaler.py @@ -22,11 +22,8 @@ class UISignaler(QThread): command, data = queues.UISignalQueue.get() if command == 'writeNewAddressToTable': label, address, streamNumber = data - self.emit( - SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), - label, - address, - str(streamNumber)) + self.emit(SIGNAL( + "writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), label, address, str(streamNumber)) elif command == 'updateStatusBar': self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"), data) elif command == 'updateSentItemStatusByToAddress': @@ -49,11 +46,7 @@ class UISignaler(QThread): toAddress, fromLabel, fromAddress, subject, message, ackdata) elif command == 'updateNetworkStatusTab': outbound, add, destination = data - self.emit( - SIGNAL("updateNetworkStatusTab(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), - outbound, - add, - destination) + self.emit(SIGNAL("updateNetworkStatusTab(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), outbound, add, destination) elif command == 'updateNumberOfMessagesProcessed': self.emit(SIGNAL("updateNumberOfMessagesProcessed()")) elif command == 'updateNumberOfPubkeysProcessed': @@ -80,11 +73,7 @@ class UISignaler(QThread): self.emit(SIGNAL("newVersionAvailable(PyQt_PyObject)"), data) elif command == 'alert': title, text, exitAfterUserClicksOk = data - self.emit( - SIGNAL("displayAlert(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), - title, - text, - exitAfterUserClicksOk) + self.emit(SIGNAL("displayAlert(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), title, text, exitAfterUserClicksOk) else: sys.stderr.write( 'Command sent to UISignaler not recognized: %s\n' % command) diff --git a/src/bitmessageqt/utils.py b/src/bitmessageqt/utils.py index 9f849b3b..369d05ef 100644 --- a/src/bitmessageqt/utils.py +++ b/src/bitmessageqt/utils.py @@ -1,70 +1,61 @@ +from PyQt4 import QtGui import hashlib import os - -from PyQt4 import QtGui - -import state from addresses import addBMIfNotPresent -from bmconfigparser import config +from bmconfigparser import BMConfigParser +import state str_broadcast_subscribers = '[Broadcast subscribers]' str_chan = '[chan]' - def identiconize(address): size = 48 - - if not config.getboolean('bitmessagesettings', 'useidenticons'): - return QtGui.QIcon() - - # If you include another identicon library, please generate an + + # If you include another identicon library, please generate an # example identicon with the following md5 hash: # 3fd4bf901b9d4ea1394f0fb358725b28 + + try: + identicon_lib = BMConfigParser().get('bitmessagesettings', 'identiconlib') + except: + # default to qidenticon_two_x + identicon_lib = 'qidenticon_two_x' - identicon_lib = config.safeGet( - 'bitmessagesettings', 'identiconlib', 'qidenticon_two_x') - - # As an 'identiconsuffix' you could put "@bitmessge.ch" or "@bm.addr" - # to make it compatible with other identicon generators. (Note however, - # that E-Mail programs might convert the BM-address to lowercase first.) - # It can be used as a pseudo-password to salt the generation of - # the identicons to decrease the risk of attacks where someone creates - # an address to mimic someone else's identicon. - identiconsuffix = config.get('bitmessagesettings', 'identiconsuffix') - if identicon_lib[:len('qidenticon')] == 'qidenticon': + # As an 'identiconsuffix' you could put "@bitmessge.ch" or "@bm.addr" to make it compatible with other identicon generators. (Note however, that E-Mail programs might convert the BM-address to lowercase first.) + # It can be used as a pseudo-password to salt the generation of the identicons to decrease the risk + # of attacks where someone creates an address to mimic someone else's identicon. + identiconsuffix = BMConfigParser().get('bitmessagesettings', 'identiconsuffix') + + if not BMConfigParser().getboolean('bitmessagesettings', 'useidenticons'): + idcon = QtGui.QIcon() + return idcon + + if (identicon_lib[:len('qidenticon')] == 'qidenticon'): + # print identicon_lib # originally by: # :Author:Shin Adachi # Licesensed under FreeBSD License. # stripped from PIL and uses QT instead (by sendiulo, same license) import qidenticon - icon_hash = hashlib.md5( - addBMIfNotPresent(address) + identiconsuffix).hexdigest() - use_two_colors = identicon_lib[:len('qidenticon_two')] == 'qidenticon_two' - opacity = int( - identicon_lib not in ( - 'qidenticon_x', 'qidenticon_two_x', - 'qidenticon_b', 'qidenticon_two_b' - )) * 255 + hash = hashlib.md5(addBMIfNotPresent(address)+identiconsuffix).hexdigest() + use_two_colors = (identicon_lib[:len('qidenticon_two')] == 'qidenticon_two') + opacity = int(not((identicon_lib == 'qidenticon_x') | (identicon_lib == 'qidenticon_two_x') | (identicon_lib == 'qidenticon_b') | (identicon_lib == 'qidenticon_two_b')))*255 penwidth = 0 - image = qidenticon.render_identicon( - int(icon_hash, 16), size, use_two_colors, opacity, penwidth) + image = qidenticon.render_identicon(int(hash, 16), size, use_two_colors, opacity, penwidth) # filename = './images/identicons/'+hash+'.png' # image.save(filename) idcon = QtGui.QIcon() idcon.addPixmap(image, QtGui.QIcon.Normal, QtGui.QIcon.Off) return idcon elif identicon_lib == 'pydenticon': - # Here you could load pydenticon.py - # (just put it in the "src" folder of your Bitmessage source) + # print identicon_lib + # Here you could load pydenticon.py (just put it in the "src" folder of your Bitmessage source) from pydenticon import Pydenticon # It is not included in the source, because it is licensed under GPLv3 # GPLv3 is a copyleft license that would influence our licensing - # Find the source here: - # https://github.com/azaghal/pydenticon - # note that it requires pillow (or PIL) to be installed: - # https://python-pillow.org/ - idcon_render = Pydenticon( - addBMIfNotPresent(address) + identiconsuffix, size * 3) + # Find the source here: http://boottunes.googlecode.com/svn-history/r302/trunk/src/pydenticon.py + # note that it requires PIL to be installed: http://www.pythonware.com/products/pil/ + idcon_render = Pydenticon(addBMIfNotPresent(address)+identiconsuffix, size*3) rendering = idcon_render._render() data = rendering.convert("RGBA").tostring("raw", "RGBA") qim = QtGui.QImage(data, size, size, QtGui.QImage.Format_ARGB32) @@ -73,31 +64,32 @@ def identiconize(address): idcon.addPixmap(pix, QtGui.QIcon.Normal, QtGui.QIcon.Off) return idcon - def avatarize(address): """ - Loads a supported image for the given address' hash form 'avatars' folder - falls back to default avatar if 'default.*' file exists - falls back to identiconize(address) + loads a supported image for the given address' hash form 'avatars' folder + falls back to default avatar if 'default.*' file exists + falls back to identiconize(address) """ idcon = QtGui.QIcon() - icon_hash = hashlib.md5(addBMIfNotPresent(address)).hexdigest() + hash = hashlib.md5(addBMIfNotPresent(address)).hexdigest() + str_broadcast_subscribers = '[Broadcast subscribers]' if address == str_broadcast_subscribers: # don't hash [Broadcast subscribers] - icon_hash = address - # https://www.riverbankcomputing.com/static/Docs/PyQt4/qimagereader.html#supportedImageFormats + hash = address + # http://pyqt.sourceforge.net/Docs/PyQt4/qimagereader.html#supportedImageFormats + # print QImageReader.supportedImageFormats () # QImageReader.supportedImageFormats () - extensions = [ - 'PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', - 'TIFF', 'XBM', 'XPM', 'TGA'] + extensions = ['PNG', 'GIF', 'JPG', 'JPEG', 'SVG', 'BMP', 'MNG', 'PBM', 'PGM', 'PPM', 'TIFF', 'XBM', 'XPM', 'TGA'] # try to find a specific avatar for ext in extensions: - lower_hash = state.appdata + 'avatars/' + icon_hash + '.' + ext.lower() - upper_hash = state.appdata + 'avatars/' + icon_hash + '.' + ext.upper() + lower_hash = state.appdata + 'avatars/' + hash + '.' + ext.lower() + upper_hash = state.appdata + 'avatars/' + hash + '.' + ext.upper() if os.path.isfile(lower_hash): + # print 'found avatar of ', address idcon.addFile(lower_hash) return idcon elif os.path.isfile(upper_hash): + # print 'found avatar of ', address idcon.addFile(upper_hash) return idcon # if we haven't found any, try to find a default avatar diff --git a/src/bitmsghash/Makefile b/src/bitmsghash/Makefile index 7a494c39..c4fb4ab5 100644 --- a/src/bitmsghash/Makefile +++ b/src/bitmsghash/Makefile @@ -1,14 +1,14 @@ UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) - CCFLAGS += -I/usr/local/opt/openssl/include - LDFLAGS += -L/usr/local/opt/openssl/lib + CCFLAGS += -I/usr/local/Cellar/openssl/1.0.2d_1/include + LDFLAGS += -L/usr/local/Cellar/openssl/1.0.2d_1/lib else ifeq ($(UNAME_S),MINGW32_NT-6.1) CCFLAGS += -IC:\OpenSSL-1.0.2j-mingw\include -D_WIN32 -march=native LDFLAGS += -static-libgcc -LC:\OpenSSL-1.0.2j-mingw\lib -lwsock32 -o bitmsghash32.dll -Wl,--out-implib,bitmsghash.a else LDFLAGS += -lpthread -o bitmsghash.so endif - + all: bitmsghash.so powtest: @@ -22,3 +22,4 @@ bitmsghash.o: clean: rm -f bitmsghash.o bitmsghash.so bitmsghash*.dll + diff --git a/src/bitmsghash/bitmsghash.cpp b/src/bitmsghash/bitmsghash.cpp index 24775475..2d0d4b50 100644 --- a/src/bitmsghash/bitmsghash.cpp +++ b/src/bitmsghash/bitmsghash.cpp @@ -1,7 +1,7 @@ // bitmessage cracker, build with g++ or MSVS to a shared library, use included python code for usage under bitmessage #ifdef _WIN32 -#include "winsock.h" -#include "windows.h" +#include "Winsock.h" +#include "Windows.h" #define uint64_t unsigned __int64 #else #include diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py index abf285ad..6a598955 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -1,154 +1,125 @@ -""" -BMConfigParser class definition and default configuration settings -""" - -import os +import ConfigParser +import datetime import shutil -from threading import Event -from datetime import datetime +import os -from six import string_types -from six.moves import configparser +from singleton import Singleton +import state -try: - import state -except ImportError: - from pybitmessage import state - -SafeConfigParser = configparser.SafeConfigParser -config_ready = Event() - - -class BMConfigParser(SafeConfigParser): - """ - Singleton class inherited from :class:`ConfigParser.SafeConfigParser` - with additional methods specific to bitmessage config. - """ - # pylint: disable=too-many-ancestors - _temp = {} +BMConfigDefaults = { + "bitmessagesettings": { + "maxaddrperstreamsend": 500, + "maxbootstrapconnections": 20, + "maxdownloadrate": 0, + "maxoutboundconnections": 8, + "maxtotalconnections": 200, + "maxuploadrate": 0, + }, + "threads": { + "receive": 3, + }, + "network": { + "bind": '', + "dandelion": 90, + }, + "inventory": { + "storage": "sqlite", + "acceptmismatch": False, + }, + "knownnodes": { + "maxnodes": 20000, + }, + "zlib": { + 'maxsize': 1048576 + } +} +@Singleton +class BMConfigParser(ConfigParser.SafeConfigParser): def set(self, section, option, value=None): if self._optcre is self.OPTCRE or value: - if not isinstance(value, string_types): + if not isinstance(value, basestring): raise TypeError("option values must be strings") if not self.validate(section, option, value): - raise ValueError("Invalid value %s" % value) - return SafeConfigParser.set(self, section, option, value) + raise ValueError("Invalid value %s" % str(value)) + return ConfigParser.ConfigParser.set(self, section, option, value) - def get(self, section, option, **kwargs): - """Try returning temporary value before using parent get()""" + def get(self, section, option, raw=False, variables=None): try: - return self._temp[section][option] - except KeyError: - pass - return SafeConfigParser.get( - self, section, option, **kwargs) + if section == "bitmessagesettings" and option == "timeformat": + return ConfigParser.ConfigParser.get(self, section, option, raw, variables) + return ConfigParser.ConfigParser.get(self, section, option, True, variables) + except ConfigParser.InterpolationError: + return ConfigParser.ConfigParser.get(self, section, option, True, variables) + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e: + try: + return BMConfigDefaults[section][option] + except (KeyError, ValueError, AttributeError): + raise e - def setTemp(self, section, option, value=None): - """Temporary set option to value, not saving.""" + def safeGetBoolean(self, section, field): try: - self._temp[section][option] = value - except KeyError: - self._temp[section] = {option: value} - - def safeGetBoolean(self, section, option): - """Return value as boolean, False on exceptions""" - try: - return self.getboolean(section, option) - except (configparser.NoSectionError, configparser.NoOptionError, - ValueError, AttributeError): + return self.getboolean(section, field) + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, ValueError, AttributeError): return False - def safeGetInt(self, section, option, default=0): - """Return value as integer, default on exceptions, - 0 if default missing""" + def safeGetInt(self, section, field, default=0): try: - return int(self.get(section, option)) - except (configparser.NoSectionError, configparser.NoOptionError, - ValueError, AttributeError): + return self.getint(section, field) + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, ValueError, AttributeError): return default - def safeGetFloat(self, section, option, default=0.0): - """Return value as float, default on exceptions, - 0.0 if default missing""" - try: - return self.getfloat(section, option) - except (configparser.NoSectionError, configparser.NoOptionError, - ValueError, AttributeError): - return default - - def safeGet(self, section, option, default=None): - """ - Return value as is, default on exceptions, None if default missing - """ + def safeGet(self, section, option, default = None): try: return self.get(section, option) - except (configparser.NoSectionError, configparser.NoOptionError, - ValueError, AttributeError): + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError, ValueError, AttributeError): return default def items(self, section, raw=False, variables=None): - # pylint: disable=signature-differs - """Return section variables as parent, - but override the "raw" argument to always True""" - return SafeConfigParser.items(self, section, True, variables) + return ConfigParser.ConfigParser.items(self, section, True, variables) - def _reset(self): - """ - Reset current config. - There doesn't appear to be a built in method for this. - """ - self._temp = {} - sections = self.sections() - for x in sections: - self.remove_section(x) + def addresses(self): + return filter(lambda x: x.startswith('BM-'), BMConfigParser().sections()) - def read(self, filenames=None): - self._reset() - SafeConfigParser.read( - self, os.path.join(os.path.dirname(__file__), 'default.ini')) - if filenames: - SafeConfigParser.read(self, filenames) - - def addresses(self, sort=False): - """Return a list of local bitmessage addresses (from section labels)""" - sections = [x for x in self.sections() if x.startswith('BM-')] - if sort: - sections.sort(key=lambda item: self.get(item, 'label').lower()) - return sections + def read(self, filenames): + ConfigParser.ConfigParser.read(self, filenames) + for section in self.sections(): + for option in self.options(section): + try: + if not self.validate(section, option, ConfigParser.ConfigParser.get(self, section, option)): + try: + newVal = BMConfigDefaults[section][option] + except KeyError: + continue + ConfigParser.ConfigParser.set(self, section, option, newVal) + except ConfigParser.InterpolationError: + continue def save(self): - """Save the runtime config onto the filesystem""" fileName = os.path.join(state.appdata, 'keys.dat') - fileNameBak = '.'.join([ - fileName, datetime.now().strftime("%Y%j%H%M%S%f"), 'bak']) - # create a backup copy to prevent the accidental loss due to - # the disk write failure + fileNameBak = fileName + "." + datetime.datetime.now().strftime("%Y%j%H%M%S%f") + '.bak' + # create a backup copy to prevent the accidental loss due to the disk write failure try: shutil.copyfile(fileName, fileNameBak) # The backup succeeded. fileNameExisted = True - except(IOError, Exception): - # The backup failed. This can happen if the file - # didn't exist before. + except (IOError, Exception): + # The backup failed. This can happen if the file didn't exist before. fileNameExisted = False - - with open(fileName, 'w') as configfile: + # write the file + with open(fileName, 'wb') as configfile: self.write(configfile) # delete the backup if fileNameExisted: os.remove(fileNameBak) def validate(self, section, option, value): - """Input validator interface (using factory pattern)""" try: - return getattr(self, 'validate_%s_%s' % (section, option))(value) + return getattr(self, "validate_%s_%s" % (section, option))(value) except AttributeError: return True - @staticmethod - def validate_bitmessagesettings_maxoutboundconnections(value): - """Reject maxoutboundconnections that are too high or too low""" + def validate_bitmessagesettings_maxoutboundconnections(self, value): try: value = int(value) except ValueError: @@ -156,24 +127,3 @@ class BMConfigParser(SafeConfigParser): if value < 0 or value > 8: return False return True - - def search_addresses(self, address, searched_text): - """Return the searched label of MyAddress""" - return [x for x in [self.get(address, 'label').lower(), - address.lower()] if searched_text in x] - - def disable_address(self, address): - """"Disabling the specific Address""" - self.set(str(address), 'enabled', 'false') - self.save() - - def enable_address(self, address): - """"Enabling the specific Address""" - self.set(address, 'enabled', 'true') - self.save() - - -if not getattr(BMConfigParser, 'read_file', False): - BMConfigParser.read_file = BMConfigParser.readfp - -config = BMConfigParser() # TODO: remove this crutch diff --git a/src/build_osx.py b/src/build_osx.py index d83e9b9b..1d8f470e 100644 --- a/src/build_osx.py +++ b/src/build_osx.py @@ -1,6 +1,5 @@ -"""Building osx.""" -import os from glob import glob +import os from PyQt4 import QtCore from setuptools import setup @@ -9,31 +8,24 @@ version = os.getenv("PYBITMESSAGEVERSION", "custom") mainscript = ["bitmessagemain.py"] DATA_FILES = [ - ('', ['sslkeys', 'images', 'default.ini']), - ('sql', glob('sql/*.sql')), + ('', ['sslkeys', 'images']), ('bitmsghash', ['bitmsghash/bitmsghash.cl', 'bitmsghash/bitmsghash.so']), ('translations', glob('translations/*.qm')), ('ui', glob('bitmessageqt/*.ui')), - ( - 'translations', - glob(os.path.join(str(QtCore.QLibraryInfo.location( - QtCore.QLibraryInfo.TranslationsPath)), 'qt_??.qm'))), - ( - 'translations', - glob(os.path.join(str(QtCore.QLibraryInfo.location( - QtCore.QLibraryInfo.TranslationsPath)), 'qt_??_??.qm'))), + ('translations', glob(str(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)) + '/qt_??.qm')), + ('translations', glob(str(QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath)) + '/qt_??_??.qm')), ] setup( - name=name, - version=version, - app=mainscript, - data_files=DATA_FILES, - setup_requires=["py2app"], - options=dict( - py2app=dict( - includes=['sip', 'PyQt4._qt'], - iconfile="images/bitmessage.icns" + name = name, + version = version, + app = mainscript, + data_files = DATA_FILES, + setup_requires = ["py2app"], + options = dict( + py2app = dict( + includes = ['sip', 'PyQt4._qt'], + iconfile = "images/bitmessage.icns" ) ) ) diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index 06a0521a..bd377c1d 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -1,408 +1,297 @@ -""" -A thread for creating addresses -""" -import hashlib -import time -from binascii import hexlify - -from six.moves import configparser, queue - -import defaults -import highlevelcrypto -import queues import shared -import state -from addresses import decodeAddress, encodeAddress, encodeVarint -from bmconfigparser import config -from fallback import RIPEMD160Hash -from network import StoppableThread -from pyelliptic import arithmetic +import threading +import time +import sys from pyelliptic.openssl import OpenSSL -from tr import _translate +import ctypes +import hashlib +import highlevelcrypto +from addresses import * +from bmconfigparser import BMConfigParser +from debug import logger +import defaults +from helper_threading import * +from pyelliptic import arithmetic +import tr +from binascii import hexlify +import queues +import state +class addressGenerator(threading.Thread, StoppableThread): -class AddressGeneratorException(Exception): - '''Generic AddressGenerator exception''' - pass - - -class addressGenerator(StoppableThread): - """A thread for creating addresses""" - - name = "addressGenerator" - + def __init__(self): + # QThread.__init__(self, parent) + threading.Thread.__init__(self, name="addressGenerator") + self.initStop() + def stopThread(self): - """Tell the thread to stop putting a special command to it's queue""" try: queues.addressGeneratorQueue.put(("stopThread", "data")) - except queue.Full: - self.logger.error('addressGeneratorQueue is Full') - + except: + pass super(addressGenerator, self).stopThread() def run(self): - """ - Process the requests for addresses generation - from `.queues.addressGeneratorQueue` - """ - # pylint: disable=too-many-locals,too-many-branches,too-many-statements - # pylint: disable=too-many-nested-blocks - while state.shutdown == 0: queueValue = queues.addressGeneratorQueue.get() nonceTrialsPerByte = 0 payloadLengthExtraBytes = 0 live = True if queueValue[0] == 'createChan': - command, addressVersionNumber, streamNumber, label, \ - deterministicPassphrase, live = queueValue + command, addressVersionNumber, streamNumber, label, deterministicPassphrase, live = queueValue eighteenByteRipe = False numberOfAddressesToMake = 1 numberOfNullBytesDemandedOnFrontOfRipeHash = 1 elif queueValue[0] == 'joinChan': - command, chanAddress, label, deterministicPassphrase, \ - live = queueValue + command, chanAddress, label, deterministicPassphrase, live = queueValue eighteenByteRipe = False addressVersionNumber = decodeAddress(chanAddress)[1] streamNumber = decodeAddress(chanAddress)[2] numberOfAddressesToMake = 1 numberOfNullBytesDemandedOnFrontOfRipeHash = 1 elif len(queueValue) == 7: - command, addressVersionNumber, streamNumber, label, \ - numberOfAddressesToMake, deterministicPassphrase, \ - eighteenByteRipe = queueValue - - numberOfNullBytesDemandedOnFrontOfRipeHash = \ - config.safeGetInt( - 'bitmessagesettings', - 'numberofnullbytesonaddress', - 2 if eighteenByteRipe else 1 - ) + command, addressVersionNumber, streamNumber, label, numberOfAddressesToMake, deterministicPassphrase, eighteenByteRipe = queueValue + try: + numberOfNullBytesDemandedOnFrontOfRipeHash = BMConfigParser().getint( + 'bitmessagesettings', 'numberofnullbytesonaddress') + except: + if eighteenByteRipe: + numberOfNullBytesDemandedOnFrontOfRipeHash = 2 + else: + numberOfNullBytesDemandedOnFrontOfRipeHash = 1 # the default elif len(queueValue) == 9: - command, addressVersionNumber, streamNumber, label, \ - numberOfAddressesToMake, deterministicPassphrase, \ - eighteenByteRipe, nonceTrialsPerByte, \ - payloadLengthExtraBytes = queueValue - - numberOfNullBytesDemandedOnFrontOfRipeHash = \ - config.safeGetInt( - 'bitmessagesettings', - 'numberofnullbytesonaddress', - 2 if eighteenByteRipe else 1 - ) + command, addressVersionNumber, streamNumber, label, numberOfAddressesToMake, deterministicPassphrase, eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes = queueValue + try: + numberOfNullBytesDemandedOnFrontOfRipeHash = BMConfigParser().getint( + 'bitmessagesettings', 'numberofnullbytesonaddress') + except: + if eighteenByteRipe: + numberOfNullBytesDemandedOnFrontOfRipeHash = 2 + else: + numberOfNullBytesDemandedOnFrontOfRipeHash = 1 # the default elif queueValue[0] == 'stopThread': break else: - self.logger.error( - 'Programming error: A structure with the wrong number' - ' of values was passed into the addressGeneratorQueue.' - ' Here is the queueValue: %r\n', queueValue) + sys.stderr.write( + 'Programming error: A structure with the wrong number of values was passed into the addressGeneratorQueue. Here is the queueValue: %s\n' % repr(queueValue)) if addressVersionNumber < 3 or addressVersionNumber > 4: - self.logger.error( - 'Program error: For some reason the address generator' - ' queue has been given a request to create at least' - ' one version %s address which it cannot do.\n', - addressVersionNumber) + sys.stderr.write( + 'Program error: For some reason the address generator queue has been given a request to create at least one version %s address which it cannot do.\n' % addressVersionNumber) if nonceTrialsPerByte == 0: - nonceTrialsPerByte = config.getint( + nonceTrialsPerByte = BMConfigParser().getint( 'bitmessagesettings', 'defaultnoncetrialsperbyte') - if nonceTrialsPerByte < \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte: - nonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte + if nonceTrialsPerByte < defaults.networkDefaultProofOfWorkNonceTrialsPerByte: + nonceTrialsPerByte = defaults.networkDefaultProofOfWorkNonceTrialsPerByte if payloadLengthExtraBytes == 0: - payloadLengthExtraBytes = config.getint( + payloadLengthExtraBytes = BMConfigParser().getint( 'bitmessagesettings', 'defaultpayloadlengthextrabytes') - if payloadLengthExtraBytes < \ - defaults.networkDefaultPayloadLengthExtraBytes: - payloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes + if payloadLengthExtraBytes < defaults.networkDefaultPayloadLengthExtraBytes: + payloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes if command == 'createRandomAddress': queues.UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", "Generating one new address") - )) - # This next section is a little bit strange. We're going - # to generate keys over and over until we find one - # that starts with either \x00 or \x00\x00. Then when - # we pack them into a Bitmessage address, we won't store - # the \x00 or \x00\x00 bytes thus making the address shorter. + 'updateStatusBar', tr._translate("MainWindow", "Generating one new address"))) + # This next section is a little bit strange. We're going to generate keys over and over until we + # find one that starts with either \x00 or \x00\x00. Then when we pack them into a Bitmessage address, + # we won't store the \x00 or \x00\x00 bytes thus making the + # address shorter. startTime = time.time() numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 potentialPrivSigningKey = OpenSSL.rand(32) - potentialPubSigningKey = highlevelcrypto.pointMult( - potentialPrivSigningKey) + potentialPubSigningKey = highlevelcrypto.pointMult(potentialPrivSigningKey) while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 potentialPrivEncryptionKey = OpenSSL.rand(32) potentialPubEncryptionKey = highlevelcrypto.pointMult( potentialPrivEncryptionKey) + ripe = hashlib.new('ripemd160') sha = hashlib.new('sha512') sha.update( potentialPubSigningKey + potentialPubEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() - if ( - ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] - == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash - ): + ripe.update(sha.digest()) + if ripe.digest()[:numberOfNullBytesDemandedOnFrontOfRipeHash] == '\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash: break - self.logger.info( - 'Generated address with ripe digest: %s', hexlify(ripe)) + logger.info('Generated address with ripe digest: %s' % hexlify(ripe.digest())) try: - self.logger.info( - 'Address generator calculated %s addresses at %s' - ' addresses per second before finding one with' - ' the correct ripe-prefix.', - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix - / (time.time() - startTime)) + logger.info('Address generator calculated %s addresses at %s addresses per second before finding one with the correct ripe-prefix.' % (numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix / (time.time() - startTime))) except ZeroDivisionError: - # The user must have a pretty fast computer. - # time.time() - startTime equaled zero. + # The user must have a pretty fast computer. time.time() - startTime equaled zero. pass - address = encodeAddress( - addressVersionNumber, streamNumber, ripe) + address = encodeAddress(addressVersionNumber, streamNumber, ripe.digest()) - # An excellent way for us to store our keys - # is in Wallet Import Format. Let us convert now. + # An excellent way for us to store our keys is in Wallet Import Format. Let us convert now. # https://en.bitcoin.it/wiki/Wallet_import_format - privSigningKey = b'\x80' + potentialPrivSigningKey + privSigningKey = '\x80' + potentialPrivSigningKey checksum = hashlib.sha256(hashlib.sha256( privSigningKey).digest()).digest()[0:4] privSigningKeyWIF = arithmetic.changebase( privSigningKey + checksum, 256, 58) - privEncryptionKey = b'\x80' + potentialPrivEncryptionKey + privEncryptionKey = '\x80' + potentialPrivEncryptionKey checksum = hashlib.sha256(hashlib.sha256( privEncryptionKey).digest()).digest()[0:4] privEncryptionKeyWIF = arithmetic.changebase( privEncryptionKey + checksum, 256, 58) - config.add_section(address) - config.set(address, 'label', label) - config.set(address, 'enabled', 'true') - config.set(address, 'decoy', 'false') - config.set(address, 'noncetrialsperbyte', str( + BMConfigParser().add_section(address) + BMConfigParser().set(address, 'label', label) + BMConfigParser().set(address, 'enabled', 'true') + BMConfigParser().set(address, 'decoy', 'false') + BMConfigParser().set(address, 'noncetrialsperbyte', str( nonceTrialsPerByte)) - config.set(address, 'payloadlengthextrabytes', str( + BMConfigParser().set(address, 'payloadlengthextrabytes', str( payloadLengthExtraBytes)) - config.set( - address, 'privsigningkey', privSigningKeyWIF.decode()) - config.set( - address, 'privencryptionkey', - privEncryptionKeyWIF.decode()) - config.save() + BMConfigParser().set( + address, 'privSigningKey', privSigningKeyWIF) + BMConfigParser().set( + address, 'privEncryptionKey', privEncryptionKeyWIF) + BMConfigParser().save() # The API and the join and create Chan functionality # both need information back from the address generator. queues.apiAddressGeneratorReturnQueue.put(address) queues.UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", - "Done generating address. Doing work necessary" - " to broadcast it...") - )) + 'updateStatusBar', tr._translate("MainWindow", "Done generating address. Doing work necessary to broadcast it..."))) queues.UISignalQueue.put(('writeNewAddressToTable', ( label, address, streamNumber))) shared.reloadMyAddressHashes() if addressVersionNumber == 3: queues.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', ripe)) + 'sendOutOrStoreMyV3Pubkey', ripe.digest())) elif addressVersionNumber == 4: queues.workerQueue.put(( 'sendOutOrStoreMyV4Pubkey', address)) - elif command in ( - 'createDeterministicAddresses', 'createChan', - 'getDeterministicAddress', 'joinChan' - ): - if not deterministicPassphrase: - self.logger.warning( - 'You are creating deterministic' - ' address(es) using a blank passphrase.' - ' Bitmessage will do it but it is rather stupid.') + elif command == 'createDeterministicAddresses' or command == 'getDeterministicAddress' or command == 'createChan' or command == 'joinChan': + if len(deterministicPassphrase) == 0: + sys.stderr.write( + 'WARNING: You are creating deterministic address(es) using a blank passphrase. Bitmessage will do it but it is rather stupid.') if command == 'createDeterministicAddresses': queues.UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", - "Generating %1 new addresses." - ).arg(str(numberOfAddressesToMake)) - )) + 'updateStatusBar', tr._translate("MainWindow","Generating %1 new addresses.").arg(str(numberOfAddressesToMake)))) signingKeyNonce = 0 encryptionKeyNonce = 1 - # We fill out this list no matter what although we only - # need it if we end up passing the info to the API. - listOfNewAddressesToSendOutThroughTheAPI = [] + listOfNewAddressesToSendOutThroughTheAPI = [ + ] # We fill out this list no matter what although we only need it if we end up passing the info to the API. - for _ in range(numberOfAddressesToMake): - # This next section is a little bit strange. We're - # going to generate keys over and over until we find - # one that has a RIPEMD hash that starts with either - # \x00 or \x00\x00. Then when we pack them into a - # Bitmessage address, we won't store the \x00 or + for i in range(numberOfAddressesToMake): + # This next section is a little bit strange. We're going to generate keys over and over until we + # find one that has a RIPEMD hash that starts with either \x00 or \x00\x00. Then when we pack them + # into a Bitmessage address, we won't store the \x00 or # \x00\x00 bytes thus making the address shorter. startTime = time.time() numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 potentialPrivSigningKey = hashlib.sha512( - deterministicPassphrase - + encodeVarint(signingKeyNonce) - ).digest()[:32] + deterministicPassphrase + encodeVarint(signingKeyNonce)).digest()[:32] potentialPrivEncryptionKey = hashlib.sha512( - deterministicPassphrase - + encodeVarint(encryptionKeyNonce) - ).digest()[:32] + deterministicPassphrase + encodeVarint(encryptionKeyNonce)).digest()[:32] potentialPubSigningKey = highlevelcrypto.pointMult( potentialPrivSigningKey) potentialPubEncryptionKey = highlevelcrypto.pointMult( potentialPrivEncryptionKey) signingKeyNonce += 2 encryptionKeyNonce += 2 + ripe = hashlib.new('ripemd160') sha = hashlib.new('sha512') sha.update( potentialPubSigningKey + potentialPubEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() - if ( - ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] - == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash - ): + ripe.update(sha.digest()) + if ripe.digest()[:numberOfNullBytesDemandedOnFrontOfRipeHash] == '\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash: break - self.logger.info( - 'Generated address with ripe digest: %s', hexlify(ripe)) + + logger.info('Generated address with ripe digest: %s' % hexlify(ripe.digest())) try: - self.logger.info( - 'Address generator calculated %s addresses' - ' at %s addresses per second before finding' - ' one with the correct ripe-prefix.', - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, - numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix - / (time.time() - startTime) - ) + logger.info('Address generator calculated %s addresses at %s addresses per second before finding one with the correct ripe-prefix.' % (numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix, numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix / (time.time() - startTime))) except ZeroDivisionError: - # The user must have a pretty fast computer. - # time.time() - startTime equaled zero. + # The user must have a pretty fast computer. time.time() - startTime equaled zero. pass - address = encodeAddress( - addressVersionNumber, streamNumber, ripe) + address = encodeAddress(addressVersionNumber, streamNumber, ripe.digest()) saveAddressToDisk = True - # If we are joining an existing chan, let us check - # to make sure it matches the provided Bitmessage address + # If we are joining an existing chan, let us check to make sure it matches the provided Bitmessage address if command == 'joinChan': if address != chanAddress: - listOfNewAddressesToSendOutThroughTheAPI.append( - 'chan name does not match address') + listOfNewAddressesToSendOutThroughTheAPI.append('chan name does not match address') saveAddressToDisk = False if command == 'getDeterministicAddress': saveAddressToDisk = False if saveAddressToDisk and live: - # An excellent way for us to store our keys is - # in Wallet Import Format. Let us convert now. + # An excellent way for us to store our keys is in Wallet Import Format. Let us convert now. # https://en.bitcoin.it/wiki/Wallet_import_format - privSigningKey = b'\x80' + potentialPrivSigningKey + privSigningKey = '\x80' + potentialPrivSigningKey checksum = hashlib.sha256(hashlib.sha256( privSigningKey).digest()).digest()[0:4] privSigningKeyWIF = arithmetic.changebase( privSigningKey + checksum, 256, 58) - privEncryptionKey = b'\x80' + \ + privEncryptionKey = '\x80' + \ potentialPrivEncryptionKey checksum = hashlib.sha256(hashlib.sha256( privEncryptionKey).digest()).digest()[0:4] privEncryptionKeyWIF = arithmetic.changebase( privEncryptionKey + checksum, 256, 58) + try: - config.add_section(address) + BMConfigParser().add_section(address) addressAlreadyExists = False - except configparser.DuplicateSectionError: + except: addressAlreadyExists = True - + if addressAlreadyExists: - self.logger.info( - '%s already exists. Not adding it again.', - address - ) + logger.info('%s already exists. Not adding it again.' % address) queues.UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", - "%1 is already in 'Your Identities'." - " Not adding it again." - ).arg(address) - )) + 'updateStatusBar', tr._translate("MainWindow","%1 is already in 'Your Identities'. Not adding it again.").arg(address))) else: - self.logger.debug('label: %s', label) - config.set(address, 'label', label) - config.set(address, 'enabled', 'true') - config.set(address, 'decoy', 'false') - if command in ('createChan', 'joinChan'): - config.set(address, 'chan', 'true') - config.set( - address, 'noncetrialsperbyte', - str(nonceTrialsPerByte)) - config.set( - address, 'payloadlengthextrabytes', - str(payloadLengthExtraBytes)) - config.set( - address, 'privsigningkey', - privSigningKeyWIF.decode()) - config.set( - address, 'privencryptionkey', - privEncryptionKeyWIF.decode()) - config.save() + logger.debug('label: %s' % label) + BMConfigParser().set(address, 'label', label) + BMConfigParser().set(address, 'enabled', 'true') + BMConfigParser().set(address, 'decoy', 'false') + if command == 'joinChan' or command == 'createChan': + BMConfigParser().set(address, 'chan', 'true') + BMConfigParser().set(address, 'noncetrialsperbyte', str( + nonceTrialsPerByte)) + BMConfigParser().set(address, 'payloadlengthextrabytes', str( + payloadLengthExtraBytes)) + BMConfigParser().set( + address, 'privSigningKey', privSigningKeyWIF) + BMConfigParser().set( + address, 'privEncryptionKey', privEncryptionKeyWIF) + BMConfigParser().save() - queues.UISignalQueue.put(( - 'writeNewAddressToTable', - (label, address, str(streamNumber)) - )) + queues.UISignalQueue.put(('writeNewAddressToTable', ( + label, address, str(streamNumber)))) listOfNewAddressesToSendOutThroughTheAPI.append( address) - shared.myECCryptorObjects[ripe] = \ - highlevelcrypto.makeCryptor( - hexlify(potentialPrivEncryptionKey)) - shared.myAddressesByHash[ripe] = address - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] + shared.myECCryptorObjects[ripe.digest()] = highlevelcrypto.makeCryptor( + hexlify(potentialPrivEncryptionKey)) + shared.myAddressesByHash[ripe.digest()] = address + tag = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + ripe.digest()).digest()).digest()[32:] shared.myAddressesByTag[tag] = address if addressVersionNumber == 3: - # If this is a chan address, - # the worker thread won't send out - # the pubkey over the network. queues.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', ripe)) + 'sendOutOrStoreMyV3Pubkey', ripe.digest())) # If this is a chan address, + # the worker thread won't send out the pubkey over the network. elif addressVersionNumber == 4: queues.workerQueue.put(( 'sendOutOrStoreMyV4Pubkey', address)) queues.UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", "Done generating address") - )) - elif saveAddressToDisk and not live \ - and not config.has_section(address): - listOfNewAddressesToSendOutThroughTheAPI.append( - address) + 'updateStatusBar', tr._translate("MainWindow", "Done generating address"))) + elif saveAddressToDisk and not live and not BMConfigParser().has_section(address): + listOfNewAddressesToSendOutThroughTheAPI.append(address) # Done generating addresses. - if command in ( - 'createDeterministicAddresses', 'createChan', 'joinChan' - ): + if command == 'createDeterministicAddresses' or command == 'joinChan' or command == 'createChan': queues.apiAddressGeneratorReturnQueue.put( listOfNewAddressesToSendOutThroughTheAPI) elif command == 'getDeterministicAddress': queues.apiAddressGeneratorReturnQueue.put(address) else: - raise AddressGeneratorException( - "Error in the addressGenerator thread. Thread was" - + " given a command it could not understand: " + command) + raise Exception( + "Error in the addressGenerator thread. Thread was given a command it could not understand: " + command) queues.addressGeneratorQueue.task_done() diff --git a/src/class_objectHashHolder.py b/src/class_objectHashHolder.py new file mode 100644 index 00000000..2e456d8c --- /dev/null +++ b/src/class_objectHashHolder.py @@ -0,0 +1,54 @@ +# objectHashHolder is a timer-driven thread. One objectHashHolder thread is used +# by each sendDataThread. The sendDataThread uses it whenever it needs to +# advertise an object to peers in an inv message, or advertise a peer to other +# peers in an addr message. Instead of sending them out immediately, it must +# wait a random number of seconds for each connection so that different peers +# get different objects at different times. Thus an attacker who is +# connecting to many network nodes who receives a message first from Alice +# cannot be sure if Alice is the node who originated the message. + +import random +import time +import threading + +class objectHashHolder(threading.Thread): + size = 10 + def __init__(self, sendDataThreadMailbox): + threading.Thread.__init__(self, name="objectHashHolder") + self.shutdown = False + self.sendDataThreadMailbox = sendDataThreadMailbox # This queue is used to submit data back to our associated sendDataThread. + self.collectionOfHashLists = [] + self.collectionOfPeerLists = [] + for i in range(objectHashHolder.size): + self.collectionOfHashLists.append([]) + self.collectionOfPeerLists.append([]) + + def run(self): + iterator = 0 + while not self.shutdown: + if len(self.collectionOfHashLists[iterator]) > 0: + self.sendDataThreadMailbox.put((0, 'sendinv', self.collectionOfHashLists[iterator])) + self.collectionOfHashLists[iterator] = [] + if len(self.collectionOfPeerLists[iterator]) > 0: + self.sendDataThreadMailbox.put((0, 'sendaddr', self.collectionOfPeerLists[iterator])) + self.collectionOfPeerLists[iterator] = [] + iterator += 1 + iterator %= objectHashHolder.size + time.sleep(1) + + def holdHash(self,hash): + self.collectionOfHashLists[random.randrange(0, objectHashHolder.size)].append(hash) + + def hasHash(self, hash): + if hash in (hashlist for hashlist in self.collectionOfHashLists): + return True + return False + + def holdPeer(self,peerDetails): + self.collectionOfPeerLists[random.randrange(0, objectHashHolder.size)].append(peerDetails) + + def hashCount(self): + return sum([len(x) for x in self.collectionOfHashLists if type(x) is list]) + + def close(self): + self.shutdown = True diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 8d2e12a8..181ce30e 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -1,43 +1,33 @@ -""" -The objectProcessor thread, of which there is only one, -processes the network objects -""" -# pylint: disable=too-many-locals,too-many-return-statements -# pylint: disable=too-many-branches,too-many-statements -import hashlib -import logging -import os -import random -import subprocess # nosec B404 -import threading import time +import threading +import shared +import hashlib +import random +from struct import unpack, pack +import sys +import string +from subprocess import call # used when the API must execute an outside program +import traceback from binascii import hexlify +from pyelliptic.openssl import OpenSSL +import highlevelcrypto +from addresses import * +from bmconfigparser import BMConfigParser +import helper_generic +from helper_generic import addDataPadding import helper_bitcoin import helper_inbox import helper_msgcoding import helper_sent -import highlevelcrypto -import l10n +from helper_sql import * +from helper_ackPayload import genAckPayload import protocol import queues -import shared import state -from addresses import ( - calculateInventoryHash, decodeAddress, decodeVarint, - encodeAddress, encodeVarint, varintDecodeError -) -from bmconfigparser import config -from fallback import RIPEMD160Hash -from helper_sql import ( - sql_ready, sql_timeout, SqlBulkExecute, sqlExecute, sqlQuery) -from inventory import Inventory -from network import knownnodes -from network.node import Peer -from tr import _translate - -logger = logging.getLogger('default') - +import tr +from debug import logger +import l10n class objectProcessor(threading.Thread): """ @@ -46,95 +36,65 @@ class objectProcessor(threading.Thread): """ def __init__(self): threading.Thread.__init__(self, name="objectProcessor") - random.seed() - if sql_ready.wait(sql_timeout) is False: - logger.fatal('SQL thread is not started in %s sec', sql_timeout) - os._exit(1) # pylint: disable=protected-access - shared.reloadMyAddressHashes() - shared.reloadBroadcastSendersForWhichImWatching() - # It may be the case that the last time Bitmessage was running, - # the user closed it before it finished processing everything in the - # objectProcessorQueue. Assuming that Bitmessage wasn't closed - # forcefully, it should have saved the data in the queue into the - # objectprocessorqueue table. Let's pull it out. + """ + It may be the case that the last time Bitmessage was running, the user + closed it before it finished processing everything in the + objectProcessorQueue. Assuming that Bitmessage wasn't closed forcefully, + it should have saved the data in the queue into the objectprocessorqueue + table. Let's pull it out. + """ queryreturn = sqlQuery( - 'SELECT objecttype, data FROM objectprocessorqueue') - for objectType, data in queryreturn: - queues.objectProcessorQueue.put((objectType, data)) - sqlExecute('DELETE FROM objectprocessorqueue') - logger.debug( - 'Loaded %s objects from disk into the objectProcessorQueue.', - len(queryreturn)) - self.successfullyDecryptMessageTimings = [] + '''SELECT objecttype, data FROM objectprocessorqueue''') + for row in queryreturn: + objectType, data = row + queues.objectProcessorQueue.put((objectType,data)) + sqlExecute('''DELETE FROM objectprocessorqueue''') + logger.debug('Loaded %s objects from disk into the objectProcessorQueue.' % str(len(queryreturn))) + def run(self): - """Process the objects from `.queues.objectProcessorQueue`""" while True: objectType, data = queues.objectProcessorQueue.get() self.checkackdata(data) try: - if objectType == protocol.OBJECT_GETPUBKEY: + if objectType == 0: # getpubkey self.processgetpubkey(data) - elif objectType == protocol.OBJECT_PUBKEY: + elif objectType == 1: #pubkey self.processpubkey(data) - elif objectType == protocol.OBJECT_MSG: + elif objectType == 2: #msg self.processmsg(data) - elif objectType == protocol.OBJECT_BROADCAST: + elif objectType == 3: #broadcast self.processbroadcast(data) - elif objectType == protocol.OBJECT_ONIONPEER: - self.processonion(data) - # 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. - elif objectType == 'checkShutdownVariable': + 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: if isinstance(objectType, int): - logger.info( - 'Don\'t know how to handle object type 0x%08X', - objectType) + 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) + 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, config.safeGetInt('zlib', 'maxsize')) + 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: - logger.critical( - 'Critical error within objectProcessorThread: \n', - exc_info=True) + logger.debug("There was a problem with a varint while processing an object. Some details: %s" % e) + except Exception as e: + logger.critical("Critical error within objectProcessorThread: \n%s" % traceback.format_exc()) if state.shutdown: - # Wait just a moment for most of the connections to close - time.sleep(.5) + time.sleep(.5) # Wait just a moment for most of the connections to close numberOfObjectsThatWereInTheObjectProcessorQueue = 0 with SqlBulkExecute() as sql: while queues.objectProcessorQueue.curSize > 0: objectType, data = queues.objectProcessorQueue.get() - sql.execute( - 'INSERT INTO objectprocessorqueue VALUES (?,?)', - objectType, data) + sql.execute('''INSERT INTO objectprocessorqueue VALUES (?,?)''', + objectType,data) numberOfObjectsThatWereInTheObjectProcessorQueue += 1 - logger.debug( - 'Saved %s objects from the objectProcessorQueue to' - ' disk. objectProcessorThread exiting.', - numberOfObjectsThatWereInTheObjectProcessorQueue) + logger.debug('Saved %s objects from the objectProcessorQueue to disk. objectProcessorThread exiting.' % str(numberOfObjectsThatWereInTheObjectProcessorQueue)) state.shutdown = 2 break - @staticmethod - def checkackdata(data): - """Checking Acknowledgement of message received or not?""" + def checkackdata(self, data): # Let's check whether this is a message acknowledgement bound for us. if len(data) < 32: return @@ -142,50 +102,22 @@ class objectProcessor(threading.Thread): # bypass nonce and time, retain object type/version/stream + body readPosition = 16 - if data[readPosition:] in state.ackdataForWhichImWatching: + if data[readPosition:] in shared.ackdataForWhichImWatching: logger.info('This object is an acknowledgement bound for me.') - del state.ackdataForWhichImWatching[data[readPosition:]] - sqlExecute( - "UPDATE sent SET status='ackreceived', lastactiontime=?" - " WHERE ackdata=?", int(time.time()), data[readPosition:]) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - data[readPosition:], - _translate( - "MainWindow", - "Acknowledgement of the message received %1" - ).arg(l10n.formatTimestamp())) - )) + 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.') - @staticmethod - def processonion(data): - """Process onionpeer object""" - readPosition = 20 # bypass the nonce, time, and object type - length = decodeVarint(data[readPosition:readPosition + 10])[1] - readPosition += length - stream, length = decodeVarint(data[readPosition:readPosition + 10]) - readPosition += length - # it seems that stream is checked in network.bmproto - port, length = decodeVarint(data[readPosition:readPosition + 10]) - host = protocol.checkIPAddress(data[readPosition + length:]) - - if not host: - return - peer = Peer(host, port) - with knownnodes.knownNodesLock: - # FIXME: adjust expirestime - knownnodes.addKnownNode( - stream, peer, is_self=state.ownAddresses.get(peer)) - - @staticmethod - def processgetpubkey(data): - """Process getpubkey object""" + + def processgetpubkey(self, data): if len(data) > 200: - return logger.info( - 'getpubkey is abnormally long. Sanity check failed.' - ' Ignoring object.') + 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]) @@ -195,40 +127,30 @@ class objectProcessor(threading.Thread): readPosition += streamNumberLength if requestedAddressVersionNumber == 0: - return logger.debug( - 'The requestedAddressVersionNumber of the pubkey request' - ' is zero. That doesn\'t make any sense. Ignoring it.') - if requestedAddressVersionNumber == 1: - return logger.debug( - 'The requestedAddressVersionNumber of the pubkey request' - ' is 1 which isn\'t supported anymore. Ignoring it.') - if requestedAddressVersionNumber > 4: - return logger.debug( - 'The requestedAddressVersionNumber of the pubkey request' - ' is too high. Can\'t understand. Ignoring it.') + logger.debug('The requestedAddressVersionNumber of the pubkey request is zero. That doesn\'t make any sense. Ignoring it.') + return + elif requestedAddressVersionNumber == 1: + logger.debug('The requestedAddressVersionNumber of the pubkey request is 1 which isn\'t supported anymore. Ignoring it.') + return + elif requestedAddressVersionNumber > 4: + logger.debug('The requestedAddressVersionNumber of the pubkey request is too high. Can\'t understand. Ignoring it.') + return myAddress = '' - if requestedAddressVersionNumber <= 3: + if requestedAddressVersionNumber <= 3 : requestedHash = data[readPosition:readPosition + 20] if len(requestedHash) != 20: - return logger.debug( - 'The length of the requested hash is not 20 bytes.' - ' Something is wrong. Ignoring.') - logger.info( - 'the hash requested in this getpubkey request is: %s', - hexlify(requestedHash)) - # if this address hash is one of mine - if requestedHash in shared.myAddressesByHash: + logger.debug('The length of the requested hash is not 20 bytes. Something is wrong. Ignoring.') + return + logger.info('the hash requested in this getpubkey request is: %s' % hexlify(requestedHash)) + if requestedHash in shared.myAddressesByHash: # if this address hash is one of mine myAddress = shared.myAddressesByHash[requestedHash] elif requestedAddressVersionNumber >= 4: requestedTag = data[readPosition:readPosition + 32] if len(requestedTag) != 32: - return logger.debug( - 'The length of the requested tag is not 32 bytes.' - ' Something is wrong. Ignoring.') - logger.debug( - 'the tag requested in this getpubkey request is: %s', - hexlify(requestedTag)) + logger.debug('The length of the requested tag is not 32 bytes. Something is wrong. Ignoring.') + return + logger.debug('the tag requested in this getpubkey request is: %s' % hexlify(requestedTag)) if requestedTag in shared.myAddressesByTag: myAddress = shared.myAddressesByTag[requestedTag] @@ -237,48 +159,39 @@ class objectProcessor(threading.Thread): return if decodeAddress(myAddress)[1] != requestedAddressVersionNumber: - return logger.warning( - '(Within the processgetpubkey function) Someone requested' - ' one of my pubkeys but the requestedAddressVersionNumber' - ' doesn\'t match my actual address version number.' - ' Ignoring.') + logger.warning('(Within the processgetpubkey function) Someone requested one of my pubkeys but the requestedAddressVersionNumber doesn\'t match my actual address version number. Ignoring.') + return if decodeAddress(myAddress)[2] != streamNumber: - return logger.warning( - '(Within the processgetpubkey function) Someone requested' - ' one of my pubkeys but the stream number on which we' - ' heard this getpubkey object doesn\'t match this' - ' address\' stream number. Ignoring.') - if config.safeGetBoolean(myAddress, 'chan'): - return logger.info( - 'Ignoring getpubkey request because it is for one of my' - ' chan addresses. The other party should already have' - ' the pubkey.') - lastPubkeySendTime = config.safeGetInt( - myAddress, 'lastpubkeysendtime') - # If the last time we sent our pubkey was more recent than - # 28 days ago... - if lastPubkeySendTime > time.time() - 2419200: - return logger.info( - 'Found getpubkey-requested-item in my list of EC hashes' - ' BUT we already sent it recently. Ignoring request.' - ' The lastPubkeySendTime is: %s', lastPubkeySendTime) - logger.info( - 'Found getpubkey-requested-hash in my list of EC hashes.' - ' Telling Worker thread to do the POW for a pubkey message' - ' and send it out.') + logger.warning('(Within the processgetpubkey function) Someone requested one of my pubkeys but the stream number on which we heard this getpubkey object doesn\'t match this address\' stream number. Ignoring.') + return + if BMConfigParser().safeGetBoolean(myAddress, 'chan'): + logger.info('Ignoring getpubkey request because it is for one of my chan addresses. The other party should already have the pubkey.') + return + try: + lastPubkeySendTime = int(BMConfigParser().get( + myAddress, 'lastpubkeysendtime')) + except: + lastPubkeySendTime = 0 + if lastPubkeySendTime > time.time() - 2419200: # If the last time we sent our pubkey was more recent than 28 days ago... + logger.info('Found getpubkey-requested-item in my list of EC hashes BUT we already sent it recently. Ignoring request. The lastPubkeySendTime is: %s' % lastPubkeySendTime) + return + logger.info('Found getpubkey-requested-hash in my list of EC hashes. Telling Worker thread to do the POW for a pubkey message and send it out.') if requestedAddressVersionNumber == 2: - queues.workerQueue.put(('doPOWForMyV2Pubkey', requestedHash)) + queues.workerQueue.put(( + 'doPOWForMyV2Pubkey', requestedHash)) elif requestedAddressVersionNumber == 3: - queues.workerQueue.put(('sendOutOrStoreMyV3Pubkey', requestedHash)) + queues.workerQueue.put(( + 'sendOutOrStoreMyV3Pubkey', requestedHash)) elif requestedAddressVersionNumber == 4: - queues.workerQueue.put(('sendOutOrStoreMyV4Pubkey', myAddress)) + queues.workerQueue.put(( + 'sendOutOrStoreMyV4Pubkey', myAddress)) def processpubkey(self, data): - """Process a pubkey object""" pubkeyProcessingStartTime = time.time() - state.numberOfPubkeysProcessed += 1 + shared.numberOfPubkeysProcessed += 1 queues.UISignalQueue.put(( 'updateNumberOfPubkeysProcessed', 'no data')) + embeddedTime, = unpack('>Q', data[8:16]) readPosition = 20 # bypass the nonce, time, and object type addressVersion, varintLength = decodeVarint( data[readPosition:readPosition + 10]) @@ -287,19 +200,16 @@ class objectProcessor(threading.Thread): data[readPosition:readPosition + 10]) readPosition += varintLength if addressVersion == 0: - return logger.debug( - '(Within processpubkey) addressVersion of 0 doesn\'t' - ' make sense.') + logger.debug('(Within processpubkey) addressVersion of 0 doesn\'t make sense.') + return if addressVersion > 4 or addressVersion == 1: - return logger.info( - 'This version of Bitmessage cannot handle version %s' - ' addresses.', addressVersion) + logger.info('This version of Bitmessage cannot handle version %s addresses.' % addressVersion) + return if addressVersion == 2: - # sanity check. This is the minimum possible length. - if len(data) < 146: - return logger.debug( - '(within processpubkey) payloadLength less than 146.' - ' Sanity check failed.') + if len(data) < 146: # sanity check. This is the minimum possible length. + logger.debug('(within processpubkey) payloadLength less than 146. Sanity check failed.') + return + bitfieldBehaviors = data[readPosition:readPosition + 4] readPosition += 4 publicSigningKey = data[readPosition:readPosition + 64] # Is it possible for a public key to be invalid such that trying to @@ -308,73 +218,65 @@ class objectProcessor(threading.Thread): readPosition += 64 publicEncryptionKey = data[readPosition:readPosition + 64] if len(publicEncryptionKey) < 64: - return logger.debug( - 'publicEncryptionKey length less than 64. Sanity check' - ' failed.') + logger.debug('publicEncryptionKey length less than 64. Sanity check failed.') + return readPosition += 64 - # The data we'll store in the pubkeys table. - dataToStore = data[20:readPosition] + dataToStore = data[20:readPosition] # The data we'll store in the pubkeys table. sha = hashlib.new('sha512') sha.update( '\x04' + publicSigningKey + '\x04' + publicEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() + ripeHasher = hashlib.new('ripemd160') + ripeHasher.update(sha.digest()) + ripe = ripeHasher.digest() - if logger.isEnabledFor(logging.DEBUG): - logger.debug( - 'within recpubkey, addressVersion: %s, streamNumber: %s' - '\nripe %s\npublicSigningKey in hex: %s' - '\npublicEncryptionKey in hex: %s', - addressVersion, streamNumber, hexlify(ripe), - hexlify(publicSigningKey), hexlify(publicEncryptionKey) - ) + logger.debug('within recpubkey, addressVersion: %s, streamNumber: %s \n\ + ripe %s\n\ + publicSigningKey in hex: %s\n\ + publicEncryptionKey in hex: %s' % (addressVersion, + streamNumber, + hexlify(ripe), + hexlify(publicSigningKey), + hexlify(publicEncryptionKey) + ) + ) + + address = encodeAddress(addressVersion, streamNumber, ripe) - + queryreturn = sqlQuery( - "SELECT usedpersonally FROM pubkeys WHERE address=?" - " AND usedpersonally='yes'", address) - # if this pubkey is already in our database and if we have - # used it personally: - if queryreturn != []: - logger.info( - 'We HAVE used this pubkey personally. Updating time.') - t = (address, addressVersion, dataToStore, - int(time.time()), 'yes') + '''SELECT usedpersonally FROM pubkeys WHERE address=? AND usedpersonally='yes' ''', address) + if queryreturn != []: # if this pubkey is already in our database and if we have used it personally: + logger.info('We HAVE used this pubkey personally. Updating time.') + t = (address, addressVersion, dataToStore, int(time.time()), 'yes') else: - logger.info( - 'We have NOT used this pubkey personally. Inserting' - ' in database.') - t = (address, addressVersion, dataToStore, - int(time.time()), 'no') + logger.info('We have NOT used this pubkey personally. Inserting in database.') + t = (address, addressVersion, dataToStore, int(time.time()), 'no') sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) self.possibleNewPubkey(address) if addressVersion == 3: if len(data) < 170: # sanity check. - logger.warning( - '(within processpubkey) payloadLength less than 170.' - ' Sanity check failed.') + logger.warning('(within processpubkey) payloadLength less than 170. Sanity check failed.') return + bitfieldBehaviors = data[readPosition:readPosition + 4] readPosition += 4 publicSigningKey = '\x04' + data[readPosition:readPosition + 64] readPosition += 64 publicEncryptionKey = '\x04' + data[readPosition:readPosition + 64] readPosition += 64 - specifiedNonceTrialsPerByteLength = decodeVarint( - data[readPosition:readPosition + 10])[1] + specifiedNonceTrialsPerByte, specifiedNonceTrialsPerByteLength = decodeVarint( + data[readPosition:readPosition + 10]) readPosition += specifiedNonceTrialsPerByteLength - specifiedPayloadLengthExtraBytesLength = decodeVarint( - data[readPosition:readPosition + 10])[1] + specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint( + data[readPosition:readPosition + 10]) readPosition += specifiedPayloadLengthExtraBytesLength endOfSignedDataPosition = readPosition - # The data we'll store in the pubkeys table. - dataToStore = data[20:readPosition] + dataToStore = data[20:readPosition] # The data we'll store in the pubkeys table. signatureLength, signatureLengthLength = decodeVarint( data[readPosition:readPosition + 10]) readPosition += signatureLengthLength signature = data[readPosition:readPosition + signatureLength] - if highlevelcrypto.verify( - data[8:endOfSignedDataPosition], - signature, hexlify(publicSigningKey)): + if highlevelcrypto.verify(data[8:endOfSignedDataPosition], signature, hexlify(publicSigningKey)): logger.debug('ECDSA verify passed (within processpubkey)') else: logger.warning('ECDSA verify failed (within processpubkey)') @@ -382,168 +284,141 @@ class objectProcessor(threading.Thread): sha = hashlib.new('sha512') sha.update(publicSigningKey + publicEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() + ripeHasher = hashlib.new('ripemd160') + ripeHasher.update(sha.digest()) + ripe = ripeHasher.digest() + - if logger.isEnabledFor(logging.DEBUG): - logger.debug( - 'within recpubkey, addressVersion: %s, streamNumber: %s' - '\nripe %s\npublicSigningKey in hex: %s' - '\npublicEncryptionKey in hex: %s', - addressVersion, streamNumber, hexlify(ripe), - hexlify(publicSigningKey), hexlify(publicEncryptionKey) - ) + logger.debug('within recpubkey, addressVersion: %s, streamNumber: %s \n\ + ripe %s\n\ + publicSigningKey in hex: %s\n\ + publicEncryptionKey in hex: %s' % (addressVersion, + streamNumber, + hexlify(ripe), + hexlify(publicSigningKey), + hexlify(publicEncryptionKey) + ) + ) address = encodeAddress(addressVersion, streamNumber, ripe) - queryreturn = sqlQuery( - "SELECT usedpersonally FROM pubkeys WHERE address=?" - " AND usedpersonally='yes'", address) - # if this pubkey is already in our database and if we have - # used it personally: - if queryreturn != []: - logger.info( - 'We HAVE used this pubkey personally. Updating time.') - t = (address, addressVersion, dataToStore, - int(time.time()), 'yes') + queryreturn = sqlQuery('''SELECT usedpersonally FROM pubkeys WHERE address=? AND usedpersonally='yes' ''', address) + if queryreturn != []: # if this pubkey is already in our database and if we have used it personally: + logger.info('We HAVE used this pubkey personally. Updating time.') + t = (address, addressVersion, dataToStore, int(time.time()), 'yes') else: - logger.info( - 'We have NOT used this pubkey personally. Inserting' - ' in database.') - t = (address, addressVersion, dataToStore, - int(time.time()), 'no') + logger.info('We have NOT used this pubkey personally. Inserting in database.') + t = (address, addressVersion, dataToStore, int(time.time()), 'no') sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) self.possibleNewPubkey(address) if addressVersion == 4: if len(data) < 350: # sanity check. - return logger.debug( - '(within processpubkey) payloadLength less than 350.' - ' Sanity check failed.') + logger.debug('(within processpubkey) payloadLength less than 350. Sanity check failed.') + return tag = data[readPosition:readPosition + 32] if tag not in state.neededPubkeys: - return logger.info( - 'We don\'t need this v4 pubkey. We didn\'t ask for it.') - + logger.info('We don\'t need this v4 pubkey. We didn\'t ask for it.') + return + # Let us try to decrypt the pubkey - toAddress = state.neededPubkeys[tag][0] - if protocol.decryptAndCheckPubkeyPayload(data, toAddress) == \ - 'successful': - # At this point we know that we have been waiting on this - # pubkey. This function will command the workerThread - # to start work on the messages that require it. + toAddress, cryptorObject = state.neededPubkeys[tag] + if shared.decryptAndCheckPubkeyPayload(data, toAddress) == 'successful': + # At this point we know that we have been waiting on this pubkey. + # This function will command the workerThread to start work on + # the messages that require it. self.possibleNewPubkey(toAddress) # Display timing data - logger.debug( - 'Time required to process this pubkey: %s', - time.time() - pubkeyProcessingStartTime) + timeRequiredToProcessPubkey = time.time( + ) - pubkeyProcessingStartTime + logger.debug('Time required to process this pubkey: %s' % timeRequiredToProcessPubkey) + def processmsg(self, data): - """Process a message object""" messageProcessingStartTime = time.time() - state.numberOfMessagesProcessed += 1 + shared.numberOfMessagesProcessed += 1 queues.UISignalQueue.put(( 'updateNumberOfMessagesProcessed', 'no data')) - readPosition = 20 # bypass the nonce, time, and object type - msgVersion, msgVersionLength = decodeVarint( - data[readPosition:readPosition + 9]) + readPosition = 20 # bypass the nonce, time, and object type + msgVersion, msgVersionLength = decodeVarint(data[readPosition:readPosition + 9]) if msgVersion != 1: - return logger.info( - 'Cannot understand message versions other than one.' - ' Ignoring message.') + logger.info('Cannot understand message versions other than one. Ignoring message.') + return readPosition += msgVersionLength - - streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = \ - decodeVarint(data[readPosition:readPosition + 9]) + + streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = decodeVarint( + data[readPosition:readPosition + 9]) readPosition += streamNumberAsClaimedByMsgLength inventoryHash = calculateInventoryHash(data) initialDecryptionSuccessful = False # 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 sorted( - shared.myECCryptorObjects.items(), - key=lambda x: random.random()): # nosec B311 + + for key, cryptorObject in sorted(shared.myECCryptorObjects.items(), key=lambda x: random.random()): try: - # continue decryption attempts to avoid timing attacks - if initialDecryptionSuccessful: + if initialDecryptionSuccessful: # continue decryption attempts to avoid timing attacks cryptorObject.decrypt(data[readPosition:]) else: decryptedData = cryptorObject.decrypt(data[readPosition:]) - # This is the RIPE hash of my pubkeys. We need this - # below to compare to the destination_ripe included - # in the encrypted data. - toRipe = key + toRipe = key # This is the RIPE hash of my pubkeys. We need this below to compare to the destination_ripe included in the encrypted data. initialDecryptionSuccessful = True - logger.info( - 'EC decryption successful using key associated' - ' with ripe hash: %s.', hexlify(key)) - except Exception: # nosec B110 + logger.info('EC decryption successful using key associated with ripe hash: %s.' % hexlify(key)) + except Exception as err: pass if not initialDecryptionSuccessful: # This is not a message bound for me. - return logger.info( - 'Length of time program spent failing to decrypt this' - ' message: %s seconds.', - time.time() - messageProcessingStartTime) + logger.info('Length of time program spent failing to decrypt this message: %s seconds.' % (time.time() - messageProcessingStartTime,)) + return # This is a message bound for me. - # Look up my address based on the RIPE hash. - toAddress = shared.myAddressesByHash[toRipe] + toAddress = shared.myAddressesByHash[ + toRipe] # Look up my address based on the RIPE hash. readPosition = 0 - sendersAddressVersionNumber, sendersAddressVersionNumberLength = \ - decodeVarint(decryptedData[readPosition:readPosition + 10]) + sendersAddressVersionNumber, sendersAddressVersionNumberLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) readPosition += sendersAddressVersionNumberLength if sendersAddressVersionNumber == 0: - return logger.info( - 'Cannot understand sendersAddressVersionNumber = 0.' - ' Ignoring message.') + logger.info('Cannot understand sendersAddressVersionNumber = 0. Ignoring message.') + return if sendersAddressVersionNumber > 4: - return logger.info( - 'Sender\'s address version number %s not yet supported.' - ' Ignoring message.', sendersAddressVersionNumber) + logger.info('Sender\'s address version number %s not yet supported. Ignoring message.' % sendersAddressVersionNumber) + return if len(decryptedData) < 170: - return logger.info( - 'Length of the unencrypted data is unreasonably short.' - ' Sanity check failed. Ignoring message.') + logger.info('Length of the unencrypted data is unreasonably short. Sanity check failed. Ignoring message.') + return sendersStreamNumber, sendersStreamNumberLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) if sendersStreamNumber == 0: logger.info('sender\'s stream number is 0. Ignoring message.') return readPosition += sendersStreamNumberLength + behaviorBitfield = decryptedData[readPosition:readPosition + 4] readPosition += 4 - pubSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] + pubSigningKey = '\x04' + decryptedData[ + readPosition:readPosition + 64] readPosition += 64 - pubEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64] + pubEncryptionKey = '\x04' + decryptedData[ + readPosition:readPosition + 64] readPosition += 64 if sendersAddressVersionNumber >= 3: - requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = \ - decodeVarint(decryptedData[readPosition:readPosition + 10]) + requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) readPosition += varintLength - logger.info( - 'sender\'s requiredAverageProofOfWorkNonceTrialsPerByte is %s', - requiredAverageProofOfWorkNonceTrialsPerByte) + logger.info('sender\'s requiredAverageProofOfWorkNonceTrialsPerByte is %s' % requiredAverageProofOfWorkNonceTrialsPerByte) requiredPayloadLengthExtraBytes, varintLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += varintLength - logger.info( - 'sender\'s requiredPayloadLengthExtraBytes is %s', - requiredPayloadLengthExtraBytes) - # needed for when we store the pubkey in our database of pubkeys - # for later use. - endOfThePublicKeyPosition = readPosition + logger.info('sender\'s requiredPayloadLengthExtraBytes is %s' % requiredPayloadLengthExtraBytes) + endOfThePublicKeyPosition = readPosition # needed for when we store the pubkey in our database of pubkeys for later use. if toRipe != decryptedData[readPosition:readPosition + 20]: - return logger.info( - 'The original sender of this message did not send it to' - ' you. Someone is attempting a Surreptitious Forwarding' - ' Attack.\nSee: ' - 'http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html' - '\nyour toRipe: %s\nembedded destination toRipe: %s', - hexlify(toRipe), - hexlify(decryptedData[readPosition:readPosition + 20]) - ) + logger.info('The original sender of this message did not send it to you. Someone is attempting a Surreptitious Forwarding Attack.\n\ + See: http://world.std.com/~dtd/sign_encrypt/sign_encrypt7.html \n\ + your toRipe: %s\n\ + embedded destination toRipe: %s' % (hexlify(toRipe), hexlify(decryptedData[readPosition:readPosition + 20])) + ) + return readPosition += 20 messageEncodingType, messageEncodingTypeLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) @@ -552,50 +427,38 @@ class objectProcessor(threading.Thread): decryptedData[readPosition:readPosition + 10]) readPosition += messageLengthLength message = decryptedData[readPosition:readPosition + messageLength] + # print 'First 150 characters of message:', repr(message[:150]) readPosition += messageLength ackLength, ackLengthLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += ackLengthLength ackData = decryptedData[readPosition:readPosition + ackLength] readPosition += ackLength - # needed to mark the end of what is covered by the signature - positionOfBottomOfAckData = readPosition + positionOfBottomOfAckData = readPosition # needed to mark the end of what is covered by the signature signatureLength, signatureLengthLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += signatureLengthLength signature = decryptedData[ readPosition:readPosition + signatureLength] - signedData = data[8:20] + encodeVarint(1) + encodeVarint( - streamNumberAsClaimedByMsg - ) + decryptedData[:positionOfBottomOfAckData] - - if not highlevelcrypto.verify( - signedData, signature, hexlify(pubSigningKey)): - return logger.debug('ECDSA verify failed') + signedData = data[8:20] + encodeVarint(1) + encodeVarint(streamNumberAsClaimedByMsg) + decryptedData[:positionOfBottomOfAckData] + + if not highlevelcrypto.verify(signedData, signature, hexlify(pubSigningKey)): + logger.debug('ECDSA verify failed') + return logger.debug('ECDSA verify passed') - if logger.isEnabledFor(logging.DEBUG): - logger.debug( - 'As a matter of intellectual curiosity, here is the Bitcoin' - ' address associated with the keys owned by the other person:' - ' %s ..and here is the testnet address: %s. The other person' - ' must take their private signing key from Bitmessage and' - ' import it into Bitcoin (or a service like Blockchain.info)' - ' for it to be of any use. Do not use this unless you know' - ' what you are doing.', - helper_bitcoin.calculateBitcoinAddressFromPubkey(pubSigningKey), - helper_bitcoin.calculateTestnetAddressFromPubkey(pubSigningKey) - ) - # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512( - hashlib.sha512(signature).digest()).digest()[32:] + logger.debug('As a matter of intellectual curiosity, here is the Bitcoin address associated with the keys owned by the other person: %s ..and here is the testnet address: %s. The other person must take their private signing key from Bitmessage and import it into Bitcoin (or a service like Blockchain.info) for it to be of any use. Do not use this unless you know what you are doing.' % + (helper_bitcoin.calculateBitcoinAddressFromPubkey(pubSigningKey), helper_bitcoin.calculateTestnetAddressFromPubkey(pubSigningKey)) + ) + sigHash = hashlib.sha512(hashlib.sha512(signature).digest()).digest()[32:] # Used to detect and ignore duplicate messages in our inbox # calculate the fromRipe. sha = hashlib.new('sha512') sha.update(pubSigningKey + pubEncryptionKey) - ripe = RIPEMD160Hash(sha.digest()).digest() + ripe = hashlib.new('ripemd160') + ripe.update(sha.digest()) fromAddress = encodeAddress( - sendersAddressVersionNumber, sendersStreamNumber, ripe) - + sendersAddressVersionNumber, sendersStreamNumber, ripe.digest()) + # Let's store the public key in case we want to reply to this # person. sqlExecute( @@ -605,42 +468,30 @@ class objectProcessor(threading.Thread): decryptedData[:endOfThePublicKeyPosition], int(time.time()), 'yes') - + # Check to see whether we happen to be awaiting this # pubkey in order to send a message. If we are, it will do the POW # and send it. self.possibleNewPubkey(fromAddress) - + # If this message is bound for one of my version 3 addresses (or # higher), then we must check to make sure it meets our demanded # proof of work requirement. If this is bound for one of my chan # addresses then we skip this check; the minimum network POW is # fine. - # If the toAddress version number is 3 or higher and not one of - # my chan addresses: - if decodeAddress(toAddress)[1] >= 3 \ - and not config.safeGetBoolean(toAddress, 'chan'): - # If I'm not friendly with this person: - if not shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist( - fromAddress): - requiredNonceTrialsPerByte = config.getint( + if decodeAddress(toAddress)[1] >= 3 and not BMConfigParser().safeGetBoolean(toAddress, 'chan'): # If the toAddress version number is 3 or higher and not one of my chan addresses: + if not shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist(fromAddress): # If I'm not friendly with this person: + requiredNonceTrialsPerByte = BMConfigParser().getint( toAddress, 'noncetrialsperbyte') - requiredPayloadLengthExtraBytes = config.getint( + requiredPayloadLengthExtraBytes = BMConfigParser().getint( toAddress, 'payloadlengthextrabytes') - if not protocol.isProofOfWorkSufficient( - data, requiredNonceTrialsPerByte, - requiredPayloadLengthExtraBytes): - return logger.info( - 'Proof of work in msg is insufficient only because' - ' it does not meet our higher requirement.') - # Gets set to True if the user shouldn't see the message according - # to black or white lists. - blockMessage = False - # If we are using a blacklist - if config.get( - 'bitmessagesettings', 'blackwhitelist') == 'black': + if not protocol.isProofOfWorkSufficient(data, requiredNonceTrialsPerByte, requiredPayloadLengthExtraBytes): + logger.info('Proof of work in msg is insufficient only because it does not meet our higher requirement.') + return + blockMessage = False # Gets set to True if the user shouldn't see the message according to black or white lists. + if BMConfigParser().get('bitmessagesettings', 'blackwhitelist') == 'black': # If we are using a blacklist queryreturn = sqlQuery( - "SELECT label FROM blacklist where address=? and enabled='1'", + '''SELECT label FROM blacklist where address=? and enabled='1' ''', fromAddress) if queryreturn != []: logger.info('Message ignored because address is in blacklist.') @@ -648,17 +499,18 @@ class objectProcessor(threading.Thread): blockMessage = True else: # We're using a whitelist queryreturn = sqlQuery( - "SELECT label FROM whitelist where address=? and enabled='1'", + '''SELECT label FROM whitelist where address=? and enabled='1' ''', fromAddress) if queryreturn == []: - logger.info( - 'Message ignored because address not in whitelist.') + logger.info('Message ignored because address not in whitelist.') blockMessage = True - # toLabel = config.safeGet(toAddress, 'label', toAddress) + toLabel = BMConfigParser().get(toAddress, 'label') + if toLabel == '': + toLabel = toAddress + try: - decodedMessage = helper_msgcoding.MsgDecode( - messageEncodingType, message) + decodedMessage = helper_msgcoding.MsgDecode(messageEncodingType, message) except helper_msgcoding.MsgDecodeException: return subject = decodedMessage.subject @@ -670,9 +522,8 @@ class objectProcessor(threading.Thread): blockMessage = True if not blockMessage: if messageEncodingType != 0: - t = (inventoryHash, toAddress, fromAddress, subject, - int(time.time()), body, 'inbox', messageEncodingType, - 0, sigHash) + t = (inventoryHash, toAddress, fromAddress, subject, int( + time.time()), body, 'inbox', messageEncodingType, 0, sigHash) helper_inbox.insert(t) queues.UISignalQueue.put(('displayNewInboxMessage', ( @@ -681,86 +532,87 @@ class objectProcessor(threading.Thread): # If we are behaving as an API then we might need to run an # outside command to let some program know that a new message # has arrived. - if config.safeGetBoolean( - 'bitmessagesettings', 'apienabled'): - apiNotifyPath = config.safeGet( - 'bitmessagesettings', 'apinotifypath') - if apiNotifyPath: - subprocess.call([apiNotifyPath, "newMessage"]) # nosec B603 + if BMConfigParser().safeGetBoolean('bitmessagesettings', 'apienabled'): + try: + apiNotifyPath = BMConfigParser().get( + 'bitmessagesettings', 'apinotifypath') + except: + apiNotifyPath = '' + if apiNotifyPath != '': + call([apiNotifyPath, "newMessage"]) # Let us now check and see whether our receiving address is # behaving as a mailing list - if config.safeGetBoolean(toAddress, 'mailinglist') \ - and messageEncodingType != 0: - mailingListName = config.safeGet( - toAddress, 'mailinglistname', '') + if BMConfigParser().safeGetBoolean(toAddress, 'mailinglist') and messageEncodingType != 0: + try: + mailingListName = BMConfigParser().get( + toAddress, 'mailinglistname') + except: + mailingListName = '' # Let us send out this message as a broadcast subject = self.addMailingListNameToSubject( subject, mailingListName) # Let us now send this message out as a broadcast - message = time.strftime( - "%a, %Y-%m-%d %H:%M:%S UTC", time.gmtime() - ) + ' Message ostensibly from ' + fromAddress \ - + ':\n\n' + body - # The fromAddress for the broadcast that we are about to - # send is the toAddress (my address) for the msg message - # we are currently processing. - fromAddress = toAddress - # 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. + 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. + # 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 = '' - ackdata = helper_sent.insert( - fromAddress=fromAddress, - status='broadcastqueued', - subject=subject, - message=message, - encoding=messageEncodingType) + # We really should have a discussion about how to + # set the TTL for mailing list broadcasts. This is obviously + # hard-coded. + TTL = 2*7*24*60*60 # 2 weeks + t = ('', + toAddress, + ripe, + fromAddress, + subject, + message, + ackdata, + int(time.time()), # sentTime (this doesn't change) + int(time.time()), # lastActionTime + 0, + 'broadcastqueued', + 0, + 'sent', + messageEncodingType, + TTL) + helper_sent.insert(t) - queues.UISignalQueue.put(( - 'displayNewSentMessage', ( - toAddress, '[Broadcast subscribers]', fromAddress, - subject, message, ackdata) - )) + queues.UISignalQueue.put(('displayNewSentMessage', ( + toAddress, '[Broadcast subscribers]', fromAddress, subject, message, ackdata))) queues.workerQueue.put(('sendbroadcast', '')) - # Don't send ACK if invalid, blacklisted senders, invisible - # messages, disabled or chan - if ( - self.ackDataHasAValidHeader(ackData) and not blockMessage - and messageEncodingType != 0 - and not config.safeGetBoolean(toAddress, 'dontsendack') - and not config.safeGetBoolean(toAddress, 'chan') - ): - ackPayload = ackData[24:] - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(ackPayload) - inventoryHash = calculateInventoryHash(ackPayload) - Inventory()[inventoryHash] = ( - objectType, toStreamNumber, ackPayload, expiresTime, b'') - queues.invQueue.put((toStreamNumber, inventoryHash)) + # Don't send ACK if invalid, blacklisted senders, invisible messages, disabled or chan + if self.ackDataHasAValidHeader(ackData) and \ + not blockMessage and \ + messageEncodingType != 0 and \ + not BMConfigParser().safeGetBoolean(toAddress, 'dontsendack') and \ + not BMConfigParser().safeGetBoolean(toAddress, 'chan'): + shared.checkAndShareObjectWithPeers(ackData[24:]) # Display timing data timeRequiredToAttemptToDecryptMessage = time.time( ) - messageProcessingStartTime - self.successfullyDecryptMessageTimings.append( + shared.successfullyDecryptMessageTimings.append( timeRequiredToAttemptToDecryptMessage) - timing_sum = 0 - for item in self.successfullyDecryptMessageTimings: - timing_sum += item - logger.debug( - 'Time to decrypt this message successfully: %s' - '\nAverage time for all message decryption successes since' - ' startup: %s.', - timeRequiredToAttemptToDecryptMessage, - timing_sum / len(self.successfullyDecryptMessageTimings) - ) + sum = 0 + for item in shared.successfullyDecryptMessageTimings: + sum += item + logger.debug('Time to decrypt this message successfully: %s\n\ + Average time for all message decryption successes since startup: %s.' % + (timeRequiredToAttemptToDecryptMessage, sum / len(shared.successfullyDecryptMessageTimings)) + ) + def processbroadcast(self, data): - """Process a broadcast object""" messageProcessingStartTime = time.time() - state.numberOfBroadcastsProcessed += 1 + shared.numberOfBroadcastsProcessed += 1 queues.UISignalQueue.put(( 'updateNumberOfBroadcastsProcessed', 'no data')) inventoryHash = calculateInventoryHash(data) @@ -769,69 +621,50 @@ class objectProcessor(threading.Thread): data[readPosition:readPosition + 9]) readPosition += broadcastVersionLength if broadcastVersion < 4 or broadcastVersion > 5: - return logger.info( - 'Cannot decode incoming broadcast versions less than 4' - ' or higher than 5. Assuming the sender isn\'t being silly,' - ' you should upgrade Bitmessage because this message shall' - ' be ignored.' - ) + logger.info('Cannot decode incoming broadcast versions less than 4 or higher than 5. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.') + return cleartextStreamNumber, cleartextStreamNumberLength = decodeVarint( data[readPosition:readPosition + 10]) readPosition += cleartextStreamNumberLength if broadcastVersion == 4: - # v4 broadcasts are encrypted the same way the msgs are - # encrypted. To see if we are interested in a v4 broadcast, - # we try to decrypt it. This was replaced with v5 broadcasts - # which include a tag which we check instead, just like we do - # with v4 pubkeys. + """ + v4 broadcasts are encrypted the same way the msgs are encrypted. To see if we are interested in a + v4 broadcast, we try to decrypt it. This was replaced with v5 broadcasts which include a tag which + we check instead, just like we do with v4 pubkeys. + """ signedData = data[8:readPosition] initialDecryptionSuccessful = False - for key, cryptorObject in sorted( - shared.MyECSubscriptionCryptorObjects.items(), - key=lambda x: random.random()): # nosec B311 + for key, cryptorObject in sorted(shared.MyECSubscriptionCryptorObjects.items(), key=lambda x: random.random()): try: - # continue decryption attempts to avoid timing attacks - if initialDecryptionSuccessful: + if initialDecryptionSuccessful: # continue decryption attempts to avoid timing attacks cryptorObject.decrypt(data[readPosition:]) else: - decryptedData = cryptorObject.decrypt( - data[readPosition:]) - # This is the RIPE hash of the sender's pubkey. - # We need this below to compare to the RIPE hash - # of the sender's address to verify that it was - # encrypted by with their key rather than some - # other key. - toRipe = key + decryptedData = cryptorObject.decrypt(data[readPosition:]) + toRipe = key # This is the RIPE hash of the sender's pubkey. We need this below to compare to the RIPE hash of the sender's address to verify that it was encrypted by with their key rather than some other key. initialDecryptionSuccessful = True - logger.info( - 'EC decryption successful using key associated' - ' with ripe hash: %s', hexlify(key)) - except Exception: - logger.debug( - 'cryptorObject.decrypt Exception:', exc_info=True) + logger.info('EC decryption successful using key associated with ripe hash: %s' % hexlify(key)) + except Exception as err: + pass + # print 'cryptorObject.decrypt Exception:', err if not initialDecryptionSuccessful: # This is not a broadcast I am interested in. - return logger.debug( - 'Length of time program spent failing to decrypt this' - ' v4 broadcast: %s seconds.', - time.time() - messageProcessingStartTime) + logger.debug('Length of time program spent failing to decrypt this v4 broadcast: %s seconds.' % (time.time() - messageProcessingStartTime,)) + return elif broadcastVersion == 5: - embeddedTag = data[readPosition:readPosition + 32] + embeddedTag = data[readPosition:readPosition+32] readPosition += 32 if embeddedTag not in shared.MyECSubscriptionCryptorObjects: - logger.debug('We\'re not interested in this broadcast.') + logger.debug('We\'re not interested in this broadcast.') return # We are interested in this broadcast because of its tag. - # We're going to add some more data which is signed further down. - signedData = data[8:readPosition] + signedData = data[8:readPosition] # We're going to add some more data which is signed further down. cryptorObject = shared.MyECSubscriptionCryptorObjects[embeddedTag] try: decryptedData = cryptorObject.decrypt(data[readPosition:]) logger.debug('EC decryption successful') - except Exception: - return logger.debug( - 'Broadcast version %s decryption Unsuccessful.', - broadcastVersion) + except Exception as err: + logger.debug('Broadcast version %s decryption Unsuccessful.' % broadcastVersion) + return # At this point this is a broadcast I have decrypted and am # interested in. readPosition = 0 @@ -839,30 +672,20 @@ class objectProcessor(threading.Thread): decryptedData[readPosition:readPosition + 9]) if broadcastVersion == 4: if sendersAddressVersion < 2 or sendersAddressVersion > 3: - return logger.warning( - 'Cannot decode senderAddressVersion other than 2 or 3.' - ' Assuming the sender isn\'t being silly, you should' - ' upgrade Bitmessage because this message shall be' - ' ignored.' - ) + logger.warning('Cannot decode senderAddressVersion other than 2 or 3. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.') + return elif broadcastVersion == 5: if sendersAddressVersion < 4: - return logger.info( - 'Cannot decode senderAddressVersion less than 4 for' - ' broadcast version number 5. Assuming the sender' - ' isn\'t being silly, you should upgrade Bitmessage' - ' because this message shall be ignored.' - ) + logger.info('Cannot decode senderAddressVersion less than 4 for broadcast version number 5. Assuming the sender isn\'t being silly, you should upgrade Bitmessage because this message shall be ignored.') + return readPosition += sendersAddressVersionLength sendersStream, sendersStreamLength = decodeVarint( decryptedData[readPosition:readPosition + 9]) if sendersStream != cleartextStreamNumber: - return logger.info( - 'The stream number outside of the encryption on which the' - ' POW was completed doesn\'t match the stream number' - ' inside the encryption. Ignoring broadcast.' - ) + logger.info('The stream number outside of the encryption on which the POW was completed doesn\'t match the stream number inside the encryption. Ignoring broadcast.') + return readPosition += sendersStreamLength + behaviorBitfield = decryptedData[readPosition:readPosition + 4] readPosition += 4 sendersPubSigningKey = '\x04' + \ decryptedData[readPosition:readPosition + 64] @@ -871,42 +694,32 @@ class objectProcessor(threading.Thread): decryptedData[readPosition:readPosition + 64] readPosition += 64 if sendersAddressVersion >= 3: - requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = \ - decodeVarint(decryptedData[readPosition:readPosition + 10]) + requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) readPosition += varintLength - logger.debug( - 'sender\'s requiredAverageProofOfWorkNonceTrialsPerByte' - ' is %s', requiredAverageProofOfWorkNonceTrialsPerByte) + logger.debug('sender\'s requiredAverageProofOfWorkNonceTrialsPerByte is %s' % requiredAverageProofOfWorkNonceTrialsPerByte) requiredPayloadLengthExtraBytes, varintLength = decodeVarint( decryptedData[readPosition:readPosition + 10]) readPosition += varintLength - logger.debug( - 'sender\'s requiredPayloadLengthExtraBytes is %s', - requiredPayloadLengthExtraBytes) + logger.debug('sender\'s requiredPayloadLengthExtraBytes is %s' % requiredPayloadLengthExtraBytes) endOfPubkeyPosition = readPosition sha = hashlib.new('sha512') sha.update(sendersPubSigningKey + sendersPubEncryptionKey) - calculatedRipe = RIPEMD160Hash(sha.digest()).digest() + ripeHasher = hashlib.new('ripemd160') + ripeHasher.update(sha.digest()) + calculatedRipe = ripeHasher.digest() if broadcastVersion == 4: if toRipe != calculatedRipe: - return logger.info( - 'The encryption key used to encrypt this message' - ' doesn\'t match the keys inbedded in the message' - ' itself. Ignoring message.' - ) + logger.info('The encryption key used to encrypt this message doesn\'t match the keys inbedded in the message itself. Ignoring message.') + return elif broadcastVersion == 5: - calculatedTag = hashlib.sha512(hashlib.sha512( - encodeVarint(sendersAddressVersion) - + encodeVarint(sendersStream) + calculatedRipe - ).digest()).digest()[32:] + calculatedTag = hashlib.sha512(hashlib.sha512(encodeVarint( + sendersAddressVersion) + encodeVarint(sendersStream) + calculatedRipe).digest()).digest()[32:] if calculatedTag != embeddedTag: - return logger.debug( - 'The tag and encryption key used to encrypt this' - ' message doesn\'t match the keys inbedded in the' - ' message itself. Ignoring message.' - ) + logger.debug('The tag and encryption key used to encrypt this message doesn\'t match the keys inbedded in the message itself. Ignoring message.') + return messageEncodingType, messageEncodingTypeLength = decodeVarint( decryptedData[readPosition:readPosition + 9]) if messageEncodingType == 0: @@ -924,18 +737,15 @@ class objectProcessor(threading.Thread): signature = decryptedData[ readPosition:readPosition + signatureLength] signedData += decryptedData[:readPositionAtBottomOfMessage] - if not highlevelcrypto.verify( - signedData, signature, hexlify(sendersPubSigningKey)): + if not highlevelcrypto.verify(signedData, signature, hexlify(sendersPubSigningKey)): logger.debug('ECDSA verify failed') return logger.debug('ECDSA verify passed') - # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512( - hashlib.sha512(signature).digest()).digest()[32:] + sigHash = hashlib.sha512(hashlib.sha512(signature).digest()).digest()[32:] # Used to detect and ignore duplicate messages in our inbox fromAddress = encodeAddress( sendersAddressVersion, sendersStream, calculatedRipe) - logger.info('fromAddress: %s', fromAddress) + logger.info('fromAddress: %s' % fromAddress) # Let's store the public key in case we want to reply to this person. sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', @@ -950,9 +760,12 @@ class objectProcessor(threading.Thread): # and send it. self.possibleNewPubkey(fromAddress) + fromAddress = encodeAddress( + sendersAddressVersion, sendersStream, calculatedRipe) + logger.debug('fromAddress: ' + fromAddress) + try: - decodedMessage = helper_msgcoding.MsgDecode( - messageEncodingType, message) + decodedMessage = helper_msgcoding.MsgDecode(messageEncodingType, message) except helper_msgcoding.MsgDecodeException: return subject = decodedMessage.subject @@ -972,91 +785,79 @@ class objectProcessor(threading.Thread): # If we are behaving as an API then we might need to run an # outside command to let some program know that a new message # has arrived. - if config.safeGetBoolean('bitmessagesettings', 'apienabled'): - apiNotifyPath = config.safeGet( - 'bitmessagesettings', 'apinotifypath') - if apiNotifyPath: - subprocess.call([apiNotifyPath, "newBroadcast"]) # nosec B603 + if BMConfigParser().safeGetBoolean('bitmessagesettings', 'apienabled'): + try: + apiNotifyPath = BMConfigParser().get( + 'bitmessagesettings', 'apinotifypath') + except: + apiNotifyPath = '' + if apiNotifyPath != '': + call([apiNotifyPath, "newBroadcast"]) # Display timing data - logger.info( - 'Time spent processing this interesting broadcast: %s', - time.time() - messageProcessingStartTime) + logger.info('Time spent processing this interesting broadcast: %s' % (time.time() - messageProcessingStartTime,)) + def possibleNewPubkey(self, address): """ - We have inserted a pubkey into our pubkey table which we received - from a pubkey, msg, or broadcast message. It might be one that we - have been waiting for. Let's check. + We have inserted a pubkey into our pubkey table which we received from a + pubkey, msg, or broadcast message. It might be one that we have been + waiting for. Let's check. """ - - # For address versions <= 3, we wait on a key with the correct - # address version, stream number and RIPE hash. - addressVersion, streamNumber, ripe = decodeAddress(address)[1:] - if addressVersion <= 3: + + # For address versions <= 3, we wait on a key with the correct address version, + # stream number, and RIPE hash. + status, addressVersion, streamNumber, ripe = decodeAddress(address) + if addressVersion <=3: if address in state.neededPubkeys: del state.neededPubkeys[address] self.sendMessages(address) else: - logger.debug( - 'We don\'t need this pub key. We didn\'t ask for it.' - ' For address: %s', address) + logger.debug('We don\'t need this pub key. We didn\'t ask for it. For address: %s' % address) # For address versions >= 4, we wait on a pubkey with the correct tag. # Let us create the tag from the address and see if we were waiting # for it. elif addressVersion >= 4: - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersion) + encodeVarint(streamNumber) - + ripe - ).digest()).digest()[32:] + tag = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersion) + encodeVarint(streamNumber) + ripe).digest()).digest()[32:] if tag in state.neededPubkeys: del state.neededPubkeys[tag] self.sendMessages(address) - @staticmethod - def sendMessages(address): + def sendMessages(self, address): """ - This method is called by the `possibleNewPubkey` when it sees - that we now have the necessary pubkey to send one or more messages. + This function is called by the possibleNewPubkey function when + that function sees that we now have the necessary pubkey + to send one or more messages. """ logger.info('We have been awaiting the arrival of this pubkey.') sqlExecute( - "UPDATE sent SET status='doingmsgpow', retrynumber=0" - " WHERE toaddress=?" - " AND (status='awaitingpubkey' OR status='doingpubkeypow')" - " AND folder='sent'", address) + '''UPDATE sent SET status='doingmsgpow', retrynumber=0 WHERE toaddress=? AND (status='awaitingpubkey' or status='doingpubkeypow') AND folder='sent' ''', + address) queues.workerQueue.put(('sendmessage', '')) - @staticmethod - def ackDataHasAValidHeader(ackData): - """Checking ackData with valid Header, not sending ackData when false""" + def ackDataHasAValidHeader(self, ackData): if len(ackData) < protocol.Header.size: - logger.info( - 'The length of ackData is unreasonably short. Not sending' - ' ackData.') + logger.info('The length of ackData is unreasonably short. Not sending ackData.') return False - - magic, command, payloadLength, checksum = protocol.Header.unpack( - ackData[:protocol.Header.size]) - if magic != protocol.magic: + + magic,command,payloadLength,checksum = protocol.Header.unpack(ackData[:protocol.Header.size]) + if magic != 0xE9BEB4D9: logger.info('Ackdata magic bytes were wrong. Not sending ackData.') return False payload = ackData[protocol.Header.size:] if len(payload) != payloadLength: - logger.info( - 'ackData payload length doesn\'t match the payload length' - ' specified in the header. Not sending ackdata.') + logger.info('ackData payload length doesn\'t match the payload length specified in the header. Not sending ackdata.') return False - # ~1.6 MB which is the maximum possible size of an inv message. - if payloadLength > 1600100: - # The largest message should be either an inv or a getdata - # message at 1.6 MB in size. - # That doesn't mean that the object may be that big. The - # shared.checkAndShareObjectWithPeers function will verify - # that it is no larger than 2^18 bytes. + if payloadLength > 1600100: # ~1.6 MB which is the maximum possible size of an inv message. + """ + The largest message should be either an inv or a getdata message at 1.6 MB in size. + That doesn't mean that the object may be that big. The + shared.checkAndShareObjectWithPeers function will verify that it is no larger than + 2^18 bytes. + """ return False - # test the checksum in the message. - if checksum != hashlib.sha512(payload).digest()[0:4]: + if checksum != hashlib.sha512(payload).digest()[0:4]: # test the checksum in the message. logger.info('ackdata checksum wrong. Not sending ackdata.') return False command = command.rstrip('\x00') @@ -1064,12 +865,27 @@ class objectProcessor(threading.Thread): return False return True - @staticmethod - def addMailingListNameToSubject(subject, mailingListName): - """Adding mailingListName to subject""" + def addMailingListNameToSubject(self, subject, mailingListName): subject = subject.strip() if subject[:3] == 'Re:' or subject[:3] == 'RE:': subject = subject[3:].strip() if '[' + mailingListName + ']' in subject: return subject - return '[' + mailingListName + '] ' + subject + else: + return '[' + mailingListName + '] ' + subject + + def decodeType2Message(self, message): + bodyPositionIndex = string.find(message, '\nBody:') + if bodyPositionIndex > 1: + subject = message[8:bodyPositionIndex] + # Only save and show the first 500 characters of the subject. + # Any more is probably an attack. + subject = subject[:500] + body = message[bodyPositionIndex + 6:] + else: + subject = '' + body = message + # Throw away any extra lines (headers) after the subject. + if subject: + subject = subject.splitlines()[0] + return subject, body diff --git a/src/class_objectProcessorQueue.py b/src/class_objectProcessorQueue.py new file mode 100644 index 00000000..6309e994 --- /dev/null +++ b/src/class_objectProcessorQueue.py @@ -0,0 +1,27 @@ +import Queue +import threading +import time + +class ObjectProcessorQueue(Queue.Queue): + maxSize = 32000000 + + def __init__(self): + Queue.Queue.__init__(self) + self.sizeLock = threading.Lock() + self.curSize = 0 # in Bytes. We maintain this to prevent nodes from flooing us with objects which take up too much memory. If this gets too big we'll sleep before asking for further objects. + + def put(self, item, block = True, timeout = None): + while self.curSize >= self.maxSize: + time.sleep(1) + with self.sizeLock: + self.curSize += len(item[1]) + Queue.Queue.put(self, item, block, timeout) + + def get(self, block = True, timeout = None): + try: + item = Queue.Queue.get(self, block, timeout) + except Queue.Empty as e: + raise Queue.Empty() + with self.sizeLock: + self.curSize -= len(item[1]) + return item diff --git a/src/class_outgoingSynSender.py b/src/class_outgoingSynSender.py new file mode 100644 index 00000000..9b3eac14 --- /dev/null +++ b/src/class_outgoingSynSender.py @@ -0,0 +1,283 @@ +import errno +import threading +import time +import random +import shared +import select +import socks +import socket +import sys +import tr + +from class_sendDataThread import * +from class_receiveDataThread import * +from bmconfigparser import BMConfigParser +from helper_threading import * +import knownnodes +import queues +import state + +# For each stream to which we connect, several outgoingSynSender threads +# will exist and will collectively create 8 connections with peers. + +class outgoingSynSender(threading.Thread, StoppableThread): + + def __init__(self): + threading.Thread.__init__(self, name="outgoingSynSender") + self.initStop() + random.seed() + + def setup(self, streamNumber, selfInitiatedConnections): + self.streamNumber = streamNumber + self.selfInitiatedConnections = selfInitiatedConnections + + def _getPeer(self): + # If the user has specified a trusted peer then we'll only + # ever connect to that. Otherwise we'll pick a random one from + # the known nodes + if state.trustedPeer: + with knownnodes.knownNodesLock: + peer = state.trustedPeer + knownnodes.knownNodes[self.streamNumber][peer] = time.time() + else: + while not self._stopped: + try: + with knownnodes.knownNodesLock: + peer, = random.sample(knownnodes.knownNodes[self.streamNumber], 1) + priority = (183600 - (time.time() - knownnodes.knownNodes[self.streamNumber][peer])) / 183600 # 2 days and 3 hours + except ValueError: # no known nodes + self.stop.wait(1) + continue + if BMConfigParser().get('bitmessagesettings', 'socksproxytype') != 'none': + if peer.host.find(".onion") == -1: + priority /= 10 # hidden services have 10x priority over plain net + else: + # don't connect to self + if peer.host == BMConfigParser().get('bitmessagesettings', 'onionhostname') and peer.port == BMConfigParser().getint("bitmessagesettings", "onionport"): + continue + elif peer.host.find(".onion") != -1: # onion address and so proxy + continue + if priority <= 0.001: # everyone has at least this much priority + priority = 0.001 + if (random.random() <= priority): + break + self.stop.wait(0.01) # prevent CPU hogging if something is broken + try: + return peer + except NameError: + return state.Peer('127.0.0.1', 8444) + + def stopThread(self): + super(outgoingSynSender, self).stopThread() + try: + self.sock.shutdown(socket.SHUT_RDWR) + except: + pass + + def run(self): + while BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect') and not self._stopped: + self.stop.wait(2) + while BMConfigParser().safeGetBoolean('bitmessagesettings', 'sendoutgoingconnections') and not self._stopped: + self.name = "outgoingSynSender" + maximumConnections = 1 if state.trustedPeer else BMConfigParser().safeGetInt('bitmessagesettings', 'maxoutboundconnections') + while len(self.selfInitiatedConnections[self.streamNumber]) >= maximumConnections and not self._stopped: + self.stop.wait(10) + if state.shutdown: + break + peer = self._getPeer() + while peer in shared.alreadyAttemptedConnectionsList or peer.host in shared.connectedHostsList: + # print 'choosing new sample' + peer = self._getPeer() + self.stop.wait(1) + if self._stopped: + break + # Clear out the shared.alreadyAttemptedConnectionsList every half + # hour so that this program will again attempt a connection + # to any nodes, even ones it has already tried. + with shared.alreadyAttemptedConnectionsListLock: + if (time.time() - shared.alreadyAttemptedConnectionsListResetTime) > 1800: + shared.alreadyAttemptedConnectionsList.clear() + shared.alreadyAttemptedConnectionsListResetTime = int( + time.time()) + shared.alreadyAttemptedConnectionsList[peer] = 0 + if self._stopped: + break + self.name = "outgoingSynSender-" + peer.host.replace(":", ".") # log parser field separator + address_family = socket.AF_INET + # Proxy IP is IPv6. Unlikely but possible + if BMConfigParser().get('bitmessagesettings', 'socksproxytype') != 'none': + if ":" in BMConfigParser().get('bitmessagesettings', 'sockshostname'): + address_family = socket.AF_INET6 + # No proxy, and destination is IPv6 + elif peer.host.find(':') >= 0 : + address_family = socket.AF_INET6 + try: + self.sock = socks.socksocket(address_family, socket.SOCK_STREAM) + except: + """ + The line can fail on Windows systems which aren't + 64-bit compatiable: + File "C:\Python27\lib\socket.py", line 187, in __init__ + _sock = _realsocket(family, type, proto) + error: [Errno 10047] An address incompatible with the requested protocol was used + + So let us remove the offending address from our knownNodes file. + """ + with knownnodes.knownNodesLock: + try: + del knownnodes.knownNodes[self.streamNumber][peer] + except KeyError: + pass + logger.debug('deleting ' + str(peer) + ' from knownnodes.knownNodes because it caused a socks.socksocket exception. We must not be 64-bit compatible.') + continue + # This option apparently avoids the TIME_WAIT state so that we + # can rebind faster + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.sock.settimeout(20) + if BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'none' and shared.verbose >= 2: + logger.debug('Trying an outgoing connection to ' + str(peer)) + + # sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + elif BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'SOCKS4a': + if shared.verbose >= 2: + logger.debug ('(Using SOCKS4a) Trying an outgoing connection to ' + str(peer)) + + proxytype = socks.PROXY_TYPE_SOCKS4 + sockshostname = BMConfigParser().get( + 'bitmessagesettings', 'sockshostname') + socksport = BMConfigParser().getint( + 'bitmessagesettings', 'socksport') + rdns = True # Do domain name lookups through the proxy; though this setting doesn't really matter since we won't be doing any domain name lookups anyway. + if BMConfigParser().getboolean('bitmessagesettings', 'socksauthentication'): + socksusername = BMConfigParser().get( + 'bitmessagesettings', 'socksusername') + sockspassword = BMConfigParser().get( + 'bitmessagesettings', 'sockspassword') + self.sock.setproxy( + proxytype, sockshostname, socksport, rdns, socksusername, sockspassword) + else: + self.sock.setproxy( + proxytype, sockshostname, socksport, rdns) + elif BMConfigParser().get('bitmessagesettings', 'socksproxytype') == 'SOCKS5': + if shared.verbose >= 2: + logger.debug ('(Using SOCKS5) Trying an outgoing connection to ' + str(peer)) + + proxytype = socks.PROXY_TYPE_SOCKS5 + sockshostname = BMConfigParser().get( + 'bitmessagesettings', 'sockshostname') + socksport = BMConfigParser().getint( + 'bitmessagesettings', 'socksport') + rdns = True # Do domain name lookups through the proxy; though this setting doesn't really matter since we won't be doing any domain name lookups anyway. + if BMConfigParser().getboolean('bitmessagesettings', 'socksauthentication'): + socksusername = BMConfigParser().get( + 'bitmessagesettings', 'socksusername') + sockspassword = BMConfigParser().get( + 'bitmessagesettings', 'sockspassword') + self.sock.setproxy( + proxytype, sockshostname, socksport, rdns, socksusername, sockspassword) + else: + self.sock.setproxy( + proxytype, sockshostname, socksport, rdns) + + try: + self.sock.connect((peer.host, peer.port)) + if self._stopped: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + return + 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) + sd.start() + + rd = receiveDataThread() + rd.daemon = True # close the main program even if there are threads left + rd.setup(self.sock, + peer.host, + peer.port, + self.streamNumber, + self.selfInitiatedConnections, + sendDataThreadQueue, + sd.objectHashHolderInstance) + rd.start() + + sd.sendVersionMessage() + + logger.debug(str(self) + ' connected to ' + str(peer) + ' during an outgoing attempt.') + except socks.GeneralProxyError as err: + if err[0][0] in [7, 8, 9]: + logger.error('Error communicating with proxy: %s', str(err)) + queues.UISignalQueue.put(( + 'updateStatusBar', + tr._translate( + "MainWindow", "Problem communicating with proxy: %1. Please check your network settings.").arg(str(err[0][1])) + )) + self.stop.wait(1) + continue + elif shared.verbose >= 2: + logger.debug('Could NOT connect to ' + str(peer) + ' during outgoing attempt. ' + str(err)) + + deletedPeer = None + with knownnodes.knownNodesLock: + """ + It is remotely possible that peer is no longer in knownnodes.knownNodes. + This could happen if two outgoingSynSender threads both try to + connect to the same peer, both fail, and then both try to remove + it from knownnodes.knownNodes. This is unlikely because of the + alreadyAttemptedConnectionsList but because we clear that list once + every half hour, it can happen. + """ + if peer in knownnodes.knownNodes[self.streamNumber]: + timeLastSeen = knownnodes.knownNodes[self.streamNumber][peer] + if (int(time.time()) - timeLastSeen) > 172800 and len(knownnodes.knownNodes[self.streamNumber]) > 1000: # for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the knownnodes.knownNodes data-structure. + del knownnodes.knownNodes[self.streamNumber][peer] + deletedPeer = peer + if deletedPeer: + str ('deleting ' + str(peer) + ' from knownnodes.knownNodes because it is more than 48 hours old and we could not connect to it.') + + except socks.Socks5AuthError as err: + queues.UISignalQueue.put(( + 'updateStatusBar', tr._translate( + "MainWindow", "SOCKS5 Authentication problem: %1. Please check your SOCKS5 settings.").arg(str(err)))) + except socks.Socks5Error as err: + if err[0][0] in [3, 4, 5, 6]: + # this is a more bening "error": host unreachable, network unreachable, connection refused, TTL expired + logger.debug('SOCKS5 error: %s', str(err)) + else: + logger.error('SOCKS5 error: %s', str(err)) + if err[0][0] == 4 or err[0][0] == 2: + state.networkProtocolAvailability[protocol.networkType(peer.host)] = False + except socks.Socks4Error as err: + logger.error('Socks4Error: ' + str(err)) + except socket.error as err: + if BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS': + logger.error('Bitmessage MIGHT be having trouble connecting to the SOCKS server. ' + str(err)) + else: + if err[0] == errno.ENETUNREACH: + state.networkProtocolAvailability[protocol.networkType(peer.host)] = False + if shared.verbose >= 1: + logger.debug('Could NOT connect to ' + str(peer) + 'during outgoing attempt. ' + str(err)) + + deletedPeer = None + with knownnodes.knownNodesLock: + """ + It is remotely possible that peer is no longer in knownnodes.knownNodes. + This could happen if two outgoingSynSender threads both try to + connect to the same peer, both fail, and then both try to remove + it from knownnodes.knownNodes. This is unlikely because of the + alreadyAttemptedConnectionsList but because we clear that list once + every half hour, it can happen. + """ + if peer in knownnodes.knownNodes[self.streamNumber]: + timeLastSeen = knownnodes.knownNodes[self.streamNumber][peer] + if (int(time.time()) - timeLastSeen) > 172800 and len(knownnodes.knownNodes[self.streamNumber]) > 1000: # for nodes older than 48 hours old if we have more than 1000 hosts in our list, delete from the knownnodes.knownNodes data-structure. + del knownnodes.knownNodes[self.streamNumber][peer] + deletedPeer = peer + if deletedPeer: + logger.debug('deleting ' + str(peer) + ' from knownnodes.knownNodes because it is more than 48 hours old and we could not connect to it.') + + except Exception as err: + import traceback + logger.exception('An exception has occurred in the outgoingSynSender thread that was not caught by other exception types:') + self.stop.wait(0.1) diff --git a/src/class_receiveDataThread.py b/src/class_receiveDataThread.py new file mode 100644 index 00000000..4e86196c --- /dev/null +++ b/src/class_receiveDataThread.py @@ -0,0 +1,879 @@ +doTimingAttackMitigation = False + +import base64 +import datetime +import errno +import math +import time +import threading +import shared +import hashlib +import os +import Queue +import select +import socket +import random +import ssl +from struct import unpack, pack +import sys +import traceback +from binascii import hexlify +#import string +#from subprocess import call # used when the API must execute an outside program +#from pyelliptic.openssl import OpenSSL + +#import highlevelcrypto +from addresses import * +from bmconfigparser import BMConfigParser +from class_objectHashHolder import objectHashHolder +from helper_generic import addDataPadding, isHostInPrivateIPRange +from helper_sql import sqlQuery +import knownnodes +from debug import logger +import paths +import protocol +from inventory import Inventory, PendingDownloadQueue, PendingUpload +import queues +import state +import throttle +import tr +from version import softwareVersion + +# This thread is created either by the synSenderThread(for outgoing +# connections) or the singleListenerThread(for incoming connections). + +class receiveDataThread(threading.Thread): + + def __init__(self): + threading.Thread.__init__(self, name="receiveData") + self.data = '' + self.verackSent = False + self.verackReceived = False + + def setup( + self, + sock, + HOST, + port, + streamNumber, + selfInitiatedConnections, + sendDataThreadQueue, + objectHashHolderInstance): + + self.sock = sock + self.peer = state.Peer(HOST, port) + self.name = "receiveData-" + self.peer.host.replace(":", ".") # ":" log parser field separator + self.streamNumber = state.streamsInWhichIAmParticipating + self.remoteStreams = [] + self.selfInitiatedConnections = selfInitiatedConnections + self.sendDataThreadQueue = sendDataThreadQueue # used to send commands and data to the sendDataThread + self.hostIdent = self.peer.port if ".onion" in BMConfigParser().get('bitmessagesettings', 'onionhostname') and protocol.checkSocksIP(self.peer.host) else self.peer.host + shared.connectedHostsList[ + self.hostIdent] = 0 # The very fact that this receiveData thread exists shows that we are connected to the remote host. Let's add it to this list so that an outgoingSynSender thread doesn't try to connect to it. + self.connectionIsOrWasFullyEstablished = False # set to true after the remote node and I accept each other's version messages. This is needed to allow the user interface to accurately reflect the current number of connections. + self.services = 0 + if streamNumber == -1: # This was an incoming connection. Send out a version message if we accept the other node's version message. + self.initiatedConnection = False + else: + self.initiatedConnection = True + for stream in self.streamNumber: + self.selfInitiatedConnections[stream][self] = 0 + self.objectHashHolderInstance = objectHashHolderInstance + self.downloadQueue = PendingDownloadQueue() + self.startTime = time.time() + + def run(self): + logger.debug('receiveDataThread starting. ID ' + str(id(self)) + '. The size of the shared.connectedHostsList is now ' + str(len(shared.connectedHostsList))) + + while state.shutdown == 0: + dataLen = len(self.data) + try: + isSSL = False + if ((self.services & protocol.NODE_SSL == protocol.NODE_SSL) and + self.connectionIsOrWasFullyEstablished and + protocol.haveSSL(not self.initiatedConnection)): + isSSL = True + dataRecv = self.sslSock.recv(throttle.ReceiveThrottle().chunkSize) + else: + dataRecv = self.sock.recv(throttle.ReceiveThrottle().chunkSize) + self.data += dataRecv + throttle.ReceiveThrottle().wait(len(dataRecv)) + except socket.timeout: + if self.connectionIsOrWasFullyEstablished: + self.sendping("Still around!") + continue + logger.error("Timeout during protocol initialisation") + break + except ssl.SSLError as err: + if err.errno == ssl.SSL_ERROR_WANT_READ: + select.select([self.sslSock], [], [], 10) + logger.debug('sock.recv retriable SSL error') + continue + if err.errno is None and 'timed out' in str(err): + if self.connectionIsOrWasFullyEstablished: + self.sendping("Still around!") + continue + logger.error ('SSL error: %i/%s', err.errno if err.errno else 0, str(err)) + break + except socket.error as err: + if err.errno in (errno.EAGAIN, errno.EWOULDBLOCK) or \ + (sys.platform.startswith('win') and \ + err.errno == errno.WSAEWOULDBLOCK): + 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, %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') + break + else: + self.processData() + + try: + for stream in self.streamNumber: + try: + del self.selfInitiatedConnections[stream][self] + except KeyError: + pass + logger.debug('removed self (a receiveDataThread) from selfInitiatedConnections') + except: + pass + self.sendDataThreadQueue.put((0, 'shutdown','no data')) # commands the corresponding sendDataThread to shut itself down. + try: + del shared.connectedHostsList[self.hostIdent] + except Exception as err: + logger.error('Could not delete ' + str(self.hostIdent) + ' from shared.connectedHostsList.' + str(err)) + + 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))) + + 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 + objectHashHolder.size/2) + # 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 + now = time.time() + if initial and now - delay < self.startTime: + logger.debug("Initial sleeping for %.2fs", delay - (now - self.startTime)) + time.sleep(delay - (now - self.startTime)) + elif not initial: + logger.debug("Sleeping due to missing object for %.2fs", delay) + time.sleep(delay) + + def checkTimeOffsetNotification(self): + if shared.timeOffsetWrongCount >= 4 and not self.connectionIsOrWasFullyEstablished: + queues.UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow", "The time on your computer, %1, may be wrong. Please verify your settings.").arg(datetime.datetime.now().strftime("%H:%M:%S")))) + + def processData(self): + if len(self.data) < protocol.Header.size: # if so little of the data has arrived that we can't even read the checksum then wait for more data. + return + + magic,command,payloadLength,checksum = protocol.Header.unpack(self.data[:protocol.Header.size]) + if magic != 0xE9BEB4D9: + self.data = "" + return + if payloadLength > 1600100: # ~1.6 MB which is the maximum possible size of an inv message. + logger.info('The incoming message, which we have not yet download, is too large. Ignoring it. (unfortunately there is no way to tell the other node to stop sending it except to disconnect.) Message size: %s' % payloadLength) + self.data = self.data[payloadLength + protocol.Header.size:] + del magic,command,payloadLength,checksum # we don't need these anymore and better to clean them now before the recursive call rather than after + self.processData() + return + if len(self.data) < payloadLength + protocol.Header.size: # check if the whole message has arrived yet. + return + payload = self.data[protocol.Header.size:payloadLength + protocol.Header.size] + if checksum != hashlib.sha512(payload).digest()[0:4]: # test the checksum in the message. + logger.error('Checksum incorrect. Clearing this message.') + self.data = self.data[payloadLength + protocol.Header.size:] + del magic,command,payloadLength,checksum,payload # better to clean up before the recursive call + self.processData() + return + + # The time we've last seen this node is obviously right now since we + # just received valid data from it. So update the knownNodes list so + # that other peers can be made aware of its existance. + if self.initiatedConnection and self.connectionIsOrWasFullyEstablished: # The remote port is only something we should share with others if it is the remote node's incoming port (rather than some random operating-system-assigned outgoing port). + with knownnodes.knownNodesLock: + for stream in self.streamNumber: + knownnodes.knownNodes[stream][self.peer] = int(time.time()) + + #Strip the nulls + command = command.rstrip('\x00') + logger.debug('remoteCommand ' + repr(command) + ' from ' + str(self.peer)) + + try: + #TODO: Use a dispatcher here + if command == 'error': + self.recerror(payload) + elif not self.connectionIsOrWasFullyEstablished: + if command == 'version': + self.recversion(payload) + elif command == 'verack': + self.recverack() + else: + if command == 'addr': + self.recaddr(payload) + elif command == 'inv': + self.recinv(payload) + elif command == 'getdata': + self.recgetdata(payload) + elif command == 'object': + self.recobject(payload) + elif command == 'ping': + self.sendpong(payload) + elif command == 'pong': + pass + else: + logger.info("Unknown command %s, ignoring", command) + except varintDecodeError as e: + logger.debug("There was a problem with a varint while processing a message from the wire. Some details: %s" % e) + except Exception as e: + logger.critical("Critical error in a receiveDataThread: \n%s" % traceback.format_exc()) + + del payload + 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: + 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): + logger.debug('Sending pong') + self.sendDataThreadQueue.put((0, 'sendRawData', protocol.CreatePacket('pong', payload))) + + def sendping(self, payload): + logger.debug('Sending ping') + self.sendDataThreadQueue.put((0, 'sendRawData', protocol.CreatePacket('ping', payload))) + + def recverack(self): + logger.debug('verack received') + self.verackReceived = True + if self.verackSent: + # We have thus both sent and received a verack. + self.connectionFullyEstablished() + + def sslHandshake(self): + self.sslSock = self.sock + if ((self.services & protocol.NODE_SSL == protocol.NODE_SSL) and + protocol.haveSSL(not self.initiatedConnection)): + logger.debug("Initialising TLS") + if sys.version_info >= (2,7,9): + context = ssl.SSLContext(protocol.sslProtocolVersion) + context.set_ciphers(protocol.sslProtocolCiphers) + 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_ALL | ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_SINGLE_ECDH_USE | ssl.OP_CIPHER_SERVER_PREFERENCE + self.sslSock = context.wrap_socket(self.sock, server_side = not self.initiatedConnection, do_handshake_on_connect=False) + else: + self.sslSock = ssl.wrap_socket(self.sock, keyfile = os.path.join(paths.codePath(), 'sslkeys', 'key.pem'), certfile = os.path.join(paths.codePath(), 'sslkeys', 'cert.pem'), server_side = not self.initiatedConnection, ssl_version=protocol.sslProtocolVersion, do_handshake_on_connect=False, ciphers=protocol.sslProtocolCiphers) + self.sendDataThreadQueue.join() + while True: + 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: + if isinstance (e, ssl.SSLWantReadError): + logger.debug("Waiting for SSL socket handhake read") + select.select([self.sslSock], [], [], 10) + continue + elif isinstance (e, ssl.SSLWantWriteError): + logger.debug("Waiting for SSL socket handhake write") + select.select([], [self.sslSock], [], 10) + continue + else: + if e.args[0] == ssl.SSL_ERROR_WANT_READ: + logger.debug("Waiting for SSL socket handhake read") + select.select([self.sslSock], [], [], 10) + continue + elif e.args[0] == ssl.SSL_ERROR_WANT_WRITE: + logger.debug("Waiting for SSL socket handhake write") + select.select([], [self.sslSock], [], 10) + continue + 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')) + return False + # SSL in the background should be blocking, otherwise the error handling is difficult + self.sslSock.settimeout(None) + return True + # no SSL + return True + + def peerValidityChecks(self): + if self.remoteProtocolVersion < 3: + self.sendDataThreadQueue.put((0, 'sendRawData',protocol.assembleErrorMessage( + fatal=2, errorText="Your is using an old protocol. Closing connection."))) + logger.debug ('Closing connection to old protocol version ' + str(self.remoteProtocolVersion) + ' node: ' + str(self.peer)) + return False + if self.timeOffset > 3600: + self.sendDataThreadQueue.put((0, 'sendRawData', 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.peer, self.timeOffset) + shared.timeOffsetWrongCount += 1 + time.sleep(2) + return False + elif self.timeOffset < -3600: + self.sendDataThreadQueue.put((0, 'sendRawData', 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.peer, self.timeOffset) + shared.timeOffsetWrongCount += 1 + return False + else: + shared.timeOffsetWrongCount = 0 + if len(self.streamNumber) == 0: + self.sendDataThreadQueue.put((0, 'sendRawData', protocol.assembleErrorMessage( + fatal=2, errorText="We don't have shared stream interests. Closing connection."))) + logger.debug ('Closed connection to ' + str(self.peer) + ' because there is no overlapping interest in streams.') + return False + return True + + def connectionFullyEstablished(self): + if self.connectionIsOrWasFullyEstablished: + # there is no reason to run this function a second time + return + + if not self.sslHandshake(): + return + + if self.peerValidityChecks() == False: + time.sleep(2) + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + self.checkTimeOffsetNotification() + return + + self.connectionIsOrWasFullyEstablished = True + shared.timeOffsetWrongCount = 0 + + # Command the corresponding sendDataThread to set its own connectionIsOrWasFullyEstablished variable to True also + self.sendDataThreadQueue.put((0, 'connectionIsOrWasFullyEstablished', (self.services, self.sslSock))) + + if not self.initiatedConnection: + shared.clientHasReceivedIncomingConnections = True + queues.UISignalQueue.put(('setStatusIcon', 'green')) + self.sock.settimeout( + 600) # We'll send out a ping every 5 minutes to make sure the connection stays alive if there has been no other traffic to send lately. + queues.UISignalQueue.put(('updateNetworkStatusTab', 'no data')) + logger.debug('Connection fully established with ' + str(self.peer) + "\n" + \ + 'The size of the connectedHostsList is now ' + str(len(shared.connectedHostsList)) + "\n" + \ + 'The length of sendDataQueues is now: ' + str(len(state.sendDataQueues)) + "\n" + \ + 'broadcasting addr from within connectionFullyEstablished function.') + + if self.initiatedConnection: + state.networkProtocolAvailability[protocol.networkType(self.peer.host)] = True + + # we need to send our own objects to this node + PendingUpload().add() + + # Let all of our peers know about this new node. + for stream in self.remoteStreams: + dataToSend = (int(time.time()), stream, self.services, self.peer.host, self.remoteNodeIncomingPort) + protocol.broadcastToSendDataQueues(( + stream, 'advertisepeer', dataToSend)) + + self.sendaddr() # This is one large addr message to this one peer. + if len(shared.connectedHostsList) > \ + BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections", 200): + logger.info ('We are connected to too many people. Closing connection.') + if self.initiatedConnection: + self.sendDataThreadQueue.put((0, 'sendRawData', protocol.assembleErrorMessage(fatal=2, errorText="Thank you for providing a listening node."))) + else: + self.sendDataThreadQueue.put((0, 'sendRawData', protocol.assembleErrorMessage(fatal=2, errorText="Server full, please try again later."))) + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + return + self.sendBigInv() + + def sendBigInv(self): + # Select all hashes for objects in this stream. + bigInvList = {} + for stream in self.streamNumber: + for hash in Inventory().unexpired_hashes_by_stream(stream): + if not self.objectHashHolderInstance.hasHash(hash): + bigInvList[hash] = 0 + numberOfObjectsInInvMessage = 0 + payload = '' + # 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 + numberOfObjectsInInvMessage += 1 + if numberOfObjectsInInvMessage == 50000: # We can only send a max of 50000 items per inv message but we may have more objects to advertise. They must be split up into multiple inv messages. + self.sendinvMessageToJustThisOnePeer( + numberOfObjectsInInvMessage, payload) + payload = '' + numberOfObjectsInInvMessage = 0 + if numberOfObjectsInInvMessage > 0: + self.sendinvMessageToJustThisOnePeer( + numberOfObjectsInInvMessage, payload) + + # Used to send a big inv message when the connection with a node is + # first fully established. Notice that there is also a broadcastinv + # function for broadcasting invs to everyone in our stream. + def sendinvMessageToJustThisOnePeer(self, numberOfObjects, payload): + payload = encodeVarint(numberOfObjects) + payload + logger.debug('Sending huge inv message with ' + str(numberOfObjects) + ' objects to just this one peer') + self.sendDataThreadQueue.put((0, 'sendRawData', protocol.CreatePacket('inv', payload))) + + def _sleepForTimingAttackMitigation(self, sleepTime): + # We don't need to do the timing attack mitigation if we are + # only connected to the trusted peer because we can trust the + # peer not to attack + if sleepTime > 0 and doTimingAttackMitigation and state.trustedPeer == None: + logger.debug('Timing attack mitigation: Sleeping for ' + str(sleepTime) + ' seconds.') + time.sleep(sleepTime) + + def recerror(self, data): + """ + The remote node has been polite enough to send you an error message. + """ + fatalStatus, readPosition = decodeVarint(data[:10]) + banTime, banTimeLength = decodeVarint(data[readPosition:readPosition+10]) + readPosition += banTimeLength + inventoryVectorLength, inventoryVectorLengthLength = decodeVarint(data[readPosition:readPosition+10]) + if inventoryVectorLength > 100: + return + readPosition += inventoryVectorLengthLength + inventoryVector = data[readPosition:readPosition+inventoryVectorLength] + readPosition += inventoryVectorLength + errorTextLength, errorTextLengthLength = decodeVarint(data[readPosition:readPosition+10]) + if errorTextLength > 1000: + return + readPosition += errorTextLengthLength + errorText = data[readPosition:readPosition+errorTextLength] + if fatalStatus == 0: + fatalHumanFriendly = 'Warning' + elif fatalStatus == 1: + fatalHumanFriendly = 'Error' + elif fatalStatus == 2: + fatalHumanFriendly = 'Fatal' + message = '%s message received from %s: %s.' % (fatalHumanFriendly, self.peer, errorText) + if inventoryVector: + message += " This concerns object %s" % hexlify(inventoryVector) + if banTime > 0: + message += " Remote node says that the ban time is %s" % banTime + logger.error(message) + + + 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 + remote node can send them. If we fall behind, the attacker could observe + that we are are slowing down the rate at which we request objects from the + network which would indicate that we own a particular address (whichever + one to which they are sending all of their attack messages). Note + that if an attacker connects to a target with many connections, this + mitigation mechanism might not be sufficient. + """ + sleepTime = lengthOfTimeWeShouldUseToProcessThisMessage - (time.time() - self.messageProcessingStartTime) + self._sleepForTimingAttackMitigation(sleepTime) + + + # We have received an inv message + def recinv(self, data): + numberOfItemsInInv, lengthOfVarint = decodeVarint(data[:10]) + if numberOfItemsInInv > 50000: + sys.stderr.write('Too many items in inv message!') + return + if len(data) < lengthOfVarint + (numberOfItemsInInv * 32): + logger.info('inv message doesn\'t contain enough data. Ignoring.') + return + + startTime = time.time() + advertisedSet = set() + for i in range(numberOfItemsInInv): + advertisedSet.add(data[lengthOfVarint + (32 * i):32 + lengthOfVarint + (32 * i)]) + objectsNewToMe = advertisedSet + 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 random.sample(objectsNewToMe, len(objectsNewToMe)): + self.downloadQueue.put(item) + + # Send a getdata message to our peer to request the object with the given + # hash + def sendgetdata(self, hashes): + if len(hashes) == 0: + return + logger.debug('sending getdata to retrieve %i objects', len(hashes)) + payload = encodeVarint(len(hashes)) + ''.join(hashes) + self.sendDataThreadQueue.put((0, 'sendRawData', protocol.CreatePacket('getdata', payload)), False) + + + # We have received a getdata request from our peer + def recgetdata(self, data): + numberOfRequestedInventoryItems, lengthOfVarint = decodeVarint( + data[:10]) + if len(data) < lengthOfVarint + (32 * numberOfRequestedInventoryItems): + logger.debug('getdata message does not contain enough data. Ignoring.') + return + self.antiIntersectionDelay(True) # only handle getdata requests if we have been connected long enough + for i in xrange(numberOfRequestedInventoryItems): + hash = data[lengthOfVarint + ( + i * 32):32 + lengthOfVarint + (i * 32)] + logger.debug('received getdata request for item:' + hexlify(hash)) + + if self.objectHashHolderInstance.hasHash(hash): + self.antiIntersectionDelay() + else: + if hash in Inventory(): + self.sendObject(hash, Inventory()[hash].payload) + else: + self.antiIntersectionDelay() + logger.warning('%s asked for an object with a getdata which is not in either our memory inventory or our SQL inventory. We probably cleaned it out after advertising it but before they got around to asking for it.' % (self.peer,)) + + # Our peer has requested (in a getdata message) that we send an object. + def sendObject(self, hash, payload): + logger.debug('sending an object.') + self.sendDataThreadQueue.put((0, 'sendRawData', (hash, protocol.CreatePacket('object',payload)))) + + def _checkIPAddress(self, host): + 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 self._checkIPv4Address(host[12:], hostStandardFormat) + elif host[0:6] == '\xfd\x87\xd8\x7e\xeb\x43': + # Onion, based on BMD/bitcoind + hostStandardFormat = base64.b32encode(host[6:]).lower() + ".onion" + 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 self._checkIPv6Address(host, hostStandardFormat) + + def _checkIPv4Address(self, host, hostStandardFormat): + if host[0] == '\x7F': # 127/8 + logger.debug('Ignoring IP address in loopback range: ' + hostStandardFormat) + return False + if host[0] == '\x0A': # 10/8 + logger.debug('Ignoring IP address in private range: ' + hostStandardFormat) + return False + if host[0:2] == '\xC0\xA8': # 192.168/16 + logger.debug('Ignoring IP address in private range: ' + hostStandardFormat) + return False + if host[0:2] >= '\xAC\x10' and host[0:2] < '\xAC\x20': # 172.16/12 + logger.debug('Ignoring IP address in private range:' + hostStandardFormat) + return False + return hostStandardFormat + + def _checkIPv6Address(self, host, hostStandardFormat): + if host == ('\x00' * 15) + '\x01': + logger.debug('Ignoring loopback address: ' + hostStandardFormat) + return False + if host[0] == '\xFE' and (ord(host[1]) & 0xc0) == 0x80: + logger.debug ('Ignoring local address: ' + hostStandardFormat) + return False + if (ord(host[0]) & 0xfe) == 0xfc: + logger.debug ('Ignoring unique local address: ' + hostStandardFormat) + return False + return hostStandardFormat + + # We have received an addr message. + def recaddr(self, data): + numberOfAddressesIncluded, lengthOfNumberOfAddresses = decodeVarint( + data[:10]) + + if shared.verbose >= 1: + logger.debug('addr message contains ' + str(numberOfAddressesIncluded) + ' IP addresses.') + + if numberOfAddressesIncluded > 1000 or numberOfAddressesIncluded == 0: + return + if len(data) != lengthOfNumberOfAddresses + (38 * numberOfAddressesIncluded): + logger.debug('addr message does not contain the correct amount of data. Ignoring.') + return + + for i in range(0, numberOfAddressesIncluded): + fullHost = data[20 + lengthOfNumberOfAddresses + (38 * i):36 + lengthOfNumberOfAddresses + (38 * i)] + recaddrStream, = unpack('>I', data[8 + lengthOfNumberOfAddresses + ( + 38 * i):12 + lengthOfNumberOfAddresses + (38 * i)]) + if recaddrStream == 0: + continue + if recaddrStream not in self.streamNumber and (recaddrStream / 2) not in self.streamNumber: # if the embedded stream number and its parent are not in my streams then ignore it. Someone might be trying funny business. + continue + recaddrServices, = unpack('>Q', data[12 + lengthOfNumberOfAddresses + ( + 38 * i):20 + lengthOfNumberOfAddresses + (38 * i)]) + recaddrPort, = unpack('>H', data[36 + lengthOfNumberOfAddresses + ( + 38 * i):38 + lengthOfNumberOfAddresses + (38 * i)]) + hostStandardFormat = self._checkIPAddress(fullHost) + if hostStandardFormat is False: + continue + if recaddrPort == 0: + continue + timeSomeoneElseReceivedMessageFromThisNode, = unpack('>Q', data[lengthOfNumberOfAddresses + ( + 38 * i):8 + lengthOfNumberOfAddresses + (38 * i)]) # This is the 'time' value in the received addr message. 64-bit. + if recaddrStream not in knownnodes.knownNodes: # knownNodes is a dictionary of dictionaries with one outer dictionary for each stream. If the outer stream dictionary doesn't exist yet then we must make it. + with knownnodes.knownNodesLock: + knownnodes.knownNodes[recaddrStream] = {} + peerFromAddrMessage = state.Peer(hostStandardFormat, recaddrPort) + if peerFromAddrMessage not in knownnodes.knownNodes[recaddrStream]: + # only if recent + if timeSomeoneElseReceivedMessageFromThisNode > (int(time.time()) - 10800) and timeSomeoneElseReceivedMessageFromThisNode < (int(time.time()) + 10800): + # 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()) - 86400 # penalise initially by 1 day + logger.debug('added new node ' + str(peerFromAddrMessage) + ' to knownNodes in stream ' + str(recaddrStream)) + shared.needToWriteKnownNodesToDisk = True + # normal mode + elif len(knownnodes.knownNodes[recaddrStream]) < 20000: + with knownnodes.knownNodesLock: + knownnodes.knownNodes[recaddrStream][peerFromAddrMessage] = timeSomeoneElseReceivedMessageFromThisNode + hostDetails = ( + timeSomeoneElseReceivedMessageFromThisNode, + recaddrStream, recaddrServices, hostStandardFormat, recaddrPort) + protocol.broadcastToSendDataQueues(( + recaddrStream, 'advertisepeer', hostDetails)) + 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): + timeLastReceivedMessageFromThisNode = knownnodes.knownNodes[recaddrStream][ + peerFromAddrMessage] + if (timeLastReceivedMessageFromThisNode < timeSomeoneElseReceivedMessageFromThisNode) and (timeSomeoneElseReceivedMessageFromThisNode < int(time.time())+900): # 900 seconds for wiggle-room in case other nodes' clocks aren't quite right. + with knownnodes.knownNodesLock: + knownnodes.knownNodes[recaddrStream][peerFromAddrMessage] = timeSomeoneElseReceivedMessageFromThisNode + + for stream in self.streamNumber: + logger.debug('knownNodes currently has %i nodes for stream %i', len(knownnodes.knownNodes[stream]), stream) + + + # Send a huge addr message to our peer. This is only used + # when we fully establish a connection with a + # peer (with the full exchange of version and verack + # messages). + def sendaddr(self): + def sendChunk(): + if numberOfAddressesInAddrMessage == 0: + return + self.sendDataThreadQueue.put((0, 'sendRawData', \ + protocol.CreatePacket('addr', \ + encodeVarint(numberOfAddressesInAddrMessage) + payload))) + + # 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) + + # protocol defines this as a maximum in one chunk + protocolAddrLimit = 1000 + + # init + numberOfAddressesInAddrMessage = 0 + payload = '' + + for stream in self.streamNumber: + addrsInMyStream = {} + addrsInChildStreamLeft = {} + addrsInChildStreamRight = {} + + with knownnodes.knownNodesLock: + if len(knownnodes.knownNodes[stream]) > 0: + filtered = {k: v for k, v in knownnodes.knownNodes[stream].items() + if v > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} + elemCount = len(filtered) + if elemCount > maxAddrCount: + elemCount = maxAddrCount + # only if more recent than 3 hours + addrsInMyStream = 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.streamNumber: + filtered = {k: v for k, v in knownnodes.knownNodes[stream*2].items() + if v > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} + elemCount = len(filtered) + if elemCount > maxAddrCount / 2: + elemCount = int(maxAddrCount / 2) + addrsInChildStreamLeft = random.sample(filtered.items(), elemCount) + if len(knownnodes.knownNodes[(stream * 2) + 1]) > 0 and stream not in self.streamNumber: + filtered = {k: v for k, v in knownnodes.knownNodes[stream*2+1].items() + if v > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)} + elemCount = len(filtered) + if elemCount > maxAddrCount / 2: + elemCount = int(maxAddrCount / 2) + addrsInChildStreamRight = random.sample(filtered.items(), elemCount) + for (HOST, PORT), timeLastReceivedMessageFromThisNode in addrsInMyStream: + numberOfAddressesInAddrMessage += 1 + payload += pack( + '>Q', timeLastReceivedMessageFromThisNode) # 64-bit time + payload += pack('>I', stream) + payload += pack( + '>q', 1) # service bit flags offered by this node + payload += protocol.encodeHost(HOST) + payload += pack('>H', PORT) # remote port + if numberOfAddressesInAddrMessage >= protocolAddrLimit: + sendChunk() + payload = '' + numberOfAddressesInAddrMessage = 0 + for (HOST, PORT), timeLastReceivedMessageFromThisNode in addrsInChildStreamLeft: + numberOfAddressesInAddrMessage += 1 + payload += pack( + '>Q', timeLastReceivedMessageFromThisNode) # 64-bit time + payload += pack('>I', stream * 2) + payload += pack( + '>q', 1) # service bit flags offered by this node + payload += protocol.encodeHost(HOST) + payload += pack('>H', PORT) # remote port + if numberOfAddressesInAddrMessage >= protocolAddrLimit: + sendChunk() + payload = '' + numberOfAddressesInAddrMessage = 0 + for (HOST, PORT), timeLastReceivedMessageFromThisNode in addrsInChildStreamRight: + numberOfAddressesInAddrMessage += 1 + payload += pack( + '>Q', timeLastReceivedMessageFromThisNode) # 64-bit time + payload += pack('>I', (stream * 2) + 1) + payload += pack( + '>q', 1) # service bit flags offered by this node + payload += protocol.encodeHost(HOST) + payload += pack('>H', PORT) # remote port + if numberOfAddressesInAddrMessage >= protocolAddrLimit: + sendChunk() + payload = '' + numberOfAddressesInAddrMessage = 0 + + # flush + sendChunk() + + # We have received a version message + def recversion(self, data): + if len(data) < 83: + # This version message is unreasonably short. Forget it. + return + if self.verackSent: + """ + We must have already processed the remote node's version message. + There might be a time in the future when we Do want to process + a new version message, like if the remote node wants to update + the streams in which they are interested. But for now we'll + ignore this version message + """ + return + + self.remoteProtocolVersion, = unpack('>L', data[:4]) + self.services, = unpack('>q', data[4:12]) + + timestamp, = unpack('>Q', data[12:20]) + self.timeOffset = timestamp - int(time.time()) + + self.myExternalIP = socket.inet_ntoa(data[40:44]) + # print 'myExternalIP', self.myExternalIP + self.remoteNodeIncomingPort, = unpack('>H', data[70:72]) + # print 'remoteNodeIncomingPort', self.remoteNodeIncomingPort + useragentLength, lengthOfUseragentVarint = decodeVarint( + data[80:84]) + readPosition = 80 + lengthOfUseragentVarint + self.userAgent = data[readPosition:readPosition + useragentLength] + + # version check + try: + userAgentName, userAgentVersion = self.userAgent[1:-1].split(":", 2) + except: + userAgentName = self.userAgent + userAgentVersion = "0.0.0" + if userAgentName == "PyBitmessage": + myVersion = [int(n) for n in softwareVersion.split(".")] + try: + remoteVersion = [int(n) for n in userAgentVersion.split(".")] + except: + remoteVersion = 0 + # remote is newer, but do not cross between stable and unstable + try: + if cmp(remoteVersion, myVersion) > 0 and \ + (myVersion[1] % 2 == remoteVersion[1] % 2): + queues.UISignalQueue.put(('newVersionAvailable', remoteVersion)) + except: + pass + + readPosition += useragentLength + numberOfStreamsInVersionMessage, lengthOfNumberOfStreamsInVersionMessage = decodeVarint( + data[readPosition:]) + readPosition += lengthOfNumberOfStreamsInVersionMessage + self.remoteStreams = [] + for i in range(numberOfStreamsInVersionMessage): + newStreamNumber, lengthOfRemoteStreamNumber = decodeVarint(data[readPosition:]) + readPosition += lengthOfRemoteStreamNumber + self.remoteStreams.append(newStreamNumber) + logger.debug('Remote node useragent: %s, streams: (%s), time offset: %is.', + self.userAgent, ', '.join(str(x) for x in self.remoteStreams), self.timeOffset) + + # find shared streams + self.streamNumber = sorted(set(state.streamsInWhichIAmParticipating).intersection(self.remoteStreams)) + + shared.connectedHostsList[ + self.hostIdent] = 1 # We use this data structure to not only keep track of what hosts we are connected to so that we don't try to connect to them again, but also to list the connections count on the Network Status tab. + self.sendDataThreadQueue.put((0, 'setStreamNumber', self.remoteStreams)) + if data[72:80] == protocol.eightBytesOfRandomDataUsedToDetectConnectionsToSelf: + self.sendDataThreadQueue.put((0, 'shutdown','no data')) + logger.debug('Closing connection to myself: ' + str(self.peer)) + return + + # The other peer's protocol version is of interest to the sendDataThread but we learn of it + # in this version message. Let us inform the sendDataThread. + self.sendDataThreadQueue.put((0, 'setRemoteProtocolVersion', self.remoteProtocolVersion)) + + if not isHostInPrivateIPRange(self.peer.host): + with knownnodes.knownNodesLock: + for stream in self.remoteStreams: + knownnodes.knownNodes[stream][state.Peer(self.peer.host, self.remoteNodeIncomingPort)] = int(time.time()) + if not self.initiatedConnection: + # bootstrap provider? + if BMConfigParser().safeGetInt('bitmessagesettings', 'maxoutboundconnections') >= \ + BMConfigParser().safeGetInt('bitmessagesettings', 'maxtotalconnections', 200): + knownnodes.knownNodes[stream][state.Peer(self.peer.host, self.remoteNodeIncomingPort)] -= 10800 # penalise inbound, 3 hours + else: + knownnodes.knownNodes[stream][state.Peer(self.peer.host, self.remoteNodeIncomingPort)] -= 7200 # penalise inbound, 2 hours + shared.needToWriteKnownNodesToDisk = True + + self.sendverack() + if self.initiatedConnection == False: + self.sendversion() + + # Sends a version message + def sendversion(self): + logger.debug('Sending version message') + self.sendDataThreadQueue.put((0, 'sendRawData', protocol.assembleVersionMessage( + self.peer.host, self.peer.port, state.streamsInWhichIAmParticipating, not self.initiatedConnection))) + + # Sends a verack message + def sendverack(self): + logger.debug('Sending verack') + self.sendDataThreadQueue.put((0, 'sendRawData', protocol.CreatePacket('verack'))) + self.verackSent = True + if self.verackReceived: + self.connectionFullyEstablished() diff --git a/src/class_sendDataThread.py b/src/class_sendDataThread.py new file mode 100644 index 00000000..792fedd0 --- /dev/null +++ b/src/class_sendDataThread.py @@ -0,0 +1,216 @@ +import errno +import time +import threading +import Queue +from struct import unpack, pack +import hashlib +import random +import select +import socket +from ssl import SSLError, SSL_ERROR_WANT_WRITE +import sys + +from helper_generic import addDataPadding +from class_objectHashHolder import * +from addresses import * +from debug import logger +from inventory import PendingUpload +import protocol +import state +import throttle + +# Every connection to a peer has a sendDataThread (and also a +# receiveDataThread). +class sendDataThread(threading.Thread): + + def __init__(self, sendDataThreadQueue): + threading.Thread.__init__(self, name="sendData") + self.sendDataThreadQueue = sendDataThreadQueue + state.sendDataQueues.append(self.sendDataThreadQueue) + self.data = '' + self.objectHashHolderInstance = objectHashHolder(self.sendDataThreadQueue) + self.objectHashHolderInstance.daemon = True + self.objectHashHolderInstance.start() + self.connectionIsOrWasFullyEstablished = False + + + def setup( + self, + sock, + HOST, + PORT, + streamNumber + ): + self.sock = sock + self.peer = state.Peer(HOST, PORT) + self.name = "sendData-" + self.peer.host.replace(":", ".") # log parser field separator + self.streamNumber = [] + self.services = 0 + self.buffer = "" + self.initiatedConnection = False + self.remoteProtocolVersion = - \ + 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. + if streamNumber == -1: # This was an incoming connection. + self.initiatedConnection = False + else: + self.initiatedConnection = True + #logger.debug('The streamNumber of this sendDataThread (ID: ' + str(id(self)) + ') at setup() is' + str(self.streamNumber)) + + + def sendVersionMessage(self): + datatosend = protocol.assembleVersionMessage( + self.peer.host, self.peer.port, state.streamsInWhichIAmParticipating, not self.initiatedConnection) # the IP and port of the remote host, and my streamNumber. + + logger.debug('Sending version packet: ' + repr(datatosend)) + + try: + self.sendBytes(datatosend) + except Exception as err: + # if not 'Bad file descriptor' in err: + logger.error('sock.sendall error: %s\n' % err) + + self.versionSent = 1 + + def sendBytes(self, data = ""): + self.buffer += data + if len(self.buffer) < throttle.SendThrottle().chunkSize and self.sendDataThreadQueue.qsize() > 1: + return True + + while self.buffer and state.shutdown == 0: + isSSL = False + try: + if ((self.services & protocol.NODE_SSL == protocol.NODE_SSL) and + self.connectionIsOrWasFullyEstablished and + protocol.haveSSL(not self.initiatedConnection)): + isSSL = True + amountSent = self.sslSock.send(self.buffer[:throttle.SendThrottle().chunkSize]) + else: + amountSent = self.sock.send(self.buffer[:throttle.SendThrottle().chunkSize]) + except socket.timeout: + continue + except SSLError as e: + if e.errno == SSL_ERROR_WANT_WRITE: + select.select([], [self.sslSock], [], 10) + logger.debug('sock.recv retriable SSL error') + continue + logger.debug('Connection error (SSL)') + return False + except socket.error as e: + if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK) or \ + (sys.platform.startswith('win') and \ + e.errno == errno.WSAEWOULDBLOCK): + 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, errno.ECONNREFUSED): + logger.debug('Connection error: %s', str(e)) + return False + raise + throttle.SendThrottle().wait(amountSent) + self.lastTimeISentData = int(time.time()) + self.buffer = self.buffer[amountSent:] + return True + + def run(self): + logger.debug('sendDataThread starting. ID: ' + str(id(self)) + '. Number of queues in sendDataQueues: ' + str(len(state.sendDataQueues))) + while self.sendBytes(): + deststream, command, data = self.sendDataThreadQueue.get() + + if deststream == 0 or deststream in self.streamNumber: + if command == 'shutdown': + logger.debug('sendDataThread (associated with ' + str(self.peer) + ') ID: ' + str(id(self)) + ' shutting down now.') + break + # When you receive an incoming connection, a sendDataThread is + # created even though you don't yet know what stream number the + # remote peer is interested in. They will tell you in a version + # message and if you too are interested in that stream then you + # will continue on with the connection and will set the + # streamNumber of this send data thread here: + elif command == 'setStreamNumber': + self.streamNumber = data + logger.debug('setting the stream number to %s', ', '.join(str(x) for x in self.streamNumber)) + elif command == 'setRemoteProtocolVersion': + specifiedRemoteProtocolVersion = data + logger.debug('setting the remote node\'s protocol version in the sendDataThread (ID: ' + str(id(self)) + ') to ' + str(specifiedRemoteProtocolVersion)) + self.remoteProtocolVersion = specifiedRemoteProtocolVersion + elif command == 'advertisepeer': + self.objectHashHolderInstance.holdPeer(data) + elif command == 'sendaddr': + if self.connectionIsOrWasFullyEstablished: # only send addr messages if we have sent and heard a verack from the remote node + numberOfAddressesInAddrMessage = len(data) + payload = '' + for hostDetails in data: + timeLastReceivedMessageFromThisNode, streamNumber, services, host, port = hostDetails + payload += pack( + '>Q', timeLastReceivedMessageFromThisNode) # now uses 64-bit time + payload += pack('>I', streamNumber) + payload += pack( + '>q', services) # service bit flags offered by this node + payload += protocol.encodeHost(host) + payload += pack('>H', port) + + payload = encodeVarint(numberOfAddressesInAddrMessage) + payload + packet = protocol.CreatePacket('addr', payload) + try: + self.sendBytes(packet) + except: + logger.error('sendaddr: self.sock.sendall failed') + break + elif command == 'advertiseobject': + self.objectHashHolderInstance.holdHash(data) + elif command == 'sendinv': + if self.connectionIsOrWasFullyEstablished: # only send inv messages if we have send and heard a verack from the remote node + payload = '' + for hash in data: + payload += hash + if payload != '': + payload = encodeVarint(len(payload)/32) + payload + packet = protocol.CreatePacket('inv', payload) + try: + self.sendBytes(packet) + except: + logger.error('sendinv: self.sock.sendall failed') + break + elif command == 'pong': + 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.') + packet = protocol.CreatePacket('pong') + try: + self.sendBytes(packet) + except: + logger.error('send pong failed') + break + elif command == 'sendRawData': + objectHash = None + if type(data) in [list, tuple]: + objectHash, data = data + try: + self.sendBytes(data) + PendingUpload().delete(objectHash) + except: + logger.error('Sending of data to ' + str(self.peer) + ' failed. sendDataThread thread ' + str(self) + ' ending now.', exc_info=True) + break + elif command == 'connectionIsOrWasFullyEstablished': + self.connectionIsOrWasFullyEstablished = True + self.services, self.sslSock = data + elif self.connectionIsOrWasFullyEstablished: + logger.error('sendDataThread ID: ' + str(id(self)) + ' ignoring command ' + command + ' because the thread is not in stream ' + str(deststream) + ' but in streams ' + ', '.join(str(x) for x in self.streamNumber)) + self.sendDataThreadQueue.task_done() + # Flush if the cycle ended with break + try: + self.sendDataThreadQueue.task_done() + except ValueError: + pass + + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + except: + pass + state.sendDataQueues.remove(self.sendDataThreadQueue) + PendingUpload().threadEnd() + logger.info('sendDataThread ending. ID: ' + str(id(self)) + '. Number of queues in sendDataQueues: ' + str(len(state.sendDataQueues))) + self.objectHashHolderInstance.close() diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py index 24812b9e..ca77881c 100644 --- a/src/class_singleCleaner.py +++ b/src/class_singleCleaner.py @@ -1,136 +1,136 @@ -""" -The `singleCleaner` class is a timer-driven thread that cleans data structures -to free memory, resends messages when a remote node doesn't respond, and -sends pong messages to keep connections alive if the network isn't busy. - -It cleans these data structures in memory: - - inventory (moves data to the on-disk sql database) - - inventorySets (clears then reloads data out of sql database) - -It cleans these tables on the disk: - - inventory (clears expired objects) - - pubkeys (clears pubkeys older than 4 weeks old which we have not used - personally) - - knownNodes (clears addresses which have not been online for over 3 days) - -It resends messages when there has been no response: - - resends getpubkey messages in 5 days (then 10 days, then 20 days, etc...) - - resends msg messages in 5 days (then 10 days, then 20 days, etc...) - -""" - import gc -import os +import threading +import shared import time +import os +import tr#anslate +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 state -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -from inventory import Inventory -from network import BMConnectionPool, knownnodes, StoppableThread -from tr import _translate + +""" +The singleCleaner class is a timer-driven thread that cleans data structures +to free memory, resends messages when a remote node doesn't respond, and +sends pong messages to keep connections alive if the network isn't busy. +It cleans these data structures in memory: +inventory (moves data to the on-disk sql database) +inventorySets (clears then reloads data out of sql database) + +It cleans these tables on the disk: +inventory (clears expired objects) +pubkeys (clears pubkeys older than 4 weeks old which we have not used personally) +knownNodes (clears addresses which have not been online for over 3 days) + +It resends messages when there has been no response: +resends getpubkey messages in 5 days (then 10 days, then 20 days, etc...) +resends msg messages in 5 days (then 10 days, then 20 days, etc...) + +""" -#: Equals 4 weeks. You could make this longer if you want -#: but making it shorter would not be advisable because -#: there is a very small possibility that it could keep you -#: from obtaining a needed pubkey for a period of time. -lengthOfTimeToHoldOnToAllPubkeys = 2419200 - - -class singleCleaner(StoppableThread): - """The singleCleaner thread class""" - name = "singleCleaner" +class singleCleaner(threading.Thread, StoppableThread): cycleLength = 300 expireDiscoveredPeers = 300 - def run(self): # pylint: disable=too-many-branches + def __init__(self): + threading.Thread.__init__(self, name="singleCleaner") + self.initStop() + + def run(self): gc.disable() timeWeLastClearedInventoryAndPubkeysTables = 0 try: - state.maximumLengthOfTimeToBotherResendingMessages = ( - config.getfloat( - 'bitmessagesettings', 'stopresendingafterxdays') - * 24 * 60 * 60 - ) + ( - config.getfloat( - 'bitmessagesettings', 'stopresendingafterxmonths') - * (60 * 60 * 24 * 365) / 12) - except: # noqa:E722 - # Either the user hasn't set stopresendingafterxdays and - # stopresendingafterxmonths yet or the options are missing - # from the config file. - state.maximumLengthOfTimeToBotherResendingMessages = float('inf') + shared.maximumLengthOfTimeToBotherResendingMessages = (float(BMConfigParser().get('bitmessagesettings', 'stopresendingafterxdays')) * 24 * 60 * 60) + (float(BMConfigParser().get('bitmessagesettings', 'stopresendingafterxmonths')) * (60 * 60 * 24 *365)/12) + except: + # 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: - self.stop.wait(self.cycleLength) queues.UISignalQueue.put(( - 'updateStatusBar', - 'Doing housekeeping (Flushing inventory in memory to disk...)' - )) + 'updateStatusBar', 'Doing housekeeping (Flushing inventory in memory to disk...)')) Inventory().flush() queues.UISignalQueue.put(('updateStatusBar', '')) - + # 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. - # FIXME redundant? - if state.thisapp.daemon or not state.enableGUI: + if shared.thisapp.daemon: queues.UISignalQueue.queue.clear() - - tick = int(time.time()) - if timeWeLastClearedInventoryAndPubkeysTables < tick - 7380: - timeWeLastClearedInventoryAndPubkeysTables = tick + if timeWeLastClearedInventoryAndPubkeysTables < int(time.time()) - 7380: + timeWeLastClearedInventoryAndPubkeysTables = int(time.time()) Inventory().clean() - queues.workerQueue.put(('sendOnionPeerObj', '')) # pubkeys sqlExecute( - "DELETE FROM pubkeys WHERE time?)", - tick, - tick - state.maximumLengthOfTimeToBotherResendingMessages - ) - for toAddress, ackData, status in queryreturn: + '''select toaddress, ackdata, status FROM sent WHERE ((status='awaitingpubkey' OR status='msgsent') AND folder='sent' AND sleeptill?) ''', + int(time.time()), + int(time.time()) - shared.maximumLengthOfTimeToBotherResendingMessages) + for row in queryreturn: + if len(row) < 2: + logger.error('Something went wrong in the singleCleaner thread: a query did not return the requested fields. ' + repr(row)) + self.stop.wait(3) + break + toAddress, ackData, status = row if status == 'awaitingpubkey': - self.resendPubkeyRequest(toAddress) + resendPubkeyRequest(toAddress) elif status == 'msgsent': - self.resendMsg(ackData) + resendMsg(ackData) - try: - # Cleanup knownnodes and handle possible severe exception - # while writing it to disk - if state.enableNetwork: - knownnodes.cleanupKnownNodes() - except Exception as err: - if "Errno 28" in str(err): - self.logger.fatal( - '(while writing knownnodes to disk)' - ' Alert: Your disk or data storage volume is full.' - ) - queues.UISignalQueue.put(( - 'alert', - (_translate("MainWindow", "Disk full"), - _translate( - "MainWindow", - 'Alert: Your disk or data storage volume' - ' is full. Bitmessage will now exit.'), - True) - )) - # FIXME redundant? - if state.thisapp.daemon or not state.enableGUI: - os._exit(1) # pylint: disable=protected-access + # cleanup old nodes + now = int(time.time()) + with knownnodes.knownNodesLock: + for stream in knownnodes.knownNodes: + 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: + try: + knownnodes.saveKnownNodes() + except Exception as err: + 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.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().connections(): + for connection in BMConnectionPool().inboundConnections.values() + BMConnectionPool().outboundConnections.values(): connection.clean() # discovery tracking @@ -141,48 +141,34 @@ class singleCleaner(StoppableThread): del state.discoveredPeers[k] except KeyError: pass - # ..todo:: cleanup pending upload / download + # TODO: cleanup pending upload / download gc.collect() - def resendPubkeyRequest(self, address): - """Resend pubkey request for address""" - self.logger.debug( - 'It has been a long time and we haven\'t heard a response to our' - ' getpubkey request. Sending again.' - ) - try: - # We need to take this entry out of the neededPubkeys structure - # because the queues.workerQueue checks to see whether the entry - # is already present and will not do the POW and send the message - # because it assumes that it has already done it recently. - del state.neededPubkeys[address] - except KeyError: - pass - except RuntimeError: - self.logger.warning( - "Can't remove %s from neededPubkeys, requesting pubkey will be delayed", address, exc_info=True) + if state.shutdown == 0: + self.stop.wait(singleCleaner.cycleLength) - queues.UISignalQueue.put(( - 'updateStatusBar', - 'Doing work necessary to again attempt to request a public key...' - )) - sqlExecute( - "UPDATE sent SET status = 'msgqueued'" - " WHERE toaddress = ? AND folder = 'sent'", address) - queues.workerQueue.put(('sendmessage', '')) - def resendMsg(self, ackdata): - """Resend message by ackdata""" - self.logger.debug( - 'It has been a long time and we haven\'t heard an acknowledgement' - ' to our msg. Sending again.' - ) - sqlExecute( - "UPDATE sent SET status = 'msgqueued'" - " WHERE ackdata = ? AND folder = 'sent'", ackdata) - queues.workerQueue.put(('sendmessage', '')) - queues.UISignalQueue.put(( - 'updateStatusBar', - 'Doing work necessary to again attempt to deliver a message...' - )) +def resendPubkeyRequest(address): + logger.debug('It has been a long time and we haven\'t heard a response to our getpubkey request. Sending again.') + try: + del state.neededPubkeys[ + address] # We need to take this entry out of the neededPubkeys structure because the queues.workerQueue checks to see whether the entry is already present and will not do the POW and send the message because it assumes that it has already done it recently. + except: + pass + + queues.UISignalQueue.put(( + 'updateStatusBar', 'Doing work necessary to again attempt to request a public key...')) + sqlExecute( + '''UPDATE sent SET status='msgqueued' WHERE toaddress=?''', + address) + queues.workerQueue.put(('sendmessage', '')) + +def resendMsg(ackdata): + logger.debug('It has been a long time and we haven\'t heard an acknowledgement to our msg. Sending again.') + sqlExecute( + '''UPDATE sent SET status='msgqueued' WHERE ackdata=?''', + ackdata) + queues.workerQueue.put(('sendmessage', '')) + queues.UISignalQueue.put(( + 'updateStatusBar', 'Doing work necessary to again attempt to deliver a message...')) diff --git a/src/class_singleListener.py b/src/class_singleListener.py new file mode 100644 index 00000000..7626542d --- /dev/null +++ b/src/class_singleListener.py @@ -0,0 +1,168 @@ +import threading +import shared +import socket +from bmconfigparser import BMConfigParser +from class_sendDataThread import * +from class_receiveDataThread import * +import helper_bootstrap +from helper_threading import * +import protocol +import errno +import re + +import state + +# Only one singleListener thread will ever exist. It creates the +# receiveDataThread and sendDataThread for each incoming connection. Note +# that it cannot set the stream number because it is not known yet- the +# other node will have to tell us its stream number in a version message. +# If we don't care about their stream, we will close the connection +# (within the recversion function of the recieveData thread) + + +class singleListener(threading.Thread, StoppableThread): + + def __init__(self): + threading.Thread.__init__(self, name="singleListener") + self.initStop() + + def setup(self, selfInitiatedConnections): + self.selfInitiatedConnections = selfInitiatedConnections + + def _createListenSocket(self, family): + 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) + if family == socket.AF_INET6: + # Make sure we can accept both IPv4 and IPv6 connections. + # This is the default on everything apart from Windows + sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + # This option apparently avoids the TIME_WAIT state so that we can + # rebind faster + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((HOST, PORT)) + sock.listen(2) + return sock + + def stopThread(self): + super(singleListener, self).stopThread() + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + for ip in ('127.0.0.1', BMConfigParser().get('bitmessagesettings', 'onionbindip')): + try: + s.connect((ip, BMConfigParser().getint('bitmessagesettings', 'port'))) + s.shutdown(socket.SHUT_RDWR) + s.close() + break + except: + pass + + def run(self): + # If there is a trusted peer then we don't want to accept + # incoming connections so we'll just abandon the thread + if state.trustedPeer: + return + + while BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect') and state.shutdown == 0: + self.stop.wait(1) + helper_bootstrap.dns() + # We typically don't want to accept incoming connections if the user is using a + # SOCKS proxy, unless they have configured otherwise. If they eventually select + # proxy 'none' or configure SOCKS listening then this will start listening for + # connections. But if on SOCKS and have an onionhostname, listen + # (socket is then only opened for localhost) + while BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and \ + (not BMConfigParser().getboolean('bitmessagesettings', 'sockslisten') and \ + ".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname')) and \ + state.shutdown == 0: + self.stop.wait(5) + + logger.info('Listening for incoming connections.') + + # First try listening on an IPv6 socket. This should also be + # able to accept connections on IPv4. If that's not available + # we'll fall back to IPv4-only. + try: + sock = self._createListenSocket(socket.AF_INET6) + except socket.error as e: + if (isinstance(e.args, tuple) and + e.args[0] in (errno.EAFNOSUPPORT, + errno.EPFNOSUPPORT, + errno.EADDRNOTAVAIL, + errno.ENOPROTOOPT, + errno.EINVAL)): + sock = self._createListenSocket(socket.AF_INET) + else: + raise + + # regexp to match an IPv4-mapped IPv6 address + mappedAddressRegexp = re.compile(r'^::ffff:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$') + + while state.shutdown == 0: + # We typically don't want to accept incoming connections if the user is using a + # SOCKS proxy, unless they have configured otherwise. If they eventually select + # proxy 'none' or configure SOCKS listening then this will start listening for + # connections. + while BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and not BMConfigParser().getboolean('bitmessagesettings', 'sockslisten') and ".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname') and state.shutdown == 0: + self.stop.wait(10) + while len(shared.connectedHostsList) > \ + BMConfigParser().safeGetInt("bitmessagesettings", "maxtotalconnections", 200) + \ + BMConfigParser().safeGetInt("bitmessagesettings", "maxbootstrapconnections", 20) \ + and state.shutdown == 0: + logger.info('We are connected to too many people. Not accepting further incoming connections for ten seconds.') + + self.stop.wait(10) + + while state.shutdown == 0: + try: + socketObject, sockaddr = sock.accept() + except socket.error as e: + if isinstance(e.args, tuple) and \ + e.args[0] in (errno.EINTR,): + continue + time.wait(1) + continue + + (HOST, PORT) = sockaddr[0:2] + + # If the address is an IPv4-mapped IPv6 address then + # convert it to just the IPv4 representation + md = mappedAddressRegexp.match(HOST) + if md != None: + HOST = md.group(1) + + # The following code will, unfortunately, block an + # incoming connection if someone else on the same LAN + # is already connected because the two computers will + # share the same external IP. This is here to prevent + # connection flooding. + # permit repeated connections from Tor + if HOST in shared.connectedHostsList and \ + (".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname') or not protocol.checkSocksIP(HOST)): + socketObject.close() + logger.info('We are already connected to ' + str(HOST) + '. Ignoring connection.') + else: + break + + 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) + sd.start() + + rd = receiveDataThread() + rd.daemon = True # close the main program even if there are threads left + rd.setup( + 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 60cfd9d7..322bb20e 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -1,148 +1,106 @@ -""" -Thread for performing PoW -""" -# pylint: disable=protected-access,too-many-branches,too-many-statements -# pylint: disable=no-self-use,too-many-lines,too-many-locals - from __future__ import division -import hashlib +import threading +import shared import time -from binascii import hexlify, unhexlify -from struct import pack -from subprocess import call # nosec - -import defaults -import helper_inbox -import helper_msgcoding -import helper_random -import helper_sql +from time import strftime, localtime, gmtime +import random +from subprocess import call # used when the API must execute an outside program +from addresses import * import highlevelcrypto -import l10n import proofofwork +import sys +import tr +from bmconfigparser import BMConfigParser +from debug import logger +import defaults +from helper_sql import * +import helper_inbox +from helper_generic import addDataPadding +import helper_msgcoding +from helper_threading import * +from inventory import Inventory, PendingUpload +import l10n import protocol import queues -import shared import state -import tr -from addresses import ( - calculateInventoryHash, decodeAddress, decodeVarint, encodeVarint -) -from bmconfigparser import config -from helper_sql import sqlExecute, sqlQuery -from inventory import Inventory -from network import knownnodes, StoppableThread -from six.moves import configparser, queue +from binascii import hexlify, unhexlify +# This thread, of which there is only one, does the heavy lifting: +# calculating POWs. def sizeof_fmt(num, suffix='h/s'): - """Format hashes per seconds nicely (SI prefix)""" - - for unit in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']: + for unit in ['','k','M','G','T','P','E','Z']: if abs(num) < 1000.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Yi', suffix) - -class singleWorker(StoppableThread): - """Thread for performing PoW""" +class singleWorker(threading.Thread, StoppableThread): def __init__(self): - super(singleWorker, self).__init__(name="singleWorker") - self.digestAlg = config.safeGet( - 'bitmessagesettings', 'digestalg', 'sha256') + # QThread.__init__(self, parent) + threading.Thread.__init__(self, name="singleWorker") + self.initStop() proofofwork.init() def stopThread(self): - """Signal through the queue that the thread should be stopped""" - try: queues.workerQueue.put(("stopThread", "data")) - except queue.Full: - self.logger.error('workerQueue is Full') + except: + pass super(singleWorker, self).stopThread() def run(self): - # pylint: disable=attribute-defined-outside-init - while not helper_sql.sql_ready.wait(1.0) and state.shutdown == 0: - self.stop.wait(1.0) + while not state.sqlReady and state.shutdown == 0: + self.stop.wait(2) if state.shutdown > 0: return - + # Initialize the neededPubkeys dictionary. queryreturn = sqlQuery( - '''SELECT DISTINCT toaddress FROM sent''' - ''' WHERE (status='awaitingpubkey' AND folder='sent')''') + '''SELECT DISTINCT toaddress FROM sent WHERE (status='awaitingpubkey' AND folder='sent')''') for row in queryreturn: toAddress, = row - # toStatus - _, toAddressVersionNumber, toStreamNumber, toRipe = \ - decodeAddress(toAddress) - if toAddressVersionNumber <= 3: + toStatus, toAddressVersionNumber, toStreamNumber, toRipe = decodeAddress(toAddress) + if toAddressVersionNumber <= 3 : state.neededPubkeys[toAddress] = 0 elif toAddressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(toAddressVersionNumber) - + encodeVarint(toStreamNumber) + toRipe - ).digest()).digest() - # Note that this is the first half of the sha512 hash. - privEncryptionKey = doubleHashOfAddressData[:32] + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe).digest()).digest() + privEncryptionKey = doubleHashOfAddressData[:32] # Note that this is the first half of the sha512 hash. tag = doubleHashOfAddressData[32:] - # We'll need this for when we receive a pubkey reply: - # it will be encrypted and we'll need to decrypt it. - state.neededPubkeys[tag] = ( - toAddress, - highlevelcrypto.makeCryptor( - hexlify(privEncryptionKey)) - ) + state.neededPubkeys[tag] = (toAddress, highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))) # We'll need this for when we receive a pubkey reply: it will be encrypted and we'll need to decrypt it. - # Initialize the state.ackdataForWhichImWatching data structure + # Initialize the shared.ackdataForWhichImWatching data structure queryreturn = sqlQuery( - '''SELECT ackdata FROM sent WHERE status = 'msgsent' AND folder = 'sent' ''') + '''SELECT ackdata FROM sent WHERE status = 'msgsent' ''') for row in queryreturn: ackdata, = row - self.logger.info('Watching for ackdata %s', hexlify(ackdata)) - state.ackdataForWhichImWatching[ackdata] = 0 + logger.info('Watching for ackdata ' + hexlify(ackdata)) + shared.ackdataForWhichImWatching[ackdata] = 0 # Fix legacy (headerless) watched ackdata to include header - for oldack in state.ackdataForWhichImWatching: - if len(oldack) == 32: + 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 - state.ackdataForWhichImWatching[newack] = 0 - sqlExecute( - '''UPDATE sent SET ackdata=? WHERE ackdata=? AND folder = 'sent' ''', - newack, oldack - ) - del state.ackdataForWhichImWatching[oldack] + shared.ackdataForWhichImWatching[newack] = 0 + sqlExecute('UPDATE sent SET ackdata=? WHERE ackdata=?', + newack, oldack ) + del shared.ackdataForWhichImWatching[oldack] - # For the case if user deleted knownnodes - # but is still having onionpeer objects in inventory - if not knownnodes.knownNodesActual: - for item in Inventory().by_type_and_tag(protocol.OBJECT_ONIONPEER): - queues.objectProcessorQueue.put(( - protocol.OBJECT_ONIONPEER, item.payload - )) - # FIXME: should also delete from inventory + self.stop.wait( + 10) # give some time for the GUI to start before we start on existing POW tasks. - # give some time for the GUI to start - # before we start on existing POW tasks. - self.stop.wait(10) - - if state.shutdown: - return - - # just in case there are any pending tasks for msg - # messages that have yet to be sent. - queues.workerQueue.put(('sendmessage', '')) - # just in case there are any tasks for Broadcasts - # that have yet to be sent. - queues.workerQueue.put(('sendbroadcast', '')) - - # send onionpeer object - queues.workerQueue.put(('sendOnionPeerObj', '')) + if state.shutdown == 0: + # just in case there are any pending tasks for msg + # messages that have yet to be sent. + queues.workerQueue.put(('sendmessage', '')) + # just in case there are any tasks for Broadcasts + # that have yet to be sent. + queues.workerQueue.put(('sendbroadcast', '')) while state.shutdown == 0: self.busy = 0 @@ -151,474 +109,347 @@ class singleWorker(StoppableThread): if command == 'sendmessage': try: self.sendMsg() - except: # noqa:E722 - self.logger.warning("sendMsg didn't work") + except: + pass elif command == 'sendbroadcast': try: self.sendBroadcast() - except: # noqa:E722 - self.logger.warning("sendBroadcast didn't work") + except: + pass elif command == 'doPOWForMyV2Pubkey': try: self.doPOWForMyV2Pubkey(data) - except: # noqa:E722 - self.logger.warning("doPOWForMyV2Pubkey didn't work") + except: + pass elif command == 'sendOutOrStoreMyV3Pubkey': try: self.sendOutOrStoreMyV3Pubkey(data) - except: # noqa:E722 - self.logger.warning("sendOutOrStoreMyV3Pubkey didn't work") + except: + pass elif command == 'sendOutOrStoreMyV4Pubkey': try: self.sendOutOrStoreMyV4Pubkey(data) - except: # noqa:E722 - self.logger.warning("sendOutOrStoreMyV4Pubkey didn't work") - elif command == 'sendOnionPeerObj': - try: - self.sendOnionPeerObj(data) - except: # noqa:E722 - self.logger.warning("sendOnionPeerObj didn't work") + except: + pass elif command == 'resetPoW': try: proofofwork.resetPoW() - except: # noqa:E722 - self.logger.warning("proofofwork.resetPoW didn't work") + except: + pass elif command == 'stopThread': self.busy = 0 return else: - self.logger.error( - 'Probable programming error: The command sent' - ' to the workerThread is weird. It is: %s\n', - command - ) + logger.error('Probable programming error: The command sent to the workerThread is weird. It is: %s\n' % command) queues.workerQueue.task_done() - self.logger.info("Quitting...") + logger.info("Quitting...") - def _getKeysForAddress(self, address): - privSigningKeyBase58 = config.get( - address, 'privsigningkey') - privEncryptionKeyBase58 = config.get( - address, 'privencryptionkey') + 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.addresses() + for addressInKeysFile in configSections: + if addressInKeysFile <> 'bitmessagesettings': + status,addressVersionNumber,streamNumber,hashFromThisParticularAddress = decodeAddress(addressInKeysFile) + if hash == hashFromThisParticularAddress: + myAddress = addressInKeysFile + break""" + myAddress = shared.myAddressesByHash[hash] + status, addressVersionNumber, streamNumber, hash = decodeAddress( + myAddress) + + TTL = int(28 * 24 * 60 * 60 + random.randrange(-300, 300))# 28 days from now plus or minus five minutes + embeddedTime = int(time.time() + TTL) + payload = pack('>Q', (embeddedTime)) + payload += '\x00\x00\x00\x01' # object type: pubkey + payload += encodeVarint(addressVersionNumber) # Address version number + payload += encodeVarint(streamNumber) + payload += protocol.getBitfield(myAddress) # bitfield of features supported by me (see the wiki). + + try: + privSigningKeyBase58 = BMConfigParser().get( + myAddress, 'privsigningkey') + privEncryptionKeyBase58 = BMConfigParser().get( + myAddress, 'privencryptionkey') + except Exception as err: + logger.error('Error within doPOWForMyV2Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) + return privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( privSigningKeyBase58)) privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( privEncryptionKeyBase58)) - - # The \x04 on the beginning of the public keys are not sent. - # This way there is only one acceptable way to encode - # and send a public key. pubSigningKey = unhexlify(highlevelcrypto.privToPub( - privSigningKeyHex))[1:] + privSigningKeyHex)) pubEncryptionKey = unhexlify(highlevelcrypto.privToPub( - privEncryptionKeyHex))[1:] + privEncryptionKeyHex)) - return privSigningKeyHex, privEncryptionKeyHex, \ - pubSigningKey, pubEncryptionKey - - def _doPOWDefaults(self, payload, TTL, - log_prefix='', - log_time=False): - target = 2 ** 64 / ( - defaults.networkDefaultProofOfWorkNonceTrialsPerByte * ( - len(payload) + 8 - + defaults.networkDefaultPayloadLengthExtraBytes + (( - TTL * ( - len(payload) + 8 - + defaults.networkDefaultPayloadLengthExtraBytes - )) / (2 ** 16)) - )) - initialHash = hashlib.sha512(payload).digest() - self.logger.info( - '%s Doing proof of work... TTL set to %s', log_prefix, TTL) - if log_time: - start_time = time.time() - trialValue, nonce = proofofwork.run(target, initialHash) - self.logger.info( - '%s Found proof of work %s Nonce: %s', - log_prefix, trialValue, nonce - ) - try: - delta = time.time() - start_time - self.logger.info( - 'PoW took %.1f seconds, speed %s.', - delta, sizeof_fmt(nonce / delta) - ) - except: # noqa:E722 # NameError - self.logger.warning("Proof of Work exception") - payload = pack('>Q', nonce) + payload - return payload - - def doPOWForMyV2Pubkey(self, adressHash): - """ 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 - myAddress = shared.myAddressesByHash[adressHash] - # status - _, addressVersionNumber, streamNumber, adressHash = ( - decodeAddress(myAddress)) - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - payload = pack('>Q', (embeddedTime)) - payload += '\x00\x00\x00\x01' # object type: pubkey - payload += encodeVarint(addressVersionNumber) # Address version number - payload += encodeVarint(streamNumber) - # bitfield of features supported by me (see the wiki). - payload += protocol.getBitfield(myAddress) - - try: - # privSigningKeyHex, privEncryptionKeyHex - _, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - except Exception as err: - self.logger.error( - 'Error within doPOWForMyV2Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - return - - payload += pubSigningKey + pubEncryptionKey + payload += pubSigningKey[1:] + payload += pubEncryptionKey[1:] # Do the POW for this pubkey message - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For pubkey message)') + target = 2 ** 64 / (defaults.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + defaults.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+defaults.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) + logger.info('(For pubkey message) Doing proof of work...') + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + logger.info('(For pubkey message) Found proof of work ' + str(trialValue), ' Nonce: ', str(nonce)) + payload = pack('>Q', nonce) + payload inventoryHash = calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') + objectType, streamNumber, payload, embeddedTime,'') + PendingUpload().add(inventoryHash) - self.logger.info( - 'broadcasting inv with hash: %s', hexlify(inventoryHash)) + logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash)) queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(('updateStatusBar', '')) try: - config.set( + BMConfigParser().set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) - config.save() - except configparser.NoSectionError: - # The user deleted the address out of the keys.dat file - # before this finished. + BMConfigParser().save() + except: + # The user deleted the address out of the keys.dat file before this + # finished. pass - except: # noqa:E722 - self.logger.warning("config.set didn't work") - def sendOutOrStoreMyV3Pubkey(self, adressHash): - """ - If this isn't a chan address, this function assembles the pubkey data, does the necessary POW and sends it out. - If it *is* a chan then it assembles the pubkey and stores is in the pubkey table so that we can send messages - to "ourselves". - """ + # If this isn't a chan address, this function assembles the pubkey data, + # does the necessary POW and sends it out. If it *is* a chan then it + # assembles the pubkey and stores is in the pubkey table so that we can + # send messages to "ourselves". + def sendOutOrStoreMyV3Pubkey(self, hash): try: - myAddress = shared.myAddressesByHash[adressHash] - except KeyError: - # The address has been deleted. - self.logger.warning("Can't find %s in myAddressByHash", hexlify(adressHash)) + myAddress = shared.myAddressesByHash[hash] + except: + #The address has been deleted. return - if config.safeGetBoolean(myAddress, 'chan'): - self.logger.info('This is a chan address. Not sending pubkey.') + if BMConfigParser().safeGetBoolean(myAddress, 'chan'): + logger.info('This is a chan address. Not sending pubkey.') return - _, addressVersionNumber, streamNumber, adressHash = decodeAddress( + status, addressVersionNumber, streamNumber, hash = decodeAddress( myAddress) - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) + + TTL = int(28 * 24 * 60 * 60 + random.randrange(-300, 300))# 28 days from now plus or minus five minutes embeddedTime = int(time.time() + TTL) - - # signedTimeForProtocolV2 = embeddedTime - TTL - # According to the protocol specification, the expiresTime - # along with the pubkey information is signed. But to be - # backwards compatible during the upgrade period, we shall sign - # not the expiresTime but rather the current time. There must be - # precisely a 28 day difference between the two. After the upgrade - # period we'll switch to signing the whole payload with the - # expiresTime time. - + signedTimeForProtocolV2 = embeddedTime - TTL + """ + According to the protocol specification, the expiresTime along with the pubkey information is + signed. But to be backwards compatible during the upgrade period, we shall sign not the + expiresTime but rather the current time. There must be precisely a 28 day difference + between the two. After the upgrade period we'll switch to signing the whole payload with the + expiresTime time. + """ payload = pack('>Q', (embeddedTime)) - payload += '\x00\x00\x00\x01' # object type: pubkey + payload += '\x00\x00\x00\x01' # object type: pubkey payload += encodeVarint(addressVersionNumber) # Address version number payload += encodeVarint(streamNumber) - # bitfield of features supported by me (see the wiki). - payload += protocol.getBitfield(myAddress) + payload += protocol.getBitfield(myAddress) # bitfield of features supported by me (see the wiki). try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) + privSigningKeyBase58 = BMConfigParser().get( + myAddress, 'privsigningkey') + privEncryptionKeyBase58 = BMConfigParser().get( + myAddress, 'privencryptionkey') except Exception as err: - self.logger.error( - 'Error within sendOutOrStoreMyV3Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) + logger.error('Error within sendOutOrStoreMyV3Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) + return - payload += pubSigningKey + pubEncryptionKey + privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( + privSigningKeyBase58)) + privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( + privEncryptionKeyBase58)) + pubSigningKey = unhexlify(highlevelcrypto.privToPub( + privSigningKeyHex)) + pubEncryptionKey = unhexlify(highlevelcrypto.privToPub( + privEncryptionKeyHex)) - payload += encodeVarint(config.getint( + payload += pubSigningKey[1:] + payload += pubEncryptionKey[1:] + + payload += encodeVarint(BMConfigParser().getint( myAddress, 'noncetrialsperbyte')) - payload += encodeVarint(config.getint( + payload += encodeVarint(BMConfigParser().getint( myAddress, 'payloadlengthextrabytes')) - - signature = highlevelcrypto.sign( - payload, privSigningKeyHex, self.digestAlg) + + signature = highlevelcrypto.sign(payload, privSigningKeyHex) payload += encodeVarint(len(signature)) payload += signature # Do the POW for this pubkey message - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For pubkey message)') + target = 2 ** 64 / (defaults.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + defaults.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+defaults.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) + logger.info('(For pubkey message) Doing proof of work...') + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + logger.info('(For pubkey message) Found proof of work. Nonce: ' + str(nonce)) + payload = pack('>Q', nonce) + payload inventoryHash = calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, '') + objectType, streamNumber, payload, embeddedTime,'') + PendingUpload().add(inventoryHash) - self.logger.info( - 'broadcasting inv with hash: %s', hexlify(inventoryHash)) + logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash)) queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(('updateStatusBar', '')) try: - config.set( + BMConfigParser().set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) - config.save() - except configparser.NoSectionError: - # The user deleted the address out of the keys.dat file - # before this finished. + BMConfigParser().save() + except: + # The user deleted the address out of the keys.dat file before this + # finished. pass - except: # noqa:E722 - self.logger.warning("BMConfigParser().set didn't work") + # If this isn't a chan address, this function assembles the pubkey data, + # does the necessary POW and sends it out. def sendOutOrStoreMyV4Pubkey(self, myAddress): - """ - It doesn't send directly anymore. It put is to a queue for another thread to send at an appropriate time, - whereas in the past it directly appended it to the outgoing buffer, I think. Same with all the other methods in - this class. - """ - if not config.has_section(myAddress): - # The address has been deleted. + if not BMConfigParser().has_section(myAddress): + #The address has been deleted. return - if config.safeGetBoolean(myAddress, 'chan'): - self.logger.info('This is a chan address. Not sending pubkey.') + if shared.BMConfigParser().safeGetBoolean(myAddress, 'chan'): + logger.info('This is a chan address. Not sending pubkey.') return - _, addressVersionNumber, streamNumber, addressHash = decodeAddress( + status, addressVersionNumber, streamNumber, hash = decodeAddress( myAddress) - - # 28 days from now plus or minus five minutes - TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) + + TTL = int(28 * 24 * 60 * 60 + random.randrange(-300, 300))# 28 days from now plus or minus five minutes embeddedTime = int(time.time() + TTL) payload = pack('>Q', (embeddedTime)) - payload += '\x00\x00\x00\x01' # object type: pubkey + payload += '\x00\x00\x00\x01' # object type: pubkey payload += encodeVarint(addressVersionNumber) # Address version number payload += encodeVarint(streamNumber) dataToEncrypt = protocol.getBitfield(myAddress) try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(myAddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) + privSigningKeyBase58 = BMConfigParser().get( + myAddress, 'privsigningkey') + privEncryptionKeyBase58 = BMConfigParser().get( + myAddress, 'privencryptionkey') except Exception as err: - self.logger.error( - 'Error within sendOutOrStoreMyV4Pubkey. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) + logger.error('Error within sendOutOrStoreMyV4Pubkey. Could not read the keys from the keys.dat file for a requested address. %s\n' % err) return - dataToEncrypt += pubSigningKey + pubEncryptionKey + privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( + privSigningKeyBase58)) + privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( + privEncryptionKeyBase58)) + pubSigningKey = unhexlify(highlevelcrypto.privToPub( + privSigningKeyHex)) + pubEncryptionKey = unhexlify(highlevelcrypto.privToPub( + privEncryptionKeyHex)) + dataToEncrypt += pubSigningKey[1:] + dataToEncrypt += pubEncryptionKey[1:] - dataToEncrypt += encodeVarint(config.getint( + dataToEncrypt += encodeVarint(BMConfigParser().getint( myAddress, 'noncetrialsperbyte')) - dataToEncrypt += encodeVarint(config.getint( + dataToEncrypt += encodeVarint(BMConfigParser().getint( myAddress, 'payloadlengthextrabytes')) - + # When we encrypt, we'll use a hash of the data - # contained in an address as a decryption key. This way - # in order to read the public keys in a pubkey message, - # a node must know the address first. We'll also tag, - # unencrypted, the pubkey with part of the hash so that nodes - # know which pubkey object to try to decrypt - # when they want to send a message. - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + addressHash - ).digest()).digest() - payload += doubleHashOfAddressData[32:] # the tag - signature = highlevelcrypto.sign( - payload + dataToEncrypt, privSigningKeyHex, self.digestAlg) + # contained in an address as a decryption key. This way in order to + # read the public keys in a pubkey message, a node must know the address + # first. We'll also tag, unencrypted, the pubkey with part of the hash + # so that nodes know which pubkey object to try to decrypt when they + # want to send a message. + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest() + payload += doubleHashOfAddressData[32:] # the tag + signature = highlevelcrypto.sign(payload + dataToEncrypt, privSigningKeyHex) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature - + privEncryptionKey = doubleHashOfAddressData[:32] pubEncryptionKey = highlevelcrypto.pointMult(privEncryptionKey) payload += highlevelcrypto.encrypt( dataToEncrypt, hexlify(pubEncryptionKey)) # Do the POW for this pubkey message - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For pubkey message)') + target = 2 ** 64 / (defaults.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + defaults.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+defaults.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) + logger.info('(For pubkey message) Doing proof of work...') + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + logger.info('(For pubkey message) Found proof of work ' + str(trialValue) + 'Nonce: ' + str(nonce)) + payload = pack('>Q', nonce) + payload inventoryHash = calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( - objectType, streamNumber, payload, embeddedTime, - doubleHashOfAddressData[32:] - ) + objectType, streamNumber, payload, embeddedTime, doubleHashOfAddressData[32:]) + PendingUpload().add(inventoryHash) - self.logger.info( - 'broadcasting inv with hash: %s', hexlify(inventoryHash)) + logger.info('broadcasting inv with hash: ' + hexlify(inventoryHash)) queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(('updateStatusBar', '')) try: - config.set( + BMConfigParser().set( myAddress, 'lastpubkeysendtime', str(int(time.time()))) - config.save() + BMConfigParser().save() except Exception as err: - self.logger.error( - 'Error: Couldn\'t add the lastpubkeysendtime' - ' to the keys.dat file. Error message: %s', err - ) - - def sendOnionPeerObj(self, peer=None): - """Send onionpeer object representing peer""" - if not peer: # find own onionhostname - for peer in state.ownAddresses: - if peer.host.endswith('.onion'): - break - else: - return - TTL = int(7 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) - embeddedTime = int(time.time() + TTL) - streamNumber = 1 # Don't know yet what should be here - objectType = protocol.OBJECT_ONIONPEER - # FIXME: ideally the objectPayload should be signed - objectPayload = encodeVarint(peer.port) + protocol.encodeHost(peer.host) - tag = calculateInventoryHash(objectPayload) - - if Inventory().by_type_and_tag(objectType, tag): - return # not expired - - payload = pack('>Q', embeddedTime) - payload += pack('>I', objectType) - payload += encodeVarint(2 if len(peer.host) == 22 else 3) - payload += encodeVarint(streamNumber) - payload += objectPayload - - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For onionpeer object)') - - inventoryHash = calculateInventoryHash(payload) - Inventory()[inventoryHash] = ( - objectType, streamNumber, buffer(payload), # noqa: F821 - embeddedTime, buffer(tag) # noqa: F821 - ) - self.logger.info( - 'sending inv (within sendOnionPeerObj function) for object: %s', - hexlify(inventoryHash)) - queues.invQueue.put((streamNumber, inventoryHash)) + logger.error('Error: Couldn\'t add the lastpubkeysendtime to the keys.dat file. Error message: %s' % err) def sendBroadcast(self): - """Send a broadcast-type object (assemble the object, perform PoW and put it to the inv announcement queue)""" # Reset just in case sqlExecute( - '''UPDATE sent SET status='broadcastqueued' ''' - - '''WHERE status = 'doingbroadcastpow' AND folder = 'sent' ''') + '''UPDATE sent SET status='broadcastqueued' WHERE status = 'doingbroadcastpow' ''') queryreturn = sqlQuery( - '''SELECT fromaddress, subject, message, ''' - ''' ackdata, ttl, encodingtype FROM sent ''' - ''' WHERE status=? and folder='sent' ''', 'broadcastqueued') + '''SELECT fromaddress, subject, message, ackdata, ttl, encodingtype FROM sent WHERE status=? and folder='sent' ''', 'broadcastqueued') for row in queryreturn: fromaddress, subject, body, ackdata, TTL, encoding = row - # status - _, addressVersionNumber, streamNumber, ripe = \ - decodeAddress(fromaddress) + status, addressVersionNumber, streamNumber, ripe = decodeAddress( + fromaddress) if addressVersionNumber <= 1: - self.logger.error( - 'Error: In the singleWorker thread, the ' - ' sendBroadcast function doesn\'t understand' - ' the address version.\n') + logger.error('Error: In the singleWorker thread, the sendBroadcast function doesn\'t understand the address version.\n') return # We need to convert our private keys to public keys in order # to include them. try: - # , privEncryptionKeyHex - privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ - self._getKeysForAddress(fromaddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error! Could not find sender address" - " (your address) in the keys.dat file.")) - )) - except Exception as err: - self.logger.error( - 'Error within sendBroadcast. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error, can't send.")) - )) + privSigningKeyBase58 = BMConfigParser().get( + fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = BMConfigParser().get( + fromaddress, 'privencryptionkey') + except: + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr._translate("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) continue - if not sqlExecute( - '''UPDATE sent SET status='doingbroadcastpow' ''' - ''' WHERE ackdata=? AND status='broadcastqueued' ''' - ''' AND folder='sent' ''', - ackdata): - continue + sqlExecute( + '''UPDATE sent SET status='doingbroadcastpow' WHERE ackdata=? AND status='broadcastqueued' ''', + ackdata) - # At this time these pubkeys are 65 bytes long - # because they include the encoding byte which we won't - # be sending in the broadcast message. - # pubSigningKey = \ - # highlevelcrypto.privToPub(privSigningKeyHex).decode('hex') + privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( + privSigningKeyBase58)) + privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( + privEncryptionKeyBase58)) + + pubSigningKey = highlevelcrypto.privToPub(privSigningKeyHex).decode( + 'hex') # At this time these pubkeys are 65 bytes long because they include the encoding byte which we won't be sending in the broadcast message. + pubEncryptionKey = unhexlify(highlevelcrypto.privToPub( + privEncryptionKeyHex)) if TTL > 28 * 24 * 60 * 60: TTL = 28 * 24 * 60 * 60 - if TTL < 60 * 60: - TTL = 60 * 60 - # add some randomness to the TTL - TTL = int(TTL + helper_random.randomrandrange(-300, 300)) + if TTL < 60*60: + TTL = 60*60 + 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\x03' # object type: broadcast + payload += '\x00\x00\x00\x03' # object type: broadcast if addressVersionNumber <= 3: payload += encodeVarint(4) # broadcast version else: payload += encodeVarint(5) # broadcast version - + payload += encodeVarint(streamNumber) if addressVersionNumber >= 4: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest() + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()).digest() tag = doubleHashOfAddressData[32:] payload += tag else: @@ -626,39 +457,31 @@ class singleWorker(StoppableThread): dataToEncrypt = encodeVarint(addressVersionNumber) dataToEncrypt += encodeVarint(streamNumber) - # behavior bitfield - dataToEncrypt += protocol.getBitfield(fromaddress) - dataToEncrypt += pubSigningKey + pubEncryptionKey + dataToEncrypt += protocol.getBitfield(fromaddress) # behavior bitfield + dataToEncrypt += pubSigningKey[1:] + dataToEncrypt += pubEncryptionKey[1:] if addressVersionNumber >= 3: - dataToEncrypt += encodeVarint(config.getint( - fromaddress, 'noncetrialsperbyte')) - dataToEncrypt += encodeVarint(config.getint( - fromaddress, 'payloadlengthextrabytes')) - # message encoding type - dataToEncrypt += encodeVarint(encoding) - encodedMessage = helper_msgcoding.MsgEncode( - {"subject": subject, "body": body}, encoding) + dataToEncrypt += encodeVarint(BMConfigParser().getint(fromaddress,'noncetrialsperbyte')) + dataToEncrypt += encodeVarint(BMConfigParser().getint(fromaddress,'payloadlengthextrabytes')) + dataToEncrypt += encodeVarint(encoding) # message encoding type + encodedMessage = helper_msgcoding.MsgEncode({"subject": subject, "body": body}, encoding) dataToEncrypt += encodeVarint(encodedMessage.length) dataToEncrypt += encodedMessage.data dataToSign = payload + dataToEncrypt - + signature = highlevelcrypto.sign( - dataToSign, privSigningKeyHex, self.digestAlg) + dataToSign, privSigningKeyHex) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature - # Encrypt the broadcast with the information - # contained in the broadcaster's address. - # Anyone who knows the address can generate - # the private encryption key to decrypt the broadcast. - # This provides virtually no privacy; its purpose is to keep - # questionable and illegal content from flowing through the - # Internet connections and being stored on the disk of 3rd parties. + # Encrypt the broadcast with the information contained in the broadcaster's address. + # Anyone who knows the address can generate the private encryption key to decrypt + # the broadcast. This provides virtually no privacy; its purpose is to keep + # questionable and illegal content from flowing through the Internet connections + # and being stored on the disk of 3rd parties. if addressVersionNumber <= 3: - privEncryptionKey = hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()[:32] + privEncryptionKey = hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + ripe).digest()[:32] else: privEncryptionKey = doubleHashOfAddressData[:32] @@ -666,327 +489,190 @@ class singleWorker(StoppableThread): payload += highlevelcrypto.encrypt( dataToEncrypt, hexlify(pubEncryptionKey)) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Doing work necessary to send broadcast...")) - )) - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For broadcast message)') + target = 2 ** 64 / (defaults.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + defaults.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+defaults.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) + logger.info('(For broadcast message) Doing proof of work...') + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr._translate("MainWindow", "Doing work necessary to send broadcast...")))) + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + logger.info('(For broadcast message) Found proof of work ' + str(trialValue) + ' Nonce: ' + str(nonce)) - # Sanity check. The payload size should never be larger - # than 256 KiB. There should be checks elsewhere in the code - # to not let the user try to send a message this large - # until we implement message continuation. - if len(payload) > 2 ** 18: # 256 KiB - self.logger.critical( - 'This broadcast object is too large to send.' - ' This should never happen. Object size: %s', - len(payload) - ) + payload = pack('>Q', nonce) + payload + + # Sanity check. The payload size should never be larger than 256 KiB. There should + # be checks elsewhere in the code to not let the user try to send a message this large + # until we implement message continuation. + if len(payload) > 2 ** 18: # 256 KiB + logger.critical('This broadcast object is too large to send. This should never happen. Object size: %s' % len(payload)) continue inventoryHash = calculateInventoryHash(payload) objectType = 3 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, tag) - self.logger.info( - 'sending inv (within sendBroadcast function)' - ' for object: %s', - hexlify(inventoryHash) - ) + PendingUpload().add(inventoryHash) + logger.info('sending inv (within sendBroadcast function) for object: ' + hexlify(inventoryHash)) queues.invQueue.put((streamNumber, inventoryHash)) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Broadcast sent on %1" - ).arg(l10n.formatTimestamp())) - )) + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr._translate("MainWindow", "Broadcast sent on %1").arg(l10n.formatTimestamp())))) # Update the status of the message in the 'sent' table to have # a 'broadcastsent' status sqlExecute( - '''UPDATE sent SET msgid=?, status=?, lastactiontime=? ''' - ''' WHERE ackdata=? AND folder='sent' ''', - inventoryHash, 'broadcastsent', int(time.time()), ackdata - ) + 'UPDATE sent SET msgid=?, status=?, lastactiontime=? WHERE ackdata=?', + inventoryHash, + 'broadcastsent', + int(time.time()), + ackdata) + def sendMsg(self): - """Send a message-type object (assemble the object, perform PoW and put it to the inv announcement queue)""" - # pylint: disable=too-many-nested-blocks # Reset just in case sqlExecute( - '''UPDATE sent SET status='msgqueued' ''' - ''' WHERE status IN ('doingpubkeypow', 'doingmsgpow') ''' - ''' AND folder='sent' ''') + '''UPDATE sent SET status='msgqueued' WHERE status IN ('doingpubkeypow', 'doingmsgpow')''') queryreturn = sqlQuery( - '''SELECT toaddress, fromaddress, subject, message, ''' - ''' ackdata, status, ttl, retrynumber, encodingtype FROM ''' - ''' sent WHERE (status='msgqueued' or status='forcepow') ''' - ''' and folder='sent' ''') - # while we have a msg that needs some work - for row in queryreturn: - toaddress, fromaddress, subject, message, \ - ackdata, status, TTL, retryNumber, encoding = row - # toStatus - _, toAddressVersionNumber, toStreamNumber, toRipe = \ - decodeAddress(toaddress) - # fromStatus, , ,fromRipe - _, fromAddressVersionNumber, fromStreamNumber, _ = \ - decodeAddress(fromaddress) - - # We may or may not already have the pubkey - # for this toAddress. Let's check. + '''SELECT toaddress, fromaddress, subject, message, ackdata, status, ttl, retrynumber, encodingtype FROM sent WHERE (status='msgqueued' or status='forcepow') and folder='sent' ''') + for row in queryreturn: # while we have a msg that needs some work + toaddress, fromaddress, subject, message, ackdata, status, TTL, retryNumber, encoding = row + toStatus, toAddressVersionNumber, toStreamNumber, toRipe = decodeAddress( + toaddress) + fromStatus, fromAddressVersionNumber, fromStreamNumber, fromRipe = decodeAddress( + fromaddress) + + # We may or may not already have the pubkey for this toAddress. Let's check. if status == 'forcepow': - # if the status of this msg is 'forcepow' - # then clearly we have the pubkey already - # because the user could not have overridden the message - # about the POW being too difficult without knowing - # the required difficulty. + # if the status of this msg is 'forcepow' then clearly we have the pubkey already + # because the user could not have overridden the message about the POW being + # too difficult without knowing the required difficulty. pass elif status == 'doingmsgpow': - # We wouldn't have set the status to doingmsgpow - # if we didn't already have the pubkey so let's assume - # that we have it. + # We wouldn't have set the status to doingmsgpow if we didn't already have the pubkey + # so let's assume that we have it. pass - # If we are sending a message to ourselves or a chan - # then we won't need an entry in the pubkeys table; - # we can calculate the needed pubkey using the private keys - # in our keys.dat file. - elif config.has_section(toaddress): - if not sqlExecute( - '''UPDATE sent SET status='doingmsgpow' ''' - ''' WHERE toaddress=? AND status='msgqueued' AND folder='sent' ''', - toaddress - ): - continue - status = 'doingmsgpow' + # If we are sending a message to ourselves or a chan then we won't need an entry in the pubkeys table; we can calculate the needed pubkey using the private keys in our keys.dat file. + elif BMConfigParser().has_section(toaddress): + sqlExecute( + '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', + toaddress) + status='doingmsgpow' elif status == 'msgqueued': # Let's see if we already have the pubkey in our pubkeys table queryreturn = sqlQuery( - '''SELECT address FROM pubkeys WHERE address=?''', - toaddress - ) - # If we have the needed pubkey in the pubkey table already, - if queryreturn != []: + '''SELECT address FROM pubkeys WHERE address=?''', toaddress) + if queryreturn != []: # If we have the needed pubkey in the pubkey table already, # set the status of this msg to doingmsgpow - if not sqlExecute( - '''UPDATE sent SET status='doingmsgpow' ''' - ''' WHERE toaddress=? AND status='msgqueued' AND folder='sent' ''', - toaddress - ): - continue + sqlExecute( + '''UPDATE sent SET status='doingmsgpow' WHERE toaddress=? AND status='msgqueued' ''', + toaddress) status = 'doingmsgpow' - # mark the pubkey as 'usedpersonally' so that - # we don't delete it later. If the pubkey version - # is >= 4 then usedpersonally will already be set - # to yes because we'll only ever have + # mark the pubkey as 'usedpersonally' so that we don't delete it later. If the pubkey version + # is >= 4 then usedpersonally will already be set to yes because we'll only ever have # usedpersonally v4 pubkeys in the pubkeys table. sqlExecute( - '''UPDATE pubkeys SET usedpersonally='yes' ''' - ''' WHERE address=?''', - toaddress - ) - # We don't have the needed pubkey in the pubkeys table already. - else: + '''UPDATE pubkeys SET usedpersonally='yes' WHERE address=?''', + toaddress) + else: # We don't have the needed pubkey in the pubkeys table already. if toAddressVersionNumber <= 3: toTag = '' else: - toTag = hashlib.sha512(hashlib.sha512( - encodeVarint(toAddressVersionNumber) - + encodeVarint(toStreamNumber) + toRipe - ).digest()).digest()[32:] - if toaddress in state.neededPubkeys or \ - toTag in state.neededPubkeys: + toTag = hashlib.sha512(hashlib.sha512(encodeVarint(toAddressVersionNumber)+encodeVarint(toStreamNumber)+toRipe).digest()).digest()[32:] + if toaddress in state.neededPubkeys or toTag in state.neededPubkeys: # We already sent a request for the pubkey sqlExecute( - '''UPDATE sent SET status='awaitingpubkey', ''' - ''' sleeptill=? WHERE toaddress=? ''' - ''' AND status='msgqueued' ''', - int(time.time()) + 2.5 * 24 * 60 * 60, - toaddress - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByToAddress', ( - toaddress, - tr._translate( - "MainWindow", - "Encryption key was requested earlier.")) - )) - # on with the next msg on which we can do some work - continue + '''UPDATE sent SET status='awaitingpubkey', sleeptill=? WHERE toaddress=? AND status='msgqueued' ''', + int(time.time()) + 2.5*24*60*60, + toaddress) + queues.UISignalQueue.put(('updateSentItemStatusByToAddress', ( + toaddress, tr._translate("MainWindow",'Encryption key was requested earlier.')))) + continue #on with the next msg on which we can do some work else: # We have not yet sent a request for the pubkey needToRequestPubkey = True - # If we are trying to send to address - # version >= 4 then the needed pubkey might be - # encrypted in the inventory. - # If we have it we'll need to decrypt it - # and put it in the pubkeys table. - - # The decryptAndCheckPubkeyPayload function - # expects that the shared.neededPubkeys dictionary - # already contains the toAddress and cryptor - # object associated with the tag for this toAddress. - if toAddressVersionNumber >= 4: - doubleHashOfToAddressData = hashlib.sha512( - hashlib.sha512( - encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - ).digest() - ).digest() - # The first half of the sha512 hash. - privEncryptionKey = doubleHashOfToAddressData[:32] - # The second half of the sha512 hash. - tag = doubleHashOfToAddressData[32:] - state.neededPubkeys[tag] = ( - toaddress, - highlevelcrypto.makeCryptor( - hexlify(privEncryptionKey)) - ) + if toAddressVersionNumber >= 4: # If we are trying to send to address version >= 4 then + # the needed pubkey might be encrypted in the inventory. + # If we have it we'll need to decrypt it and put it in + # the pubkeys table. + + # The decryptAndCheckPubkeyPayload function expects that the shared.neededPubkeys + # dictionary already contains the toAddress and cryptor object associated with + # the tag for this toAddress. + doubleHashOfToAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe).digest()).digest() + privEncryptionKey = doubleHashOfToAddressData[:32] # The first half of the sha512 hash. + tag = doubleHashOfToAddressData[32:] # The second half of the sha512 hash. + state.neededPubkeys[tag] = (toaddress, highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))) for value in Inventory().by_type_and_tag(1, toTag): - # if valid, this function also puts it - # in the pubkeys table. - if protocol.decryptAndCheckPubkeyPayload( - value.payload, toaddress - ) == 'successful': + if shared.decryptAndCheckPubkeyPayload(value.payload, toaddress) == 'successful': #if valid, this function also puts it in the pubkeys table. needToRequestPubkey = False sqlExecute( - '''UPDATE sent SET ''' - ''' status='doingmsgpow', ''' - ''' retrynumber=0 WHERE ''' - ''' toaddress=? AND ''' - ''' (status='msgqueued' or ''' - ''' status='awaitingpubkey' or ''' - ''' status='doingpubkeypow') AND ''' - ''' folder='sent' ''', + '''UPDATE sent SET status='doingmsgpow', retrynumber=0 WHERE toaddress=? AND (status='msgqueued' or status='awaitingpubkey' or status='doingpubkeypow')''', toaddress) del state.neededPubkeys[tag] break - # else: - # There was something wrong with this - # pubkey object even though it had - # the correct tag- almost certainly - # because of malicious behavior or - # a badly programmed client. If there are - # any other pubkeys in our inventory - # with the correct tag then we'll try - # to decrypt those. + #else: # There was something wrong with this pubkey object even + # though it had the correct tag- almost certainly because + # of malicious behavior or a badly programmed client. If + # there are any other pubkeys in our inventory with the correct + # tag then we'll try to decrypt those. if needToRequestPubkey: sqlExecute( - '''UPDATE sent SET ''' - ''' status='doingpubkeypow' WHERE ''' - ''' toaddress=? AND status='msgqueued' AND folder='sent' ''', - toaddress - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByToAddress', ( - toaddress, - tr._translate( - "MainWindow", - "Sending a request for the" - " recipient\'s encryption key.")) - )) + '''UPDATE sent SET status='doingpubkeypow' WHERE toaddress=? AND status='msgqueued' ''', + toaddress) + queues.UISignalQueue.put(('updateSentItemStatusByToAddress', ( + toaddress, tr._translate("MainWindow",'Sending a request for the recipient\'s encryption key.')))) self.requestPubKey(toaddress) - # on with the next msg on which we can do some work - continue - - # At this point we know that we have the necessary pubkey - # in the pubkeys table. - + continue #on with the next msg on which we can do some work + + # At this point we know that we have the necessary pubkey in the pubkeys table. + TTL *= 2**retryNumber if TTL > 28 * 24 * 60 * 60: TTL = 28 * 24 * 60 * 60 - # add some randomness to the TTL - TTL = int(TTL + helper_random.randomrandrange(-300, 300)) + TTL = int(TTL + random.randrange(-300, 300))# add some randomness to the TTL embeddedTime = int(time.time() + TTL) + + if not BMConfigParser().has_section(toaddress): # if we aren't sending this to ourselves or a chan + shared.ackdataForWhichImWatching[ackdata] = 0 + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr._translate("MainWindow", "Looking up the receiver\'s public key")))) + logger.info('Sending a message.') + logger.debug('First 150 characters of message: ' + repr(message[:150])) - # if we aren't sending this to ourselves or a chan - if not config.has_section(toaddress): - state.ackdataForWhichImWatching[ackdata] = 0 - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Looking up the receiver\'s public key")) - )) - self.logger.info('Sending a message.') - self.logger.debug( - 'First 150 characters of message: %s', - repr(message[:150]) - ) - - # Let us fetch the recipient's public key out of - # our database. If the required proof of work difficulty - # is too hard then we'll abort. + # Let us fetch the recipient's public key out of our database. If + # the required proof of work difficulty is too hard then we'll + # abort. queryreturn = sqlQuery( 'SELECT transmitdata FROM pubkeys WHERE address=?', toaddress) - for row in queryreturn: # pylint: disable=redefined-outer-name + for row in queryreturn: pubkeyPayload, = row - # The pubkey message is stored with the following items - # all appended: + # The pubkey message is stored with the following items all appended: # -address version # -stream number # -behavior bitfield # -pub signing key # -pub encryption key - # -nonce trials per byte (if address version is >= 3) + # -nonce trials per byte (if address version is >= 3) # -length extra bytes (if address version is >= 3) - # to bypass the address version whose length is definitely 1 - readPosition = 1 - _, streamNumberLength = decodeVarint( + readPosition = 1 # to bypass the address version whose length is definitely 1 + streamNumber, streamNumberLength = decodeVarint( pubkeyPayload[readPosition:readPosition + 10]) readPosition += streamNumberLength behaviorBitfield = pubkeyPayload[readPosition:readPosition + 4] - # Mobile users may ask us to include their address's - # RIPE hash on a message unencrypted. Before we actually - # do it the sending human must check a box + # Mobile users may ask us to include their address's RIPE hash on a message + # unencrypted. Before we actually do it the sending human must check a box # in the settings menu to allow it. - - # if receiver is a mobile device who expects that their - # address RIPE is included unencrypted on the front of - # the message.. - if protocol.isBitSetWithinBitfield(behaviorBitfield, 30): - # if we are Not willing to include the receiver's - # RIPE hash on the message.. - if not config.safeGetBoolean( - 'bitmessagesettings', 'willinglysendtomobile' - ): - self.logger.info( - 'The receiver is a mobile user but the' - ' sender (you) has not selected that you' - ' are willing to send to mobiles. Aborting' - ' send.' - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Problem: Destination is a mobile" - " device who requests that the" - " destination be included in the" - " message but this is disallowed in" - " your settings. %1" - ).arg(l10n.formatTimestamp())) - )) - # if the human changes their setting and then - # sends another message or restarts their client, - # this one will send at that time. + if shared.isBitSetWithinBitfield(behaviorBitfield,30): # if receiver is a mobile device who expects that their address RIPE is included unencrypted on the front of the message.. + if not shared.BMConfigParser().safeGetBoolean('bitmessagesettings','willinglysendtomobile'): # if we are Not willing to include the receiver's RIPE hash on the message.. + logger.info('The receiver is a mobile user but the sender (you) has not selected that you are willing to send to mobiles. Aborting send.') + queues.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,tr._translate("MainWindow",'Problem: Destination is a mobile device who requests that the destination be included in the message but this is disallowed in your settings. %1').arg(l10n.formatTimestamp())))) + # if the human changes their setting and then sends another message or restarts their client, this one will send at that time. continue readPosition += 4 # to bypass the bitfield of behaviors - # We don't use this key for anything here. - # pubSigningKeyBase256 = - # pubkeyPayload[readPosition:readPosition+64] + # pubSigningKeyBase256 = pubkeyPayload[readPosition:readPosition+64] # We don't use this key for anything here. readPosition += 64 pubEncryptionKeyBase256 = pubkeyPayload[ readPosition:readPosition + 64] @@ -994,368 +680,188 @@ class singleWorker(StoppableThread): # Let us fetch the amount of work required by the recipient. if toAddressVersionNumber == 2: - requiredAverageProofOfWorkNonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - requiredPayloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Doing work necessary to send message.\n" - "There is no required difficulty for" - " version 2 addresses like this.")) - )) + requiredAverageProofOfWorkNonceTrialsPerByte = defaults.networkDefaultProofOfWorkNonceTrialsPerByte + requiredPayloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr._translate("MainWindow", "Doing work necessary to send message.\nThere is no required difficulty for version 2 addresses like this.")))) elif toAddressVersionNumber >= 3: - requiredAverageProofOfWorkNonceTrialsPerByte, \ - varintLength = decodeVarint( - pubkeyPayload[readPosition:readPosition + 10]) + requiredAverageProofOfWorkNonceTrialsPerByte, varintLength = decodeVarint( + pubkeyPayload[readPosition:readPosition + 10]) readPosition += varintLength - requiredPayloadLengthExtraBytes, varintLength = \ - decodeVarint( - pubkeyPayload[readPosition:readPosition + 10]) + requiredPayloadLengthExtraBytes, varintLength = decodeVarint( + pubkeyPayload[readPosition:readPosition + 10]) readPosition += varintLength - # We still have to meet a minimum POW difficulty - # regardless of what they say is allowed in order - # to get our message to propagate through the network. - if requiredAverageProofOfWorkNonceTrialsPerByte < \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte: - requiredAverageProofOfWorkNonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - if requiredPayloadLengthExtraBytes < \ - defaults.networkDefaultPayloadLengthExtraBytes: - requiredPayloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes - self.logger.debug( - 'Using averageProofOfWorkNonceTrialsPerByte: %s' - ' and payloadLengthExtraBytes: %s.', - requiredAverageProofOfWorkNonceTrialsPerByte, - requiredPayloadLengthExtraBytes - ) - - queues.UISignalQueue.put( - ( - 'updateSentItemStatusByAckdata', - ( - ackdata, - tr._translate( - "MainWindow", - "Doing work necessary to send message.\n" - "Receiver\'s required difficulty: %1" - " and %2" - ).arg( - str( - float(requiredAverageProofOfWorkNonceTrialsPerByte) - / defaults.networkDefaultProofOfWorkNonceTrialsPerByte - ) - ).arg( - str( - float(requiredPayloadLengthExtraBytes) - / defaults.networkDefaultPayloadLengthExtraBytes - ) - ) - ) - ) - ) - + if requiredAverageProofOfWorkNonceTrialsPerByte < defaults.networkDefaultProofOfWorkNonceTrialsPerByte: # We still have to meet a minimum POW difficulty regardless of what they say is allowed in order to get our message to propagate through the network. + requiredAverageProofOfWorkNonceTrialsPerByte = defaults.networkDefaultProofOfWorkNonceTrialsPerByte + if requiredPayloadLengthExtraBytes < defaults.networkDefaultPayloadLengthExtraBytes: + requiredPayloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes + logger.debug('Using averageProofOfWorkNonceTrialsPerByte: %s and payloadLengthExtraBytes: %s.' % (requiredAverageProofOfWorkNonceTrialsPerByte, requiredPayloadLengthExtraBytes)) + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr._translate("MainWindow", "Doing work necessary to send message.\nReceiver\'s required difficulty: %1 and %2").arg(str(float( + requiredAverageProofOfWorkNonceTrialsPerByte) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte)).arg(str(float(requiredPayloadLengthExtraBytes) / defaults.networkDefaultPayloadLengthExtraBytes))))) if status != 'forcepow': - maxacceptablenoncetrialsperbyte = config.getint( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte') - maxacceptablepayloadlengthextrabytes = config.getint( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') - cond1 = maxacceptablenoncetrialsperbyte and \ - requiredAverageProofOfWorkNonceTrialsPerByte > maxacceptablenoncetrialsperbyte - cond2 = maxacceptablepayloadlengthextrabytes and \ - requiredPayloadLengthExtraBytes > maxacceptablepayloadlengthextrabytes - - if cond1 or cond2: - # The demanded difficulty is more than - # we are willing to do. + if (requiredAverageProofOfWorkNonceTrialsPerByte > BMConfigParser().getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte') and BMConfigParser().getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte') != 0) or (requiredPayloadLengthExtraBytes > BMConfigParser().getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') and BMConfigParser().getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') != 0): + # The demanded difficulty is more than we are willing + # to do. sqlExecute( - '''UPDATE sent SET status='toodifficult' ''' - ''' WHERE ackdata=? AND folder='sent' ''', + '''UPDATE sent SET status='toodifficult' WHERE ackdata=? ''', ackdata) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Problem: The work demanded by" - " the recipient (%1 and %2) is" - " more difficult than you are" - " willing to do. %3" - ).arg(str(float(requiredAverageProofOfWorkNonceTrialsPerByte) - / defaults.networkDefaultProofOfWorkNonceTrialsPerByte) - ).arg(str(float(requiredPayloadLengthExtraBytes) - / defaults.networkDefaultPayloadLengthExtraBytes) - ).arg(l10n.formatTimestamp())))) + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr._translate("MainWindow", "Problem: The work demanded by the recipient (%1 and %2) is more difficult than you are willing to do. %3").arg(str(float(requiredAverageProofOfWorkNonceTrialsPerByte) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte)).arg(str(float( + requiredPayloadLengthExtraBytes) / defaults.networkDefaultPayloadLengthExtraBytes)).arg(l10n.formatTimestamp())))) continue - else: # if we are sending a message to ourselves or a chan.. - self.logger.info('Sending a message.') - self.logger.debug( - 'First 150 characters of message: %r', message[:150]) + else: # if we are sending a message to ourselves or a chan.. + logger.info('Sending a message.') + logger.debug('First 150 characters of message: ' + repr(message[:150])) behaviorBitfield = protocol.getBitfield(fromaddress) try: - privEncryptionKeyBase58 = config.get( + privEncryptionKeyBase58 = BMConfigParser().get( toaddress, 'privencryptionkey') - except (configparser.NoSectionError, configparser.NoOptionError) as err: - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "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" - ).arg(l10n.formatTimestamp())) - )) - self.logger.error( - 'Error within sendMsg. Could not read the keys' - ' from the keys.dat file for our own address. %s\n', - err) + except Exception as err: + queues.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,tr._translate("MainWindow",'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').arg(l10n.formatTimestamp())))) + logger.error('Error within sendMsg. Could not read the keys from the keys.dat file for our own address. %s\n' % err) continue privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( privEncryptionKeyBase58)) pubEncryptionKeyBase256 = unhexlify(highlevelcrypto.privToPub( privEncryptionKeyHex))[1:] - requiredAverageProofOfWorkNonceTrialsPerByte = \ - defaults.networkDefaultProofOfWorkNonceTrialsPerByte - requiredPayloadLengthExtraBytes = \ - defaults.networkDefaultPayloadLengthExtraBytes - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Doing work necessary to send message.")) - )) + requiredAverageProofOfWorkNonceTrialsPerByte = defaults.networkDefaultProofOfWorkNonceTrialsPerByte + requiredPayloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr._translate("MainWindow", "Doing work necessary to send message.")))) # Now we can start to assemble our message. payload = encodeVarint(fromAddressVersionNumber) payload += encodeVarint(fromStreamNumber) - # Bitfield of features and behaviors - # that can be expected from me. (See - # https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features) - payload += protocol.getBitfield(fromaddress) + payload += protocol.getBitfield(fromaddress) # Bitfield of features and behaviors that can be expected from me. (See https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features ) # We need to convert our private keys to public keys in order # to include them. try: - privSigningKeyHex, privEncryptionKeyHex, \ - pubSigningKey, pubEncryptionKey = self._getKeysForAddress( - fromaddress) - except (configparser.NoSectionError, configparser.NoOptionError) as err: - self.logger.warning("Section or Option did not found: %s", err) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error! Could not find sender address" - " (your address) in the keys.dat file.")) - )) - except Exception as err: - self.logger.error( - 'Error within sendMsg. Could not read' - ' the keys from the keys.dat file for a requested' - ' address. %s\n', err - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Error, can't send.")) - )) + privSigningKeyBase58 = BMConfigParser().get( + fromaddress, 'privsigningkey') + privEncryptionKeyBase58 = BMConfigParser().get( + fromaddress, 'privencryptionkey') + except: + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', ( + ackdata, tr._translate("MainWindow", "Error! Could not find sender address (your address) in the keys.dat file.")))) continue - payload += pubSigningKey + pubEncryptionKey + privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( + privSigningKeyBase58)) + privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( + privEncryptionKeyBase58)) + + pubSigningKey = unhexlify(highlevelcrypto.privToPub( + privSigningKeyHex)) + pubEncryptionKey = unhexlify(highlevelcrypto.privToPub( + privEncryptionKeyHex)) + + payload += pubSigningKey[ + 1:] # The \x04 on the beginning of the public keys are not sent. This way there is only one acceptable way to encode and send a public key. + payload += pubEncryptionKey[1:] if fromAddressVersionNumber >= 3: # If the receiver of our message is in our address book, # subscriptions list, or whitelist then we will allow them to # do the network-minimum proof of work. Let us check to see if # the receiver is in any of those lists. - if shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist( - toaddress): + if shared.isAddressInMyAddressBookSubscriptionsListOrWhitelist(toaddress): payload += encodeVarint( defaults.networkDefaultProofOfWorkNonceTrialsPerByte) payload += encodeVarint( defaults.networkDefaultPayloadLengthExtraBytes) else: - payload += encodeVarint(config.getint( + payload += encodeVarint(BMConfigParser().getint( fromaddress, 'noncetrialsperbyte')) - payload += encodeVarint(config.getint( + payload += encodeVarint(BMConfigParser().getint( fromaddress, 'payloadlengthextrabytes')) - # This hash will be checked by the receiver of the message - # to verify that toRipe belongs to them. This prevents - # a Surreptitious Forwarding Attack. - payload += toRipe - payload += encodeVarint(encoding) # message encoding type - encodedMessage = helper_msgcoding.MsgEncode( - {"subject": subject, "body": message}, encoding - ) + payload += toRipe # This hash will be checked by the receiver of the message to verify that toRipe belongs to them. This prevents a Surreptitious Forwarding Attack. + payload += encodeVarint(encoding) # message encoding type + encodedMessage = helper_msgcoding.MsgEncode({"subject": subject, "body": message}, encoding) payload += encodeVarint(encodedMessage.length) payload += encodedMessage.data - if config.has_section(toaddress): - self.logger.info( - 'Not bothering to include ackdata because we are' - ' sending to ourselves or a chan.' - ) - fullAckPayload = '' - elif not protocol.checkBitfield( - behaviorBitfield, protocol.BITFIELD_DOESACK): - self.logger.info( - 'Not bothering to include ackdata because' - ' the receiver said that they won\'t relay it anyway.' - ) + if BMConfigParser().has_section(toaddress): + logger.info('Not bothering to include ackdata because we are sending to ourselves or a chan.') fullAckPayload = '' + elif not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK): + logger.info('Not bothering to include ackdata because the receiver said that they won\'t relay it anyway.') + fullAckPayload = '' else: - # The fullAckPayload is a normal msg protocol message - # with the proof of work already completed that the - # receiver of this message can easily send out. fullAckPayload = self.generateFullAckMessage( - ackdata, toStreamNumber, TTL) + ackdata, toStreamNumber, TTL) # The fullAckPayload is a normal msg protocol message with the proof of work already completed that the receiver of this message can easily send out. payload += encodeVarint(len(fullAckPayload)) payload += fullAckPayload - dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + \ - encodeVarint(1) + encodeVarint(toStreamNumber) + payload - signature = highlevelcrypto.sign( - dataToSign, privSigningKeyHex, self.digestAlg) + dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + encodeVarint(1) + encodeVarint(toStreamNumber) + payload + signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex) payload += encodeVarint(len(signature)) payload += signature # We have assembled the data that will be encrypted. try: - encrypted = highlevelcrypto.encrypt( - payload, "04" + hexlify(pubEncryptionKeyBase256) - ) - except: # noqa:E722 - self.logger.warning("highlevelcrypto.encrypt didn't work") - sqlExecute( - '''UPDATE sent SET status='badkey' WHERE ackdata=? AND folder='sent' ''', - ackdata - ) - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Problem: The recipient\'s encryption key is" - " no good. Could not encrypt message. %1" - ).arg(l10n.formatTimestamp())) - )) + encrypted = highlevelcrypto.encrypt(payload,"04"+hexlify(pubEncryptionKeyBase256)) + except: + sqlExecute('''UPDATE sent SET status='badkey' WHERE ackdata=?''', ackdata) + queues.UISignalQueue.put(('updateSentItemStatusByAckdata',(ackdata,tr._translate("MainWindow",'Problem: The recipient\'s encryption key is no good. Could not encrypt message. %1').arg(l10n.formatTimestamp())))) continue - + encryptedPayload = pack('>Q', embeddedTime) - encryptedPayload += '\x00\x00\x00\x02' # object type: msg - encryptedPayload += encodeVarint(1) # msg version + encryptedPayload += '\x00\x00\x00\x02' # object type: msg + encryptedPayload += encodeVarint(1) # msg version encryptedPayload += encodeVarint(toStreamNumber) + encrypted - target = 2 ** 64 / ( - requiredAverageProofOfWorkNonceTrialsPerByte * ( - len(encryptedPayload) + 8 - + requiredPayloadLengthExtraBytes + (( - TTL * ( - len(encryptedPayload) + 8 - + requiredPayloadLengthExtraBytes - )) / (2 ** 16)) - )) - self.logger.info( - '(For msg message) Doing proof of work. Total required' - ' difficulty: %f. Required small message difficulty: %f.', - float(requiredAverageProofOfWorkNonceTrialsPerByte) - / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, - float(requiredPayloadLengthExtraBytes) - / defaults.networkDefaultPayloadLengthExtraBytes - ) + target = 2 ** 64 / (requiredAverageProofOfWorkNonceTrialsPerByte*(len(encryptedPayload) + 8 + requiredPayloadLengthExtraBytes + ((TTL*(len(encryptedPayload)+8+requiredPayloadLengthExtraBytes))/(2 ** 16)))) + logger.info('(For msg message) Doing proof of work. Total required difficulty: %f. Required small message difficulty: %f.', float(requiredAverageProofOfWorkNonceTrialsPerByte) / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, float(requiredPayloadLengthExtraBytes) / defaults.networkDefaultPayloadLengthExtraBytes) powStartTime = time.time() initialHash = hashlib.sha512(encryptedPayload).digest() trialValue, nonce = proofofwork.run(target, initialHash) - self.logger.info( - '(For msg message) Found proof of work %s Nonce: %s', - trialValue, nonce - ) + logger.info('(For msg message) Found proof of work ' + str(trialValue) + ' Nonce: ' + str(nonce)) try: - self.logger.info( - 'PoW took %.1f seconds, speed %s.', - time.time() - powStartTime, - sizeof_fmt(nonce / (time.time() - powStartTime)) - ) - except: # noqa:E722 - self.logger.warning("Proof of Work exception") + logger.info('PoW took %.1f seconds, speed %s.', time.time() - powStartTime, sizeof_fmt(nonce / (time.time() - powStartTime))) + except: + pass encryptedPayload = pack('>Q', nonce) + encryptedPayload - - # Sanity check. The encryptedPayload size should never be - # larger than 256 KiB. There should be checks elsewhere - # in the code to not let the user try to send a message - # this large until we implement message continuation. - if len(encryptedPayload) > 2 ** 18: # 256 KiB - self.logger.critical( - 'This msg object is too large to send. This should' - ' never happen. Object size: %i', - len(encryptedPayload) - ) + + # Sanity check. The encryptedPayload size should never be larger than 256 KiB. There should + # be checks elsewhere in the code to not let the user try to send a message this large + # until we implement message continuation. + if len(encryptedPayload) > 2 ** 18: # 256 KiB + logger.critical('This msg object is too large to send. This should never happen. Object size: %s' % len(encryptedPayload)) continue inventoryHash = calculateInventoryHash(encryptedPayload) objectType = 2 Inventory()[inventoryHash] = ( objectType, toStreamNumber, encryptedPayload, embeddedTime, '') - if config.has_section(toaddress) or \ - not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK): - queues.UISignalQueue.put(( - 'updateSentItemStatusByAckdata', ( - ackdata, - tr._translate( - "MainWindow", - "Message sent. Sent at %1" - ).arg(l10n.formatTimestamp())))) + PendingUpload().add(inventoryHash) + if BMConfigParser().has_section(toaddress) or not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK): + queues.UISignalQueue.put(('updateSentItemStatusByAckdata', (ackdata, tr._translate("MainWindow", "Message sent. Sent at %1").arg(l10n.formatTimestamp())))) else: # 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())) - )) - self.logger.info( - 'Broadcasting inv for my msg(within sendmsg function): %s', - hexlify(inventoryHash) - ) + 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)) queues.invQueue.put((toStreamNumber, inventoryHash)) - # Update the sent message in the sent table with the - # necessary information. - if config.has_section(toaddress) or \ - not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK): + # 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): newStatus = 'msgsentnoackexpected' else: newStatus = 'msgsent' # wait 10% past expiration sleepTill = int(time.time() + TTL * 1.1) - sqlExecute( - '''UPDATE sent SET msgid=?, status=?, retrynumber=?, ''' - ''' sleeptill=?, lastactiontime=? WHERE ackdata=? AND folder='sent' ''', - inventoryHash, newStatus, retryNumber + 1, - sleepTill, int(time.time()), ackdata - ) + sqlExecute('''UPDATE sent SET msgid=?, status=?, retrynumber=?, sleeptill=?, lastactiontime=? WHERE ackdata=?''', + inventoryHash, + newStatus, + retryNumber+1, + sleepTill, + int(time.time()), + ackdata) - # If we are sending to ourselves or a chan, let's put - # the message in our own inbox. - if config.has_section(toaddress): - # Used to detect and ignore duplicate messages in our inbox - sigHash = hashlib.sha512(hashlib.sha512( - signature).digest()).digest()[32:] + # If we are sending to ourselves or a chan, let's put the message in + # our own inbox. + if BMConfigParser().has_section(toaddress): + sigHash = hashlib.sha512(hashlib.sha512(signature).digest()).digest()[32:] # Used to detect and ignore duplicate messages in our inbox t = (inventoryHash, toaddress, fromaddress, subject, int( time.time()), message, 'inbox', encoding, 0, sigHash) helper_inbox.insert(t) @@ -1366,159 +872,126 @@ class singleWorker(StoppableThread): # If we are behaving as an API then we might need to run an # outside command to let some program know that a new message # has arrived. - if config.safeGetBoolean( - 'bitmessagesettings', 'apienabled'): - - apiNotifyPath = config.safeGet( - 'bitmessagesettings', 'apinotifypath') - - if apiNotifyPath: - # There is no additional risk of remote exploitation or - # privilege escalation - call([apiNotifyPath, "newMessage"]) # nosec B603 + if BMConfigParser().safeGetBoolean('bitmessagesettings', 'apienabled'): + try: + apiNotifyPath = BMConfigParser().get( + 'bitmessagesettings', 'apinotifypath') + except: + apiNotifyPath = '' + if apiNotifyPath != '': + call([apiNotifyPath, "newMessage"]) def requestPubKey(self, toAddress): - """Send a getpubkey object""" toStatus, addressVersionNumber, streamNumber, ripe = decodeAddress( toAddress) if toStatus != 'success': - self.logger.error( - 'Very abnormal error occurred in requestPubKey.' - ' toAddress is: %r. Please report this error to Atheros.', - toAddress - ) + logger.error('Very abnormal error occurred in requestPubKey. toAddress is: ' + repr( + toAddress) + '. Please report this error to Atheros.') return - + queryReturn = sqlQuery( - '''SELECT retrynumber FROM sent WHERE toaddress=? ''' - ''' AND (status='doingpubkeypow' OR status='awaitingpubkey') ''' - ''' AND folder='sent' LIMIT 1''', - toAddress - ) - if not queryReturn: - self.logger.critical( - 'BUG: Why are we requesting the pubkey for %s' - ' if there are no messages in the sent folder' - ' to that address?', toAddress - ) + '''SELECT retrynumber FROM sent WHERE toaddress=? AND (status='doingpubkeypow' OR status='awaitingpubkey') LIMIT 1''', + toAddress) + if len(queryReturn) == 0: + logger.critical("BUG: Why are we requesting the pubkey for %s if there are no messages in the sent folder to that address?" % toAddress) return retryNumber = queryReturn[0][0] if addressVersionNumber <= 3: state.neededPubkeys[toAddress] = 0 elif addressVersionNumber >= 4: - # If the user just clicked 'send' then the tag - # (and other information) will already be in the - # neededPubkeys dictionary. But if we are recovering - # from a restart of the client then we have to put it in now. - - # Note that this is the first half of the sha512 hash. - privEncryptionKey = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest()[:32] - # Note that this is the second half of the sha512 hash. - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + ripe - ).digest()).digest()[32:] + # If the user just clicked 'send' then the tag (and other information) will already + # be in the neededPubkeys dictionary. But if we are recovering from a restart + # of the client then we have to put it in now. + privEncryptionKey = hashlib.sha512(hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+ripe).digest()).digest()[:32] # Note that this is the first half of the sha512 hash. + tag = hashlib.sha512(hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+ripe).digest()).digest()[32:] # Note that this is the second half of the sha512 hash. if tag not in state.neededPubkeys: - # We'll need this for when we receive a pubkey reply: - # it will be encrypted and we'll need to decrypt it. - state.neededPubkeys[tag] = ( - toAddress, - highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) - ) - - # 2.5 days. This was chosen fairly arbitrarily. - TTL = 2.5 * 24 * 60 * 60 - TTL *= 2 ** retryNumber - if TTL > 28 * 24 * 60 * 60: - TTL = 28 * 24 * 60 * 60 - # add some randomness to the TTL - TTL = TTL + helper_random.randomrandrange(-300, 300) + state.neededPubkeys[tag] = (toAddress, highlevelcrypto.makeCryptor(hexlify(privEncryptionKey))) # We'll need this for when we receive a pubkey reply: it will be encrypted and we'll need to decrypt it. + + TTL = 2.5*24*60*60 # 2.5 days. This was chosen fairly arbitrarily. + TTL *= 2**retryNumber + if TTL > 28*24*60*60: + TTL = 28*24*60*60 + TTL = TTL + random.randrange(-300, 300) # add some randomness to the TTL embeddedTime = int(time.time() + TTL) payload = pack('>Q', embeddedTime) - payload += '\x00\x00\x00\x00' # object type: getpubkey + payload += '\x00\x00\x00\x00' # object type: getpubkey payload += encodeVarint(addressVersionNumber) payload += encodeVarint(streamNumber) if addressVersionNumber <= 3: payload += ripe - self.logger.info( - 'making request for pubkey with ripe: %s', hexlify(ripe)) + logger.info('making request for pubkey with ripe: %s', hexlify(ripe)) else: payload += tag - self.logger.info( - 'making request for v4 pubkey with tag: %s', hexlify(tag)) + logger.info('making request for v4 pubkey with tag: %s', hexlify(tag)) - statusbar = 'Doing the computations necessary to request' +\ - ' the recipient\'s public key.' + # print 'trial value', trialValue + statusbar = 'Doing the computations necessary to request the recipient\'s public key.' queues.UISignalQueue.put(('updateStatusBar', statusbar)) - queues.UISignalQueue.put(( - 'updateSentItemStatusByToAddress', ( - toAddress, - tr._translate( - "MainWindow", - "Doing work necessary to request encryption key.")) - )) - - payload = self._doPOWDefaults(payload, TTL) + queues.UISignalQueue.put(('updateSentItemStatusByToAddress', ( + toAddress, tr._translate("MainWindow",'Doing work necessary to request encryption key.')))) + + target = 2 ** 64 / (defaults.networkDefaultProofOfWorkNonceTrialsPerByte*(len(payload) + 8 + defaults.networkDefaultPayloadLengthExtraBytes + ((TTL*(len(payload)+8+defaults.networkDefaultPayloadLengthExtraBytes))/(2 ** 16)))) + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + logger.info('Found proof of work ' + str(trialValue) + ' Nonce: ' + str(nonce)) + payload = pack('>Q', nonce) + payload inventoryHash = calculateInventoryHash(payload) objectType = 1 Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') - self.logger.info('sending inv (for the getpubkey message)') + PendingUpload().add(inventoryHash) + logger.info('sending inv (for the getpubkey message)') queues.invQueue.put((streamNumber, inventoryHash)) - + # wait 10% past expiration sleeptill = int(time.time() + TTL * 1.1) sqlExecute( - '''UPDATE sent SET lastactiontime=?, ''' - ''' status='awaitingpubkey', retrynumber=?, sleeptill=? ''' - ''' WHERE toaddress=? AND (status='doingpubkeypow' OR ''' - ''' status='awaitingpubkey') AND folder='sent' ''', - int(time.time()), retryNumber + 1, sleeptill, toAddress) + '''UPDATE sent SET lastactiontime=?, status='awaitingpubkey', retrynumber=?, sleeptill=? WHERE toaddress=? AND (status='doingpubkeypow' OR status='awaitingpubkey') ''', + int(time.time()), + retryNumber+1, + sleeptill, + toAddress) queues.UISignalQueue.put(( - 'updateStatusBar', - tr._translate( - "MainWindow", - "Broadcasting the public key request. This program will" - " auto-retry if they are offline.") - )) - queues.UISignalQueue.put(( - 'updateSentItemStatusByToAddress', ( - toAddress, - tr._translate( - "MainWindow", - "Sending public key request. Waiting for reply." - " Requested at %1" - ).arg(l10n.formatTimestamp())) - )) + 'updateStatusBar', tr._translate("MainWindow",'Broadcasting the public key request. This program will auto-retry if they are offline.'))) + queues.UISignalQueue.put(('updateSentItemStatusByToAddress', (toAddress, tr._translate("MainWindow",'Sending public key request. Waiting for reply. Requested at %1').arg(l10n.formatTimestamp())))) - def generateFullAckMessage(self, ackdata, _, TTL): - """ - It might be perfectly fine to just use the same TTL for the ackdata that we use for the message. But I would - rather it be more difficult for attackers to associate ackData with the associated msg object. However, users - would want the TTL of the acknowledgement to be about the same as they set for the message itself. So let's set - the TTL of the acknowledgement to be in one of three 'buckets': 1 hour, 7 days, or 28 days, whichever is - relatively close to what the user specified. - """ - if TTL < 24 * 60 * 60: # 1 day - TTL = 24 * 60 * 60 # 1 day - elif TTL < 7 * 24 * 60 * 60: # 1 week - TTL = 7 * 24 * 60 * 60 # 1 week + def generateFullAckMessage(self, ackdata, toStreamNumber, TTL): + + # It might be perfectly fine to just use the same TTL for + # the ackdata that we use for the message. But I would rather + # it be more difficult for attackers to associate ackData with + # the associated msg object. However, users would want the TTL + # of the acknowledgement to be about the same as they set + # for the message itself. So let's set the TTL of the + # acknowledgement to be in one of three 'buckets': 1 hour, 7 + # days, or 28 days, whichever is relatively close to what the + # user specified. + if TTL < 24*60*60: # 1 day + TTL = 24*60*60 # 1 day + elif TTL < 7*24*60*60: # 1 week + TTL = 7*24*60*60 # 1 week else: - TTL = 28 * 24 * 60 * 60 # 4 weeks - # Add some randomness to the TTL - TTL = int(TTL + helper_random.randomrandrange(-300, 300)) + 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) - # type/version/stream already included + # type/version/stream already included payload = pack('>Q', (embeddedTime)) + ackdata - payload = self._doPOWDefaults( - payload, TTL, log_prefix='(For ack message)', log_time=True) + 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)) + powStartTime = time.time() + initialHash = hashlib.sha512(payload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + logger.info('(For ack message) Found proof of work ' + str(trialValue) + ' Nonce: ' + str(nonce)) + try: + logger.info('PoW took %.1f seconds, speed %s.', time.time() - powStartTime, sizeof_fmt(nonce / (time.time() - powStartTime))) + except: + pass + + payload = pack('>Q', nonce) + payload return protocol.CreatePacket('object', payload) diff --git a/src/class_smtpDeliver.py b/src/class_smtpDeliver.py index 9e3b8ab3..bb659ebe 100644 --- a/src/class_smtpDeliver.py +++ b/src/class_smtpDeliver.py @@ -1,54 +1,55 @@ -""" -SMTP client thread for delivering emails -""" -# pylint: disable=unused-variable - -import smtplib -import urlparse -from email.header import Header from email.mime.text import MIMEText +from email.header import Header +import smtplib +import sys +import threading +import urlparse +from bmconfigparser import BMConfigParser +from debug import logger +from helper_threading import * import queues import state -from bmconfigparser import config -from network.threads import StoppableThread SMTPDOMAIN = "bmaddr.lan" - -class smtpDeliver(StoppableThread): - """SMTP client thread for delivery""" - name = "smtpDeliver" +class smtpDeliver(threading.Thread, StoppableThread): _instance = None + def __init__(self, parent=None): + threading.Thread.__init__(self, name="smtpDeliver") + self.initStop() + def stopThread(self): - """Relay shutdown instruction""" - queues.UISignalQueue.put(("stopThread", "data")) + try: + queues.UISignallerQueue.put(("stopThread", "data")) + except: + pass super(smtpDeliver, self).stopThread() @classmethod def get(cls): - """(probably) Singleton functionality""" if not cls._instance: cls._instance = smtpDeliver() return cls._instance def run(self): - # pylint: disable=too-many-branches,too-many-statements,too-many-locals - # pylint: disable=deprecated-lambda while state.shutdown == 0: command, data = queues.UISignalQueue.get() if command == 'writeNewAddressToTable': label, address, streamNumber = data + pass elif command == 'updateStatusBar': pass elif command == 'updateSentItemStatusByToAddress': toAddress, message = data + pass elif command == 'updateSentItemStatusByAckdata': ackData, message = data + pass elif command == 'displayNewInboxMessage': inventoryHash, toAddress, fromAddress, subject, body = data - dest = config.safeGet("bitmessagesettings", "smtpdeliver", '') + dest = BMConfigParser().safeGet("bitmessagesettings", "smtpdeliver", '') if dest == '': continue try: @@ -58,12 +59,8 @@ class smtpDeliver(StoppableThread): msg = MIMEText(body, 'plain', 'utf-8') msg['Subject'] = Header(subject, 'utf-8') msg['From'] = fromAddress + '@' + SMTPDOMAIN - toLabel = map( - lambda y: config.safeGet(y, "label"), - filter( - lambda x: x == toAddress, config.addresses()) - ) - if toLabel: + 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: msg['To'] = toAddress + '@' + SMTPDOMAIN @@ -71,14 +68,13 @@ class smtpDeliver(StoppableThread): client.starttls() client.ehlo() client.sendmail(msg['From'], [to], msg.as_string()) - self.logger.info( - 'Delivered via SMTP to %s through %s:%i ...', - to, u.hostname, u.port) + logger.info("Delivered via SMTP to %s through %s:%i ...", to, u.hostname, u.port) client.quit() - except: # noqa:E722 - self.logger.error('smtp delivery error', exc_info=True) + except: + logger.error("smtp delivery error", exc_info=True) elif command == 'displayNewSentMessage': toAddress, fromLabel, fromAddress, subject, message, ackdata = data + pass elif command == 'updateNetworkStatusTab': pass elif command == 'updateNumberOfMessagesProcessed': @@ -107,8 +103,9 @@ class smtpDeliver(StoppableThread): pass elif command == 'alert': title, text, exitAfterUserClicksOk = data + pass elif command == 'stopThread': break else: - self.logger.warning( - 'Command sent to smtpDeliver not recognized: %s', command) + sys.stderr.write( + 'Command sent to smtpDeliver not recognized: %s\n' % command) diff --git a/src/class_smtpServer.py b/src/class_smtpServer.py index 44ea7c9c..b62a7130 100644 --- a/src/class_smtpServer.py +++ b/src/class_smtpServer.py @@ -1,42 +1,30 @@ -""" -SMTP server thread -""" import asyncore import base64 import email -import logging +from email.parser import Parser +from email.header import decode_header import re import signal import smtpd +import socket import threading import time -from email.header import decode_header -from email.parser import Parser -import queues from addresses import decodeAddress -from bmconfigparser import config -from helper_ackPayload import genAckPayload +from bmconfigparser import BMConfigParser +from debug import logger from helper_sql import sqlExecute -from network.threads import StoppableThread +from helper_ackPayload import genAckPayload +from helper_threading import StoppableThread +from pyelliptic.openssl import OpenSSL +import queues from version import softwareVersion SMTPDOMAIN = "bmaddr.lan" LISTENPORT = 8425 -logger = logging.getLogger('default') -# pylint: disable=attribute-defined-outside-init - - -class SmtpServerChannelException(Exception): - """Generic smtp server channel exception.""" - pass - - class smtpServerChannel(smtpd.SMTPChannel): - """Asyncore channel for SMTP protocol (server)""" def smtp_EHLO(self, arg): - """Process an EHLO""" if not arg: self.push('501 Syntax: HELO hostname') return @@ -44,48 +32,43 @@ class smtpServerChannel(smtpd.SMTPChannel): self.push('250 AUTH PLAIN') def smtp_AUTH(self, arg): - """Process AUTH""" if not arg or arg[0:5] not in ["PLAIN"]: self.push('501 Syntax: AUTH PLAIN') return authstring = arg[6:] try: decoded = base64.b64decode(authstring) - correctauth = "\x00" + config.safeGet( - "bitmessagesettings", "smtpdusername", "") + "\x00" + config.safeGet( - "bitmessagesettings", "smtpdpassword", "") - logger.debug('authstring: %s / %s', correctauth, decoded) + correctauth = "\x00" + BMConfigParser().safeGet("bitmessagesettings", "smtpdusername", "") + \ + "\x00" + BMConfigParser().safeGet("bitmessagesettings", "smtpdpassword", "") + logger.debug("authstring: %s / %s", correctauth, decoded) if correctauth == decoded: self.auth = True self.push('235 2.7.0 Authentication successful') else: - raise SmtpServerChannelException("Auth fail") - except: # noqa:E722 + raise Exception("Auth fail") + except: self.push('501 Authentication fail') def smtp_DATA(self, arg): - """Process DATA""" if not hasattr(self, "auth") or not self.auth: - self.push('530 Authentication required') + self.push ("530 Authentication required") return smtpd.SMTPChannel.smtp_DATA(self, arg) class smtpServerPyBitmessage(smtpd.SMTPServer): - """Asyncore SMTP server class""" def handle_accept(self): - """Accept a connection""" pair = self.accept() if pair is not None: conn, addr = pair +# print >> DEBUGSTREAM, 'Incoming connection from %s' % repr(addr) self.channel = smtpServerChannel(self, conn, addr) def send(self, fromAddress, toAddress, subject, message): - """Send a bitmessage""" - # pylint: disable=arguments-differ - streamNumber, ripe = decodeAddress(toAddress)[2:] - stealthLevel = config.safeGetInt('bitmessagesettings', 'ackstealthlevel') + status, addressVersionNumber, streamNumber, ripe = decodeAddress(toAddress) + stealthLevel = BMConfigParser().safeGetInt('bitmessagesettings', 'ackstealthlevel') ackdata = genAckPayload(streamNumber, stealthLevel) + t = () sqlExecute( '''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', '', @@ -95,66 +78,64 @@ class smtpServerPyBitmessage(smtpd.SMTPServer): subject, message, ackdata, - int(time.time()), # sentTime (this will never change) - int(time.time()), # lastActionTime - 0, # sleepTill time. This will get set when the POW gets done. + int(time.time()), # sentTime (this will never change) + int(time.time()), # lastActionTime + 0, # sleepTill time. This will get set when the POW gets done. 'msgqueued', - 0, # retryNumber - 'sent', # folder - 2, # encodingtype - # not necessary to have a TTL higher than 2 days - min(config.getint('bitmessagesettings', 'ttl'), 86400 * 2) + 0, # retryNumber + 'sent', # folder + 2, # encodingtype + min(BMConfigParser().getint('bitmessagesettings', 'ttl'), 86400 * 2) # not necessary to have a TTL higher than 2 days ) queues.workerQueue.put(('sendmessage', toAddress)) def decode_header(self, hdr): - """Email header decoding""" ret = [] for h in decode_header(self.msg_headers[hdr]): if h[1]: - ret.append(h[0].decode(h[1])) + ret.append(unicode(h[0], h[1])) else: ret.append(h[0].decode("utf-8", errors='replace')) - + return ret + def process_message(self, peer, mailfrom, rcpttos, data): - """Process an email""" - # pylint: disable=too-many-locals, too-many-branches +# print 'Receiving message from:', peer p = re.compile(".*<([^>]+)>") if not hasattr(self.channel, "auth") or not self.channel.auth: - logger.error('Missing or invalid auth') + logger.error("Missing or invalid auth") return try: self.msg_headers = Parser().parsestr(data) - except: # noqa:E722 - logger.error('Invalid headers') + except: + logger.error("Invalid headers") return try: sender, domain = p.sub(r'\1', mailfrom).split("@") if domain != SMTPDOMAIN: - raise Exception("Bad domain %s" % domain) - if sender not in config.addresses(): - raise Exception("Nonexisting user %s" % sender) + raise Exception("Bad domain %s", domain) + if sender not in BMConfigParser().addresses(): + raise Exception("Nonexisting user %s", sender) except Exception as err: - logger.debug('Bad envelope from %s: %r', mailfrom, err) + logger.debug("Bad envelope from %s: %s", mailfrom, repr(err)) msg_from = self.decode_header("from") try: msg_from = p.sub(r'\1', self.decode_header("from")[0]) sender, domain = msg_from.split("@") if domain != SMTPDOMAIN: - raise Exception("Bad domain %s" % domain) - if sender not in config.addresses(): - raise Exception("Nonexisting user %s" % sender) + raise Exception("Bad domain %s", domain) + if sender not in BMConfigParser().addresses(): + raise Exception("Nonexisting user %s", sender) except Exception as err: - logger.error('Bad headers from %s: %r', msg_from, err) + logger.error("Bad headers from %s: %s", msg_from, repr(err)) return try: msg_subject = self.decode_header('subject')[0] - except: # noqa:E722 + except: msg_subject = "Subject missing..." msg_tmp = email.message_from_string(data) @@ -167,51 +148,54 @@ class smtpServerPyBitmessage(smtpd.SMTPServer): try: rcpt, domain = p.sub(r'\1', to).split("@") if domain != SMTPDOMAIN: - raise Exception("Bad domain %s" % domain) - logger.debug( - 'Sending %s to %s about %s', sender, rcpt, msg_subject) + raise Exception("Bad domain %s", domain) + logger.debug("Sending %s to %s about %s", sender, rcpt, msg_subject) self.send(sender, rcpt, msg_subject, body) - logger.info('Relayed %s to %s', sender, rcpt) + logger.info("Relayed %s to %s", sender, rcpt) except Exception as err: - logger.error('Bad to %s: %r', to, err) + logger.error( "Bad to %s: %s", to, repr(err)) continue return - -class smtpServer(StoppableThread): - """SMTP server thread""" - def __init__(self, _=None): - super(smtpServer, self).__init__(name="smtpServerThread") +class smtpServer(threading.Thread, StoppableThread): + def __init__(self, parent=None): + threading.Thread.__init__(self, name="smtpServerThread") + self.initStop() self.server = smtpServerPyBitmessage(('127.0.0.1', LISTENPORT), None) - + def stopThread(self): super(smtpServer, self).stopThread() self.server.close() return + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +# for ip in ('127.0.0.1', BMConfigParser().get('bitmessagesettings', 'onionbindip')): + for ip in ('127.0.0.1'): + try: + s.connect((ip, LISTENPORT)) + s.shutdown(socket.SHUT_RDWR) + s.close() + break + except: + pass def run(self): asyncore.loop(1) - -def signals(_, __): - """Signal handler""" - logger.warning('Got signal, terminating') +def signals(signal, frame): + print "Got signal, terminating" for thread in threading.enumerate(): if thread.isAlive() and isinstance(thread, StoppableThread): thread.stopThread() - def runServer(): - """Run SMTP server as a standalone python process""" - logger.warning('Running SMTPd thread') + print "Running SMTPd thread" smtpThread = smtpServer() smtpThread.start() signal.signal(signal.SIGINT, signals) signal.signal(signal.SIGTERM, signals) - logger.warning('Processing') + print "Processing" smtpThread.join() - logger.warning('The end') - + print "The end" if __name__ == "__main__": runServer() diff --git a/src/class_sqlThread.py b/src/class_sqlThread.py index 7df9e253..18606e74 100644 --- a/src/class_sqlThread.py +++ b/src/class_sqlThread.py @@ -1,85 +1,64 @@ -""" -sqlThread is defined here -""" - -import os -import shutil # used for moving the messages.dat file -import sqlite3 -import sys import threading +from bmconfigparser import BMConfigParser +import sqlite3 import time +import shutil # used for moving the messages.dat file +import sys +import os +from debug import logger +import defaults +import helper_sql +from namecoin import ensureNamecoinOptions +import paths +import queues +import random +import state +import string +import tr#anslate -try: - import helper_sql - import helper_startup - import paths - import queues - import state - from addresses import encodeAddress - from bmconfigparser import config, config_ready - from debug import logger - from tr import _translate -except ImportError: - from . import helper_sql, helper_startup, paths, queues, state - from .addresses import encodeAddress - from .bmconfigparser import config, config_ready - from .debug import logger - from .tr import _translate +# This thread exists because SQLITE3 is so un-threadsafe that we must +# submit queries to it and it puts results back in a different queue. They +# won't let us just use locks. class sqlThread(threading.Thread): - """A thread for all SQL operations""" def __init__(self): threading.Thread.__init__(self, name="SQL") - def run(self): # pylint: disable=too-many-locals, too-many-branches, too-many-statements - """Process SQL queries from `.helper_sql.sqlSubmitQueue`""" - helper_sql.sql_available = True - config_ready.wait() + def run(self): self.conn = sqlite3.connect(state.appdata + 'messages.dat') self.conn.text_factory = str self.cur = self.conn.cursor() - + self.cur.execute('PRAGMA secure_delete = true') - # call create_function for encode address - self.create_function() - try: self.cur.execute( - '''CREATE TABLE inbox (msgid blob, toaddress text, fromaddress text, subject text,''' - ''' received text, message text, folder text, encodingtype int, read bool, sighash blob,''' - ''' UNIQUE(msgid) ON CONFLICT REPLACE)''') + '''CREATE TABLE inbox (msgid blob, toaddress text, fromaddress text, subject text, received text, message text, folder text, encodingtype int, read bool, sighash blob, UNIQUE(msgid) ON CONFLICT REPLACE)''' ) self.cur.execute( - '''CREATE TABLE sent (msgid blob, toaddress text, toripe blob, fromaddress text, subject text,''' - ''' message text, ackdata blob, senttime integer, lastactiontime integer,''' - ''' sleeptill integer, status text, retrynumber integer, folder text, encodingtype int, ttl int)''') + '''CREATE TABLE sent (msgid blob, toaddress text, toripe blob, fromaddress text, subject text, message text, ackdata blob, senttime integer, lastactiontime integer, sleeptill integer, status text, retrynumber integer, folder text, encodingtype int, ttl int)''' ) self.cur.execute( - '''CREATE TABLE subscriptions (label text, address text, enabled bool)''') + '''CREATE TABLE subscriptions (label text, address text, enabled bool)''' ) self.cur.execute( - '''CREATE TABLE addressbook (label text, address text, UNIQUE(address) ON CONFLICT IGNORE)''') + '''CREATE TABLE addressbook (label text, address text)''' ) self.cur.execute( - '''CREATE TABLE blacklist (label text, address text, enabled bool)''') + '''CREATE TABLE blacklist (label text, address text, enabled bool)''' ) self.cur.execute( - '''CREATE TABLE whitelist (label text, address text, enabled bool)''') + '''CREATE TABLE whitelist (label text, address text, enabled bool)''' ) self.cur.execute( - '''CREATE TABLE pubkeys (address text, addressversion int, transmitdata blob, time int,''' - ''' usedpersonally text, UNIQUE(address) ON CONFLICT REPLACE)''') + '''CREATE TABLE pubkeys (address text, addressversion int, transmitdata blob, time int, usedpersonally text, UNIQUE(address) ON CONFLICT REPLACE)''' ) self.cur.execute( - '''CREATE TABLE inventory (hash blob, objecttype int, streamnumber int, payload blob,''' - ''' expirestime integer, tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''') + '''CREATE TABLE inventory (hash blob, objecttype int, streamnumber int, payload blob, expirestime integer, tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''' ) self.cur.execute( - '''INSERT INTO subscriptions VALUES''' - '''('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') + '''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') self.cur.execute( - '''CREATE TABLE settings (key blob, value blob, UNIQUE(key) ON CONFLICT REPLACE)''') - self.cur.execute('''INSERT INTO settings VALUES('version','11')''') - self.cur.execute('''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( + '''CREATE TABLE settings (key blob, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' ) + self.cur.execute( '''INSERT INTO settings VALUES('version','10')''') + self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( int(time.time()),)) self.cur.execute( - '''CREATE TABLE objectprocessorqueue''' - ''' (objecttype int, data blob, UNIQUE(objecttype, data) ON CONFLICT REPLACE)''') + '''CREATE TABLE objectprocessorqueue (objecttype int, data blob, UNIQUE(objecttype, data) ON CONFLICT REPLACE)''' ) self.conn.commit() logger.info('Created messages database file') except Exception as err: @@ -91,26 +70,35 @@ class sqlThread(threading.Thread): 'ERROR trying to create database file (message.dat). Error message: %s\n' % str(err)) os._exit(0) - # If the settings version is equal to 2 or 3 then the - # sqlThread will modify the pubkeys table and change - # the settings version to 4. - settingsversion = config.getint( - 'bitmessagesettings', 'settingsversion') + if BMConfigParser().getint('bitmessagesettings', 'settingsversion') == 1: + BMConfigParser().set('bitmessagesettings', 'settingsversion', '2') + # If the settings version is equal to 2 or 3 then the + # sqlThread will modify the pubkeys table and change + # the settings version to 4. + BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'none') + BMConfigParser().set('bitmessagesettings', 'sockshostname', 'localhost') + BMConfigParser().set('bitmessagesettings', 'socksport', '9050') + BMConfigParser().set('bitmessagesettings', 'socksauthentication', 'false') + BMConfigParser().set('bitmessagesettings', 'socksusername', '') + BMConfigParser().set('bitmessagesettings', 'sockspassword', '') + BMConfigParser().set('bitmessagesettings', 'sockslisten', 'false') + BMConfigParser().set('bitmessagesettings', 'keysencrypted', 'false') + BMConfigParser().set('bitmessagesettings', 'messagesencrypted', 'false') # People running earlier versions of PyBitmessage do not have the # usedpersonally field in their pubkeys table. Let's add it. - if settingsversion == 2: + if BMConfigParser().getint('bitmessagesettings', 'settingsversion') == 2: item = '''ALTER TABLE pubkeys ADD usedpersonally text DEFAULT 'no' ''' parameters = '' self.cur.execute(item, parameters) self.conn.commit() - settingsversion = 3 + BMConfigParser().set('bitmessagesettings', 'settingsversion', '3') # People running earlier versions of PyBitmessage do not have the # encodingtype field in their inbox and sent tables or the read field # in the inbox table. Let's add them. - if settingsversion == 3: + if BMConfigParser().getint('bitmessagesettings', 'settingsversion') == 3: item = '''ALTER TABLE inbox ADD encodingtype int DEFAULT '2' ''' parameters = '' self.cur.execute(item, parameters) @@ -124,13 +112,21 @@ class sqlThread(threading.Thread): self.cur.execute(item, parameters) self.conn.commit() - settingsversion = 4 + BMConfigParser().set('bitmessagesettings', 'settingsversion', '4') - config.set( - 'bitmessagesettings', 'settingsversion', str(settingsversion)) - config.save() + if BMConfigParser().getint('bitmessagesettings', 'settingsversion') == 4: + BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) + BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str( + defaults.networkDefaultPayloadLengthExtraBytes)) + BMConfigParser().set('bitmessagesettings', 'settingsversion', '5') - helper_startup.updateConfig() + if BMConfigParser().getint('bitmessagesettings', 'settingsversion') == 5: + BMConfigParser().set( + 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', '0') + BMConfigParser().set( + 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', '0') + BMConfigParser().set('bitmessagesettings', 'settingsversion', '6') # From now on, let us keep a 'version' embedded in the messages.dat # file so that when we make changes to the database, the database @@ -141,41 +137,35 @@ class sqlThread(threading.Thread): self.cur.execute(item, parameters) if self.cur.fetchall() == []: # The settings table doesn't exist. We need to make it. - logger.debug( - "In messages.dat database, creating new 'settings' table.") + logger.debug('In messages.dat database, creating new \'settings\' table.') self.cur.execute( - '''CREATE TABLE settings (key text, value blob, UNIQUE(key) ON CONFLICT REPLACE)''') - self.cur.execute('''INSERT INTO settings VALUES('version','1')''') - self.cur.execute('''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( + '''CREATE TABLE settings (key text, value blob, UNIQUE(key) ON CONFLICT REPLACE)''' ) + self.cur.execute( '''INSERT INTO settings VALUES('version','1')''') + self.cur.execute( '''INSERT INTO settings VALUES('lastvacuumtime',?)''', ( int(time.time()),)) logger.debug('In messages.dat database, removing an obsolete field from the pubkeys table.') self.cur.execute( - '''CREATE TEMPORARY TABLE pubkeys_backup(hash blob, transmitdata blob, time int,''' - ''' usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE);''') + '''CREATE TEMPORARY TABLE pubkeys_backup(hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE);''') self.cur.execute( '''INSERT INTO pubkeys_backup SELECT hash, transmitdata, time, usedpersonally FROM pubkeys;''') - self.cur.execute('''DROP TABLE pubkeys''') + self.cur.execute( '''DROP TABLE pubkeys''') self.cur.execute( - '''CREATE TABLE pubkeys''' - ''' (hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE)''') + '''CREATE TABLE pubkeys (hash blob, transmitdata blob, time int, usedpersonally text, UNIQUE(hash) ON CONFLICT REPLACE)''' ) self.cur.execute( '''INSERT INTO pubkeys SELECT hash, transmitdata, time, usedpersonally FROM pubkeys_backup;''') - self.cur.execute('''DROP TABLE pubkeys_backup;''') - logger.debug( - 'Deleting all pubkeys from inventory.' - ' They will be redownloaded and then saved with the correct times.') + self.cur.execute( '''DROP TABLE pubkeys_backup;''') + logger.debug('Deleting all pubkeys from inventory. They will be redownloaded and then saved with the correct times.') self.cur.execute( '''delete from inventory where objecttype = 'pubkey';''') logger.debug('replacing Bitmessage announcements mailing list with a new one.') self.cur.execute( '''delete from subscriptions where address='BM-BbkPSZbzPwpVcYZpU4yHwf9ZPEapN5Zx' ''') self.cur.execute( - '''INSERT INTO subscriptions VALUES''' - '''('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') + '''INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1)''') logger.debug('Commiting.') self.conn.commit() logger.debug('Vacuuming message.dat. You might notice that the file size gets much smaller.') - self.cur.execute(''' VACUUM ''') + self.cur.execute( ''' VACUUM ''') # After code refactoring, the possible status values for sent messages # have changed. @@ -188,32 +178,41 @@ class sqlThread(threading.Thread): self.cur.execute( '''update sent set status='broadcastqueued' where status='broadcastpending' ''') self.conn.commit() + + if not BMConfigParser().has_option('bitmessagesettings', 'sockslisten'): + BMConfigParser().set('bitmessagesettings', 'sockslisten', 'false') + + ensureNamecoinOptions() + + """# Add a new column to the inventory table to store the first 20 bytes of encrypted messages to support Android app + item = '''SELECT value FROM settings WHERE key='version';''' + parameters = '' + self.cur.execute(item, parameters) + if int(self.cur.fetchall()[0][0]) == 1: + print 'upgrading database' + item = '''ALTER TABLE inventory ADD first20bytesofencryptedmessage blob DEFAULT '' ''' + parameters = '' + self.cur.execute(item, parameters) + item = '''update settings set value=? WHERE key='version';''' + parameters = (2,) + self.cur.execute(item, parameters)""" - # Let's get rid of the first20bytesofencryptedmessage field in - # the inventory table. + # Let's get rid of the first20bytesofencryptedmessage field in the inventory table. item = '''SELECT value FROM settings WHERE key='version';''' parameters = '' self.cur.execute(item, parameters) if int(self.cur.fetchall()[0][0]) == 2: - logger.debug( - 'In messages.dat database, removing an obsolete field from' - ' the inventory table.') + logger.debug('In messages.dat database, removing an obsolete field from the inventory table.') self.cur.execute( - '''CREATE TEMPORARY TABLE inventory_backup''' - '''(hash blob, objecttype text, streamnumber int, payload blob,''' - ''' receivedtime integer, UNIQUE(hash) ON CONFLICT REPLACE);''') + '''CREATE TEMPORARY TABLE inventory_backup(hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, UNIQUE(hash) ON CONFLICT REPLACE);''') self.cur.execute( - '''INSERT INTO inventory_backup SELECT hash, objecttype, streamnumber, payload, receivedtime''' - ''' FROM inventory;''') - self.cur.execute('''DROP TABLE inventory''') + '''INSERT INTO inventory_backup SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory;''') + self.cur.execute( '''DROP TABLE inventory''') self.cur.execute( - '''CREATE TABLE inventory''' - ''' (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer,''' - ''' UNIQUE(hash) ON CONFLICT REPLACE)''') + '''CREATE TABLE inventory (hash blob, objecttype text, streamnumber int, payload blob, receivedtime integer, UNIQUE(hash) ON CONFLICT REPLACE)''' ) self.cur.execute( - '''INSERT INTO inventory SELECT hash, objecttype, streamnumber, payload, receivedtime''' - ''' FROM inventory_backup;''') - self.cur.execute('''DROP TABLE inventory_backup;''') + '''INSERT INTO inventory SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory_backup;''') + self.cur.execute( '''DROP TABLE inventory_backup;''') item = '''update settings set value=? WHERE key='version';''' parameters = (3,) self.cur.execute(item, parameters) @@ -224,9 +223,7 @@ class sqlThread(threading.Thread): self.cur.execute(item, parameters) currentVersion = int(self.cur.fetchall()[0][0]) if currentVersion == 1 or currentVersion == 3: - logger.debug( - 'In messages.dat database, adding tag field to' - ' the inventory table.') + logger.debug('In messages.dat database, adding tag field to the inventory table.') item = '''ALTER TABLE inventory ADD tag blob DEFAULT '' ''' parameters = '' self.cur.execute(item, parameters) @@ -234,6 +231,19 @@ class sqlThread(threading.Thread): parameters = (4,) self.cur.execute(item, parameters) + if not BMConfigParser().has_option('bitmessagesettings', 'userlocale'): + BMConfigParser().set('bitmessagesettings', 'userlocale', 'system') + if not BMConfigParser().has_option('bitmessagesettings', 'sendoutgoingconnections'): + BMConfigParser().set('bitmessagesettings', 'sendoutgoingconnections', 'True') + + # Raise the default required difficulty from 1 to 2 + # With the change to protocol v3, this is obsolete. + if BMConfigParser().getint('bitmessagesettings', 'settingsversion') == 6: + """if int(shared.config.get('bitmessagesettings','defaultnoncetrialsperbyte')) == defaults.networkDefaultProofOfWorkNonceTrialsPerByte: + shared.config.set('bitmessagesettings','defaultnoncetrialsperbyte', str(defaults.networkDefaultProofOfWorkNonceTrialsPerByte * 2)) + """ + BMConfigParser().set('bitmessagesettings', 'settingsversion', '7') + # Add a new column to the pubkeys table to store the address version. # We're going to trash all of our pubkeys and let them be redownloaded. item = '''SELECT value FROM settings WHERE key='version';''' @@ -241,15 +251,27 @@ class sqlThread(threading.Thread): self.cur.execute(item, parameters) currentVersion = int(self.cur.fetchall()[0][0]) if currentVersion == 4: - self.cur.execute('''DROP TABLE pubkeys''') + self.cur.execute( '''DROP TABLE pubkeys''') self.cur.execute( - '''CREATE TABLE pubkeys (hash blob, addressversion int, transmitdata blob, time int,''' - '''usedpersonally text, UNIQUE(hash, addressversion) ON CONFLICT REPLACE)''') + '''CREATE TABLE pubkeys (hash blob, addressversion int, transmitdata blob, time int, usedpersonally text, UNIQUE(hash, addressversion) ON CONFLICT REPLACE)''' ) self.cur.execute( '''delete from inventory where objecttype = 'pubkey';''') item = '''update settings set value=? WHERE key='version';''' parameters = (5,) self.cur.execute(item, parameters) + + if not BMConfigParser().has_option('bitmessagesettings', 'useidenticons'): + BMConfigParser().set('bitmessagesettings', 'useidenticons', 'True') + if not BMConfigParser().has_option('bitmessagesettings', 'identiconsuffix'): # acts as a salt + BMConfigParser().set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons + + #Add settings to support no longer resending messages after a certain period of time even if we never get an ack + if BMConfigParser().getint('bitmessagesettings', 'settingsversion') == 7: + BMConfigParser().set( + 'bitmessagesettings', 'stopresendingafterxdays', '') + BMConfigParser().set( + 'bitmessagesettings', 'stopresendingafterxmonths', '') + BMConfigParser().set('bitmessagesettings', 'settingsversion', '8') # Add a new table: objectprocessorqueue with which to hold objects # that have yet to be processed if the user shuts down Bitmessage. @@ -258,57 +280,79 @@ class sqlThread(threading.Thread): self.cur.execute(item, parameters) currentVersion = int(self.cur.fetchall()[0][0]) if currentVersion == 5: - self.cur.execute('''DROP TABLE knownnodes''') + self.cur.execute( '''DROP TABLE knownnodes''') self.cur.execute( - '''CREATE TABLE objectprocessorqueue''' - ''' (objecttype text, data blob, UNIQUE(objecttype, data) ON CONFLICT REPLACE)''') + '''CREATE TABLE objectprocessorqueue (objecttype text, data blob, UNIQUE(objecttype, data) ON CONFLICT REPLACE)''' ) item = '''update settings set value=? WHERE key='version';''' parameters = (6,) self.cur.execute(item, parameters) - + # changes related to protocol v3 - # In table inventory and objectprocessorqueue, objecttype is now - # an integer (it was a human-friendly string previously) + # In table inventory and objectprocessorqueue, objecttype is now an integer (it was a human-friendly string previously) item = '''SELECT value FROM settings WHERE key='version';''' parameters = '' self.cur.execute(item, parameters) currentVersion = int(self.cur.fetchall()[0][0]) if currentVersion == 6: - logger.debug( - 'In messages.dat database, dropping and recreating' - ' the inventory table.') - self.cur.execute('''DROP TABLE inventory''') - self.cur.execute( - '''CREATE TABLE inventory''' - ''' (hash blob, objecttype int, streamnumber int, payload blob, expirestime integer,''' - ''' tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''') - self.cur.execute('''DROP TABLE objectprocessorqueue''') - self.cur.execute( - '''CREATE TABLE objectprocessorqueue''' - ''' (objecttype int, data blob, UNIQUE(objecttype, data) ON CONFLICT REPLACE)''') + logger.debug('In messages.dat database, dropping and recreating the inventory table.') + self.cur.execute( '''DROP TABLE inventory''') + self.cur.execute( '''CREATE TABLE inventory (hash blob, objecttype int, streamnumber int, payload blob, expirestime integer, tag blob, UNIQUE(hash) ON CONFLICT REPLACE)''' ) + self.cur.execute( '''DROP TABLE objectprocessorqueue''') + self.cur.execute( '''CREATE TABLE objectprocessorqueue (objecttype int, data blob, UNIQUE(objecttype, data) ON CONFLICT REPLACE)''' ) item = '''update settings set value=? WHERE key='version';''' parameters = (7,) self.cur.execute(item, parameters) - logger.debug( - 'Finished dropping and recreating the inventory table.') + logger.debug('Finished dropping and recreating the inventory table.') + + # With the change to protocol version 3, reset the user-settable difficulties to 1 + if BMConfigParser().getint('bitmessagesettings', 'settingsversion') == 8: + BMConfigParser().set('bitmessagesettings','defaultnoncetrialsperbyte', str(defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) + BMConfigParser().set('bitmessagesettings','defaultpayloadlengthextrabytes', str(defaults.networkDefaultPayloadLengthExtraBytes)) + previousTotalDifficulty = int(BMConfigParser().getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte')) / 320 + previousSmallMessageDifficulty = int(BMConfigParser().getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes')) / 14000 + BMConfigParser().set('bitmessagesettings','maxacceptablenoncetrialsperbyte', str(previousTotalDifficulty * 1000)) + BMConfigParser().set('bitmessagesettings','maxacceptablepayloadlengthextrabytes', str(previousSmallMessageDifficulty * 1000)) + BMConfigParser().set('bitmessagesettings', 'settingsversion', '9') + + # 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().addressses(): + try: + previousTotalDifficulty = float(BMConfigParser().getint(addressInKeysFile, 'noncetrialsperbyte')) / 320 + previousSmallMessageDifficulty = float(BMConfigParser().getint(addressInKeysFile, 'payloadlengthextrabytes')) / 14000 + if previousTotalDifficulty <= 2: + previousTotalDifficulty = 1 + if previousSmallMessageDifficulty < 1: + previousSmallMessageDifficulty = 1 + BMConfigParser().set(addressInKeysFile,'noncetrialsperbyte', str(int(previousTotalDifficulty * 1000))) + BMConfigParser().set(addressInKeysFile,'payloadlengthextrabytes', str(int(previousSmallMessageDifficulty * 1000))) + except: + continue + BMConfigParser().set('bitmessagesettings', 'maxdownloadrate', '0') + BMConfigParser().set('bitmessagesettings', 'maxuploadrate', '0') + BMConfigParser().set('bitmessagesettings', 'settingsversion', '10') + BMConfigParser().save() + + # sanity check + if BMConfigParser().getint('bitmessagesettings', 'maxacceptablenoncetrialsperbyte') == 0: + BMConfigParser().set('bitmessagesettings','maxacceptablenoncetrialsperbyte', str(defaults.ridiculousDifficulty * defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) + if BMConfigParser().getint('bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') == 0: + BMConfigParser().set('bitmessagesettings','maxacceptablepayloadlengthextrabytes', str(defaults.ridiculousDifficulty * defaults.networkDefaultPayloadLengthExtraBytes)) # The format of data stored in the pubkeys table has changed. Let's - # clear it, and the pubkeys from inventory, so that they'll - # be re-downloaded. + # clear it, and the pubkeys from inventory, so that they'll be re-downloaded. item = '''SELECT value FROM settings WHERE key='version';''' parameters = '' self.cur.execute(item, parameters) currentVersion = int(self.cur.fetchall()[0][0]) if currentVersion == 7: - logger.debug( - 'In messages.dat database, clearing pubkeys table' - ' because the data format has been updated.') + logger.debug('In messages.dat database, clearing pubkeys table because the data format has been updated.') self.cur.execute( '''delete from inventory where objecttype = 1;''') self.cur.execute( '''delete from pubkeys;''') - # Any sending messages for which we *thought* that we had - # the pubkey must be rechecked. + # Any sending messages for which we *thought* that we had the pubkey must + # be rechecked. self.cur.execute( '''UPDATE sent SET status='msgqueued' WHERE status='doingmsgpow' or status='badkey';''') query = '''update settings set value=? WHERE key='version';''' @@ -316,114 +360,99 @@ class sqlThread(threading.Thread): self.cur.execute(query, parameters) logger.debug('Finished clearing currently held pubkeys.') - # Add a new column to the inbox table to store the hash of - # the message signature. We'll use this as temporary message UUID - # in order to detect duplicates. + # Add a new column to the inbox table to store the hash of the message signature. + # We'll use this as temporary message UUID in order to detect duplicates. item = '''SELECT value FROM settings WHERE key='version';''' parameters = '' self.cur.execute(item, parameters) currentVersion = int(self.cur.fetchall()[0][0]) if currentVersion == 8: - logger.debug( - 'In messages.dat database, adding sighash field to' - ' the inbox table.') + logger.debug('In messages.dat database, adding sighash field to the inbox table.') item = '''ALTER TABLE inbox ADD sighash blob DEFAULT '' ''' parameters = '' self.cur.execute(item, parameters) item = '''update settings set value=? WHERE key='version';''' parameters = (9,) self.cur.execute(item, parameters) - - # We'll also need a `sleeptill` field and a `ttl` field. Also we - # can combine the pubkeyretrynumber and msgretrynumber into one. - + + # TTL is now user-specifiable. Let's add an option to save whatever the user selects. + if not BMConfigParser().has_option('bitmessagesettings', 'ttl'): + BMConfigParser().set('bitmessagesettings', 'ttl', '367200') + # We'll also need a `sleeptill` field and a `ttl` field. Also we can combine + # the pubkeyretrynumber and msgretrynumber into one. item = '''SELECT value FROM settings WHERE key='version';''' parameters = '' self.cur.execute(item, parameters) currentVersion = int(self.cur.fetchall()[0][0]) if currentVersion == 9: - logger.info( - 'In messages.dat database, making TTL-related changes:' - ' combining the pubkeyretrynumber and msgretrynumber' - ' fields into the retrynumber field and adding the' - ' sleeptill and ttl fields...') + logger.info('In messages.dat database, making TTL-related changes: combining the pubkeyretrynumber and msgretrynumber fields into the retrynumber field and adding the sleeptill and ttl fields...') self.cur.execute( - '''CREATE TEMPORARY TABLE sent_backup''' - ''' (msgid blob, toaddress text, toripe blob, fromaddress text, subject text, message text,''' - ''' ackdata blob, lastactiontime integer, status text, retrynumber integer,''' - ''' folder text, encodingtype int)''') + '''CREATE TEMPORARY TABLE sent_backup (msgid blob, toaddress text, toripe blob, fromaddress text, subject text, message text, ackdata blob, lastactiontime integer, status text, retrynumber integer, folder text, encodingtype int)''' ) self.cur.execute( - '''INSERT INTO sent_backup SELECT msgid, toaddress, toripe, fromaddress,''' - ''' subject, message, ackdata, lastactiontime,''' - ''' status, 0, folder, encodingtype FROM sent;''') - self.cur.execute('''DROP TABLE sent''') + '''INSERT INTO sent_backup SELECT msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, status, 0, folder, encodingtype FROM sent;''') + self.cur.execute( '''DROP TABLE sent''') self.cur.execute( - '''CREATE TABLE sent''' - ''' (msgid blob, toaddress text, toripe blob, fromaddress text, subject text, message text,''' - ''' ackdata blob, senttime integer, lastactiontime integer, sleeptill int, status text,''' - ''' retrynumber integer, folder text, encodingtype int, ttl int)''') + '''CREATE TABLE sent (msgid blob, toaddress text, toripe blob, fromaddress text, subject text, message text, ackdata blob, senttime integer, lastactiontime integer, sleeptill int, status text, retrynumber integer, folder text, encodingtype int, ttl int)''' ) self.cur.execute( - '''INSERT INTO sent SELECT msgid, toaddress, toripe, fromaddress, subject, message, ackdata,''' - ''' lastactiontime, lastactiontime, 0, status, 0, folder, encodingtype, 216000 FROM sent_backup;''') - self.cur.execute('''DROP TABLE sent_backup''') + '''INSERT INTO sent SELECT msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, lastactiontime, 0, status, 0, folder, encodingtype, 216000 FROM sent_backup;''') + self.cur.execute( '''DROP TABLE sent_backup''') logger.info('In messages.dat database, finished making TTL-related changes.') logger.debug('In messages.dat database, adding address field to the pubkeys table.') # We're going to have to calculate the address for each row in the pubkeys - # table. Then we can take out the hash field. - self.cur.execute('''ALTER TABLE pubkeys ADD address text DEFAULT '' ;''') - - # replica for loop to update hashed address - self.cur.execute('''UPDATE pubkeys SET address=(enaddr(pubkeys.addressversion, 1, hash)); ''') - + # table. Then we can take out the hash field. + self.cur.execute('''ALTER TABLE pubkeys ADD address text DEFAULT '' ''') + self.cur.execute('''SELECT hash, addressversion FROM pubkeys''') + queryResult = self.cur.fetchall() + from addresses import encodeAddress + for row in queryResult: + hash, addressVersion = row + address = encodeAddress(addressVersion, 1, hash) + item = '''UPDATE pubkeys SET address=? WHERE hash=?;''' + parameters = (address, hash) + self.cur.execute(item, parameters) # Now we can remove the hash field from the pubkeys table. self.cur.execute( - '''CREATE TEMPORARY TABLE pubkeys_backup''' - ''' (address text, addressversion int, transmitdata blob, time int,''' - ''' usedpersonally text, UNIQUE(address) ON CONFLICT REPLACE)''') + '''CREATE TEMPORARY TABLE pubkeys_backup (address text, addressversion int, transmitdata blob, time int, usedpersonally text, UNIQUE(address) ON CONFLICT REPLACE)''' ) self.cur.execute( - '''INSERT INTO pubkeys_backup''' - ''' SELECT address, addressversion, transmitdata, time, usedpersonally FROM pubkeys;''') - self.cur.execute('''DROP TABLE pubkeys''') + '''INSERT INTO pubkeys_backup SELECT address, addressversion, transmitdata, time, usedpersonally FROM pubkeys;''') + self.cur.execute( '''DROP TABLE pubkeys''') self.cur.execute( - '''CREATE TABLE pubkeys''' - ''' (address text, addressversion int, transmitdata blob, time int, usedpersonally text,''' - ''' UNIQUE(address) ON CONFLICT REPLACE)''') + '''CREATE TABLE pubkeys (address text, addressversion int, transmitdata blob, time int, usedpersonally text, UNIQUE(address) ON CONFLICT REPLACE)''' ) self.cur.execute( - '''INSERT INTO pubkeys SELECT''' - ''' address, addressversion, transmitdata, time, usedpersonally FROM pubkeys_backup;''') - self.cur.execute('''DROP TABLE pubkeys_backup''') - logger.debug( - 'In messages.dat database, done adding address field to the pubkeys table' - ' and removing the hash field.') + '''INSERT INTO pubkeys SELECT address, addressversion, transmitdata, time, usedpersonally FROM pubkeys_backup;''') + self.cur.execute( '''DROP TABLE pubkeys_backup''') + logger.debug('In messages.dat database, done adding address field to the pubkeys table and removing the hash field.') self.cur.execute('''update settings set value=10 WHERE key='version';''') + + if not BMConfigParser().has_option('bitmessagesettings', 'onionhostname'): + BMConfigParser().set('bitmessagesettings', 'onionhostname', '') + if not BMConfigParser().has_option('bitmessagesettings', 'onionport'): + BMConfigParser().set('bitmessagesettings', 'onionport', '8444') + if not BMConfigParser().has_option('bitmessagesettings', 'onionbindip'): + BMConfigParser().set('bitmessagesettings', 'onionbindip', '127.0.0.1') + if not BMConfigParser().has_option('bitmessagesettings', 'smtpdeliver'): + BMConfigParser().set('bitmessagesettings', 'smtpdeliver', '') + if not BMConfigParser().has_option('bitmessagesettings', 'hidetrayconnectionnotifications'): + BMConfigParser().set('bitmessagesettings', 'hidetrayconnectionnotifications', 'false') + if BMConfigParser().has_option('bitmessagesettings', 'maxoutboundconnections'): + try: + if BMConfigParser().getint('bitmessagesettings', 'maxoutboundconnections') < 1: raise ValueError + except ValueError as err: + BMConfigParser().remove_option('bitmessagesettings', 'maxoutboundconnections') + logger.error('Your maximum outbound connections must be a number.') + if not BMConfigParser().has_option('bitmessagesettings', 'maxoutboundconnections'): + logger.info('Setting maximum outbound connections to 8.') + BMConfigParser().set('bitmessagesettings', 'maxoutboundconnections', '8') - # Update the address colunm to unique in addressbook table - item = '''SELECT value FROM settings WHERE key='version';''' - parameters = '' - self.cur.execute(item, parameters) - currentVersion = int(self.cur.fetchall()[0][0]) - if currentVersion == 10: - logger.debug( - 'In messages.dat database, updating address column to UNIQUE' - ' in the addressbook table.') - self.cur.execute( - '''ALTER TABLE addressbook RENAME TO old_addressbook''') - self.cur.execute( - '''CREATE TABLE addressbook''' - ''' (label text, address text, UNIQUE(address) ON CONFLICT IGNORE)''') - self.cur.execute( - '''INSERT INTO addressbook SELECT label, address FROM old_addressbook;''') - self.cur.execute('''DROP TABLE old_addressbook''') - self.cur.execute('''update settings set value=11 WHERE key='version';''') - + BMConfigParser().save() + # Are you hoping to add a new option to the keys.dat file of existing - # Bitmessage users or modify the SQLite database? Add it right - # above this line! - + # Bitmessage users or modify the SQLite database? Add it right above this line! + try: testpayload = '\x00\x00' t = ('1234', 1, testpayload, '12345678', 'no') - self.cur.execute('''INSERT INTO pubkeys VALUES(?,?,?,?,?)''', t) + self.cur.execute( '''INSERT INTO pubkeys VALUES(?,?,?,?,?)''', t) self.conn.commit() self.cur.execute( '''SELECT transmitdata FROM pubkeys WHERE address='1234' ''') @@ -433,29 +462,13 @@ class sqlThread(threading.Thread): self.cur.execute('''DELETE FROM pubkeys WHERE address='1234' ''') self.conn.commit() if transmitdata == '': - logger.fatal( - 'Problem: The version of SQLite you have cannot store Null values.' - ' Please download and install the latest revision of your version of Python' - ' (for example, the latest Python 2.7 revision) and try again.\n') - logger.fatal( - 'PyBitmessage will now exit very abruptly.' - ' You may now see threading errors related to this abrupt exit' - ' but the problem you need to solve is related to SQLite.\n\n') + logger.fatal('Problem: The version of SQLite you have cannot store Null values. Please download and install the latest revision of your version of Python (for example, the latest Python 2.7 revision) and try again.\n') + logger.fatal('PyBitmessage will now exit very abruptly. You may now see threading errors related to this abrupt exit but the problem you need to solve is related to SQLite.\n\n') os._exit(0) except Exception as err: if str(err) == 'database or disk is full': - logger.fatal( - '(While null value test) Alert: Your disk or data storage volume is full.' - ' sqlThread will now exit.') - queues.UISignalQueue.put(( - 'alert', ( - _translate( - "MainWindow", - "Disk full"), - _translate( - "MainWindow", - 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), - True))) + logger.fatal('(While null value test) Alert: Your disk or data storage volume is full. sqlThread will now exit.') + 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))) os._exit(0) else: logger.error(err) @@ -471,27 +484,17 @@ class sqlThread(threading.Thread): if int(value) < int(time.time()) - 86400: logger.info('It has been a long time since the messages.dat file has been vacuumed. Vacuuming now...') try: - self.cur.execute(''' VACUUM ''') + self.cur.execute( ''' VACUUM ''') except Exception as err: if str(err) == 'database or disk is full': - logger.fatal( - '(While VACUUM) Alert: Your disk or data storage volume is full.' - ' sqlThread will now exit.') - queues.UISignalQueue.put(( - 'alert', ( - _translate( - "MainWindow", - "Disk full"), - _translate( - "MainWindow", - 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), - True))) + logger.fatal('(While VACUUM) Alert: Your disk or data storage volume is full. sqlThread will now exit.') + 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))) os._exit(0) item = '''update settings set value=? WHERE key='lastvacuumtime';''' parameters = (int(time.time()),) self.cur.execute(item, parameters) - helper_sql.sql_ready.set() + state.sqlReady = True while True: item = helper_sql.sqlSubmitQueue.get() @@ -500,18 +503,8 @@ class sqlThread(threading.Thread): self.conn.commit() except Exception as err: if str(err) == 'database or disk is full': - logger.fatal( - '(While committing) Alert: Your disk or data storage volume is full.' - ' sqlThread will now exit.') - queues.UISignalQueue.put(( - 'alert', ( - _translate( - "MainWindow", - "Disk full"), - _translate( - "MainWindow", - 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), - True))) + logger.fatal('(While committing) Alert: Your disk or data storage volume is full. sqlThread will now exit.') + 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))) os._exit(0) elif item == 'exit': self.conn.close() @@ -525,18 +518,8 @@ class sqlThread(threading.Thread): self.conn.commit() except Exception as err: if str(err) == 'database or disk is full': - logger.fatal( - '(while movemessagstoprog) Alert: Your disk or data storage volume is full.' - ' sqlThread will now exit.') - queues.UISignalQueue.put(( - 'alert', ( - _translate( - "MainWindow", - "Disk full"), - _translate( - "MainWindow", - 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), - True))) + logger.fatal('(while movemessagstoprog) Alert: Your disk or data storage volume is full. sqlThread will now exit.') + 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))) os._exit(0) self.conn.close() shutil.move( @@ -551,18 +534,8 @@ class sqlThread(threading.Thread): self.conn.commit() except Exception as err: if str(err) == 'database or disk is full': - logger.fatal( - '(while movemessagstoappdata) Alert: Your disk or data storage volume is full.' - ' sqlThread will now exit.') - queues.UISignalQueue.put(( - 'alert', ( - _translate( - "MainWindow", - "Disk full"), - _translate( - "MainWindow", - 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), - True))) + logger.fatal('(while movemessagstoappdata) Alert: Your disk or data storage volume is full. sqlThread will now exit.') + 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))) os._exit(0) self.conn.close() shutil.move( @@ -575,66 +548,30 @@ class sqlThread(threading.Thread): self.cur.execute('''delete from sent where folder='trash' ''') self.conn.commit() try: - self.cur.execute(''' VACUUM ''') + self.cur.execute( ''' VACUUM ''') except Exception as err: if str(err) == 'database or disk is full': - logger.fatal( - '(while deleteandvacuume) Alert: Your disk or data storage volume is full.' - ' sqlThread will now exit.') - queues.UISignalQueue.put(( - 'alert', ( - _translate( - "MainWindow", - "Disk full"), - _translate( - "MainWindow", - 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), - True))) + logger.fatal('(while deleteandvacuume) Alert: Your disk or data storage volume is full. sqlThread will now exit.') + 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))) os._exit(0) else: parameters = helper_sql.sqlSubmitQueue.get() rowcount = 0 + # print 'item', item + # print 'parameters', parameters try: self.cur.execute(item, parameters) rowcount = self.cur.rowcount except Exception as err: if str(err) == 'database or disk is full': - logger.fatal( - '(while cur.execute) Alert: Your disk or data storage volume is full.' - ' sqlThread will now exit.') - queues.UISignalQueue.put(( - 'alert', ( - _translate( - "MainWindow", - "Disk full"), - _translate( - "MainWindow", - 'Alert: Your disk or data storage volume is full. Bitmessage will now exit.'), - True))) + logger.fatal('(while cur.execute) Alert: Your disk or data storage volume is full. sqlThread will now exit.') + 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))) os._exit(0) else: - logger.fatal( - 'Major error occurred when trying to execute a SQL statement within the sqlThread.' - ' Please tell Atheros about this error message or post it in the forum!' - ' Error occurred while trying to execute statement: "%s" Here are the parameters;' - ' you might want to censor this data with asterisks (***)' - ' as it can contain private information: %s.' - ' Here is the actual error message thrown by the sqlThread: %s', - str(item), - str(repr(parameters)), - str(err)) + logger.fatal('Major error occurred when trying to execute a SQL statement within the sqlThread. Please tell Atheros about this error message or post it in the forum! Error occurred while trying to execute statement: "%s" Here are the parameters; you might want to censor this data with asterisks (***) as it can contain private information: %s. Here is the actual error message thrown by the sqlThread: %s', str(item), str(repr(parameters)), str(err)) logger.fatal('This program shall now abruptly exit!') os._exit(0) helper_sql.sqlReturnQueue.put((self.cur.fetchall(), rowcount)) # helper_sql.sqlSubmitQueue.task_done() - - def create_function(self): - # create_function - try: - self.conn.create_function("enaddr", 3, func=encodeAddress, deterministic=True) - except (TypeError, sqlite3.NotSupportedError) as err: - logger.debug( - "Got error while pass deterministic in sqlite create function {}, Passing 3 params".format(err)) - self.conn.create_function("enaddr", 3, encodeAddress) diff --git a/src/debug.py b/src/debug.py index 639be123..79c6e64e 100644 --- a/src/debug.py +++ b/src/debug.py @@ -1,92 +1,56 @@ -""" +# -*- coding: utf-8 -*- +''' Logging and debuging facility ------------------------------ +============================= Levels: + DEBUG Detailed information, typically of interest only when diagnosing problems. + INFO Confirmation that things are working as expected. + WARNING An indication that something unexpected happened, or indicative of some problem in the + near future (e.g. ‘disk space low’). The software is still working as expected. + ERROR Due to a more serious problem, the software has not been able to perform some function. + CRITICAL A serious error, indicating that the program itself may be unable to continue running. - DEBUG - Detailed information, typically of interest only when diagnosing problems. - INFO - Confirmation that things are working as expected. - WARNING - An indication that something unexpected happened, or indicative of - some problem in the near future (e.g. 'disk space low'). The software - is still working as expected. - ERROR - Due to a more serious problem, the software has not been able to - perform some function. - CRITICAL - A serious error, indicating that the program itself may be unable to - continue running. - -There are three loggers by default: `console_only`, `file_only` and `both`. -You can configure logging in the logging.dat in the appdata dir. -It's format is described in the :func:`logging.config.fileConfig` doc. - -Use: - ->>> import logging ->>> logger = logging.getLogger('default') - -The old form: ``from debug import logger`` is also may be used, -but only in the top level modules. - -Logging is thread-safe so you don't have to worry about locks, -just import and log. -""" +There are three loggers: `console_only`, `file_only` and `both`. +Use: `from debug import logger` to import this facility into whatever module you wish to log messages from. + Logging is thread-safe so you don't have to worry about locks, just import and log. +''' import logging import logging.config import os import sys - -from six.moves import configparser - import helper_startup import state - helper_startup.loadConfig() -# Now can be overriden from a config file, which uses standard python -# logging.config.fileConfig interface -# examples are here: -# https://web.archive.org/web/20170712122006/https://bitmessage.org/forum/index.php/topic,4820.msg11163.html#msg11163 +# Now can be overriden from a config file, which uses standard python logging.config.fileConfig interface +# examples are here: https://bitmessage.org/forum/index.php/topic,4820.msg11163.html#msg11163 log_level = 'WARNING' - def log_uncaught_exceptions(ex_cls, ex, tb): - """The last resort logging function used for sys.excepthook""" logging.critical('Unhandled exception', exc_info=(ex_cls, ex, tb)) - def configureLogging(): - """ - Configure logging, - using either logging.dat file in the state.appdata dir - or dictionary with hardcoded settings. - """ - sys.excepthook = log_uncaught_exceptions - fail_msg = '' + have_logging = False try: - logging_config = os.path.join(state.appdata, 'logging.dat') - logging.config.fileConfig( - logging_config, disable_existing_loggers=False) - return ( - False, - 'Loaded logger configuration from %s' % logging_config - ) - except (OSError, configparser.NoSectionError, KeyError): - if os.path.isfile(logging_config): - fail_msg = \ - 'Failed to load logger configuration from %s, using default' \ - ' logging config\n%s' % \ - (logging_config, sys.exc_info()) + logging.config.fileConfig(os.path.join (state.appdata, 'logging.dat')) + have_logging = True + print "Loaded logger configuration from %s" % (os.path.join(state.appdata, 'logging.dat')) + except: + if os.path.isfile(os.path.join(state.appdata, 'logging.dat')): + print "Failed to load logger configuration from %s, using default logging config" % (os.path.join(state.appdata, 'logging.dat')) + print sys.exc_info() else: - # no need to confuse the user if the logger config - # is missing entirely - fail_msg = 'Using default logger configuration' + # no need to confuse the user if the logger config is missing entirely + print "Using default logger configuration" + + sys.excepthook = log_uncaught_exceptions - logging_config = { + if have_logging: + return False + + logging.config.dictConfig({ 'version': 1, 'formatters': { 'default': { @@ -104,8 +68,8 @@ def configureLogging(): 'class': 'logging.handlers.RotatingFileHandler', 'formatter': 'default', 'level': log_level, - 'filename': os.path.join(state.appdata, 'debug.log'), - 'maxBytes': 2097152, # 2 MiB + 'filename': state.appdata + 'debug.log', + 'maxBytes': 2097152, # 2 MiB 'backupCount': 1, 'encoding': 'UTF-8', } @@ -113,45 +77,45 @@ def configureLogging(): 'loggers': { 'console_only': { 'handlers': ['console'], - 'propagate': 0 + 'propagate' : 0 }, 'file_only': { 'handlers': ['file'], - 'propagate': 0 + 'propagate' : 0 }, 'both': { 'handlers': ['console', 'file'], - 'propagate': 0 + 'propagate' : 0 }, }, 'root': { 'level': log_level, 'handlers': ['console'], }, - } + }) + return True - logging_config['loggers']['default'] = logging_config['loggers'][ - 'file_only' if '-c' in sys.argv else 'both'] - logging.config.dictConfig(logging_config) +# TODO (xj9): Get from a config file. +#logger = logging.getLogger('console_only') +if configureLogging(): + if '-c' in sys.argv: + logger = logging.getLogger('file_only') + else: + logger = logging.getLogger('both') +else: + logger = logging.getLogger('default') - return True, fail_msg - - -def resetLogging(): - """Reconfigure logging in runtime when state.appdata dir changed""" - # pylint: disable=global-statement, used-before-assignment +def restartLoggingInUpdatedAppdataLocation(): global logger - for i in logger.handlers: + for i in list(logger.handlers): logger.removeHandler(i) i.flush() i.close() - configureLogging() - logger = logging.getLogger('default') + if configureLogging(): + if '-c' in sys.argv: + logger = logging.getLogger('file_only') + else: + logger = logging.getLogger('both') + else: + logger = logging.getLogger('default') - -# ! - -preconfigured, msg = configureLogging() -logger = logging.getLogger('default') -if msg: - logger.log(logging.WARNING if preconfigured else logging.INFO, msg) diff --git a/src/default.ini b/src/default.ini deleted file mode 100644 index d4420ba5..00000000 --- a/src/default.ini +++ /dev/null @@ -1,46 +0,0 @@ -[bitmessagesettings] -maxaddrperstreamsend = 500 -maxbootstrapconnections = 20 -maxdownloadrate = 0 -maxoutboundconnections = 8 -maxtotalconnections = 200 -maxuploadrate = 0 -apiinterface = 127.0.0.1 -apiport = 8442 -udp = True -port = 8444 -timeformat = %%c -blackwhitelist = black -startonlogon = False -showtraynotifications = True -startintray = False -socksproxytype = none -sockshostname = localhost -socksport = 9050 -socksauthentication = False -socksusername = -sockspassword = -keysencrypted = False -messagesencrypted = False -minimizeonclose = False -replybelow = False -stopresendingafterxdays = -stopresendingafterxmonths = -opencl = - -[threads] -receive = 3 - -[network] -bind = -dandelion = 90 - -[inventory] -storage = sqlite -acceptmismatch = False - -[knownnodes] -maxnodes = 20000 - -[zlib] -maxsize = 1048576 diff --git a/src/defaultKnownNodes.py b/src/defaultKnownNodes.py new file mode 100644 index 00000000..05b65014 --- /dev/null +++ b/src/defaultKnownNodes.py @@ -0,0 +1,82 @@ +import pickle +import socket +from struct import * +import time +import random +import sys +from time import strftime, localtime +import state + +def createDefaultKnownNodes(appdata): + ############## Stream 1 ################ + stream1 = {} + + #stream1[state.Peer('2604:2000:1380:9f:82e:148b:2746:d0c7', 8080)] = 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 = {} + # None yet + + ############# Stream 3 ################# + stream3 = {} + # None yet + + allKnownNodes = {} + allKnownNodes[1] = stream1 + allKnownNodes[2] = stream2 + allKnownNodes[3] = stream3 + + #print stream1 + #print allKnownNodes + + with open(appdata + 'knownnodes.dat', 'wb') as output: + # Pickle dictionary using protocol 0. + pickle.dump(allKnownNodes, output) + + return allKnownNodes + +def readDefaultKnownNodes(appdata): + pickleFile = open(appdata + 'knownnodes.dat', 'rb') + knownNodes = pickle.load(pickleFile) + pickleFile.close() + for stream, storedValue in knownNodes.items(): + for host,value in storedValue.items(): + try: + # Old knownNodes format. + port, storedtime = value + except: + # New knownNodes format. + host, port = host + storedtime = value + print host, '\t', port, '\t', unicode(strftime('%a, %d %b %Y %I:%M %p',localtime(storedtime)),'utf-8') + +if __name__ == "__main__": + + APPNAME = "PyBitmessage" + from os import path, environ + if sys.platform == 'darwin': + from AppKit import NSSearchPathForDirectoriesInDomains # @UnresolvedImport + # http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/Reference/reference.html#//apple_ref/c/func/NSSearchPathForDirectoriesInDomains + # NSApplicationSupportDirectory = 14 + # NSUserDomainMask = 1 + # True for expanding the tilde into a fully qualified path + appdata = path.join(NSSearchPathForDirectoriesInDomains(14, 1, True)[0], APPNAME) + '/' + elif 'win' in sys.platform: + appdata = path.join(environ['APPDATA'], APPNAME) + '\\' + else: + appdata = path.expanduser(path.join("~", "." + APPNAME + "/")) + + + print 'New list of all known nodes:', createDefaultKnownNodes(appdata) + readDefaultKnownNodes(appdata) + + diff --git a/src/defaults.py b/src/defaults.py index 32162b56..8401bf2e 100644 --- a/src/defaults.py +++ b/src/defaults.py @@ -1,24 +1,13 @@ -""" -Common default values -""" - -#: sanity check, prevent doing ridiculous PoW -#: 20 million PoWs equals approximately 2 days on dev's dual R9 290 +# sanity check, prevent doing ridiculous PoW +# 20 million PoWs equals approximately 2 days on dev's dual R9 290 ridiculousDifficulty = 20000000 -#: Remember here the RPC port read from namecoin.conf so we can restore to -#: it as default whenever the user changes the "method" selection for -#: namecoin integration to "namecoind". +# Remember here the RPC port read from namecoin.conf so we can restore to +# it as default whenever the user changes the "method" selection for +# namecoin integration to "namecoind". namecoinDefaultRpcPort = "8336" -# If changed, these values will cause particularly unexpected behavior: -# You won't be able to either send or receive messages because the proof -# of work you do (or demand) won't match that done or demanded by others. -# Don't change them! -#: The amount of work that should be performed (and demanded) per byte -#: of the payload. -networkDefaultProofOfWorkNonceTrialsPerByte = 1000 -#: To make sending short messages a little more difficult, this value is -#: added to the payload length for use in calculating the proof of work -#: target. -networkDefaultPayloadLengthExtraBytes = 1000 +#If changed, these values will cause particularly unexpected behavior: You won't be able to either send or receive messages because the proof of work you do (or demand) won't match that done or demanded by others. Don't change them! +networkDefaultProofOfWorkNonceTrialsPerByte = 1000 #The amount of work that should be performed (and demanded) per byte of the payload. +networkDefaultPayloadLengthExtraBytes = 1000 #To make sending short messages a little more difficult, this value is added to the payload length for use in calculating the proof of work target. + diff --git a/src/depends.py b/src/depends.py index d966d5fe..d66663b1 100755 --- a/src/depends.py +++ b/src/depends.py @@ -1,249 +1,75 @@ -""" -Utility functions to check the availability of dependencies -and suggest how it may be installed -""" +#! python -import os -import re import sys +import os +import pyelliptic.openssl -# Only really old versions of Python don't have sys.hexversion. We don't -# support them. The logging module was introduced in Python 2.3 +#Only really old versions of Python don't have sys.hexversion. We don't support +#them. The logging module was introduced in Python 2.3 if not hasattr(sys, 'hexversion') or sys.hexversion < 0x20300F0: - sys.exit( - 'Python version: %s\n' - 'PyBitmessage requires Python 2.7.4 or greater (but not Python 3)' - % sys.version - ) + sys.stdout.write('Python version: ' + sys.version) + sys.stdout.write('PyBitmessage requires Python 2.7.3 or greater (but not Python 3)') + sys.exit() -import logging # noqa:E402 -import subprocess # nosec B404 - -from importlib import import_module - -# We can now use logging so set up a simple configuration -formatter = logging.Formatter('%(levelname)s: %(message)s') +#We can now use logging so set up a simple configuration +import logging +formatter = logging.Formatter( + '%(levelname)s: %(message)s' +) handler = logging.StreamHandler(sys.stdout) handler.setFormatter(formatter) -logger = logging.getLogger('both') +logger = logging.getLogger(__name__) logger.addHandler(handler) logger.setLevel(logging.ERROR) - -OS_RELEASE = { - "Debian GNU/Linux".lower(): "Debian", - "fedora": "Fedora", - "opensuse": "openSUSE", - "ubuntu": "Ubuntu", - "gentoo": "Gentoo", - "calculate": "Gentoo" -} - -PACKAGE_MANAGER = { - "OpenBSD": "pkg_add", - "FreeBSD": "pkg install", - "Debian": "apt-get install", - "Ubuntu": "apt-get install", - "Ubuntu 12": "apt-get install", - "Ubuntu 20": "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", - "Ubuntu 20": "", - "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", - "Ubuntu 20": "", - "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", - "Ubuntu 20": "", - "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", - "Ubuntu 20": "python-setuptools", - "Fedora": "python2-setuptools", - "openSUSE": "python-setuptools", - "Guix": "python2-setuptools", - "Gentoo": "dev-python/setuptools", - "optional": False, - }, - "six": { - "OpenBSD": "py-six", - "FreeBSD": "py27-six", - "Debian": "python-six", - "Ubuntu": "python-six", - "Ubuntu 12": "python-six", - "Ubuntu 20": "python-six", - "Fedora": "python-six", - "openSUSE": "python-six", - "Guix": "python-six", - "Gentoo": "dev-python/six", - "optional": False, - } -} - - -def detectOS(): - """Finding out what Operating System is running""" - 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 - - -detectOS.result = None - - -def detectOSRelease(): - """Detecting the release of OS""" - with open("/etc/os-release", 'r') as osRelease: - version = None - for line in osRelease: - if line.startswith("NAME="): - detectOS.result = OS_RELEASE.get( - line.replace('"', '').split("=")[-1].strip().lower()) - elif 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" - elif detectOS.result == "Ubuntu" and version >= 20: - detectOS.result = "Ubuntu 20" - - -def try_import(module, log_extra=False): - """Try to import the non imported packages""" - try: - return import_module(module) - except ImportError: - module = module.split('.')[0] - logger.error('The %s module is not available.', module) - if log_extra: - logger.error(log_extra) - dist = detectOS() - logger.error( - 'On %s, try running "%s %s" as root.', - dist, PACKAGE_MANAGER[dist], PACKAGES[module][dist]) +#We need to check hashlib for RIPEMD-160, as it won't be available if OpenSSL is +#not linked against or the linked OpenSSL has RIPEMD disabled. +def check_hashlib(): + if sys.hexversion < 0x020500F0: + logger.error('The hashlib module is not included in this version of Python.') return False - - -def check_ripemd160(): - """Check availability of the RIPEMD160 hash function""" - try: - from fallback import RIPEMD160Hash # pylint: disable=relative-import - except ImportError: + import hashlib + if '_hashlib' not in hashlib.__dict__: + logger.error('The RIPEMD-160 hash algorithm is not available. The hashlib module is not linked against OpenSSL.') return False - return RIPEMD160Hash is not None - + try: + hashlib.new('ripemd160') + except ValueError: + logger.error('The RIPEMD-160 hash algorithm is not available. The hashlib module utilizes an OpenSSL library with RIPEMD disabled.') + return False + return True def check_sqlite(): - """Do sqlite check. - - Simply check sqlite3 module if exist or not with hexversion - support in python version for specifieed platform. - """ if sys.hexversion < 0x020500F0: - logger.error( - 'The sqlite3 module is not included in this version of Python.') + logger.error('The sqlite3 module is not included in this version of Python.') if sys.platform.startswith('freebsd'): - logger.error( - 'On FreeBSD, try running "pkg install py27-sqlite3" as root.') + logger.error('On FreeBSD, try running "pkg install py27-sqlite3" as root.') + return False + try: + import sqlite3 + except ImportError: + logger.error('The sqlite3 module is not available') return False - sqlite3 = try_import('sqlite3') - if not sqlite3: - return False - - logger.info('sqlite3 Module Version: %s', sqlite3.version) - logger.info('SQLite Library Version: %s', sqlite3.sqlite_version) - # sqlite_version_number formula: https://sqlite.org/c3ref/c_source_id.html - sqlite_version_number = ( - sqlite3.sqlite_version_info[0] * 1000000 - + sqlite3.sqlite_version_info[1] * 1000 - + sqlite3.sqlite_version_info[2] - ) + logger.info('sqlite3 Module Version: ' + sqlite3.version) + logger.info('SQLite Library Version: ' + sqlite3.sqlite_version) + #sqlite_version_number formula: https://sqlite.org/c3ref/c_source_id.html + sqlite_version_number = sqlite3.sqlite_version_info[0] * 1000000 + sqlite3.sqlite_version_info[1] * 1000 + sqlite3.sqlite_version_info[2] conn = None try: try: conn = sqlite3.connect(':memory:') if sqlite_version_number >= 3006018: - sqlite_source_id = conn.execute( - 'SELECT sqlite_source_id();' - ).fetchone()[0] - logger.info('SQLite Library Source ID: %s', sqlite_source_id) + sqlite_source_id = conn.execute('SELECT sqlite_source_id();').fetchone()[0] + logger.info('SQLite Library Source ID: ' + sqlite_source_id) if sqlite_version_number >= 3006023: - compile_options = ', '.join( - [row[0] for row in conn.execute('PRAGMA compile_options;')]) - logger.info( - 'SQLite Library Compile Options: %s', compile_options) - # There is no specific version requirement as yet, so we just - # use the first version that was included with Python. + compile_options = ', '.join(map(lambda row: row[0], conn.execute('PRAGMA compile_options;'))) + logger.info('SQLite Library Compile Options: ' + compile_options) + #There is no specific version requirement as yet, so we just use the + #first version that was included with Python. if sqlite_version_number < 3000008: - logger.error( - 'This version of SQLite is too old.' - ' PyBitmessage requires SQLite 3.0.8 or later') + logger.error('This version of SQLite is too old. PyBitmessage requires SQLite 3.0.8 or later') return False return True except sqlite3.Error: @@ -253,41 +79,35 @@ def check_sqlite(): if conn: conn.close() - def check_openssl(): - """Do openssl dependency check. - - Here we are checking for openssl with its all dependent libraries - and version checking. - """ - # pylint: disable=too-many-branches, too-many-return-statements - # pylint: disable=protected-access, redefined-outer-name - ctypes = try_import('ctypes') - if not ctypes: - logger.error('Unable to check OpenSSL.') + try: + import ctypes + except ImportError: + logger.error('Unable to check OpenSSL. The ctypes module is not available.') return False - # We need to emulate the way PyElliptic searches for OpenSSL. + #We need to emulate the way PyElliptic searches for OpenSSL. if sys.platform == 'win32': paths = ['libeay32.dll'] if getattr(sys, 'frozen', False): + import os.path paths.insert(0, os.path.join(sys._MEIPASS, 'libeay32.dll')) else: - paths = ['libcrypto.so', 'libcrypto.so.1.0.0'] + paths = ['libcrypto.so'] if sys.platform == 'darwin': paths.extend([ 'libcrypto.dylib', '/usr/local/opt/openssl/lib/libcrypto.dylib', './../Frameworks/libcrypto.dylib' ]) - + import re if re.match(r'linux|darwin|freebsd', sys.platform): try: import ctypes.util path = ctypes.util.find_library('ssl') if path not in paths: paths.append(path) - except: # nosec B110 # pylint:disable=bare-except + except: pass openssl_version = None @@ -296,175 +116,150 @@ def check_openssl(): cflags_regex = re.compile(r'(?:OPENSSL_NO_)(AES|EC|ECDH|ECDSA)(?!\w)') - import pyelliptic.openssl - for path in paths: - logger.info('Checking OpenSSL at %s', path) + logger.info('Checking OpenSSL at ' + path) try: library = ctypes.CDLL(path) except OSError: continue - logger.info('OpenSSL Name: %s', library._name) - try: - openssl_version, openssl_hexversion, openssl_cflags = \ - pyelliptic.openssl.get_version(library) - except AttributeError: # sphinx chokes - return True + logger.info('OpenSSL Name: ' + library._name) + openssl_version, openssl_hexversion, openssl_cflags = pyelliptic.openssl.get_version(library) if not openssl_version: logger.error('Cannot determine version of this OpenSSL library.') return False - logger.info('OpenSSL Version: %s', openssl_version) - logger.info('OpenSSL Compile Options: %s', openssl_cflags) - # PyElliptic uses EVP_CIPHER_CTX_new and EVP_CIPHER_CTX_free which were - # introduced in 0.9.8b. + logger.info('OpenSSL Version: ' + openssl_version) + logger.info('OpenSSL Compile Options: ' + openssl_cflags) + #PyElliptic uses EVP_CIPHER_CTX_new and EVP_CIPHER_CTX_free which were + #introduced in 0.9.8b. if openssl_hexversion < 0x90802F: - logger.error( - 'This OpenSSL library is too old. PyBitmessage requires' - ' OpenSSL 0.9.8b or later with AES, Elliptic Curves (EC),' - ' ECDH, and ECDSA enabled.') + logger.error('This OpenSSL library is too old. PyBitmessage requires OpenSSL 0.9.8b or later with AES, Elliptic Curves (EC), ECDH, and ECDSA enabled.') return False - matches = cflags_regex.findall(openssl_cflags.decode('utf-8', "ignore")) - if matches: - logger.error( - 'This OpenSSL library is missing the following required' - ' features: %s. PyBitmessage requires OpenSSL 0.9.8b' - ' or later with AES, Elliptic Curves (EC), ECDH,' - ' and ECDSA enabled.', ', '.join(matches)) + matches = cflags_regex.findall(openssl_cflags) + if len(matches) > 0: + logger.error('This OpenSSL library is missing the following required features: ' + ', '.join(matches) + '. PyBitmessage requires OpenSSL 0.9.8b or later with AES, Elliptic Curves (EC), ECDH, and ECDSA enabled.') return False return True return False - -# ..todo:: The minimum versions of pythondialog and dialog need to be determined +#TODO: The minimum versions of pythondialog and dialog need to be determined def check_curses(): - """Do curses dependency check. - - Here we are checking for curses if available or not with check as interface - requires the `pythondialog `_ package - and the dialog utility. - """ if sys.hexversion < 0x20600F0: - logger.error( - 'The curses interface requires the pythondialog package and' - ' the dialog utility.') + logger.error('The curses interface requires the pythondialog package and the dialog utility.') return False - curses = try_import('curses') - if not curses: - logger.error('The curses interface can not be used.') - return False - - logger.info('curses Module Version: %s', curses.version) - - dialog = try_import('dialog') - if not dialog: - logger.error('The curses interface can not be used.') - return False - try: - subprocess.check_call(['which', 'dialog']) # nosec B603, B607 - except subprocess.CalledProcessError: - logger.error( - 'Curses requires the `dialog` command to be installed as well as' - ' the python library.') + import curses + except ImportError: + logger.error('The curses interface can not be used. The curses module is not available.') return False - - logger.info('pythondialog Package Version: %s', dialog.__version__) + logger.info('curses Module Version: ' + curses.version) + try: + import dialog + except ImportError: + logger.error('The curses interface can not be used. The pythondialog package is not available.') + return False + logger.info('pythondialog Package Version: ' + dialog.__version__) dialog_util_version = dialog.Dialog().cached_backend_version - # The pythondialog author does not like Python2 str, so we have to use - # unicode for just the version otherwise we get the repr form which - # includes the module and class names along with the actual version. - logger.info('dialog Utility Version %s', dialog_util_version.decode('utf-8')) + #The pythondialog author does not like Python2 str, so we have to use + #unicode for just the version otherwise we get the repr form which includes + #the module and class names along with the actual version. + logger.info('dialog Utility Version' + unicode(dialog_util_version)) return True - def check_pyqt(): - """Do pyqt dependency check. - - Here we are checking for PyQt4 with its version, as for it require - PyQt 4.8 or later. - """ - QtCore = try_import( - 'PyQt4.QtCore', 'PyBitmessage requires PyQt 4.8 or later and Qt 4.7 or later.') - - if not QtCore: + try: + import PyQt4.QtCore + except ImportError: + logger.error('The PyQt4 package is not available. PyBitmessage requires PyQt 4.8 or later and Qt 4.7 or later.') + if sys.platform.startswith('openbsd'): + logger.error('On OpenBSD, try running "pkg_add py-qt4" as root.') + elif sys.platform.startswith('freebsd'): + logger.error('On FreeBSD, try running "pkg install py27-qt4" as root.') + elif os.path.isfile("/etc/os-release"): + with open("/etc/os-release", 'rt') as osRelease: + for line in osRelease: + if line.startswith("NAME="): + if "fedora" in line.lower(): + logger.error('On Fedora, try running "dnf install PyQt4" as root.') + elif "opensuse" in line.lower(): + logger.error('On openSUSE, try running "zypper install python-qt" as root.') + elif "ubuntu" in line.lower(): + logger.error('On Ubuntu, try running "apt-get install python-qt4" as root.') + elif "debian" in line.lower(): + logger.error('On Debian, try running "apt-get install python-qt4" as root.') + else: + logger.error('If your package manager does not have this package, try running "pip install PyQt4".') return False - - logger.info('PyQt Version: %s', QtCore.PYQT_VERSION_STR) - logger.info('Qt Version: %s', QtCore.QT_VERSION_STR) + logger.info('PyQt Version: ' + PyQt4.QtCore.PYQT_VERSION_STR) + logger.info('Qt Version: ' + PyQt4.QtCore.QT_VERSION_STR) passed = True - if QtCore.PYQT_VERSION < 0x40800: - logger.error( - 'This version of PyQt is too old. PyBitmessage requries' - ' PyQt 4.8 or later.') + if PyQt4.QtCore.PYQT_VERSION < 0x40800: + logger.error('This version of PyQt is too old. PyBitmessage requries PyQt 4.8 or later.') passed = False - if QtCore.QT_VERSION < 0x40700: - logger.error( - 'This version of Qt is too old. PyBitmessage requries' - ' Qt 4.7 or later.') + if PyQt4.QtCore.QT_VERSION < 0x40700: + logger.error('This version of Qt is too old. PyBitmessage requries Qt 4.7 or later.') passed = False return passed - def check_msgpack(): - """Do sgpack module check. + try: + import msgpack + except ImportError: + 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'): + logger.error('On FreeBSD, try running "pkg install py27-msgpack-python" as root.') + elif os.path.isfile("/etc/os-release"): + with open("/etc/os-release", 'rt') as osRelease: + for line in osRelease: + if line.startswith("NAME="): + if "fedora" in line.lower(): + logger.error('On Fedora, try running "dnf install python2-msgpack" as root.') + elif "opensuse" in line.lower(): + logger.error('On openSUSE, try running "zypper install python-msgpack-python" as root.') + elif "ubuntu" in line.lower(): + logger.error('On Ubuntu, try running "apt-get install python-msgpack" as root.') + elif "debian" in line.lower(): + logger.error('On Debian, try running "apt-get install python-msgpack" as root.') + else: + logger.error('If your package manager does not have this package, try running "pip install msgpack-python".') - simply checking if msgpack package with all its dependency - is available or not as recommended for messages coding. - """ - return try_import( - 'msgpack', 'It is highly recommended for messages coding.') is not False + return True - -def check_dependencies(verbose=False, optional=False): - """Do dependency check. - - It identifies project dependencies and checks if there are - any known, publicly disclosed, vulnerabilities.basically - scan applications (and their dependent libraries) so that - easily identify any known vulnerable components. - """ +def check_dependencies(verbose = False, optional = False): if verbose: logger.setLevel(logging.INFO) has_all_dependencies = True - # Python 2.7.4 is the required minimum. - # (https://bitmessage.org/forum/index.php?topic=4081.0) - # Python 3+ is not supported, but it is still useful to provide - # information about our other requirements. + #Python 2.7.3 is the required minimum. Python 3+ is not supported, but it is + #still useful to provide information about our other requirements. logger.info('Python version: %s', sys.version) - if sys.hexversion < 0x20704F0: - logger.error( - 'PyBitmessage requires Python 2.7.4 or greater' - ' (but not Python 3+)') + if sys.hexversion < 0x20703F0: + logger.error('PyBitmessage requires Python 2.7.3 or greater (but not Python 3+)') has_all_dependencies = False if sys.hexversion >= 0x3000000: - logger.error( - 'PyBitmessage does not support Python 3+. Python 2.7.4' - ' or greater is required. Python 2.7.18 is recommended.') - sys.exit() + logger.error('PyBitmessage does not support Python 3+. Python 2.7.3 or greater is required.') + has_all_dependencies = False - # FIXME: This needs to be uncommented when more of the code is python3 compatible - # if sys.hexversion >= 0x3000000 and sys.hexversion < 0x3060000: - # print("PyBitmessage requires python >= 3.6 if using python 3") - - check_functions = [check_ripemd160, check_sqlite, check_openssl] + check_functions = [check_hashlib, check_sqlite, check_openssl, check_msgpack] if optional: - check_functions.extend([check_msgpack, check_pyqt, check_curses]) + check_functions.extend([check_pyqt, check_curses]) - # Unexpected exceptions are handled here + #Unexpected exceptions are handled here for check in check_functions: try: has_all_dependencies &= check() - except: # noqa:E722 - logger.exception('%s failed unexpectedly.', check.__name__) + except: + logger.exception(check.__name__ + ' failed unexpectedly.') has_all_dependencies = False - + if not has_all_dependencies: - sys.exit( - 'PyBitmessage cannot start. One or more dependencies are' - ' unavailable.' - ) + logger.critical('PyBitmessage cannot start. One or more dependencies are unavailable.') + sys.exit() +if __name__ == '__main__': + check_dependencies(True, True) -logger.setLevel(0) diff --git a/src/fallback/__init__.py b/src/fallback/__init__.py index f65999a1..e69de29b 100644 --- a/src/fallback/__init__.py +++ b/src/fallback/__init__.py @@ -1,32 +0,0 @@ -""" -Fallback expressions help PyBitmessage modules to run without some external -dependencies. - - -RIPEMD160Hash -------------- - -We need to check :mod:`hashlib` for RIPEMD-160, as it won't be available -if OpenSSL is not linked against or the linked OpenSSL has RIPEMD disabled. -Try to use `pycryptodome `_ -in that case. -""" - -import hashlib - -try: - hashlib.new('ripemd160') -except ValueError: - try: - from Crypto.Hash import RIPEMD160 - except ImportError: - RIPEMD160Hash = None - else: - RIPEMD160Hash = RIPEMD160.new -else: - def RIPEMD160Hash(data=None): - """hashlib based RIPEMD160Hash""" - hasher = hashlib.new('ripemd160') - if data: - hasher.update(data) - return hasher diff --git a/src/fallback/umsgpack/umsgpack.py b/src/fallback/umsgpack/umsgpack.py index 34938614..cd7a2037 100644 --- a/src/fallback/umsgpack/umsgpack.py +++ b/src/fallback/umsgpack/umsgpack.py @@ -31,9 +31,6 @@ # THE SOFTWARE. # """ -src/fallback/umsgpack/umsgpack.py -================================= - u-msgpack-python v2.4.1 - v at sergeev.io https://github.com/vsergeev/u-msgpack-python @@ -46,13 +43,10 @@ types. License: MIT """ -# pylint: disable=too-many-lines,too-many-branches,too-many-statements,global-statement,too-many-return-statements -# pylint: disable=unused-argument - -import collections -import io import struct +import collections import sys +import io __version__ = "2.4.1" "Module version string" @@ -66,7 +60,7 @@ version = (2, 4, 1) ############################################################################## # Extension type for application-defined types and data -class Ext: # pylint: disable=old-style-class +class Ext: """ The Ext class facilitates creating a serializable extension object to store an application-defined type and data byte array. @@ -93,8 +87,6 @@ class Ext: # pylint: disable=old-style-class Ext Object (Type: 0x05, Data: 01 02 03) >>> """ - # pylint:disable=redefined-builtin - # 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") @@ -213,7 +205,7 @@ load = None loads = None compatibility = False -u""" +""" Compatibility mode boolean. When compatibility mode is enabled, u-msgpack-python will serialize both @@ -420,7 +412,7 @@ def _pack2(obj, fp, **options): _pack_ext(ext_handlers[obj.__class__](obj), fp, options) elif isinstance(obj, bool): _pack_boolean(obj, fp, options) - elif isinstance(obj, (int, long)): + elif isinstance(obj, int) or isinstance(obj, long): _pack_integer(obj, fp, options) elif isinstance(obj, float): _pack_float(obj, fp, options) @@ -432,7 +424,7 @@ def _pack2(obj, fp, **options): _pack_string(obj, fp, options) elif isinstance(obj, str): _pack_binary(obj, fp, options) - elif isinstance(obj, (list, tuple)): + elif isinstance(obj, list) or isinstance(obj, tuple): _pack_array(obj, fp, options) elif isinstance(obj, dict): _pack_map(obj, fp, options) @@ -502,7 +494,7 @@ def _pack3(obj, fp, **options): _pack_string(obj, fp, options) elif isinstance(obj, bytes): _pack_binary(obj, fp, options) - elif isinstance(obj, (list, tuple)): + elif isinstance(obj, list) or isinstance(obj, tuple): _pack_array(obj, fp, options) elif isinstance(obj, dict): _pack_map(obj, fp, options) @@ -731,7 +723,7 @@ def _unpack_array(code, fp, options): else: raise Exception("logic error, not array: 0x%02x" % ord(code)) - return [_unpack(fp, options) for _ in xrange(length)] + return [_unpack(fp, options) for i in xrange(length)] def _deep_list_to_tuple(obj): @@ -965,8 +957,6 @@ def _unpackb3(s, **options): def __init(): - # pylint: disable=global-variable-undefined - global pack global packb global unpack @@ -999,7 +989,7 @@ def __init(): unpackb = _unpackb3 load = _unpack3 loads = _unpackb3 - xrange = range # pylint: disable=redefined-builtin + xrange = range else: pack = _pack2 packb = _packb2 diff --git a/src/helper_ackPayload.py b/src/helper_ackPayload.py index d30f4c0d..ef99ec2a 100644 --- a/src/helper_ackPayload.py +++ b/src/helper_ackPayload.py @@ -1,41 +1,31 @@ -""" -This module is for generating ack payload -""" - -from binascii import hexlify -from struct import pack - -import helper_random +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): - """ - Generate and return payload obj. - - 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 - """ - if stealthLevel == 2: # Generate privacy-enhanced payload + if (stealthLevel==2): # Generate privacy-enhanced payload # Generate a dummy privkey and derive the pubkey - dummyPubKeyHex = highlevelcrypto.privToPub( - hexlify(helper_random.randomBytes(32))) + 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( - helper_random.randomrandrange(234, 801)) + 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) + elif (stealthLevel==1): # Basic privacy payload (random getpubkey) ackdata = helper_random.randomBytes(32) acktype = 0 # getpubkey version = 4 @@ -45,7 +35,6 @@ def genAckPayload(streamNumber=1, stealthLevel=0): acktype = 2 # message version = 1 - ackobject = pack('>I', acktype) + encodeVarint( - version) + encodeVarint(streamNumber) + ackdata + ackobject = pack('>I', acktype) + encodeVarint(version) + encodeVarint(streamNumber) + ackdata return ackobject diff --git a/src/helper_addressbook.py b/src/helper_addressbook.py deleted file mode 100644 index 6d354113..00000000 --- a/src/helper_addressbook.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Insert value into addressbook -""" - -from bmconfigparser import config -from helper_sql import sqlExecute - - -def insert(address, label): - """perform insert into addressbook""" - - if address not in config.addresses(): - return sqlExecute('''INSERT INTO addressbook VALUES (?,?)''', label, address) == 1 - return False diff --git a/src/helper_bitcoin.py b/src/helper_bitcoin.py index d4f1d105..d56e395b 100644 --- a/src/helper_bitcoin.py +++ b/src/helper_bitcoin.py @@ -1,19 +1,10 @@ -""" -Calculates bitcoin and testnet address from pubkey -""" - import hashlib - -from debug import logger from pyelliptic import arithmetic - +# This function expects that pubkey begin with \x04 def calculateBitcoinAddressFromPubkey(pubkey): - """Calculate bitcoin address from given pubkey (65 bytes long hex string)""" if len(pubkey) != 65: - logger.error('Could not calculate Bitcoin address from pubkey because' - ' function was passed a pubkey that was' - ' %i bytes long rather than 65.', len(pubkey)) + print 'Could not calculate Bitcoin address from pubkey because function was passed a pubkey that was', len(pubkey), 'bytes long rather than 65.' return "error" ripe = hashlib.new('ripemd160') sha = hashlib.new('sha256') @@ -33,11 +24,8 @@ def calculateBitcoinAddressFromPubkey(pubkey): def calculateTestnetAddressFromPubkey(pubkey): - """This function expects that pubkey begin with the testnet prefix""" if len(pubkey) != 65: - logger.error('Could not calculate Bitcoin address from pubkey because' - ' function was passed a pubkey that was' - ' %i bytes long rather than 65.', len(pubkey)) + print 'Could not calculate Bitcoin address from pubkey because function was passed a pubkey that was', len(pubkey), 'bytes long rather than 65.' return "error" ripe = hashlib.new('ripemd160') sha = hashlib.new('sha256') diff --git a/src/helper_bootstrap.py b/src/helper_bootstrap.py new file mode 100644 index 00000000..0ba86348 --- /dev/null +++ b/src/helper_bootstrap.py @@ -0,0 +1,110 @@ +import socket +import defaultKnownNodes +import pickle +import time + +from bmconfigparser import BMConfigParser +from debug import logger +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: + 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'): + 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. + def try_add_known_node(stream, addr, port, method=''): + try: + 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) + address_family = socket.AF_INET + sock = socks.socksocket(address_family, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.settimeout(20) + proxytype = socks.PROXY_TYPE_SOCKS5 + sockshostname = BMConfigParser().get( + 'bitmessagesettings', 'sockshostname') + socksport = BMConfigParser().getint( + 'bitmessagesettings', 'socksport') + rdns = True # Do domain name lookups through the proxy; though this setting doesn't really matter since we won't be doing any domain name lookups anyway. + if BMConfigParser().getboolean('bitmessagesettings', 'socksauthentication'): + socksusername = BMConfigParser().get( + 'bitmessagesettings', 'socksusername') + sockspassword = BMConfigParser().get( + 'bitmessagesettings', 'sockspassword') + sock.setproxy( + proxytype, sockshostname, socksport, rdns, socksusername, sockspassword) + else: + sock.setproxy( + proxytype, sockshostname, socksport, rdns) + try: + ip = sock.resolve("bootstrap" + str(port) + ".bitmessage.org") + sock.shutdown(socket.SHUT_RDWR) + sock.close() + except: + logger.error("SOCKS DNS resolving failed", exc_info=True) + else: + try_add_known_node(1, ip, port, 'SOCKS') + else: + 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 new file mode 100644 index 00000000..588ae8f1 --- /dev/null +++ b/src/helper_generic.py @@ -0,0 +1,88 @@ +import os +import socket +import sys +from binascii import hexlify, unhexlify +from multiprocessing import current_process +from threading import current_thread, enumerate +import traceback + +import shared +from debug import logger +import queues +import shutdown + +def powQueueSize(): + curWorkerQueue = queues.workerQueue.qsize() + for thread in enumerate(): + try: + if thread.name == "singleWorker": + curWorkerQueue += thread.busy + except: + pass + return curWorkerQueue + +def convertIntToString(n): + a = __builtins__.hex(n) + if a[-1:] == 'L': + a = a[:-1] + if (len(a) % 2) == 0: + return unhexlify(a[2:]) + 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": + # on Windows this isn't triggered, but it's fine, it has its own process termination thing + raise SystemExit + if "PoolWorker" in current_process().name: + raise SystemExit + if current_thread().name not in ("PyBitmessage", "MainThread"): + return + logger.error("Got signal %i", signal) + 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): + if ":" in host: #IPv6 + hostAddr = socket.inet_pton(socket.AF_INET6, host) + if hostAddr == ('\x00' * 15) + '\x01': + return False + if hostAddr[0] == '\xFE' and (ord(hostAddr[1]) & 0xc0) == 0x80: + return False + if (ord(hostAddr[0]) & 0xfe) == 0xfc: + return False + pass + elif ".onion" not in host: + if host[:3] == '10.': + return True + if host[:4] == '172.': + if host[6] == '.': + if int(host[4:6]) >= 16 and int(host[4:6]) <= 31: + return True + if host[:8] == '192.168.': + return True + # Multicast + if host[:3] >= 224 and host[:3] <= 239 and host[4] == '.': + return True + return False + +def addDataPadding(data, desiredMsgLength = 12, paddingChar = '\x00'): + return data + paddingChar * (desiredMsgLength - len(data)) diff --git a/src/helper_inbox.py b/src/helper_inbox.py index 555795df..3506150a 100644 --- a/src/helper_inbox.py +++ b/src/helper_inbox.py @@ -1,35 +1,16 @@ -"""Helper Inbox performs inbox messages related operations""" - +from helper_sql import * import queues -from helper_sql import sqlExecute, sqlQuery - def insert(t): - """Perform an insert into the "inbox" table""" sqlExecute('''INSERT INTO inbox VALUES (?,?,?,?,?,?,?,?,?,?)''', *t) - # shouldn't emit changedInboxUnread and displayNewInboxMessage - # at the same time - # queues.UISignalQueue.put(('changedInboxUnread', None)) - - + #shouldn't emit changedInboxUnread and displayNewInboxMessage at the same time + #queues.UISignalQueue.put(('changedInboxUnread', None)) + def trash(msgid): - """Mark a message in the `inbox` as `trash`""" sqlExecute('''UPDATE inbox SET folder='trash' WHERE msgid=?''', msgid) - queues.UISignalQueue.put(('removeInboxRowByMsgid', msgid)) - - -def delete(ack_data): - """Permanent delete message from trash""" - sqlExecute("DELETE FROM inbox WHERE msgid = ?", ack_data) - - -def undeleteMessage(msgid): - """Undelte the message""" - sqlExecute('''UPDATE inbox SET folder='inbox' WHERE msgid=?''', msgid) - - + queues.UISignalQueue.put(('removeInboxRowByMsgid',msgid)) + def isMessageAlreadyInInbox(sigHash): - """Check for previous instances of this message""" queryReturn = sqlQuery( '''SELECT COUNT(*) FROM inbox WHERE sighash=?''', sigHash) return queryReturn[0][0] != 0 diff --git a/src/helper_msgcoding.py b/src/helper_msgcoding.py index 05fa1c1b..f8bc95a6 100644 --- a/src/helper_msgcoding.py +++ b/src/helper_msgcoding.py @@ -1,14 +1,4 @@ -""" -Message encoding end decoding functions -""" - -import string -import zlib - -import messagetypes -from bmconfigparser import config -from debug import logger -from tr import _translate +#!/usr/bin/python2.7 try: import msgpack @@ -17,6 +7,13 @@ except ImportError: import umsgpack as msgpack except ImportError: import fallback.umsgpack.umsgpack as msgpack +import string +import zlib + +from bmconfigparser import BMConfigParser +from debug import logger +import messagetypes +from tr import _translate BITMESSAGE_ENCODING_IGNORE = 0 BITMESSAGE_ENCODING_TRIVIAL = 1 @@ -25,24 +22,19 @@ BITMESSAGE_ENCODING_EXTENDED = 3 class MsgEncodeException(Exception): - """Exception during message encoding""" pass class MsgDecodeException(Exception): - """Exception during message decoding""" pass class DecompressionSizeException(MsgDecodeException): - # pylint: disable=super-init-not-called - """Decompression resulted in too much data (attack protection)""" def __init__(self, size): self.size = size class MsgEncode(object): - """Message encoder class""" def __init__(self, message, encoding=BITMESSAGE_ENCODING_SIMPLE): self.data = None self.encoding = encoding @@ -57,7 +49,6 @@ class MsgEncode(object): raise MsgEncodeException("Unknown encoding %i" % (encoding)) def encodeExtended(self, message): - """Handle extended encoding""" try: msgObj = messagetypes.message.Message() self.data = zlib.compress(msgpack.dumps(msgObj.encode(message)), 9) @@ -70,41 +61,31 @@ class MsgEncode(object): self.length = len(self.data) def encodeSimple(self, message): - """Handle simple encoding""" - self.data = 'Subject:%(subject)s\nBody:%(body)s' % message + self.data = 'Subject:' + message['subject'] + '\n' + 'Body:' + message['body'] self.length = len(self.data) def encodeTrivial(self, message): - """Handle trivial encoding""" self.data = message['body'] self.length = len(self.data) class MsgDecode(object): - """Message decoder class""" def __init__(self, encoding, data): self.encoding = encoding if self.encoding == BITMESSAGE_ENCODING_EXTENDED: self.decodeExtended(data) - elif self.encoding in ( - BITMESSAGE_ENCODING_SIMPLE, BITMESSAGE_ENCODING_TRIVIAL): + elif self.encoding in [BITMESSAGE_ENCODING_SIMPLE, BITMESSAGE_ENCODING_TRIVIAL]: self.decodeSimple(data) else: - self.body = _translate( - "MsgDecode", - "The message has an unknown encoding.\n" - "Perhaps you should upgrade Bitmessage.") + self.body = _translate("MsgDecode", "The message has an unknown encoding.\nPerhaps you should upgrade Bitmessage.") self.subject = _translate("MsgDecode", "Unknown encoding") def decodeExtended(self, data): - """Handle extended encoding""" dc = zlib.decompressobj() tmp = "" - while len(tmp) <= config.safeGetInt("zlib", "maxsize"): + while len(tmp) <= BMConfigParser().safeGetInt("zlib", "maxsize"): try: - got = dc.decompress( - data, config.safeGetInt("zlib", "maxsize") - + 1 - len(tmp)) + got = dc.decompress(data, BMConfigParser().safeGetInt("zlib", "maxsize") + 1 - len(tmp)) # EOF if got == "": break @@ -134,14 +115,13 @@ class MsgDecode(object): raise MsgDecodeException("Malformed message") try: msgObj.process() - except: # noqa:E722 + except: raise MsgDecodeException("Malformed message") if msgType == "message": self.subject = msgObj.subject self.body = msgObj.body def decodeSimple(self, data): - """Handle simple encoding""" bodyPositionIndex = string.find(data, '\nBody:') if bodyPositionIndex > 1: subject = data[8:bodyPositionIndex] @@ -157,3 +137,24 @@ class MsgDecode(object): subject = subject.splitlines()[0] self.subject = subject self.body = body + +if __name__ == '__main__': + import random + messageData = { + "subject": ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(40)), + "body": ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10000)) + } + obj1 = MsgEncode(messageData, 1) + obj2 = MsgEncode(messageData, 2) + obj3 = MsgEncode(messageData, 3) + print "1:%i 2:%i 3:%i" %(len(obj1.data), len(obj2.data), len(obj3.data)) + + obj1e = MsgDecode(1, obj1.data) + # no subject in trivial encoding + assert messageData["body"] == obj1e.body + obj2e = MsgDecode(2, obj2.data) + assert messageData["subject"] == obj2e.subject + assert messageData["body"] == obj2e.body + obj3e = MsgDecode(3, obj3.data) + assert messageData["subject"] == obj3e.subject + assert messageData["body"] == obj3e.body diff --git a/src/helper_random.py b/src/helper_random.py index 2e6a151b..a0fb08f1 100644 --- a/src/helper_random.py +++ b/src/helper_random.py @@ -1,74 +1,9 @@ -"""Convenience functions for random operations. Not suitable for security / cryptography operations.""" - import os -import random - -try: - from pyelliptic.openssl import OpenSSL -except ImportError: - from .pyelliptic.openssl import OpenSSL - -NoneType = type(None) - - -def seed(): - """Initialize random number generator""" - random.seed() +from pyelliptic.openssl import OpenSSL def randomBytes(n): - """Method randomBytes.""" try: return os.urandom(n) except NotImplementedError: return OpenSSL.rand(n) - - -def randomshuffle(population): - """Method randomShuffle. - - shuffle the sequence x in place. - shuffles the elements in list in place, - so they are in a random order. - As Shuffle will alter data in-place, - so its input must be a mutable sequence. - In contrast, sample produces a new list - and its input can be much more varied - (tuple, string, xrange, bytearray, set, etc) - """ - random.shuffle(population) - - -def randomsample(population, k): - """Method randomSample. - - return a k length list of unique elements - chosen from the population sequence. - Used for random sampling - without replacement, its called - partial shuffle. - """ - return random.sample(population, k) - - -def randomrandrange(x, y=None): - """Method randomRandrange. - - return a randomly selected element from - range(start, stop). This is equivalent to - choice(range(start, stop)), - but doesnt actually build a range object. - """ - if isinstance(y, NoneType): - return random.randrange(x) # nosec - return random.randrange(x, y) # nosec - - -def randomchoice(population): - """Method randomchoice. - - Return a random element from the non-empty - sequence seq. If seq is empty, raises - IndexError. - """ - return random.choice(population) # nosec diff --git a/src/helper_search.py b/src/helper_search.py index 9fcb88b5..2217974f 100644 --- a/src/helper_search.py +++ b/src/helper_search.py @@ -1,113 +1,85 @@ -""" -Additional SQL helper for searching messages. -Used by :mod:`.bitmessageqt`. -""" +#!/usr/bin/python2.7 -from helper_sql import sqlQuery -from tr import _translate +from helper_sql import * +try: + from PyQt4 import QtCore, QtGui + haveQt = True +except: + haveQt = False -def search_sql( - xAddress='toaddress', account=None, folder='inbox', where=None, - what=None, unreadOnly=False -): - """ - Search for messages from given account and folder having search term - in one of it's fields. +def search_translate (context, text): + if haveQt: + return QtGui.QApplication.translate(context, text) + else: + return text.lower() - :param str xAddress: address field checked - ('fromaddress', 'toaddress' or 'both') - :param account: the account which is checked - :type account: :class:`.bitmessageqt.account.BMAccount` - instance - :param str folder: the folder which is checked - :param str where: message field which is checked ('toaddress', - 'fromaddress', 'subject' or 'message'), by default check any field - :param str what: the search term - :param bool unreadOnly: if True, search only for unread messages - :return: all messages where field contains - :rtype: list[list] - """ - # pylint: disable=too-many-arguments, too-many-branches - if what: - what = '%' + what + '%' - if where == _translate("MainWindow", "To"): - where = 'toaddress' - elif where == _translate("MainWindow", "From"): - where = 'fromaddress' - elif where == _translate("MainWindow", "Subject"): - where = 'subject' - elif where == _translate("MainWindow", "Message"): - where = 'message' +def search_sql(xAddress = "toaddress", account = None, folder = "inbox", where = None, what = None, unreadOnly = False): + if what is not None and what != "": + what = "%" + what + "%" + if where == search_translate("MainWindow", "To"): + where = "toaddress" + elif where == search_translate("MainWindow", "From"): + where = "fromaddress" + elif where == search_translate("MainWindow", "Subject"): + where = "subject" + elif where == search_translate("MainWindow", "Message"): + where = "message" else: - where = 'toaddress || fromaddress || subject || message' + where = "toaddress || fromaddress || subject || message" + else: + what = None - sqlStatementBase = 'SELECT toaddress, fromaddress, subject, ' + ( - 'status, ackdata, lastactiontime FROM sent ' if folder == 'sent' - else 'folder, msgid, received, read FROM inbox ' - ) + if folder == "sent": + sqlStatementBase = ''' + SELECT toaddress, fromaddress, subject, status, ackdata, lastactiontime + FROM sent ''' + else: + sqlStatementBase = '''SELECT folder, msgid, toaddress, fromaddress, subject, received, read + FROM inbox ''' sqlStatementParts = [] sqlArguments = [] if account is not None: if xAddress == 'both': - sqlStatementParts.append('(fromaddress = ? OR toaddress = ?)') + sqlStatementParts.append("(fromaddress = ? OR toaddress = ?)") sqlArguments.append(account) sqlArguments.append(account) else: - sqlStatementParts.append(xAddress + ' = ? ') + sqlStatementParts.append(xAddress + " = ? ") sqlArguments.append(account) if folder is not None: - if folder == 'new': - folder = 'inbox' + if folder == "new": + folder = "inbox" unreadOnly = True - sqlStatementParts.append('folder = ? ') + sqlStatementParts.append("folder = ? ") sqlArguments.append(folder) else: - sqlStatementParts.append('folder != ?') - sqlArguments.append('trash') - if what: - sqlStatementParts.append('%s LIKE ?' % (where)) + sqlStatementParts.append("folder != ?") + sqlArguments.append("trash") + if what is not None: + sqlStatementParts.append("%s LIKE ?" % (where)) sqlArguments.append(what) if unreadOnly: - sqlStatementParts.append('read = 0') - if sqlStatementParts: - sqlStatementBase += 'WHERE ' + ' AND '.join(sqlStatementParts) - if folder == 'sent': - sqlStatementBase += ' ORDER BY lastactiontime' + sqlStatementParts.append("read = 0") + if len(sqlStatementParts) > 0: + sqlStatementBase += "WHERE " + " AND ".join(sqlStatementParts) + if folder == "sent": + sqlStatementBase += " ORDER BY lastactiontime" return sqlQuery(sqlStatementBase, sqlArguments) - -def check_match( - toAddress, fromAddress, subject, message, where=None, what=None): - """ - Check if a single message matches a filter (used when new messages - are added to messagelists) - """ - # pylint: disable=too-many-arguments - if not what: - return True - - if where in ( - _translate("MainWindow", "To"), _translate("MainWindow", "All") - ): - if what.lower() not in toAddress.lower(): - return False - elif where in ( - _translate("MainWindow", "From"), _translate("MainWindow", "All") - ): - if what.lower() not in fromAddress.lower(): - return False - elif where in ( - _translate("MainWindow", "Subject"), - _translate("MainWindow", "All") - ): - if what.lower() not in subject.lower(): - return False - elif where in ( - _translate("MainWindow", "Message"), - _translate("MainWindow", "All") - ): - if what.lower() not in message.lower(): - return False +def check_match(toAddress, fromAddress, subject, message, where = None, what = None): + if what is not None and what != "": + if where in (search_translate("MainWindow", "To"), search_translate("MainWindow", "All")): + if what.lower() not in toAddress.lower(): + return False + elif where in (search_translate("MainWindow", "From"), search_translate("MainWindow", "All")): + if what.lower() not in fromAddress.lower(): + return False + elif where in (search_translate("MainWindow", "Subject"), search_translate("MainWindow", "All")): + if what.lower() not in subject.lower(): + return False + elif where in (search_translate("MainWindow", "Message"), search_translate("MainWindow", "All")): + if what.lower() not in message.lower(): + return False return True diff --git a/src/helper_sent.py b/src/helper_sent.py index aa76e756..8dde7215 100644 --- a/src/helper_sent.py +++ b/src/helper_sent.py @@ -1,69 +1,4 @@ -""" -Insert values into sent table -""" +from helper_sql import * -import time -import uuid -from addresses import decodeAddress -from bmconfigparser import config -from helper_ackPayload import genAckPayload -from helper_sql import sqlExecute, sqlQuery - - -# pylint: disable=too-many-arguments -def insert(msgid=None, toAddress='[Broadcast subscribers]', fromAddress=None, subject=None, - message=None, status='msgqueued', ripe=None, ackdata=None, sentTime=None, - lastActionTime=None, sleeptill=0, retryNumber=0, encoding=2, ttl=None, folder='sent'): - """Perform an insert into the `sent` table""" - # pylint: disable=unused-variable - # pylint: disable-msg=too-many-locals - - valid_addr = True - if not ripe or not ackdata: - addr = fromAddress if toAddress == '[Broadcast subscribers]' else toAddress - new_status, addressVersionNumber, streamNumber, new_ripe = decodeAddress(addr) - valid_addr = True if new_status == 'success' else False - if not ripe: - ripe = new_ripe - - if not ackdata: - stealthLevel = config.safeGetInt( - 'bitmessagesettings', 'ackstealthlevel') - new_ackdata = genAckPayload(streamNumber, stealthLevel) - ackdata = new_ackdata - if valid_addr: - msgid = msgid if msgid else uuid.uuid4().bytes - sentTime = sentTime if sentTime else int(time.time()) # sentTime (this doesn't change) - lastActionTime = lastActionTime if lastActionTime else int(time.time()) - - ttl = ttl if ttl else config.getint('bitmessagesettings', 'ttl') - - t = (msgid, toAddress, ripe, fromAddress, subject, message, ackdata, - sentTime, lastActionTime, sleeptill, status, retryNumber, folder, - encoding, ttl) - - sqlExecute('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', *t) - return ackdata - else: - return None - - -def delete(ack_data): - """Perform Delete query""" - sqlExecute("DELETE FROM sent WHERE ackdata = ?", ack_data) - - -def retrieve_message_details(ack_data): - """Retrieving Message details""" - data = sqlQuery( - "select toaddress, fromaddress, subject, message, received from inbox where msgid = ?", ack_data - ) - return data - - -def trash(ackdata): - """Mark a message in the `sent` as `trash`""" - rowcount = sqlExecute( - '''UPDATE sent SET folder='trash' WHERE ackdata=?''', ackdata - ) - return rowcount +def insert(t): + sqlExecute('''INSERT INTO sent VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''', *t) diff --git a/src/helper_sql.py b/src/helper_sql.py index 8dee9e0c..fec67bef 100644 --- a/src/helper_sql.py +++ b/src/helper_sql.py @@ -1,153 +1,100 @@ -""" -SQL-related functions defined here are really pass the queries (or other SQL -commands) to :class:`.threads.sqlThread` through `sqlSubmitQueue` queue and check -or return the result got from `sqlReturnQueue`. - -This is done that way because :mod:`sqlite3` is so thread-unsafe that they -won't even let you call it from different threads using your own locks. -SQLite objects can only be used from one thread. - -.. note:: This actually only applies for certain deployments, and/or - really old version of sqlite. I haven't actually seen it anywhere. - Current versions do have support for threading and multiprocessing. - I don't see an urgent reason to refactor this, but it should be noted - in the comment that the problem is mostly not valid. Sadly, last time - I checked, there is no reliable way to check whether the library is - or isn't thread-safe. -""" - import threading +import Queue -from six.moves import queue +sqlSubmitQueue = Queue.Queue() #SQLITE3 is so thread-unsafe that they won't even let you call it from different threads using your own locks. SQL objects can only be called from one thread. +sqlReturnQueue = Queue.Queue() +sqlLock = threading.Lock() - -sqlSubmitQueue = queue.Queue() -"""the queue for SQL""" -sqlReturnQueue = queue.Queue() -"""the queue for results""" -sql_lock = threading.Lock() -""" lock to prevent queueing a new request until the previous response - is available """ -sql_available = False -"""set to True by `.threads.sqlThread` immediately upon start""" -sql_ready = threading.Event() -"""set by `.threads.sqlThread` when ready for processing (after - initialization is done)""" -sql_timeout = 60 -"""timeout for waiting for sql_ready in seconds""" - - -def sqlQuery(sql_statement, *args): - """ - Query sqlite and return results - - :param str sql_statement: SQL statement string - :param list args: SQL query parameters - :rtype: list - """ - assert sql_available - sql_lock.acquire() - sqlSubmitQueue.put(sql_statement) +def sqlQuery(sqlStatement, *args): + sqlLock.acquire() + sqlSubmitQueue.put(sqlStatement) if args == (): sqlSubmitQueue.put('') - elif isinstance(args[0], (list, tuple)): + elif type(args[0]) in [list, tuple]: sqlSubmitQueue.put(args[0]) else: sqlSubmitQueue.put(args) - queryreturn, _ = sqlReturnQueue.get() - sql_lock.release() + + queryreturn, rowcount = sqlReturnQueue.get() + sqlLock.release() return queryreturn -def sqlExecuteChunked(sql_statement, idCount, *args): - """Execute chunked SQL statement to avoid argument limit""" +def sqlExecuteChunked(sqlStatement, idCount, *args): # SQLITE_MAX_VARIABLE_NUMBER, # unfortunately getting/setting isn't exposed to python - assert sql_available sqlExecuteChunked.chunkSize = 999 if idCount == 0 or idCount > len(args): return 0 - total_row_count = 0 - with sql_lock: + totalRowCount = 0 + with sqlLock: for i in range( - len(args) - idCount, len(args), - sqlExecuteChunked.chunkSize - (len(args) - idCount) + len(args) - idCount, len(args), + sqlExecuteChunked.chunkSize - (len(args) - idCount) ): chunk_slice = args[ - i:i + sqlExecuteChunked.chunkSize - (len(args) - idCount) + i:i+sqlExecuteChunked.chunkSize - (len(args) - idCount) ] sqlSubmitQueue.put( - sql_statement.format(','.join('?' * len(chunk_slice))) + sqlStatement.format(','.join('?' * len(chunk_slice))) ) # first static args, and then iterative chunk sqlSubmitQueue.put( - args[0:len(args) - idCount] + chunk_slice + args[0:len(args)-idCount] + chunk_slice ) - ret_val = sqlReturnQueue.get() - total_row_count += ret_val[1] + retVal = sqlReturnQueue.get() + totalRowCount += retVal[1] sqlSubmitQueue.put('commit') - return total_row_count + return totalRowCount -def sqlExecute(sql_statement, *args): - """Execute SQL statement (optionally with arguments)""" - assert sql_available - sql_lock.acquire() - sqlSubmitQueue.put(sql_statement) +def sqlExecute(sqlStatement, *args): + sqlLock.acquire() + sqlSubmitQueue.put(sqlStatement) if args == (): sqlSubmitQueue.put('') else: sqlSubmitQueue.put(args) - _, rowcount = sqlReturnQueue.get() + + queryreturn, rowcount = sqlReturnQueue.get() sqlSubmitQueue.put('commit') - sql_lock.release() + sqlLock.release() return rowcount - -def sqlExecuteScript(sql_statement): - """Execute SQL script statement""" - - statements = sql_statement.split(";") - with SqlBulkExecute() as sql: - for q in statements: - sql.execute("{}".format(q)) - - def sqlStoredProcedure(procName): - """Schedule procName to be run""" - assert sql_available - sql_lock.acquire() + sqlLock.acquire() sqlSubmitQueue.put(procName) - if procName == "exit": - sqlSubmitQueue.task_done() - sqlSubmitQueue.put("terminate") - sql_lock.release() - - -class SqlBulkExecute(object): - """This is used when you have to execute the same statement in a cycle.""" + sqlLock.release() +class SqlBulkExecute: def __enter__(self): - sql_lock.acquire() + sqlLock.acquire() return self - def __exit__(self, exc_type, value, traceback): + def __exit__(self, type, value, traceback): sqlSubmitQueue.put('commit') - sql_lock.release() - - @staticmethod - def execute(sql_statement, *args): - """Used for statements that do not return results.""" - assert sql_available - sqlSubmitQueue.put(sql_statement) + sqlLock.release() + def execute(self, sqlStatement, *args): + sqlSubmitQueue.put(sqlStatement) + if args == (): sqlSubmitQueue.put('') else: sqlSubmitQueue.put(args) sqlReturnQueue.get() + + def query(self, sqlStatement, *args): + sqlSubmitQueue.put(sqlStatement) + + if args == (): + sqlSubmitQueue.put('') + else: + sqlSubmitQueue.put(args) + return sqlReturnQueue.get() + diff --git a/src/helper_startup.py b/src/helper_startup.py index 52e1bf7a..6402ee89 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -1,373 +1,152 @@ -""" -Startup operations. -""" -# pylint: disable=too-many-branches,too-many-statements - -import ctypes -import logging -import os -import platform -import socket +import ConfigParser +from bmconfigparser import BMConfigParser +import defaults import sys -import time +import os +import locale +import random +import string +import platform from distutils.version import StrictVersion -from struct import pack -from six.moves import configparser -try: - import defaults - import helper_random - import paths - import state - from bmconfigparser import config, config_ready -except ImportError: - from . import defaults, helper_random, paths, state - from .bmconfigparser import config, config_ready +from namecoin import ensureNamecoinOptions +import paths +import state -try: - from plugins.plugin import get_plugin -except ImportError: - get_plugin = None +storeConfigFilesInSameDirectoryAsProgramByDefault = False # The user may de-select Portable Mode in the settings if they want the config files to stay in the application data folder. +def _loadTrustedPeer(): + try: + trustedPeer = BMConfigParser().get('bitmessagesettings', 'trustedpeer') + except ConfigParser.Error: + # This probably means the trusted peer wasn't specified so we + # can just leave it as None + return -logger = logging.getLogger('default') - -# The user may de-select Portable Mode in the settings if they want -# the config files to stay in the application data folder. -StoreConfigFilesInSameDirectoryAsProgramByDefault = False - + host, port = trustedPeer.split(':') + state.trustedPeer = state.Peer(host, int(port)) def loadConfig(): - """Load the config""" if state.appdata: - config.read(state.appdata + 'keys.dat') - # state.appdata must have been specified as a startup option. - needToCreateKeysFile = config.safeGet( - 'bitmessagesettings', 'settingsversion') is None - if not needToCreateKeysFile: - logger.info( - 'Loading config files from directory specified' - ' on startup: %s', state.appdata) - else: - config.read(paths.lookupExeFolder() + 'keys.dat') + BMConfigParser().read(state.appdata + 'keys.dat') + #state.appdata must have been specified as a startup option. + try: + BMConfigParser().get('bitmessagesettings', 'settingsversion') + print 'Loading config files from directory specified on startup: ' + state.appdata + needToCreateKeysFile = False + except: + needToCreateKeysFile = True - if config.safeGet('bitmessagesettings', 'settingsversion'): - logger.info('Loading config files from same directory as program.') + else: + BMConfigParser().read(paths.lookupExeFolder() + 'keys.dat') + try: + BMConfigParser().get('bitmessagesettings', 'settingsversion') + print 'Loading config files from same directory as program.' needToCreateKeysFile = False state.appdata = paths.lookupExeFolder() - else: - # Could not load the keys.dat file in the program directory. - # Perhaps it is in the appdata directory. + except: + # Could not load the keys.dat file in the program directory. Perhaps it + # is in the appdata directory. state.appdata = paths.lookupAppdataFolder() - config.read(state.appdata + 'keys.dat') - needToCreateKeysFile = config.safeGet( - 'bitmessagesettings', 'settingsversion') is None - if not needToCreateKeysFile: - logger.info( - 'Loading existing config files from %s', state.appdata) + BMConfigParser().read(state.appdata + 'keys.dat') + try: + BMConfigParser().get('bitmessagesettings', 'settingsversion') + print 'Loading existing config files from', state.appdata + needToCreateKeysFile = False + except: + needToCreateKeysFile = True if needToCreateKeysFile: - # This appears to be the first time running the program; there is # no config file (or it cannot be accessed). Create config file. - # config.add_section('bitmessagesettings') - config.read() - config.set('bitmessagesettings', 'settingsversion', '10') + BMConfigParser().add_section('bitmessagesettings') + BMConfigParser().set('bitmessagesettings', 'settingsversion', '10') + BMConfigParser().set('bitmessagesettings', 'port', '8444') + BMConfigParser().set( + 'bitmessagesettings', 'timeformat', '%%c') + BMConfigParser().set('bitmessagesettings', 'blackwhitelist', 'black') + BMConfigParser().set('bitmessagesettings', 'startonlogon', 'false') if 'linux' in sys.platform: - config.set('bitmessagesettings', 'minimizetotray', 'false') - # This isn't implimented yet and when True on - # Ubuntu causes Bitmessage to disappear while - # running when minimized. + BMConfigParser().set( + 'bitmessagesettings', 'minimizetotray', 'false') + # This isn't implimented yet and when True on + # Ubuntu causes Bitmessage to disappear while + # running when minimized. else: - config.set('bitmessagesettings', 'minimizetotray', 'true') - config.set( - 'bitmessagesettings', 'defaultnoncetrialsperbyte', - str(defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) - config.set( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes', - str(defaults.networkDefaultPayloadLengthExtraBytes)) - config.set('bitmessagesettings', 'dontconnect', 'true') - # UI setting to stop trying to send messages after X days/months - # config.set('bitmessagesettings', 'stopresendingafterxdays', '') - # config.set('bitmessagesettings', 'stopresendingafterxmonths', '') + BMConfigParser().set( + 'bitmessagesettings', 'minimizetotray', 'true') + BMConfigParser().set( + 'bitmessagesettings', 'showtraynotifications', 'true') + BMConfigParser().set('bitmessagesettings', 'startintray', 'false') + BMConfigParser().set('bitmessagesettings', 'socksproxytype', 'none') + BMConfigParser().set( + 'bitmessagesettings', 'sockshostname', 'localhost') + BMConfigParser().set('bitmessagesettings', 'socksport', '9050') + BMConfigParser().set( + 'bitmessagesettings', 'socksauthentication', 'false') + BMConfigParser().set( + 'bitmessagesettings', 'sockslisten', 'false') + BMConfigParser().set('bitmessagesettings', 'socksusername', '') + BMConfigParser().set('bitmessagesettings', 'sockspassword', '') + BMConfigParser().set('bitmessagesettings', 'keysencrypted', 'false') + BMConfigParser().set( + 'bitmessagesettings', 'messagesencrypted', 'false') + BMConfigParser().set('bitmessagesettings', 'defaultnoncetrialsperbyte', str( + defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) + BMConfigParser().set('bitmessagesettings', 'defaultpayloadlengthextrabytes', str( + defaults.networkDefaultPayloadLengthExtraBytes)) + BMConfigParser().set('bitmessagesettings', 'minimizeonclose', 'false') + BMConfigParser().set( + 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', '0') + BMConfigParser().set( + 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', '0') + BMConfigParser().set('bitmessagesettings', 'dontconnect', 'true') + BMConfigParser().set('bitmessagesettings', 'userlocale', 'system') + BMConfigParser().set('bitmessagesettings', 'useidenticons', 'True') + BMConfigParser().set('bitmessagesettings', 'identiconsuffix', ''.join(random.choice("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz") for x in range(12))) # a twelve character pseudo-password to salt the identicons + BMConfigParser().set('bitmessagesettings', 'replybelow', 'False') + BMConfigParser().set('bitmessagesettings', 'maxdownloadrate', '0') + BMConfigParser().set('bitmessagesettings', 'maxuploadrate', '0') + BMConfigParser().set('bitmessagesettings', 'maxoutboundconnections', '8') + BMConfigParser().set('bitmessagesettings', 'ttl', '367200') + + #start:UI setting to stop trying to send messages after X days/months + BMConfigParser().set( + 'bitmessagesettings', 'stopresendingafterxdays', '') + BMConfigParser().set( + 'bitmessagesettings', 'stopresendingafterxmonths', '') + #BMConfigParser().set( + # 'bitmessagesettings', 'timeperiod', '-1') + #end # Are you hoping to add a new option to the keys.dat file? You're in # the right place for adding it to users who install the software for # the first time. But you must also add it to the keys.dat file of - # existing users. To do that, search the class_sqlThread.py file - # for the text: "right above this line!" + # existing users. To do that, search the class_sqlThread.py file for the + # text: "right above this line!" - if StoreConfigFilesInSameDirectoryAsProgramByDefault: + ensureNamecoinOptions() + + if storeConfigFilesInSameDirectoryAsProgramByDefault: # Just use the same directory as the program and forget about # the appdata folder state.appdata = '' - logger.info( - 'Creating new config files in same directory as program.') + print 'Creating new config files in same directory as program.' else: - logger.info('Creating new config files in %s', state.appdata) + print 'Creating new config files in', state.appdata if not os.path.exists(state.appdata): os.makedirs(state.appdata) if not sys.platform.startswith('win'): os.umask(0o077) - config.save() - else: - updateConfig() - config_ready.set() + BMConfigParser().save() + _loadTrustedPeer() -def updateConfig(): - """Save the config""" - settingsversion = config.getint('bitmessagesettings', 'settingsversion') - if settingsversion == 1: - config.set('bitmessagesettings', 'socksproxytype', 'none') - config.set('bitmessagesettings', 'sockshostname', 'localhost') - config.set('bitmessagesettings', 'socksport', '9050') - config.set('bitmessagesettings', 'socksauthentication', 'false') - config.set('bitmessagesettings', 'socksusername', '') - config.set('bitmessagesettings', 'sockspassword', '') - config.set('bitmessagesettings', 'sockslisten', 'false') - config.set('bitmessagesettings', 'keysencrypted', 'false') - config.set('bitmessagesettings', 'messagesencrypted', 'false') - settingsversion = 2 - # let class_sqlThread update SQL and continue - elif settingsversion == 4: - config.set( - 'bitmessagesettings', 'defaultnoncetrialsperbyte', - str(defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) - config.set( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes', - str(defaults.networkDefaultPayloadLengthExtraBytes)) - settingsversion = 5 - - if settingsversion == 5: - config.set( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', '0') - config.set( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', '0') - settingsversion = 7 - - if not config.has_option('bitmessagesettings', 'sockslisten'): - config.set('bitmessagesettings', 'sockslisten', 'false') - - if not config.has_option('bitmessagesettings', 'userlocale'): - config.set('bitmessagesettings', 'userlocale', 'system') - - if not config.has_option('bitmessagesettings', 'sendoutgoingconnections'): - config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') - - if not config.has_option('bitmessagesettings', 'useidenticons'): - config.set('bitmessagesettings', 'useidenticons', 'True') - if not config.has_option('bitmessagesettings', 'identiconsuffix'): - # acts as a salt - config.set( - 'bitmessagesettings', 'identiconsuffix', ''.join( - helper_random.randomchoice( - "123456789ABCDEFGHJKLMNPQRSTUVWXYZ" - "abcdefghijkmnopqrstuvwxyz") for x in range(12)) - ) # a twelve character pseudo-password to salt the identicons - - # Add settings to support no longer resending messages after - # a certain period of time even if we never get an ack - if settingsversion == 7: - config.set('bitmessagesettings', 'stopresendingafterxdays', '') - config.set('bitmessagesettings', 'stopresendingafterxmonths', '') - settingsversion = 8 - - # With the change to protocol version 3, reset the user-settable - # difficulties to 1 - if settingsversion == 8: - config.set( - 'bitmessagesettings', 'defaultnoncetrialsperbyte', - str(defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) - config.set( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes', - str(defaults.networkDefaultPayloadLengthExtraBytes)) - previousTotalDifficulty = int( - config.getint( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte') - ) / 320 - previousSmallMessageDifficulty = int( - config.getint( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') - ) / 14000 - config.set( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', - str(previousTotalDifficulty * 1000)) - config.set( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', - str(previousSmallMessageDifficulty * 1000)) - settingsversion = 9 - - # Adjust the required POW values for each of this user's addresses - # to conform to protocol v3 norms. - if settingsversion == 9: - for addressInKeysFile in config.addresses(): - try: - previousTotalDifficulty = float( - config.getint( - addressInKeysFile, 'noncetrialsperbyte')) / 320 - previousSmallMessageDifficulty = float( - config.getint( - addressInKeysFile, 'payloadlengthextrabytes')) / 14000 - if previousTotalDifficulty <= 2: - previousTotalDifficulty = 1 - if previousSmallMessageDifficulty < 1: - previousSmallMessageDifficulty = 1 - config.set( - addressInKeysFile, 'noncetrialsperbyte', - str(int(previousTotalDifficulty * 1000))) - config.set( - addressInKeysFile, 'payloadlengthextrabytes', - str(int(previousSmallMessageDifficulty * 1000))) - except (ValueError, TypeError, configparser.NoSectionError, - configparser.NoOptionError): - continue - config.set('bitmessagesettings', 'maxdownloadrate', '0') - config.set('bitmessagesettings', 'maxuploadrate', '0') - settingsversion = 10 - - # sanity check - if config.safeGetInt( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte') == 0: - config.set( - 'bitmessagesettings', 'maxacceptablenoncetrialsperbyte', - str(defaults.ridiculousDifficulty - * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) - ) - if config.safeGetInt( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes') == 0: - config.set( - 'bitmessagesettings', 'maxacceptablepayloadlengthextrabytes', - str(defaults.ridiculousDifficulty - * defaults.networkDefaultPayloadLengthExtraBytes) - ) - - if not config.has_option('bitmessagesettings', 'onionport'): - config.set('bitmessagesettings', 'onionport', '8444') - if not config.has_option('bitmessagesettings', 'onionbindip'): - config.set('bitmessagesettings', 'onionbindip', '127.0.0.1') - if not config.has_option('bitmessagesettings', 'smtpdeliver'): - config.set('bitmessagesettings', 'smtpdeliver', '') - if not config.has_option( - 'bitmessagesettings', 'hidetrayconnectionnotifications'): - config.set( - 'bitmessagesettings', 'hidetrayconnectionnotifications', 'false') - if config.safeGetInt('bitmessagesettings', 'maxoutboundconnections') < 1: - config.set('bitmessagesettings', 'maxoutboundconnections', '8') - logger.warning('Your maximum outbound connections must be a number.') - - # TTL is now user-specifiable. Let's add an option to save - # whatever the user selects. - if not config.has_option('bitmessagesettings', 'ttl'): - config.set('bitmessagesettings', 'ttl', '367200') - - config.set('bitmessagesettings', 'settingsversion', str(settingsversion)) - config.save() - - -def adjustHalfOpenConnectionsLimit(): - """Check and satisfy half-open connections limit (mainly XP and Vista)""" - if config.safeGet( - 'bitmessagesettings', 'socksproxytype', 'none') != 'none': - state.maximumNumberOfHalfOpenConnections = 4 - return - - is_limited = False +def isOurOperatingSystemLimitedToHavingVeryFewHalfOpenConnections(): try: - if sys.platform[0:3] == "win": - # Some XP and Vista systems can only have 10 outgoing - # connections at a time. - VER_THIS = StrictVersion(platform.version()) - is_limited = ( - StrictVersion("5.1.2600") <= VER_THIS - and StrictVersion("6.0.6000") >= VER_THIS - ) - except ValueError: - pass - - state.maximumNumberOfHalfOpenConnections = 9 if is_limited else 64 - - -def fixSocket(): - """Add missing socket options and methods mainly on Windows""" - 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 - # socket.inet_ntop but we can make one ourselves using ctypes - if not hasattr(socket, 'inet_ntop'): - addressToString = ctypes.windll.ws2_32.WSAAddressToStringA - - def inet_ntop(family, host): - """Converting an IP address in packed - binary format to string format""" - if family == socket.AF_INET: - if len(host) != 4: - raise ValueError("invalid IPv4 host") - host = pack("hH4s8s", socket.AF_INET, 0, host, "\0" * 8) - elif family == socket.AF_INET6: - if len(host) != 16: - raise ValueError("invalid IPv6 host") - host = pack("hHL16sL", socket.AF_INET6, 0, 0, host, 0) - else: - raise ValueError("invalid address family") - buf = "\0" * 64 - lengthBuf = pack("I", len(buf)) - addressToString(host, len(host), None, buf, lengthBuf) - return buf[0:buf.index("\0")] - socket.inet_ntop = inet_ntop - - # Same for inet_pton - if not hasattr(socket, 'inet_pton'): - stringToAddress = ctypes.windll.ws2_32.WSAStringToAddressA - - def inet_pton(family, host): - """Converting an IP address in string format - to a packed binary format""" - buf = "\0" * 28 - lengthBuf = pack("I", len(buf)) - if stringToAddress(str(host), - int(family), - None, - buf, - lengthBuf) != 0: - raise socket.error("illegal IP address passed to inet_pton") - if family == socket.AF_INET: - return buf[4:8] - elif family == socket.AF_INET6: - return buf[8:24] - else: - raise ValueError("invalid address family") - socket.inet_pton = inet_pton - - # These sockopts are needed on for IPv6 support - if not hasattr(socket, 'IPPROTO_IPV6'): - socket.IPPROTO_IPV6 = 41 - if not hasattr(socket, 'IPV6_V6ONLY'): - socket.IPV6_V6ONLY = 27 - - -def start_proxyconfig(): - """Check socksproxytype and start any proxy configuration plugin""" - if not get_plugin: - return - config_ready.wait() - proxy_type = config.safeGet('bitmessagesettings', 'socksproxytype') - if proxy_type and proxy_type not in ('none', 'SOCKS4a', 'SOCKS5'): - try: - proxyconfig_start = time.time() - if not get_plugin('proxyconfig', name=proxy_type)(config): - raise TypeError() - except TypeError: - # cannot import shutdown here ): - logger.error( - 'Failed to run proxy config plugin %s', - proxy_type, exc_info=True) - config.setTemp('bitmessagesettings', 'dontconnect', 'true') - else: - logger.info( - 'Started proxy config plugin %s in %s sec', - proxy_type, time.time() - proxyconfig_start) + if sys.platform[0:3]=="win": + VER_THIS=StrictVersion(platform.version()) + return StrictVersion("5.1.2600")<=VER_THIS and StrictVersion("6.0.6000")>=VER_THIS + return False + except Exception as err: + return False diff --git a/src/helper_threading.py b/src/helper_threading.py new file mode 100644 index 00000000..3b7ba378 --- /dev/null +++ b/src/helper_threading.py @@ -0,0 +1,37 @@ +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() + self._stopped = False + + def stopThread(self): + self._stopped = True + 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 1bdb1593..8729ec5c 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -1,144 +1,101 @@ -""" -High level cryptographic functions based on `.pyelliptic` OpenSSL bindings. - -.. note:: - Upstream pyelliptic was upgraded from SHA1 to SHA256 for signing. We must - `upgrade PyBitmessage gracefully. `_ - `More discussion. `_ -""" - from binascii import hexlify - +from bmconfigparser import BMConfigParser import pyelliptic -from pyelliptic import OpenSSL -from pyelliptic import arithmetic as a - - -__all__ = ['encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'sign', 'verify'] - - -def makeCryptor(privkey, curve='secp256k1'): - """Return a private `.pyelliptic.ECC` instance""" +from pyelliptic import arithmetic as a, OpenSSL +def makeCryptor(privkey): private_key = a.changebase(privkey, 16, 256, minlen=32) public_key = pointMult(private_key) - cryptor = pyelliptic.ECC( - pubkey_x=public_key[1:-32], pubkey_y=public_key[-32:], - raw_privkey=private_key, curve=curve) + privkey_bin = '\x02\xca\x00\x20' + private_key + pubkey_bin = '\x02\xca\x00\x20' + public_key[1:-32] + '\x00\x20' + public_key[-32:] + cryptor = pyelliptic.ECC(curve='secp256k1',privkey=privkey_bin,pubkey=pubkey_bin) return cryptor - - def hexToPubkey(pubkey): - """Convert a pubkey from hex to binary""" - pubkey_raw = a.changebase(pubkey[2:], 16, 256, minlen=64) - pubkey_bin = b'\x02\xca\x00 ' + pubkey_raw[:32] + b'\x00 ' + pubkey_raw[32:] + pubkey_raw = a.changebase(pubkey[2:],16,256,minlen=64) + pubkey_bin = '\x02\xca\x00 '+pubkey_raw[:32]+'\x00 '+pubkey_raw[32:] return pubkey_bin - - def makePubCryptor(pubkey): - """Return a public `.pyelliptic.ECC` instance""" pubkey_bin = hexToPubkey(pubkey) - return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin) - - + return pyelliptic.ECC(curve='secp256k1',pubkey=pubkey_bin) +# Converts hex private key into hex public key def privToPub(privkey): - """Converts hex private key into hex public key""" private_key = a.changebase(privkey, 16, 256, minlen=32) public_key = pointMult(private_key) return hexlify(public_key) - - -def encrypt(msg, hexPubkey): - """Encrypts message with hex public key""" - return pyelliptic.ECC(curve='secp256k1').encrypt( - msg, hexToPubkey(hexPubkey)) - - -def decrypt(msg, hexPrivkey): - """Decrypts message with hex private key""" +# Encrypts message with hex public key +def encrypt(msg,hexPubkey): + return pyelliptic.ECC(curve='secp256k1').encrypt(msg,hexToPubkey(hexPubkey)) +# Decrypts message with hex private key +def decrypt(msg,hexPrivkey): return makeCryptor(hexPrivkey).decrypt(msg) - - -def decryptFast(msg, cryptor): - """Decrypts message with an existing `.pyelliptic.ECC` object""" +# Decrypts message with an existing pyelliptic.ECC.ECC object +def decryptFast(msg,cryptor): return cryptor.decrypt(msg) - - -def _choose_digest_alg(name): - """ - Choose openssl digest constant by name raises ValueError if not appropriate - """ - if name not in ("sha1", "sha256"): - raise ValueError("Unknown digest algorithm %s" % name) - return ( +# Signs with hex private key +def sign(msg,hexPrivkey): + # pyelliptic is upgrading from SHA1 to SHA256 for signing. We must + # upgrade PyBitmessage gracefully. + # https://github.com/yann2192/pyelliptic/pull/33 + # More discussion: https://github.com/yann2192/pyelliptic/issues/32 + digestAlg = BMConfigParser().safeGet('bitmessagesettings', 'digestalg', 'sha1') + if digestAlg == "sha1": # SHA1, this will eventually be deprecated - OpenSSL.digest_ecdsa_sha1 if name == "sha1" else OpenSSL.EVP_sha256) - - -def sign(msg, hexPrivkey, digestAlg="sha256"): - """ - Signs with hex private key using SHA1 or SHA256 depending on - *digestAlg* keyword. - """ - return makeCryptor(hexPrivkey).sign( - msg, digest_alg=_choose_digest_alg(digestAlg)) - - -def verify(msg, sig, hexPubkey, digestAlg=None): - """Verifies with hex public key using SHA1 or SHA256""" + 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 # let us check the signature using both SHA1 and SHA256 and if one - # of them passes then we will be satisfied. Eventually this can - # be simplified and we'll only check with SHA256. - if digestAlg is None: - # old SHA1 algorithm. - sigVerifyPassed = verify(msg, sig, hexPubkey, "sha1") - if sigVerifyPassed: - # The signature check passed using SHA1 - return True - # The signature check using SHA1 failed. Let us try it with SHA256. - return verify(msg, sig, hexPubkey, "sha256") - + # of them passes then we will be satisfied. Eventually this can + # be simplified and we'll only check with SHA256. try: - return makePubCryptor(hexPubkey).verify( - sig, msg, digest_alg=_choose_digest_alg(digestAlg)) + sigVerifyPassed = makePubCryptor(hexPubkey).verify(sig,msg,digest_alg=OpenSSL.digest_ecdsa_sha1) # old SHA1 algorithm. + except: + sigVerifyPassed = False + if sigVerifyPassed: + # The signature check passed using SHA1 + return True + # The signature check using SHA1 failed. Let us try it with SHA256. + try: + return makePubCryptor(hexPubkey).verify(sig,msg,digest_alg=OpenSSL.EVP_sha256) except: return False - +# Does an EC point multiplication; turns a private key into a public key. def pointMult(secret): - """ - Does an EC point multiplication; turns a private key into a public key. - - Evidently, this type of error can occur very rarely: - - >>> File "highlevelcrypto.py", line 54, in pointMult - >>> group = OpenSSL.EC_KEY_get0_group(k) - >>> WindowsError: exception: access violation reading 0x0000000000000008 - """ while True: try: - k = OpenSSL.EC_KEY_new_by_curve_name( - OpenSSL.get_curve('secp256k1')) + """ + Evidently, this type of error can occur very rarely: + + File "highlevelcrypto.py", line 54, in pointMult + group = OpenSSL.EC_KEY_get0_group(k) + WindowsError: exception: access violation reading 0x0000000000000008 + """ + k = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1')) priv_key = OpenSSL.BN_bin2bn(secret, 32, None) group = OpenSSL.EC_KEY_get0_group(k) pub_key = OpenSSL.EC_POINT_new(group) - + OpenSSL.EC_POINT_mul(group, pub_key, priv_key, None, None, None) OpenSSL.EC_KEY_set_private_key(k, priv_key) OpenSSL.EC_KEY_set_public_key(k, pub_key) - + size = OpenSSL.i2o_ECPublicKey(k, None) mb = OpenSSL.create_string_buffer(size) OpenSSL.i2o_ECPublicKey(k, OpenSSL.byref(OpenSSL.pointer(mb))) - + + OpenSSL.EC_POINT_free(pub_key) + OpenSSL.BN_free(priv_key) + OpenSSL.EC_KEY_free(k) return mb.raw - except Exception: + except Exception as e: import traceback import time traceback.print_exc() time.sleep(0.2) - finally: - OpenSSL.EC_POINT_free(pub_key) - OpenSSL.BN_free(priv_key) - OpenSSL.EC_KEY_free(k) + diff --git a/src/images/blue-plus-icon-12.png b/src/images/blue-plus-icon-12.png deleted file mode 100644 index f9007861..00000000 Binary files a/src/images/blue-plus-icon-12.png and /dev/null differ diff --git a/src/images/kivy/down-arrow.png b/src/images/kivy/down-arrow.png deleted file mode 100644 index bf3e864c..00000000 Binary files a/src/images/kivy/down-arrow.png and /dev/null differ diff --git a/src/images/kivy/draft-icon.png b/src/images/kivy/draft-icon.png deleted file mode 100644 index 9fc38f31..00000000 Binary files a/src/images/kivy/draft-icon.png and /dev/null differ diff --git a/src/images/kivy/drawer_logo1.png b/src/images/kivy/drawer_logo1.png deleted file mode 100644 index 256f9be6..00000000 Binary files a/src/images/kivy/drawer_logo1.png and /dev/null differ diff --git a/src/images/kivy/loader.gif b/src/images/kivy/loader.gif deleted file mode 100644 index 34ab1943..00000000 Binary files a/src/images/kivy/loader.gif and /dev/null differ diff --git a/src/images/kivy/payment/btc.png b/src/images/kivy/payment/btc.png deleted file mode 100644 index 33302ff8..00000000 Binary files a/src/images/kivy/payment/btc.png and /dev/null differ diff --git a/src/images/kivy/payment/buy.png b/src/images/kivy/payment/buy.png deleted file mode 100644 index 3a63af11..00000000 Binary files a/src/images/kivy/payment/buy.png and /dev/null differ diff --git a/src/images/kivy/payment/buynew1.png b/src/images/kivy/payment/buynew1.png deleted file mode 100644 index f02090f8..00000000 Binary files a/src/images/kivy/payment/buynew1.png and /dev/null differ diff --git a/src/images/kivy/payment/gplay.png b/src/images/kivy/payment/gplay.png deleted file mode 100644 index 69550edd..00000000 Binary files a/src/images/kivy/payment/gplay.png and /dev/null differ diff --git a/src/images/kivy/payment/paypal.png b/src/images/kivy/payment/paypal.png deleted file mode 100644 index f994130d..00000000 Binary files a/src/images/kivy/payment/paypal.png and /dev/null differ diff --git a/src/images/kivy/right-arrow.png b/src/images/kivy/right-arrow.png deleted file mode 100644 index 8f136a77..00000000 Binary files a/src/images/kivy/right-arrow.png and /dev/null differ diff --git a/src/images/kivy/search.png b/src/images/kivy/search.png deleted file mode 100644 index 42a1e45a..00000000 Binary files a/src/images/kivy/search.png and /dev/null differ diff --git a/src/images/kivy/text_images/!.png b/src/images/kivy/text_images/!.png deleted file mode 100644 index bac2f246..00000000 Binary files a/src/images/kivy/text_images/!.png and /dev/null differ diff --git a/src/images/kivy/text_images/0.png b/src/images/kivy/text_images/0.png deleted file mode 100644 index 2b8b63e3..00000000 Binary files a/src/images/kivy/text_images/0.png and /dev/null differ diff --git a/src/images/kivy/text_images/1.png b/src/images/kivy/text_images/1.png deleted file mode 100644 index 3918f6d3..00000000 Binary files a/src/images/kivy/text_images/1.png and /dev/null differ diff --git a/src/images/kivy/text_images/2.png b/src/images/kivy/text_images/2.png deleted file mode 100644 index 0cf202e9..00000000 Binary files a/src/images/kivy/text_images/2.png and /dev/null differ diff --git a/src/images/kivy/text_images/3.png b/src/images/kivy/text_images/3.png deleted file mode 100644 index f9d612dd..00000000 Binary files a/src/images/kivy/text_images/3.png and /dev/null differ diff --git a/src/images/kivy/text_images/4.png b/src/images/kivy/text_images/4.png deleted file mode 100644 index f2ab33e1..00000000 Binary files a/src/images/kivy/text_images/4.png and /dev/null differ diff --git a/src/images/kivy/text_images/5.png b/src/images/kivy/text_images/5.png deleted file mode 100644 index 09d6e56e..00000000 Binary files a/src/images/kivy/text_images/5.png and /dev/null differ diff --git a/src/images/kivy/text_images/6.png b/src/images/kivy/text_images/6.png deleted file mode 100644 index e385a954..00000000 Binary files a/src/images/kivy/text_images/6.png and /dev/null differ diff --git a/src/images/kivy/text_images/7.png b/src/images/kivy/text_images/7.png deleted file mode 100644 index 55fc4f77..00000000 Binary files a/src/images/kivy/text_images/7.png and /dev/null differ diff --git a/src/images/kivy/text_images/8.png b/src/images/kivy/text_images/8.png deleted file mode 100644 index 2a3fa76f..00000000 Binary files a/src/images/kivy/text_images/8.png and /dev/null differ diff --git a/src/images/kivy/text_images/9.png b/src/images/kivy/text_images/9.png deleted file mode 100644 index 81ad9084..00000000 Binary files a/src/images/kivy/text_images/9.png and /dev/null differ diff --git a/src/images/kivy/text_images/A.png b/src/images/kivy/text_images/A.png deleted file mode 100644 index 64ed6110..00000000 Binary files a/src/images/kivy/text_images/A.png and /dev/null differ diff --git a/src/images/kivy/text_images/B.png b/src/images/kivy/text_images/B.png deleted file mode 100644 index 2db56c1f..00000000 Binary files a/src/images/kivy/text_images/B.png and /dev/null differ diff --git a/src/images/kivy/text_images/C.png b/src/images/kivy/text_images/C.png deleted file mode 100644 index 47a4052c..00000000 Binary files a/src/images/kivy/text_images/C.png and /dev/null differ diff --git a/src/images/kivy/text_images/D.png b/src/images/kivy/text_images/D.png deleted file mode 100644 index 2549ffc2..00000000 Binary files a/src/images/kivy/text_images/D.png and /dev/null differ diff --git a/src/images/kivy/text_images/E.png b/src/images/kivy/text_images/E.png deleted file mode 100644 index 5d631611..00000000 Binary files a/src/images/kivy/text_images/E.png and /dev/null differ diff --git a/src/images/kivy/text_images/F.png b/src/images/kivy/text_images/F.png deleted file mode 100644 index 43086f38..00000000 Binary files a/src/images/kivy/text_images/F.png and /dev/null differ diff --git a/src/images/kivy/text_images/G.png b/src/images/kivy/text_images/G.png deleted file mode 100644 index 32d1709d..00000000 Binary files a/src/images/kivy/text_images/G.png and /dev/null differ diff --git a/src/images/kivy/text_images/H.png b/src/images/kivy/text_images/H.png deleted file mode 100644 index 279bd1ce..00000000 Binary files a/src/images/kivy/text_images/H.png and /dev/null differ diff --git a/src/images/kivy/text_images/I.png b/src/images/kivy/text_images/I.png deleted file mode 100644 index c88f048d..00000000 Binary files a/src/images/kivy/text_images/I.png and /dev/null differ diff --git a/src/images/kivy/text_images/J.png b/src/images/kivy/text_images/J.png deleted file mode 100644 index 15331171..00000000 Binary files a/src/images/kivy/text_images/J.png and /dev/null differ diff --git a/src/images/kivy/text_images/K.png b/src/images/kivy/text_images/K.png deleted file mode 100644 index 9afcadd7..00000000 Binary files a/src/images/kivy/text_images/K.png and /dev/null differ diff --git a/src/images/kivy/text_images/L.png b/src/images/kivy/text_images/L.png deleted file mode 100644 index e841b9d9..00000000 Binary files a/src/images/kivy/text_images/L.png and /dev/null differ diff --git a/src/images/kivy/text_images/M.png b/src/images/kivy/text_images/M.png deleted file mode 100644 index 10de35e9..00000000 Binary files a/src/images/kivy/text_images/M.png and /dev/null differ diff --git a/src/images/kivy/text_images/N.png b/src/images/kivy/text_images/N.png deleted file mode 100644 index 2d235d06..00000000 Binary files a/src/images/kivy/text_images/N.png and /dev/null differ diff --git a/src/images/kivy/text_images/O.png b/src/images/kivy/text_images/O.png deleted file mode 100644 index c0cc972a..00000000 Binary files a/src/images/kivy/text_images/O.png and /dev/null differ diff --git a/src/images/kivy/text_images/P.png b/src/images/kivy/text_images/P.png deleted file mode 100644 index 57ec5012..00000000 Binary files a/src/images/kivy/text_images/P.png and /dev/null differ diff --git a/src/images/kivy/text_images/Q.png b/src/images/kivy/text_images/Q.png deleted file mode 100644 index 27ffd18b..00000000 Binary files a/src/images/kivy/text_images/Q.png and /dev/null differ diff --git a/src/images/kivy/text_images/R.png b/src/images/kivy/text_images/R.png deleted file mode 100644 index 090646f5..00000000 Binary files a/src/images/kivy/text_images/R.png and /dev/null differ diff --git a/src/images/kivy/text_images/S.png b/src/images/kivy/text_images/S.png deleted file mode 100644 index 444419cf..00000000 Binary files a/src/images/kivy/text_images/S.png and /dev/null differ diff --git a/src/images/kivy/text_images/T.png b/src/images/kivy/text_images/T.png deleted file mode 100644 index ace7b36b..00000000 Binary files a/src/images/kivy/text_images/T.png and /dev/null differ diff --git a/src/images/kivy/text_images/U.png b/src/images/kivy/text_images/U.png deleted file mode 100644 index a47f326e..00000000 Binary files a/src/images/kivy/text_images/U.png and /dev/null differ diff --git a/src/images/kivy/text_images/V.png b/src/images/kivy/text_images/V.png deleted file mode 100644 index da07d0ac..00000000 Binary files a/src/images/kivy/text_images/V.png and /dev/null differ diff --git a/src/images/kivy/text_images/W.png b/src/images/kivy/text_images/W.png deleted file mode 100644 index a00f9d7c..00000000 Binary files a/src/images/kivy/text_images/W.png and /dev/null differ diff --git a/src/images/kivy/text_images/X.png b/src/images/kivy/text_images/X.png deleted file mode 100644 index be919fc4..00000000 Binary files a/src/images/kivy/text_images/X.png and /dev/null differ diff --git a/src/images/kivy/text_images/Y.png b/src/images/kivy/text_images/Y.png deleted file mode 100644 index 4819bbd1..00000000 Binary files a/src/images/kivy/text_images/Y.png and /dev/null differ diff --git a/src/images/kivy/text_images/Z.png b/src/images/kivy/text_images/Z.png deleted file mode 100644 index 7d1c8e01..00000000 Binary files a/src/images/kivy/text_images/Z.png and /dev/null differ diff --git a/src/images/plus-4-xxl.png b/src/images/plus-4-xxl.png deleted file mode 100644 index 1f178267..00000000 Binary files a/src/images/plus-4-xxl.png and /dev/null differ diff --git a/src/images/plus.png b/src/images/plus.png deleted file mode 100644 index 4fd3478c..00000000 Binary files a/src/images/plus.png and /dev/null differ diff --git a/src/inventory.py b/src/inventory.py index dc8e36bf..598021fb 100644 --- a/src/inventory.py +++ b/src/inventory.py @@ -1,50 +1,198 @@ -"""The Inventory singleton""" +import collections +from importlib import import_module +from threading import current_thread, enumerate as threadingEnumerate, RLock +import Queue +import time +import sys -# TODO make this dynamic, and watch out for frozen, like with messagetypes -import storage.filesystem -import storage.sqlite -from bmconfigparser import config +from bmconfigparser import BMConfigParser +from helper_sql import * from singleton import Singleton - -def create_inventory_instance(backend="sqlite"): - """ - Create an instance of the inventory class - defined in `storage.`. - """ - return getattr( - getattr(storage, backend), - "{}Inventory".format(backend.title()))() - +# TODO make this dynamic, and watch out for frozen, like with messagetypes +import storage.sqlite +import storage.filesystem @Singleton class Inventory(): - """ - Inventory singleton class which uses storage backends - to manage the inventory. - """ def __init__(self): - self._moduleName = config.safeGet("inventory", "storage") - self._realInventory = create_inventory_instance(self._moduleName) + #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 # cheap inheritance copied from asyncore def __getattr__(self, attr): - if attr == "__contains__": - self.numberOfInventoryLookupsPerformed += 1 try: + 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) - ) + raise AttributeError("%s instance has no attribute '%s'" %(self.__class__.__name__, attr)) else: return realRet - # hint for pylint: this is dictionary like object - def __getitem__(self, key): - return self._realInventory[key] - def __setitem__(self, key, value): - self._realInventory[key] = value +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 + + 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: + self.pending[retval] = time.time() + return retval + + def clear(self): + with self.lock: + newPending = {} + for hashId in self.pending: + if self.pending[hashId] + PendingDownloadQueue.maxWait > time.time(): + newPending[hashId] = self.pending[hashId] + self.pending = newPending + + @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 + + @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): + pass + + +@Singleton +class PendingUpload(object): +# keep a track of objects that we have created but haven't distributed yet + def __init__(self): + super(self.__class__, self).__init__() + self.lock = RLock() + self.hashes = {} + # end by this time in any case + self.deadline = 0 + self.maxLen = 0 + # during shutdown, wait up to 20 seconds to finish uploading + self.shutdownWait = 20 + # forget tracking objects after 60 seconds + self.objectWait = 60 + # wait 10 seconds between clears + self.clearDelay = 10 + self.lastCleared = time.time() + + def add(self, objectHash = None): + with self.lock: + # add a new object into existing thread lists + if objectHash: + if objectHash not in self.hashes: + self.hashes[objectHash] = {'created': time.time(), 'sendCount': 0, 'peers': []} + for thread in threadingEnumerate(): + if thread.isAlive() and hasattr(thread, 'peer') and \ + thread.peer not in self.hashes[objectHash]['peers']: + self.hashes[objectHash]['peers'].append(thread.peer) + # add all objects into the current thread + else: + for objectHash in self.hashes: + if current_thread().peer not in self.hashes[objectHash]['peers']: + self.hashes[objectHash]['peers'].append(current_thread().peer) + + def len(self): + self.clearHashes() + with self.lock: + return sum(1 + for x in self.hashes if (self.hashes[x]['created'] + self.objectWait < time.time() or + self.hashes[x]['sendCount'] == 0)) + + def _progress(self): + with self.lock: + return float(sum(len(self.hashes[x]['peers']) + for x in self.hashes if (self.hashes[x]['created'] + self.objectWait < time.time()) or + self.hashes[x]['sendCount'] == 0)) + + def progress(self, raiseDeadline=True): + if self.maxLen < self._progress(): + self.maxLen = self._progress() + if self.deadline < time.time(): + if self.deadline > 0 and raiseDeadline: + raise PendingUploadDeadlineException + self.deadline = time.time() + 20 + try: + return 1.0 - self._progress() / self.maxLen + except ZeroDivisionError: + return 1.0 + + def clearHashes(self, objectHash=None): + if objectHash is None: + if self.lastCleared > time.time() - self.clearDelay: + return + objects = self.hashes.keys() + else: + objects = objectHash, + with self.lock: + for i in objects: + try: + if self.hashes[i]['sendCount'] > 0 and ( + len(self.hashes[i]['peers']) == 0 or + self.hashes[i]['created'] + self.objectWait < time.time()): + del self.hashes[i] + except KeyError: + pass + self.lastCleared = time.time() + + def delete(self, objectHash=None): + if not hasattr(current_thread(), 'peer'): + return + if objectHash is None: + return + with self.lock: + try: + if objectHash in self.hashes and current_thread().peer in self.hashes[objectHash]['peers']: + self.hashes[objectHash]['sendCount'] += 1 + self.hashes[objectHash]['peers'].remove(current_thread().peer) + except KeyError: + pass + self.clearHashes(objectHash) + + def stop(self): + with self.lock: + self.hashes = {} + + def threadEnd(self): + with self.lock: + for objectHash in self.hashes: + try: + if current_thread().peer in self.hashes[objectHash]['peers']: + self.hashes[objectHash]['peers'].remove(current_thread().peer) + except KeyError: + pass + self.clearHashes() diff --git a/src/knownnodes.py b/src/knownnodes.py new file mode 100644 index 00000000..aa080128 --- /dev/null +++ b/src/knownnodes.py @@ -0,0 +1,49 @@ +import pickle +import os +import threading + +from bmconfigparser import BMConfigParser +import state + +knownNodesLock = threading.Lock() +knownNodes = {} + +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(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]) < int(BMConfigParser().get("knownnodes", "maxnodes")): + return + with knownNodesLock: + oldestList = sorted(knownNodes[recAddrStream], key=lambda x: x['lastseen'])[:knownNodesTrimAmount] + for oldest in oldestList: + del knownNodes[recAddrStream][oldest] diff --git a/src/l10n.py b/src/l10n.py index fe02d3f4..b3b16341 100644 --- a/src/l10n.py +++ b/src/l10n.py @@ -1,33 +1,21 @@ -"""Localization helpers""" import logging import os -import re -import sys import time -from six.moves import range +from bmconfigparser import BMConfigParser -from bmconfigparser import config -logger = logging.getLogger('default') +#logger = logging.getLogger(__name__) +logger = logging.getLogger('file_only') + DEFAULT_ENCODING = 'ISO8859-1' DEFAULT_LANGUAGE = 'en_US' DEFAULT_TIME_FORMAT = '%Y-%m-%d %H:%M:%S' -try: - import locale - encoding = locale.getpreferredencoding(True) or DEFAULT_ENCODING - language = ( - locale.getlocale()[0] or locale.getdefaultlocale()[0] - or DEFAULT_LANGUAGE) -except (ImportError, AttributeError): # FIXME: it never happens - logger.exception('Could not determine language or encoding') - locale = None - encoding = DEFAULT_ENCODING - language = DEFAULT_LANGUAGE - +encoding = DEFAULT_ENCODING +language = DEFAULT_LANGUAGE windowsLanguageMap = { "ar": "arabic", @@ -52,91 +40,87 @@ windowsLanguageMap = { "zh_TW": "chinese-traditional" } +try: + import locale + encoding = locale.getpreferredencoding(True) or DEFAULT_ENCODING + language = locale.getlocale()[0] or locale.getdefaultlocale()[0] or DEFAULT_LANGUAGE +except: + logger.exception('Could not determine language or encoding') -time_format = config.safeGet( - 'bitmessagesettings', 'timeformat', DEFAULT_TIME_FORMAT) -if not re.search(r'\d', time.strftime(time_format)): +if BMConfigParser().has_option('bitmessagesettings', 'timeformat'): + time_format = BMConfigParser().get('bitmessagesettings', 'timeformat') + #Test the format string + try: + time.strftime(time_format) + except: + logger.exception('Could not format timestamp') + time_format = DEFAULT_TIME_FORMAT +else: time_format = DEFAULT_TIME_FORMAT -# It seems some systems lie about the encoding they use -# so we perform comprehensive decoding tests -elif sys.version_info[0] == 2: +#It seems some systems lie about the encoding they use so we perform +#comprehensive decoding tests +if time_format != DEFAULT_TIME_FORMAT: try: - # Check day names - for i in range(7): - time.strftime( - time_format, (0, 0, 0, 0, 0, 0, i, 0, 0)).decode(encoding) - # Check month names - for i in range(1, 13): - time.strftime( - time_format, (0, i, 0, 0, 0, 0, 0, 0, 0)).decode(encoding) - # Check AM/PM - time.strftime( - time_format, (0, 0, 0, 11, 0, 0, 0, 0, 0)).decode(encoding) - time.strftime( - time_format, (0, 0, 0, 13, 0, 0, 0, 0, 0)).decode(encoding) - # Check DST - time.strftime( - time_format, (0, 0, 0, 0, 0, 0, 0, 0, 1)).decode(encoding) - except Exception: # TODO: write tests and determine exception types + #Check day names + for i in xrange(7): + unicode(time.strftime(time_format, (0, 0, 0, 0, 0, 0, i, 0, 0)), encoding) + #Check month names + for i in xrange(1, 13): + unicode(time.strftime(time_format, (0, i, 0, 0, 0, 0, 0, 0, 0)), encoding) + #Check AM/PM + unicode(time.strftime(time_format, (0, 0, 0, 11, 0, 0, 0, 0, 0)), encoding) + unicode(time.strftime(time_format, (0, 0, 0, 13, 0, 0, 0, 0, 0)), encoding) + #Check DST + unicode(time.strftime(time_format, (0, 0, 0, 0, 0, 0, 0, 0, 1)), encoding) + except: logger.exception('Could not decode locale formatted timestamp') - # time_format = DEFAULT_TIME_FORMAT + time_format = DEFAULT_TIME_FORMAT encoding = DEFAULT_ENCODING - -def setlocale(newlocale): - """Set the locale""" - try: - locale.setlocale(locale.LC_ALL, newlocale) - except AttributeError: # locale is None - pass +def setlocale(category, newlocale): + locale.setlocale(category, newlocale) # it looks like some stuff isn't initialised yet when this is called the # first time and its init gets the locale settings from the environment os.environ["LC_ALL"] = newlocale - -def formatTimestamp(timestamp=None): - """Return a formatted timestamp""" - # For some reason some timestamps are strings so we need to sanitize. +def formatTimestamp(timestamp = None, as_unicode = True): + #For some reason some timestamps are strings so we need to sanitize. if timestamp is not None and not isinstance(timestamp, int): try: timestamp = int(timestamp) - except (ValueError, TypeError): + except: timestamp = None - # timestamp can't be less than 0. + #timestamp can't be less than 0. if timestamp is not None and timestamp < 0: timestamp = None if timestamp is None: timestring = time.strftime(time_format) else: - # In case timestamp is too far in the future + #In case timestamp is too far in the future try: timestring = time.strftime(time_format, time.localtime(timestamp)) except ValueError: timestring = time.strftime(time_format) - if sys.version_info[0] == 2: - return timestring.decode(encoding) + if as_unicode: + return unicode(timestring, encoding) return timestring - def getTranslationLanguage(): - """Return the user's language choice""" - userlocale = config.safeGet( - 'bitmessagesettings', 'userlocale', 'system') - return userlocale if userlocale and userlocale != 'system' else language + userlocale = None + if BMConfigParser().has_option('bitmessagesettings', 'userlocale'): + userlocale = BMConfigParser().get('bitmessagesettings', 'userlocale') + if userlocale in [None, '', 'system']: + return language + return userlocale + def getWindowsLocale(posixLocale): - """ - Get the Windows locale - Technically this converts the locale string from UNIX to Windows format, - because they use different ones in their - libraries. E.g. "en_EN.UTF-8" to "english". - """ if posixLocale in windowsLanguageMap: return windowsLanguageMap[posixLocale] if "." in posixLocale: diff --git a/src/main.py b/src/main.py deleted file mode 100644 index e1644436..00000000 --- a/src/main.py +++ /dev/null @@ -1,13 +0,0 @@ -"""This module is for thread start.""" -import state -import sys -from bitmessagemain import main -from termcolor import colored -print(colored('kivy is not supported at the moment for this version..', 'red')) -sys.exit() - - -if __name__ == '__main__': - state.kivy = True - print("Kivy Loading......") - main() diff --git a/src/message_data_reader.py b/src/message_data_reader.py new file mode 100644 index 00000000..a0659807 --- /dev/null +++ b/src/message_data_reader.py @@ -0,0 +1,112 @@ +#This program can be used to print out everything in your Inbox or Sent folders and also take things out of the trash. +#Scroll down to the bottom to see the functions that you can uncomment. Save then run this file. +#The functions which only read the database file seem to function just fine even if you have Bitmessage running but you should definitly close it before running the functions that make changes (like taking items out of the trash). + +import sqlite3 +from time import strftime, localtime +import sys +import paths +import queues +import state +from binascii import hexlify + +appdata = paths.lookupAppdataFolder() + +conn = sqlite3.connect( appdata + 'messages.dat' ) +conn.text_factory = str +cur = conn.cursor() + +def readInbox(): + print 'Printing everything in inbox table:' + item = '''select * from inbox''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + for row in output: + print row + +def readSent(): + print 'Printing everything in Sent table:' + item = '''select * from sent where folder !='trash' ''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + for row in output: + msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, sleeptill, status, retrynumber, folder, encodingtype, ttl = row + print hexlify(msgid), toaddress, 'toripe:', hexlify(toripe), 'fromaddress:', fromaddress, 'ENCODING TYPE:', encodingtype, 'SUBJECT:', repr(subject), 'MESSAGE:', repr(message), 'ACKDATA:', hexlify(ackdata), lastactiontime, status, retrynumber, folder + +def readSubscriptions(): + print 'Printing everything in subscriptions table:' + item = '''select * from subscriptions''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + for row in output: + print row + +def readPubkeys(): + print 'Printing everything in pubkeys table:' + item = '''select address, transmitdata, time, usedpersonally from pubkeys''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + for row in output: + address, transmitdata, time, usedpersonally = row + print 'Address:', address, '\tTime first broadcast:', unicode(strftime('%a, %d %b %Y %I:%M %p',localtime(time)),'utf-8'), '\tUsed by me personally:', usedpersonally, '\tFull pubkey message:', hexlify(transmitdata) + +def readInventory(): + print 'Printing everything in inventory table:' + item = '''select hash, objecttype, streamnumber, payload, expirestime from inventory''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + for row in output: + hash, objecttype, streamnumber, payload, expirestime = row + print 'Hash:', hexlify(hash), objecttype, streamnumber, '\t', hexlify(payload), '\t', unicode(strftime('%a, %d %b %Y %I:%M %p',localtime(expirestime)),'utf-8') + + +def takeInboxMessagesOutOfTrash(): + item = '''update inbox set folder='inbox' where folder='trash' ''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + conn.commit() + print 'done' + +def takeSentMessagesOutOfTrash(): + item = '''update sent set folder='sent' where folder='trash' ''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + conn.commit() + print 'done' + +def markAllInboxMessagesAsUnread(): + item = '''update inbox set read='0' ''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + conn.commit() + queues.UISignalQueue.put(('changedInboxUnread', None)) + print 'done' + +def vacuum(): + item = '''VACUUM''' + parameters = '' + cur.execute(item, parameters) + output = cur.fetchall() + conn.commit() + print 'done' + +#takeInboxMessagesOutOfTrash() +#takeSentMessagesOutOfTrash() +#markAllInboxMessagesAsUnread() +readInbox() +#readSent() +#readPubkeys() +#readSubscriptions() +#readInventory() +#vacuum() #will defragment and clean empty space from the messages.dat file. + + + diff --git a/src/messagetypes/__init__.py b/src/messagetypes/__init__.py index b9ddd1e9..1a5223df 100644 --- a/src/messagetypes/__init__.py +++ b/src/messagetypes/__init__.py @@ -1,54 +1,49 @@ -import logging - from importlib import import_module +from os import path, listdir +from string import lower -logger = logging.getLogger('default') +from debug import logger +import paths + +class MsgBase(object): + def encode(self): + self.data = {"": lower(type(self).__name__)} def constructObject(data): - """Constructing an object""" whitelist = ["message"] if data[""] not in whitelist: return None try: - classBase = getattr(import_module(".{}".format(data[""]), __name__), data[""].title()) - except (NameError, AttributeError, ValueError, ImportError): + 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 - except: # noqa:E722 - logger.error("Don't know how to handle message type: \"%s\"", data[""], exc_info=True) - return None - try: returnObj = classBase() returnObj.decode(data) except KeyError as e: logger.error("Missing mandatory key %s", e) return None - except: # noqa:E722 + except: logger.error("classBase fail", exc_info=True) return None else: return returnObj - -try: - from pybitmessage import paths -except ImportError: - paths = None - -if paths and paths.frozen is not None: - from . import message, vote # noqa: F401 flake8: disable=unused-import +if paths.frozen is not None: + import messagetypes.message + import messagetypes.vote else: - import os - for mod in os.listdir(os.path.dirname(__file__)): + for mod in listdir(path.dirname(__file__)): if mod == "__init__.py": continue - splitted = os.path.splitext(mod) + splitted = path.splitext(mod) if splitted[1] != ".py": continue try: - import_module(".{}".format(splitted[0]), __name__) + import_module("." + splitted[0], "messagetypes") except ImportError: logger.error("Error importing %s", mod, exc_info=True) else: diff --git a/src/messagetypes/message.py b/src/messagetypes/message.py index 245c753f..ab61e375 100644 --- a/src/messagetypes/message.py +++ b/src/messagetypes/message.py @@ -1,45 +1,31 @@ -import logging - -logger = logging.getLogger('default') - - -class MsgBase(object): # pylint: disable=too-few-public-methods - """Base class for message types""" - def __init__(self): - self.data = {"": type(self).__name__.lower()} +from debug import logger +from messagetypes import MsgBase class Message(MsgBase): - """Encapsulate a message""" - # pylint: disable=attribute-defined-outside-init + def __init__(self): + return def decode(self, data): - """Decode a message""" # UTF-8 and variable type validator - subject = data.get("subject", "") - body = data.get("body", "") - try: - data["subject"] = subject.decode('utf-8', 'replace') - except: - data["subject"] = '' - - try: - data["body"] = body.decode('utf-8', 'replace') - except: - data["body"] = '' - - self.subject = data["subject"] - self.body = data["body"] + if type(data["subject"]) is str: + self.subject = unicode(data["subject"], 'utf-8', 'replace') + else: + self.subject = unicode(str(data["subject"]), 'utf-8', 'replace') + if type(data["body"]) is str: + self.body = unicode(data["body"], 'utf-8', 'replace') + else: + self.body = unicode(str(data["body"]), 'utf-8', 'replace') def encode(self, data): - """Encode a message""" - super(Message, self).__init__() - self.data["subject"] = data.get("subject", "") - self.data["body"] = data.get("body", "") - + super(Message, self).encode() + try: + self.data["subject"] = data["subject"] + self.data["body"] = data["body"] + except KeyError as e: + logger.error("Missing key %s", e.name) return self.data def process(self): - """Process a message""" logger.debug("Subject: %i bytes", len(self.subject)) logger.debug("Body: %i bytes", len(self.body)) diff --git a/src/messagetypes/vote.py b/src/messagetypes/vote.py index b3e96513..df8d267f 100644 --- a/src/messagetypes/vote.py +++ b/src/messagetypes/vote.py @@ -1,30 +1,23 @@ -import logging - -from .message import MsgBase - -logger = logging.getLogger('default') - +from debug import logger +from messagetypes import MsgBase class Vote(MsgBase): - """Module used to vote""" + def __init__(self): + return def decode(self, data): - """decode a vote""" - # pylint: disable=attribute-defined-outside-init self.msgid = data["msgid"] self.vote = data["vote"] def encode(self, data): - """Encode a vote""" - super(Vote, self).__init__() + super(Vote, self).encode() try: self.data["msgid"] = data["msgid"] self.data["vote"] = data["vote"] except KeyError as e: - logger.error("Missing key %s", e) + logger.error("Missing key %s", e.name) return self.data def process(self): - """Encode a vote""" logger.debug("msgid: %s", self.msgid) logger.debug("vote: %s", self.vote) diff --git a/src/mockbm/__init__.py b/src/mockbm/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/mockbm/class_addressGenerator.py b/src/mockbm/class_addressGenerator.py deleted file mode 100644 index c84b92d5..00000000 --- a/src/mockbm/class_addressGenerator.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -A thread for creating addresses -""" - -import logging -import random -import threading - -from six.moves import queue - -from pybitmessage import state -from pybitmessage import queues - -from pybitmessage.bmconfigparser import config - -# from network.threads import StoppableThread - - -fake_addresses = { - 'BM-2cUgQGcTLWAkC6dNsv2Bc8XB3Y1GEesVLV': { - 'privsigningkey': '5KWXwYq1oJMzghUSJaJoWPn8VdeBbhDN8zFot1cBd6ezKKReqBd', - 'privencryptionkey': '5JaeFJs8iPcQT3N8676r3gHKvJ5mTWXy1VLhGCEDqRs4vpvpxV8' - }, - 'BM-2cUd2dm8MVMokruMTcGhhteTpyRZCAMhnA': { - 'privsigningkey': '5JnJ79nkcwjo4Aj7iG8sFMkzYoQqWfpUjTcitTuFJZ1YKHZz98J', - 'privencryptionkey': '5JXgNzTRouFLqSRFJvuHMDHCYPBvTeMPBiHt4Jeb6smNjhUNTYq' - }, - 'BM-2cWyvL54WytfALrJHZqbsDHca5QkrtByAW': { - 'privsigningkey': '5KVE4gLmcfYVicLdgyD4GmnbBTFSnY7Yj2UCuytQqgBBsfwDhpi', - 'privencryptionkey': '5JTw48CGm5CP8fyJUJQMq8HQANQMHDHp2ETUe1dgm6EFpT1egD7' - }, - 'BM-2cTE65PK9Y4AQEkCZbazV86pcQACocnRXd': { - 'privsigningkey': '5KCuyReHx9MB4m5hhEyCWcLEXqc8rxhD1T2VWk8CicPFc8B6LaZ', - 'privencryptionkey': '5KBRpwXdX3n2tP7f583SbFgfzgs6Jemx7qfYqhdH7B1Vhe2jqY6' - }, - 'BM-2cX5z1EgmJ87f2oKAwXdv4VQtEVwr2V3BG': { - 'privsigningkey': '5K5UK7qED7F1uWCVsehudQrszLyMZxFVnP6vN2VDQAjtn5qnyRK', - 'privencryptionkey': '5J5coocoJBX6hy5DFTWKtyEgPmADpSwfQTazMpU7QPeART6oMAu' - } -} - - -class StoppableThread(threading.Thread): - """Base class for application threads with stopThread method""" - name = None - logger = logging.getLogger('default') - - def __init__(self, name=None): - if name: - self.name = name - super(StoppableThread, self).__init__(name=self.name) - self.stop = threading.Event() - self._stopped = False - random.seed() - self.logger.info('Init thread %s', self.name) - - def stopThread(self): - """Stop the thread""" - self._stopped = True - self.stop.set() - - -class FakeAddressGenerator(StoppableThread): - """A thread for creating fake addresses""" - name = "addressGenerator" - address_list = list(fake_addresses.keys()) - - def stopThread(self): - try: - queues.addressGeneratorQueue.put(("stopThread", "data")) - except queue.Full: - self.logger.warning('addressGeneratorQueue is Full') - super(FakeAddressGenerator, self).stopThread() - - def run(self): - """ - Process the requests for addresses generation - from `.queues.addressGeneratorQueue` - """ - while state.shutdown == 0: - queueValue = queues.addressGeneratorQueue.get() - try: - address = self.address_list.pop(0) - label = queueValue[3] - - config.add_section(address) - config.set(address, 'label', label) - config.set(address, 'enabled', 'true') - config.set( - address, 'privsigningkey', fake_addresses[address]['privsigningkey']) - config.set( - address, 'privencryptionkey', fake_addresses[address]['privencryptionkey']) - config.save() - - queues.addressGeneratorQueue.task_done() - except IndexError: - self.logger.error( - 'Program error: you can only create 5 fake addresses') diff --git a/src/mockbm/helper_startup.py b/src/mockbm/helper_startup.py deleted file mode 100644 index 838302ae..00000000 --- a/src/mockbm/helper_startup.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from pybitmessage.bmconfigparser import config - - -def loadConfig(): - """Loading mock test data""" - config.read(os.path.join(os.environ['BITMESSAGE_HOME'], 'keys.dat')) - - -def total_encrypted_messages_per_month(): - """Loading mock total encrypted message """ - encrypted_messages_per_month = 0 - return encrypted_messages_per_month diff --git a/src/mockbm/kivy_main.py b/src/mockbm/kivy_main.py deleted file mode 100644 index 4cf4da98..00000000 --- a/src/mockbm/kivy_main.py +++ /dev/null @@ -1,40 +0,0 @@ -# pylint: disable=unused-import, wrong-import-position, ungrouped-imports -# flake8: noqa:E401, E402 - -"""Mock kivy app with mock threads.""" - -import os -from kivy.config import Config -from pybitmessage.mockbm import multiqueue -from pybitmessage import state - -if os.environ.get("INSTALL_TESTS", False): - Config.set("graphics", "height", 1280) - Config.set("graphics", "width", 720) - Config.set("graphics", "position", "custom") - Config.set("graphics", "top", 0) - Config.set("graphics", "left", 0) - - -from pybitmessage.mockbm.class_addressGenerator import FakeAddressGenerator # noqa:E402 -from pybitmessage.bitmessagekivy.mpybit import NavigateApp # noqa:E402 -from pybitmessage.mockbm import network # noqa:E402 - -stats = network.stats -objectracker = network.objectracker - - -def main(): - """main method for starting threads""" - # Start the address generation thread - addressGeneratorThread = FakeAddressGenerator() - # close the main program even if there are threads left - addressGeneratorThread.daemon = True - addressGeneratorThread.start() - state.kivyapp = NavigateApp() - state.kivyapp.run() - addressGeneratorThread.stopThread() - - -if __name__ == "__main__": - main() diff --git a/src/mockbm/multiqueue.py b/src/mockbm/multiqueue.py deleted file mode 100644 index 8ec76920..00000000 --- a/src/mockbm/multiqueue.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Mock MultiQueue (just normal Queue) -""" - -from six.moves import queue - -MultiQueue = queue.Queue diff --git a/src/mockbm/network.py b/src/mockbm/network.py deleted file mode 100644 index 3f33c91b..00000000 --- a/src/mockbm/network.py +++ /dev/null @@ -1,25 +0,0 @@ -# pylint: disable=too-few-public-methods - -""" -Mock Network -""" - - -class objectracker(object): - """Mock object tracker""" - - missingObjects = {} - - -class stats(object): - """Mock network statistics""" - - @staticmethod - def connectedHostsList(): - """Mock list of all the connected hosts""" - return ["conn1", "conn2", "conn3", "conn4"] - - @staticmethod - def pendingDownload(): - """Mock pending download count""" - return 0 diff --git a/src/multiqueue.py b/src/multiqueue.py index 88b6a4dd..62b0fa87 100644 --- a/src/multiqueue.py +++ b/src/multiqueue.py @@ -1,35 +1,21 @@ -""" -A queue with multiple internal subqueues. -Elements are added into a random subqueue, and retrieval rotates -""" - from collections import deque +import Queue +import random -from six.moves import queue - -try: - import helper_random -except ImportError: - from . import helper_random - - -class MultiQueue(queue.Queue): - """A base queue class""" - # pylint: disable=redefined-builtin,attribute-defined-outside-init +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) + Queue.Queue.__init__(self, maxsize) # Initialize the queue representation def _init(self, maxsize): self.iter = 0 self.queues = [] - for _ in range(self.queueCount): + for i in range(self.queueCount): self.queues.append(deque()) def _qsize(self, len=len): @@ -37,18 +23,15 @@ class MultiQueue(queue.Queue): # Put a new item in the queue def _put(self, item): - # self.queue.append(item) - self.queues[helper_random.randomrandrange(self.queueCount)].append( - (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): - """Increment the iteration counter""" self.iter = (self.iter + 1) % self.queueCount def totalSize(self): - """Return the total number of items in all the queues""" return sum(len(x) for x in self.queues) diff --git a/src/namecoin.py b/src/namecoin.py index a16cb3d7..9b3c3c3e 100644 --- a/src/namecoin.py +++ b/src/namecoin.py @@ -1,40 +1,54 @@ -""" -Namecoin queries -""" -# pylint: disable=too-many-branches,protected-access +# Copyright (C) 2013 by Daniel Kraft +# This file is part of the Bitmessage project. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. import base64 import httplib import json -import os import socket import sys +import os +from bmconfigparser import BMConfigParser import defaults -from addresses import decodeAddress -from bmconfigparser import config -from debug import logger -from tr import _translate # translate +import tr # translate + +# FIXME: from debug import logger crashes PyBitmessage due to a circular +# dependency. The debug module will also override/disable logging.getLogger() +# loggers so module level logging functions are used instead +import logging as logger configSection = "bitmessagesettings" - -class RPCError(Exception): - """Error thrown when the RPC call returns an error.""" - +# Error thrown when the RPC call returns an error. +class RPCError (Exception): error = None - def __init__(self, data): - super(RPCError, self).__init__() + def __init__ (self, data): self.error = data - + def __str__(self): - return "{0}: {1}".format(type(self).__name__, self.error) - - -class namecoinConnection(object): - """This class handles the Namecoin identity integration.""" + return '{0}: {1}'.format(type(self).__name__, self.error) +# This class handles the Namecoin identity integration. +class namecoinConnection (object): user = None password = None host = None @@ -44,60 +58,47 @@ class namecoinConnection(object): queryid = 1 con = None - def __init__(self, options=None): - """ - Initialise. If options are given, take the connection settings from - them instead of loading from the configs. This can be used to test - currently entered connection settings in the config dialog without - actually changing the values (yet). - """ + # Initialise. If options are given, take the connection settings from + # them instead of loading from the configs. This can be used to test + # currently entered connection settings in the config dialog without + # actually changing the values (yet). + def __init__ (self, options = None): if options is None: - self.nmctype = config.get( - configSection, "namecoinrpctype") - self.host = config.get( - configSection, "namecoinrpchost") - self.port = int(config.get( - configSection, "namecoinrpcport")) - self.user = config.get( - configSection, "namecoinrpcuser") - self.password = config.get( - configSection, "namecoinrpcpassword") + self.nmctype = BMConfigParser().get (configSection, "namecoinrpctype") + self.host = BMConfigParser().get (configSection, "namecoinrpchost") + self.port = int(BMConfigParser().get (configSection, "namecoinrpcport")) + self.user = BMConfigParser().get (configSection, "namecoinrpcuser") + self.password = BMConfigParser().get (configSection, + "namecoinrpcpassword") else: - self.nmctype = options["type"] - self.host = options["host"] - self.port = int(options["port"]) - self.user = options["user"] - self.password = options["password"] + self.nmctype = options["type"] + self.host = options["host"] + self.port = int(options["port"]) + self.user = options["user"] + self.password = options["password"] assert self.nmctype == "namecoind" or self.nmctype == "nmcontrol" if self.nmctype == "namecoind": - self.con = httplib.HTTPConnection(self.host, self.port, timeout=3) + self.con = httplib.HTTPConnection(self.host, self.port, timeout = 3) - def query(self, identity): - """ - Query for the bitmessage address corresponding to the given identity - string. If it doesn't contain a slash, id/ is prepended. We return - the result as (Error, Address) pair, where the Error is an error - message to display or None in case of success. - """ - slashPos = identity.find("/") + # Query for the bitmessage address corresponding to the given identity + # string. If it doesn't contain a slash, id/ is prepended. We return + # the result as (Error, Address) pair, where the Error is an error + # message to display or None in case of success. + def query (self, string): + slashPos = string.find ("/") if slashPos < 0: - display_name = identity - identity = "id/" + identity - else: - display_name = identity.split("/")[1] + string = "id/" + string try: if self.nmctype == "namecoind": - res = self.callRPC("name_show", [identity]) + res = self.callRPC ("name_show", [string]) res = res["value"] elif self.nmctype == "nmcontrol": - res = self.callRPC("data", ["getValue", identity]) + res = self.callRPC ("data", ["getValue", string]) res = res["reply"] - if not res: - return (_translate( - "MainWindow", "The name %1 was not found." - ).arg(identity.decode("utf-8", "ignore")), None) + if res == False: + return (tr._translate("MainWindow",'The name %1 was not found.').arg(unicode(string)), None) else: assert False except RPCError as exc: @@ -106,44 +107,29 @@ class namecoinConnection(object): errmsg = exc.error["message"] else: errmsg = exc.error - return (_translate( - "MainWindow", "The namecoin query failed (%1)" - ).arg(errmsg.decode("utf-8", "ignore")), None) - except AssertionError: - return (_translate( - "MainWindow", "Unknown namecoin interface type: %1" - ).arg(self.nmctype.decode("utf-8", "ignore")), None) - except Exception: + return (tr._translate("MainWindow",'The namecoin query failed (%1)').arg(unicode(errmsg)), None) + except Exception as exc: logger.exception("Namecoin query exception") - return (_translate( - "MainWindow", "The namecoin query failed."), None) + return (tr._translate("MainWindow",'The namecoin query failed.'), None) try: - res = json.loads(res) - except ValueError: - pass - else: - try: - display_name = res["name"] - except KeyError: - pass - res = res.get("bitmessage") + val = json.loads (res) + except: + logger.exception("Namecoin query json exception") + return (tr._translate("MainWindow",'The name %1 has no valid JSON data.').arg(unicode(string)), None) - valid = decodeAddress(res)[0] == "success" - return ( - None, "%s <%s>" % (display_name, res) - ) if valid else ( - _translate( - "MainWindow", - "The name %1 has no associated Bitmessage address." - ).arg(identity.decode("utf-8", "ignore")), None) + if "bitmessage" in val: + if "name" in val: + ret = "%s <%s>" % (val["name"], val["bitmessage"]) + else: + ret = val["bitmessage"] + return (None, ret) + return (tr._translate("MainWindow",'The name %1 has no associated Bitmessage address.').arg(unicode(string)), None) + # Test the connection settings. This routine tries to query a "getinfo" + # command, and builds either an error message or a success message with + # some info from it. def test(self): - """ - 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. - """ try: if self.nmctype == "namecoind": try: @@ -157,65 +143,45 @@ class namecoinConnection(object): vers = vers / 100 v1 = vers if v3 == 0: - versStr = "0.%d.%d" % (v1, v2) + versStr = "0.%d.%d" % (v1, v2) else: - versStr = "0.%d.%d.%d" % (v1, v2, v3) - message = ( - "success", - _translate( - "MainWindow", - "Success! Namecoind version %1 running.").arg( - versStr.decode("utf-8", "ignore"))) + versStr = "0.%d.%d.%d" % (v1, v2, v3) + return ('success', tr._translate("MainWindow",'Success! Namecoind version %1 running.').arg(unicode(versStr)) ) elif self.nmctype == "nmcontrol": - res = self.callRPC("data", ["status"]) + res = self.callRPC ("data", ["status"]) prefix = "Plugin data running" if ("reply" in res) and res["reply"][:len(prefix)] == prefix: - return ( - "success", - _translate( - "MainWindow", - "Success! NMControll is up and running." - ) - ) + return ('success', tr._translate("MainWindow",'Success! NMControll is up and running.')) logger.error("Unexpected nmcontrol reply: %s", res) - message = ( - "failed", - _translate( - "MainWindow", - "Couldn\'t understand NMControl." - ) - ) + return ('failed', tr._translate("MainWindow",'Couldn\'t understand NMControl.')) else: - sys.exit("Unsupported Namecoin type") - - return message + assert False except Exception: logger.info("Namecoin connection test failure") return ( - "failed", - _translate( + 'failed', + tr._translate( "MainWindow", "The connection to namecoin failed.") ) - def callRPC(self, method, params): - """Helper routine that actually performs an JSON RPC call.""" - + # Helper routine that actually performs an JSON RPC call. + def callRPC (self, method, params): data = {"method": method, "params": params, "id": self.queryid} if self.nmctype == "namecoind": - resp = self.queryHTTP(json.dumps(data)) + resp = self.queryHTTP (json.dumps (data)) elif self.nmctype == "nmcontrol": - resp = self.queryServer(json.dumps(data)) + resp = self.queryServer (json.dumps (data)) else: - assert False - val = json.loads(resp) + assert False + val = json.loads (resp) if val["id"] != self.queryid: - raise Exception("ID mismatch in JSON RPC answer.") - + raise Exception ("ID mismatch in JSON RPC answer.") + if self.nmctype == "namecoind": self.queryid = self.queryid + 1 @@ -224,12 +190,11 @@ class namecoinConnection(object): return val["result"] if isinstance(error, bool): - raise RPCError(val["result"]) - raise RPCError(error) - - def queryHTTP(self, data): - """Query the server via HTTP.""" + raise RPCError (val["result"]) + raise RPCError (error) + # Query the server via HTTP. + def queryHTTP (self, data): result = None try: @@ -241,71 +206,57 @@ class namecoinConnection(object): self.con.putheader("Content-Length", str(len(data))) self.con.putheader("Accept", "application/json") authstr = "%s:%s" % (self.user, self.password) - self.con.putheader( - "Authorization", "Basic %s" % base64.b64encode(authstr)) + self.con.putheader("Authorization", "Basic %s" % base64.b64encode (authstr)) self.con.endheaders() self.con.send(data) - except: # noqa:E722 + try: + resp = self.con.getresponse() + result = resp.read() + if resp.status != 200: + raise Exception ("Namecoin returned status %i: %s", resp.status, resp.reason) + except: + logger.info("HTTP receive error") + except: logger.info("HTTP connection error") - return None - - try: - resp = self.con.getresponse() - result = resp.read() - if resp.status != 200: - raise Exception( - "Namecoin returned status" - " %i: %s" % (resp.status, resp.reason)) - except: # noqa:E722 - logger.info("HTTP receive error") - return None return result - def queryServer(self, data): - """Helper routine sending data to the RPC " - "server and returning the result.""" - + # Helper routine sending data to the RPC server and returning the result. + def queryServer (self, data): try: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.settimeout(3) - s.connect((self.host, self.port)) - s.sendall(data) + s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.settimeout(3) + s.connect ((self.host, self.port)) + s.sendall (data) result = "" while True: - tmp = s.recv(self.bufsize) + tmp = s.recv (self.bufsize) if not tmp: - break + break result += tmp - s.close() + s.close () return result except socket.error as exc: - raise Exception("Socket error in RPC connection: %s" % exc) - - -def lookupNamecoinFolder(): - """ - Look up the namecoin data folder. - - .. todo:: Check whether this works on other platforms as well! - """ + raise Exception ("Socket error in RPC connection: %s" % str (exc)) +# Look up the namecoin data folder. +# FIXME: Check whether this works on other platforms as well! +def lookupNamecoinFolder (): app = "namecoin" from os import path, environ if sys.platform == "darwin": if "HOME" in environ: - dataFolder = path.join(os.environ["HOME"], - "Library/Application Support/", app) + "/" + dataFolder = path.join (os.environ["HOME"], + "Library/Application Support/", app) + '/' else: - sys.exit( - "Could not find home folder, please report this message" - " and your OS X version to the BitMessage Github." - ) + print ("Could not find home folder, please report this message" + + " and your OS X version to the BitMessage Github.") + sys.exit() elif "win32" in sys.platform or "win64" in sys.platform: dataFolder = path.join(environ["APPDATA"], app) + "\\" @@ -314,38 +265,34 @@ def lookupNamecoinFolder(): return dataFolder +# Ensure all namecoin options are set, by setting those to default values +# that aren't there. +def ensureNamecoinOptions (): + if not BMConfigParser().has_option (configSection, "namecoinrpctype"): + BMConfigParser().set (configSection, "namecoinrpctype", "namecoind") + if not BMConfigParser().has_option (configSection, "namecoinrpchost"): + BMConfigParser().set (configSection, "namecoinrpchost", "localhost") -def ensureNamecoinOptions(): - """ - Ensure all namecoin options are set, by setting those to default values - that aren't there. - """ - - if not config.has_option(configSection, "namecoinrpctype"): - config.set(configSection, "namecoinrpctype", "namecoind") - if not config.has_option(configSection, "namecoinrpchost"): - config.set(configSection, "namecoinrpchost", "localhost") - - hasUser = config.has_option(configSection, "namecoinrpcuser") - hasPass = config.has_option(configSection, "namecoinrpcpassword") - hasPort = config.has_option(configSection, "namecoinrpcport") + hasUser = BMConfigParser().has_option (configSection, "namecoinrpcuser") + hasPass = BMConfigParser().has_option (configSection, "namecoinrpcpassword") + hasPort = BMConfigParser().has_option (configSection, "namecoinrpcport") # Try to read user/password from .namecoin configuration file. defaultUser = "" defaultPass = "" - nmcFolder = lookupNamecoinFolder() + nmcFolder = lookupNamecoinFolder () nmcConfig = nmcFolder + "namecoin.conf" try: - nmc = open(nmcConfig, "r") + nmc = open (nmcConfig, "r") while True: - line = nmc.readline() + line = nmc.readline () if line == "": break - parts = line.split("=") - if len(parts) == 2: + parts = line.split ("=") + if len (parts) == 2: key = parts[0] - val = parts[1].rstrip() + val = parts[1].rstrip () if key == "rpcuser" and not hasUser: defaultUser = val @@ -353,21 +300,20 @@ def ensureNamecoinOptions(): defaultPass = val if key == "rpcport": defaults.namecoinDefaultRpcPort = val - - nmc.close() + + nmc.close () except IOError: - logger.warning( - "%s unreadable or missing, Namecoin support deactivated", - nmcConfig) - except Exception: + logger.error("%s unreadable or missing, Namecoin support deactivated", nmcConfig) + except Exception as exc: logger.warning("Error processing namecoin.conf", exc_info=True) # If still nothing found, set empty at least. - if not hasUser: - config.set(configSection, "namecoinrpcuser", defaultUser) - if not hasPass: - config.set(configSection, "namecoinrpcpassword", defaultPass) + if (not hasUser): + BMConfigParser().set (configSection, "namecoinrpcuser", defaultUser) + if (not hasPass): + BMConfigParser().set (configSection, "namecoinrpcpassword", defaultPass) # Set default port now, possibly to found value. - if not hasPort: - config.set(configSection, "namecoinrpcport", defaults.namecoinDefaultRpcPort) + if (not hasPort): + BMConfigParser().set (configSection, "namecoinrpcport", + defaults.namecoinDefaultRpcPort) diff --git a/src/network/__init__.py b/src/network/__init__.py index 1b5aef92..e69de29b 100644 --- a/src/network/__init__.py +++ b/src/network/__init__.py @@ -1,47 +0,0 @@ -""" -Network subsystem package -""" - -try: - from .announcethread import AnnounceThread - from .connectionpool import BMConnectionPool -except ImportError: - AnnounceThread = None - BMConnectionPool = None -from .threads import StoppableThread - - -__all__ = ["AnnounceThread", "BMConnectionPool", "StoppableThread"] - - -def start(config, state): - """Start network threads""" - from .addrthread import AddrThread - from .dandelion import Dandelion - from .downloadthread import DownloadThread - from .invthread import InvThread - from .networkthread import BMNetworkThread - from .knownnodes import readKnownNodes - from .receivequeuethread import ReceiveQueueThread - from .uploadthread import UploadThread - - readKnownNodes() - # init, needs to be early because other thread may access it early - Dandelion() - BMConnectionPool().connectToStream(1) - for thread in ( - BMNetworkThread(), InvThread(), AddrThread(), - DownloadThread(), UploadThread() - ): - thread.daemon = True - thread.start() - - # Optional components - for i in range(config.getint('threads', 'receive')): - thread = ReceiveQueueThread(i) - thread.daemon = True - thread.start() - if config.safeGetBoolean('bitmessagesettings', 'udp'): - state.announceThread = AnnounceThread() - state.announceThread.daemon = True - state.announceThread.start() diff --git a/src/network/addrthread.py b/src/network/addrthread.py index fea0910e..5b0ea638 100644 --- a/src/network/addrthread.py +++ b/src/network/addrthread.py @@ -1,21 +1,18 @@ -""" -Announce addresses as they are received from other hosts -""" -from six.moves import queue +import Queue +import threading -# magic imports! -import state -from helper_random import randomshuffle -from protocol import assembleAddrMessage -from queues import addrQueue # FIXME: init with queue +import addresses +from helper_threading import StoppableThread from network.connectionpool import BMConnectionPool +from queues import addrQueue +import protocol +import state -from threads import StoppableThread - - -class AddrThread(StoppableThread): - """(Node) address broadcasting thread""" - name = "AddrBroadcaster" +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: @@ -23,26 +20,15 @@ class AddrThread(StoppableThread): while True: try: data = addrQueue.get(False) - chunk.append(data) - except queue.Empty: + chunk.append((data[0], data[1])) + if len(data) > 2: + source = BMConnectionPool().getConnectionByAddr(data[2]) + except Queue.Empty: break + except KeyError: + continue - if chunk: - # Choose peers randomly - connections = BMConnectionPool().establishedConnections() - randomshuffle(connections) - for i in connections: - randomshuffle(chunk) - filtered = [] - for stream, peer, seen, destination in chunk: - # peer's own address or address received from peer - if i.destination in (peer, destination): - continue - if stream not in i.streams: - continue - filtered.append((stream, peer, seen)) - if filtered: - i.append_write_buf(assembleAddrMessage(filtered)) + #finish addrQueue.iterate() for i in range(len(chunk)): diff --git a/src/network/advanceddispatcher.py b/src/network/advanceddispatcher.py index 49f0d19d..6f857398 100644 --- a/src/network/advanceddispatcher.py +++ b/src/network/advanceddispatcher.py @@ -1,37 +1,18 @@ -""" -Improved version of asyncore dispatcher -""" import socket import threading import time -import network.asyncore_pollchoose as asyncore +import asyncore_pollchoose as asyncore +from debug import logger +from helper_threading import BusyError, nonBlocking import state -from threads import BusyError, nonBlocking - - -class ProcessingError(Exception): - """General class for protocol parser exception, - use as a base for others.""" - pass - - -class UnknownStateError(ProcessingError): - """Parser points to an unknown (unimplemented) state.""" - pass - class AdvancedDispatcher(asyncore.dispatcher): - """Improved version of asyncore dispatcher, - with buffers and protocol state.""" - # pylint: disable=too-many-instance-attributes - _buf_len = 131072 # 128kB + _buf_len = 131072 # 128kB def __init__(self, sock=None): if not hasattr(self, '_map'): asyncore.dispatcher.__init__(self, sock) - self.connectedAt = 0 - self.close_reason = None self.read_buf = bytearray() self.write_buf = bytearray() self.state = "init" @@ -42,10 +23,8 @@ class AdvancedDispatcher(asyncore.dispatcher): self.readLock = threading.RLock() self.writeLock = threading.RLock() self.processingLock = threading.RLock() - self.uploadChunk = self.downloadChunk = 0 def append_write_buf(self, data): - """Append binary data to the end of stream write buffer.""" if data: if isinstance(data, list): with self.writeLock: @@ -56,7 +35,6 @@ class AdvancedDispatcher(asyncore.dispatcher): self.write_buf.extend(data) def slice_write_buf(self, length=0): - """Cut the beginning of the stream write buffer.""" if length > 0: with self.writeLock: if length >= len(self.write_buf): @@ -65,7 +43,6 @@ class AdvancedDispatcher(asyncore.dispatcher): del self.write_buf[0:length] def slice_read_buf(self, length=0): - """Cut the beginning of the stream read buffer.""" if length > 0: with self.readLock: if length >= len(self.read_buf): @@ -74,8 +51,6 @@ class AdvancedDispatcher(asyncore.dispatcher): del self.read_buf[0:length] def process(self): - """Process (parse) data that's in the buffer, - as long as there is enough data and the connection is open.""" while self.connected and not state.shutdown: try: with nonBlocking(self.processingLock): @@ -83,53 +58,43 @@ class AdvancedDispatcher(asyncore.dispatcher): break if len(self.read_buf) < self.expectBytes: return False - try: - cmd = getattr(self, "state_" + str(self.state)) - except AttributeError: - self.logger.error( - 'Unknown state %s', self.state, exc_info=True) - raise UnknownStateError(self.state) - if not cmd(): + if not getattr(self, "state_" + str(self.state))(): break + except AttributeError: + logger.error("Unknown state %s", self.state, exc_info=True) + raise except BusyError: return False return False - def set_state(self, state_str, length=0, expectBytes=0): - """Set the next processing state.""" + def set_state(self, state, length=0, expectBytes=0): self.expectBytes = expectBytes self.slice_read_buf(length) - self.state = state_str + self.state = state def writable(self): - """Is data from the write buffer ready to be sent to the network?""" 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)) + return asyncore.dispatcher.writable(self) and \ + (self.connecting or (self.connected and self.uploadChunk > 0)) def readable(self): - """Is the read buffer ready to accept data from the network?""" 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)) + 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)) + return asyncore.dispatcher.readable(self) and \ + (self.connecting or self.accepting or (self.connected and self.downloadChunk > 0)) def handle_read(self): - """Append incoming data to the read buffer.""" self.lastTx = time.time() newData = self.recv(self.downloadChunk) self.receivedBytes += len(newData) @@ -138,7 +103,6 @@ class AdvancedDispatcher(asyncore.dispatcher): self.read_buf.extend(newData) def handle_write(self): - """Send outgoing data from write buffer.""" self.lastTx = time.time() written = self.send(self.write_buf[0:self.uploadChunk]) asyncore.update_sent(written) @@ -146,25 +110,19 @@ class AdvancedDispatcher(asyncore.dispatcher): self.slice_write_buf(written) def handle_connect_event(self): - """Callback for connection established event.""" try: asyncore.dispatcher.handle_connect_event(self) except socket.error as e: - # pylint: disable=protected-access if e.args[0] not in asyncore._DISCONNECTED: raise def handle_connect(self): - """Method for handling connection established implementations.""" self.lastTx = time.time() - def state_close(self): # pylint: disable=no-self-use - """Signal to the processing loop to end.""" + def state_close(self): return False def handle_close(self): - """Callback for connection being closed, - but can also be called directly when you want connection to close.""" with self.readLock: self.read_buf = bytearray() with self.writeLock: diff --git a/src/network/announcethread.py b/src/network/announcethread.py index 84807757..a94eeb36 100644 --- a/src/network/announcethread.py +++ b/src/network/announcethread.py @@ -1,44 +1,35 @@ -""" -Announce myself (node address) -""" +import threading import time -# magic imports! -import state -from bmconfigparser import config -from protocol import assembleAddrMessage +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 -from node import Peer -from threads import StoppableThread - - -class AnnounceThread(StoppableThread): - """A thread to manage regular announcing of this node""" - name = "Announcer" - announceInterval = 60 +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() - self.announceInterval: + if lastSelfAnnounced < time.time() - UDPSocket.announceInterval: self.announceSelf() lastSelfAnnounced = time.time() if processed == 0: self.stop.wait(10) - @staticmethod - def announceSelf(): - """Announce our presence""" + def announceSelf(self): for connection in BMConnectionPool().udpSockets.values(): if not connection.announcing: continue for stream in state.streamsInWhichIAmParticipating: - addr = ( - stream, - Peer( - '127.0.0.1', - config.safeGetInt('bitmessagesettings', 'port')), - int(time.time())) - connection.append_write_buf(assembleAddrMessage([addr])) + 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 index bdd312c6..cd19063a 100644 --- a/src/network/asyncore_pollchoose.py +++ b/src/network/asyncore_pollchoose.py @@ -1,26 +1,66 @@ -""" -Basic infrastructure for asynchronous socket service clients and servers. -""" # -*- Mode: Python -*- # Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp # Author: Sam Rushing -# pylint: disable=too-many-branches,too-many-lines,global-statement -# pylint: disable=redefined-builtin,no-self-use -import os + +# ====================================================================== +# 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 -import warnings -from errno import ( - EADDRINUSE, EAGAIN, EALREADY, EBADF, ECONNABORTED, ECONNREFUSED, - ECONNRESET, EHOSTUNREACH, EINPROGRESS, EINTR, EINVAL, EISCONN, ENETUNREACH, - ENOTCONN, ENOTSOCK, EPIPE, ESHUTDOWN, ETIMEDOUT, EWOULDBLOCK, errorcode -) from threading import current_thread +import warnings -import helper_random - +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): @@ -34,15 +74,13 @@ try: except (ImportError, AttributeError): WSAECONNRESET = ECONNRESET try: - # Desirable side-effects on Windows; imports winsock error numbers - from errno import WSAEADDRINUSE # pylint: disable=unused-import + from errno import WSAEADDRINUSE except (ImportError, AttributeError): WSAEADDRINUSE = EADDRINUSE - -_DISCONNECTED = frozenset(( - ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, EBADF, ECONNREFUSED, - EHOSTUNREACH, ENETUNREACH, ETIMEDOUT, WSAECONNRESET)) +_DISCONNECTED = frozenset((ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, + EBADF, ECONNREFUSED, EHOSTUNREACH, ENETUNREACH, ETIMEDOUT, + WSAECONNRESET)) OP_READ = 1 OP_WRITE = 2 @@ -52,22 +90,17 @@ try: 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 - + return "Unknown error %s" %err class ExitNow(Exception): - """We don't use directly but may be necessary as we replace - asyncore due to some library raising or expecting it""" pass - _reraised_exceptions = (ExitNow, KeyboardInterrupt, SystemExit) maxDownloadRate = 0 @@ -79,39 +112,28 @@ uploadTimestamp = 0 uploadBucket = 0 sentBytes = 0 - def read(obj): - """Event to read from the object, i.e. its network socket.""" - if not can_receive(): return try: obj.handle_read_event() except _reraised_exceptions: raise - except BaseException: + except: obj.handle_error() - def write(obj): - """Event to write to the object, i.e. its network socket.""" - if not can_send(): return try: obj.handle_write_event() except _reraised_exceptions: raise - except BaseException: + except: obj.handle_error() - def set_rates(download, upload): - """Set throttling rates""" - - global maxDownloadRate, maxUploadRate, downloadBucket - global uploadBucket, downloadTimestamp, uploadTimestamp - + global maxDownloadRate, maxUploadRate, downloadBucket, uploadBucket, downloadTimestamp, uploadTimestamp maxDownloadRate = float(download) * 1024 maxUploadRate = float(upload) * 1024 downloadBucket = maxDownloadRate @@ -119,41 +141,26 @@ def set_rates(download, upload): downloadTimestamp = time.time() uploadTimestamp = time.time() - def can_receive(): - """Predicate indicating whether the download throttle is in effect""" - return maxDownloadRate == 0 or downloadBucket > 0 - def can_send(): - """Predicate indicating whether the upload throttle is in effect""" - return maxUploadRate == 0 or uploadBucket > 0 - def update_received(download=0): - """Update the receiving throttle""" - global receivedBytes, downloadBucket, downloadTimestamp - currentTimestamp = time.time() receivedBytes += download if maxDownloadRate > 0: - bucketIncrease = \ - maxDownloadRate * (currentTimestamp - downloadTimestamp) + bucketIncrease = maxDownloadRate * (currentTimestamp - downloadTimestamp) downloadBucket += bucketIncrease if downloadBucket > maxDownloadRate: downloadBucket = int(maxDownloadRate) downloadBucket -= download downloadTimestamp = currentTimestamp - def update_sent(upload=0): - """Update the sending throttle""" - global sentBytes, uploadBucket, uploadTimestamp - currentTimestamp = time.time() sentBytes += upload if maxUploadRate > 0: @@ -164,21 +171,15 @@ def update_sent(upload=0): uploadBucket -= upload uploadTimestamp = currentTimestamp - def _exception(obj): - """Handle exceptions as appropriate""" - try: obj.handle_expt_event() except _reraised_exceptions: raise - except BaseException: + except: obj.handle_error() - def readwrite(obj, flags): - """Read and write any pending data to/from the object""" - try: if flags & select.POLLIN and can_receive(): obj.handle_read_event() @@ -195,19 +196,15 @@ def readwrite(obj, flags): obj.handle_close() except _reraised_exceptions: raise - except BaseException: + 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 = [] + r = []; w = []; e = [] for fd, obj in list(map.items()): is_r = obj.readable() is_w = obj.writable() @@ -233,13 +230,13 @@ def select_poller(timeout=0.0, map=None): if err.args[0] in (WSAENOTSOCK, ): return - for fd in helper_random.randomsample(r, len(r)): + for fd in random.sample(r, len(r)): obj = map.get(fd) if obj is None: continue read(obj) - for fd in helper_random.randomsample(w, len(w)): + for fd in random.sample(w, len(w)): obj = map.get(fd) if obj is None: continue @@ -253,15 +250,13 @@ def select_poller(timeout=0.0, map=None): 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) + timeout = int(timeout*1000) try: poll_poller.pollster except AttributeError: @@ -297,7 +292,7 @@ def poll_poller(timeout=0.0, map=None): except socket.error as err: if err.args[0] in (EBADF, WSAENOTSOCK, EINTR): return - for fd, flags in helper_random.randomsample(r, len(r)): + for fd, flags in random.sample(r, len(r)): obj = map.get(fd) if obj is None: continue @@ -305,15 +300,12 @@ def poll_poller(timeout=0.0, map=None): 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: @@ -353,23 +345,20 @@ def epoll_poller(timeout=0.0, map=None): if e.errno != EINTR: raise r = [] - except select.error as err: + except select.error, err: if err.args[0] != EINTR: raise r = [] - for fd, flags in helper_random.randomsample(r, len(r)): + for fd, flags in random.sample(r, len(r)): obj = map.get(fd) if obj is None: continue - readwrite(obj, flags) + readwrite(obj, flags) else: current_thread().stop.wait(timeout) - def kqueue_poller(timeout=0.0, map=None): """A poller which uses kqueue(), BSD specific.""" - # pylint: disable=no-member,too-many-statements - if map is None: map = socket_map try: @@ -396,20 +385,14 @@ def kqueue_poller(timeout=0.0, map=None): 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)) + 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)) + updates.append(select.kevent(fd, filter=select.KQ_FILTER_WRITE, flags=poller_flags)) obj.poller_filter = kq_filter if not selectables: @@ -420,11 +403,11 @@ def kqueue_poller(timeout=0.0, map=None): events = kqueue_poller.pollster.control(updates, selectables, timeout) if len(events) > 1: - events = helper_random.randomsample(events, len(events)) + events = random.sample(events, len(events)) for event in events: fd = event.ident - obj = map.get(fd) + obj = map.get(fd) if obj is None: continue if event.flags & select.KQ_EV_ERROR: @@ -441,14 +424,13 @@ def kqueue_poller(timeout=0.0, map=None): current_thread().stop.wait(timeout) -def loop(timeout=30.0, use_poll=False, map=None, count=None, poller=None): - """Poll in a loop, until count or timeout is reached""" - +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" + count = True + # code which grants backward compatibility with "use_poll" # argument which should no longer be used in favor of # "poller" @@ -477,13 +459,10 @@ def loop(timeout=30.0, use_poll=False, map=None, count=None, poller=None): break # then poll poller(subtimeout, map) - if isinstance(count, int): + if type(count) is int: count = count - 1 - -class dispatcher(object): - """Dispatcher for socket objects""" - # pylint: disable=too-many-public-methods,too-many-instance-attributes +class dispatcher: debug = False connected = False @@ -530,7 +509,7 @@ class dispatcher(object): self.socket = None def __repr__(self): - status = [self.__class__.__module__ + "." + self.__class__.__name__] + status = [self.__class__.__module__+"."+self.__class__.__name__] if self.accepting and self.addr: status.append('listening') elif self.connected: @@ -545,8 +524,7 @@ class dispatcher(object): __str__ = __repr__ def add_channel(self, map=None): - """Add a channel""" - # pylint: disable=attribute-defined-outside-init + #self.log_info('adding channel %s' % self) if map is None: map = self._map map[self._fileno] = self @@ -554,22 +532,20 @@ class dispatcher(object): self.poller_filter = 0 def del_channel(self, map=None): - """Delete a channel""" 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): + 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): + 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) @@ -586,28 +562,26 @@ class dispatcher(object): self.poller_filter = 0 self.poller_registered = False - def create_socket( - self, family=socket.AF_INET, socket_type=socket.SOCK_STREAM): - """Create a socket""" - # pylint: disable=attribute-defined-outside-init + 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): - """Set socket""" 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 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 - ) + socket.SOL_SOCKET, socket.SO_REUSEADDR, + self.socket.getsockopt(socket.SOL_SOCKET, + socket.SO_REUSEADDR) | 1 + ) except socket.error: pass @@ -618,13 +592,11 @@ class dispatcher(object): # ================================================== def readable(self): - """Predicate to indicate download throttle status""" if maxDownloadRate > 0: return downloadBucket > dispatcher.minTx return True def writable(self): - """Predicate to indicate upload throttle status""" if maxUploadRate > 0: return uploadBucket > dispatcher.minTx return True @@ -634,24 +606,21 @@ class dispatcher(object): # ================================================== def listen(self, num): - """Listen on a port""" self.accepting = True if os.name == 'nt' and num > 5: num = 5 return self.socket.listen(num) def bind(self, addr): - """Bind to an address""" self.addr = addr return self.socket.bind(addr) def connect(self, address): - """Connect to an 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'): + or err == EINVAL and os.name in ('nt', 'ce'): self.addr = address return if err in (0, EISCONN): @@ -661,16 +630,13 @@ class dispatcher(object): raise socket.error(err, errorcode[err]) def accept(self): - """Accept incoming connections. - Returns either an address pair or None.""" + # 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): + if why.args[0] in (EWOULDBLOCK, WSAEWOULDBLOCK, ECONNABORTED, EAGAIN, ENOTCONN): return None else: raise @@ -678,7 +644,6 @@ class dispatcher(object): return conn, addr def send(self, data): - """Send data""" try: result = self.socket.send(data) return result @@ -692,7 +657,6 @@ class dispatcher(object): raise def recv(self, buffer_size): - """Receive data""" try: data = self.socket.recv(buffer_size) if not data: @@ -700,7 +664,8 @@ class dispatcher(object): # a read condition, and having recv() return 0. self.handle_close() return b'' - return data + else: + return data except socket.error as why: # winsock sometimes raises ENOTCONN if why.args[0] in (EAGAIN, EWOULDBLOCK, WSAEWOULDBLOCK): @@ -712,7 +677,6 @@ class dispatcher(object): raise def close(self): - """Close connection""" self.connected = False self.accepting = False self.connecting = False @@ -729,12 +693,11 @@ class dispatcher(object): try: retattr = getattr(self.socket, attr) except AttributeError: - raise AttributeError( - "%s instance has no attribute '%s'" - % (self.__class__.__name__, attr)) + 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} + 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 @@ -743,16 +706,13 @@ class dispatcher(object): # and 'log_info' is for informational, warning and error logging. def log(self, message): - """Log a message to stderr""" sys.stderr.write('log: %s\n' % str(message)) def log_info(self, message, log_type='info'): - """Conditionally print a message""" if log_type not in self.ignore_log_types: print('%s: %s' % (log_type, message)) def handle_read_event(self): - """Handle a read event""" if self.accepting: # accepting sockets are never connected, they "spawn" new # sockets that are connected @@ -765,7 +725,6 @@ class dispatcher(object): self.handle_read() def handle_connect_event(self): - """Handle a connection event""" err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) if err != 0: raise socket.error(err, _strerror(err)) @@ -774,7 +733,6 @@ class dispatcher(object): self.connecting = False def handle_write_event(self): - """Handle a write event""" if self.accepting: # Accepting sockets shouldn't get a write event. # We will pretend it didn't happen. @@ -786,7 +744,6 @@ class dispatcher(object): self.handle_write() def handle_expt_event(self): - """Handle expected exceptions""" # 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 @@ -805,117 +762,103 @@ class dispatcher(object): self.handle_expt() def handle_error(self): - """Handle unexpected exceptions""" - _, t, v, tbinfo = compact_traceback() + nil, t, v, tbinfo = compact_traceback() # sometimes a user repr method will crash. try: self_repr = repr(self) - except BaseException: + 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_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): - """Handle an accept event""" pair = self.accept() if pair is not None: self.handle_accepted(*pair) - def handle_expt(self): - """Log that the subclass does not implement handle_expt""" - self.log_info('unhandled incoming priority event', 'warning') - - def handle_read(self): - """Log that the subclass does not implement handle_read""" - self.log_info('unhandled read event', 'warning') - - def handle_write(self): - """Log that the subclass does not implement handle_write""" - self.log_info('unhandled write event', 'warning') - - def handle_connect(self): - """Log that the subclass does not implement handle_connect""" - self.log_info('unhandled connect event', 'warning') - def handle_accepted(self, sock, addr): - """Log that the subclass does not implement handle_accepted""" sock.close() self.log_info('unhandled accepted event on %s' % (addr), 'warning') def handle_close(self): - """Log that the subclass does not implement handle_close""" 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): - """ - adds simple buffered output capability, useful for simple clients. - [for more sophisticated usage use asynchat.async_chat] - """ def __init__(self, sock=None, map=None): dispatcher.__init__(self, sock, map) self.out_buffer = b'' def initiate_send(self): - """Initiate a send""" num_sent = 0 num_sent = dispatcher.send(self, self.out_buffer[:512]) self.out_buffer = self.out_buffer[num_sent:] def handle_write(self): - """Handle a write event""" self.initiate_send() def writable(self): - """Predicate to indicate if the object is writable""" - return not self.connected or len(self.out_buffer) + return (not self.connected) or len(self.out_buffer) def send(self, data): - """Send 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(): - """Return a compact traceback""" t, v, tb = sys.exc_info() tbinfo = [] - # Must have a traceback - if not tb: + 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 - filename, function, line = tbinfo[-1] + file, function, line = tbinfo[-1] info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo]) - return (filename, function, line), t, v, info - + return (file, function, line), t, v, info def close_all(map=None, ignore_all=False): - """Close all connections""" - if map is None: map = socket_map for x in list(map.values()): @@ -928,12 +871,11 @@ def close_all(map=None, ignore_all=False): raise except _reraised_exceptions: raise - except BaseException: + except: if not ignore_all: raise map.clear() - # Asynchronous File I/O: # # After a little research (reading man pages on various unixen, and @@ -947,50 +889,41 @@ def close_all(map=None, ignore_all=False): # # Regardless, this is useful for pipes, and stdin/stdout... - if os.name == 'posix': import fcntl - class file_wrapper: # pylint: disable=old-style-class - """ - 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 - """ + 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): - """Fake recv()""" return os.read(self.fd, *args) def send(self, *args): - """Fake send()""" return os.write(self.fd, *args) def getsockopt(self, level, optname, buflen=None): - """Fake getsockopt()""" - if (level == socket.SOL_SOCKET and optname == socket.SO_ERROR - and not buflen): + if (level == socket.SOL_SOCKET and + optname == socket.SO_ERROR and + not buflen): return 0 - raise NotImplementedError( - "Only asyncore specific behaviour implemented.") + raise NotImplementedError("Only asyncore specific behaviour " + "implemented.") read = recv write = send def close(self): - """Fake close()""" os.close(self.fd) def fileno(self): - """Fake fileno()""" return self.fd class file_dispatcher(dispatcher): - """A dispatcher for file_wrapper objects""" def __init__(self, fd, map=None): dispatcher.__init__(self, None, map) @@ -1006,7 +939,6 @@ if os.name == 'posix': fcntl.fcntl(fd, fcntl.F_SETFL, flags) def set_file(self, fd): - """Set file""" 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 index 5d7fdcbd..2e7dd092 100644 --- a/src/network/bmobject.py +++ b/src/network/bmobject.py @@ -1,63 +1,44 @@ -""" -BMObject and it's exceptions. -""" -import logging +from binascii import hexlify import time -import protocol -import state from addresses import calculateInventoryHash +from debug import logger from inventory import Inventory from network.dandelion import Dandelion - -logger = logging.getLogger('default') - +import protocol +import state class BMObjectInsufficientPOWError(Exception): - """Exception indicating the object - doesn't have sufficient proof of work.""" errorCodes = ("Insufficient proof of work") +class BMObjectInvalidDataError(Exception): + errorCodes = ("Data invalid") + + class BMObjectExpiredError(Exception): - """Exception indicating the object's lifetime has expired.""" errorCodes = ("Object expired") class BMObjectUnwantedStreamError(Exception): - """Exception indicating the object is in a stream - we didn't advertise as being interested in.""" errorCodes = ("Object in unwanted stream") class BMObjectInvalidError(Exception): - """The object's data does not match object specification.""" errorCodes = ("Invalid object") class BMObjectAlreadyHaveError(Exception): - """We received a duplicate object (one we already have)""" errorCodes = ("Already have this object") -class BMObject(object): # pylint: disable=too-many-instance-attributes - """Bitmessage Object as a class.""" - +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 - ): # pylint: disable=too-many-arguments + def __init__(self, nonce, expiresTime, objectType, version, streamNumber, data, payloadOffset): self.nonce = nonce self.expiresTime = expiresTime self.objectType = objectType @@ -66,52 +47,32 @@ class BMObject(object): # pylint: disable=too-many-instance-attributes self.inventoryHash = calculateInventoryHash(data) # copy to avoid memory issues self.data = bytearray(data) - self.tag = self.data[payloadOffset:payloadOffset + 32] + self.tag = self.data[payloadOffset:payloadOffset+32] def checkProofOfWorkSufficient(self): - """Perform a proof of work check for sufficiency.""" # 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): - """Check if object's lifetime - isn't ridiculously far in the past or future.""" # 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 + 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 + 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): - """Check if object's stream matches streams we are interested in""" - if self.streamNumber < protocol.MIN_VALID_STREAM \ - or self.streamNumber > protocol.MAX_VALID_STREAM: - logger.warning( - 'The object has invalid stream: %s', self.streamNumber) - raise BMObjectInvalidError() if self.streamNumber not in state.streamsInWhichIAmParticipating: - logger.debug( - 'The streamNumber %i isn\'t one we are interested in.', - self.streamNumber) + logger.debug('The streamNumber %i isn\'t one we are interested in.', self.streamNumber) raise BMObjectUnwantedStreamError() def checkAlreadyHave(self): - """ - Check if we already have the object - (so that we don't duplicate it in inventory - or advertise it unnecessarily) - """ # if it's a stem duplicate, pretend we don't have it if Dandelion().hasHash(self.inventoryHash): return @@ -119,8 +80,6 @@ class BMObject(object): # pylint: disable=too-many-instance-attributes raise BMObjectAlreadyHaveError() def checkObjectByType(self): - """Call a object type specific check - (objects can have additional checks based on their types)""" if self.objectType == protocol.OBJECT_GETPUBKEY: self.checkGetpubkey() elif self.objectType == protocol.OBJECT_PUBKEY: @@ -131,31 +90,22 @@ class BMObject(object): # pylint: disable=too-many-instance-attributes self.checkBroadcast() # other objects don't require other types of tests - def checkMessage(self): # pylint: disable=no-self-use - """"Message" object type checks.""" + def checkMessage(self): return def checkGetpubkey(self): - """"Getpubkey" object type checks.""" if len(self.data) < 42: - logger.info( - 'getpubkey message doesn\'t contain enough data. Ignoring.') + logger.info('getpubkey message doesn\'t contain enough data. Ignoring.') raise BMObjectInvalidError() def checkPubkey(self): - """"Pubkey" object type checks.""" - # sanity check - if len(self.data) < 146 or len(self.data) > 440: + 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): - """"Broadcast" object type checks.""" 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.') + 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 diff --git a/src/network/bmproto.py b/src/network/bmproto.py index b03626eb..28277f52 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -1,75 +1,62 @@ -""" -Class BMProto defines bitmessage's network protocol workflow. -""" - import base64 import hashlib -import logging -import re +import random import socket import struct import time -# magic imports! -import addresses -import connectionpool -import knownnodes -import protocol -import state -from bmconfigparser import config +from bmconfigparser import BMConfigParser +from debug import logger from inventory import Inventory -from queues import invQueue, objectProcessorQueue, portCheckerQueue -from randomtrackingdict import RandomTrackingDict +import knownnodes from network.advanceddispatcher import AdvancedDispatcher -from network.bmobject import ( - BMObject, BMObjectAlreadyHaveError, BMObjectExpiredError, - BMObjectInsufficientPOWError, BMObjectInvalidError, - BMObjectUnwantedStreamError -) from network.dandelion import Dandelion -from network.proxy import ProxyError - -from node import Node, Peer -from objectracker import ObjectTracker, missingObjects - - -logger = logging.getLogger('default') +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): - """A Bitmessage Protocol Base Error""" errorCodes = ("Protocol error") class BMProtoInsufficientDataError(BMProtoError): - """A Bitmessage Protocol Insufficient Data Error""" errorCodes = ("Insufficient data") class BMProtoExcessiveDataError(BMProtoError): - """A Bitmessage Protocol Excessive Data Error""" errorCodes = ("Too much data") class BMProto(AdvancedDispatcher, ObjectTracker): - """A parser for the Bitmessage Protocol""" - # pylint: disable=too-many-instance-attributes, too-many-public-methods - timeOffsetWrongCount = 0 + # ~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): - # pylint: disable=unused-argument, super-init-not-called AdvancedDispatcher.__init__(self, sock) self.isOutbound = False # packet/connection from a local IP self.local = False - self.pendingUpload = RandomTrackingDict() - # canonical identifier of network group - self.network_group = None - # userAgent initialization - self.userAgent = '' def bm_proto_reset(self): - """Reset the bitmessage object parser""" self.magic = None self.command = None self.payloadLength = 0 @@ -81,70 +68,62 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.object = None def state_bm_header(self): - """Process incoming header""" - self.magic, self.command, self.payloadLength, self.checksum = \ - protocol.Header.unpack(self.read_buf[:protocol.Header.size]) + 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 != protocol.magic: + 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') + 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 > protocol.MAX_MESSAGE_SIZE: + if self.payloadLength > BMProto.maxMessageSize: self.invalid = True - self.set_state( - "bm_command", - length=protocol.Header.size, expectBytes=self.payloadLength) + self.set_state("bm_command", length=protocol.Header.size, expectBytes=self.payloadLength) return True - - def state_bm_command(self): # pylint: disable=too-many-branches - """Process incoming command""" + + 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') + 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) + 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())() + retval = getattr(self, "bm_command_" + str(self.command).lower())() except AttributeError: # unimplemented command - logger.debug('unimplemented command %s', self.command) + logger.debug("unimplemented command %s", self.command) except BMProtoInsufficientDataError: - logger.debug('packet length too short, skipping') + logger.debug("packet length too short, skipping") except BMProtoExcessiveDataError: - logger.debug('too much data, skipping') + logger.debug("too much data, skipping") except BMObjectInsufficientPOWError: - logger.debug('insufficient PoW, skipping') + logger.debug("insufficient PoW, skipping") + except BMObjectInvalidDataError: + logger.debug("object invalid data, skipping") except BMObjectExpiredError: - logger.debug('object expired, skipping') + logger.debug("object expired, skipping") except BMObjectUnwantedStreamError: - logger.debug('object not in wanted stream, skipping') + logger.debug("object not in wanted stream, skipping") except BMObjectInvalidError: - logger.debug('object invalid, skipping') + logger.debug("object invalid, skipping") except BMObjectAlreadyHaveError: - logger.debug( - '%(host)s:%(port)i already got object, skipping', - self.destination._asdict()) + logger.debug("%s:%i already got object, skipping", self.destination.host, self.destination.port) except struct.error: - logger.debug('decoding error, skipping') + logger.debug("decoding error, skipping") elif self.socket.type == socket.SOCK_DGRAM: # broken read, ignore pass else: - logger.debug('Closing due to invalid command %s', self.command) - self.close_reason = "Invalid command %s" % self.command + #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: @@ -154,21 +133,16 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return True def decode_payload_string(self, length): - """Read and return `length` bytes from payload""" - value = self.payload[self.payloadOffset:self.payloadOffset + length] + value = self.payload[self.payloadOffset:self.payloadOffset+length] self.payloadOffset += length return value def decode_payload_varint(self): - """Decode a varint from the payload""" - value, offset = addresses.decodeVarint( - self.payload[self.payloadOffset:]) + value, offset = addresses.decodeVarint(self.payload[self.payloadOffset:]) self.payloadOffset += offset return value def decode_payload_node(self): - """Decode node details from the payload""" - # protocol.checkIPAddress() 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])) @@ -178,48 +152,38 @@ class BMProto(AdvancedDispatcher, ObjectTracker): 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. + # 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) - # pylint: disable=too-many-branches,too-many-statements - def decode_payload_content(self, pattern="v"): - """ - Decode the payload depending on pattern: - - 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_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"): - """Decode the payload using one char pattern""" 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] + 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] + 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] - return None + return struct.unpack(">Q", self.payload[self.payloadOffset-8:self.payloadOffset])[0] size = None isArray = False @@ -232,11 +196,16 @@ class BMProto(AdvancedDispatcher, ObjectTracker): # 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"): + 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: @@ -244,43 +213,34 @@ class BMProto(AdvancedDispatcher, ObjectTracker): isArray = False elif i in "Ll" and size is None: size = self.decode_payload_varint() - isArray = i == "L" + 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.append([size, size, isArray, parserStack[-1][3][parserStack[-1][4]:], 0, []]) parserStack[-2][4] = len(parserStack[-2][3]) else: - j = 0 - for j in range( - parserStack[-1][4], len(parserStack[-1][3])): + 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.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]] + #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] + #del parserStack[-1] size = None elif i in "viHIQ": - parserStack[-1][5].append(decode_simple( - self, parserStack[-1][3][parserStack[-1][4]])) + parserStack[-1][5].append(decode_simple(self, parserStack[-1][3][parserStack[-1][4]])) size = None else: size = None @@ -291,65 +251,59 @@ class BMProto(AdvancedDispatcher, ObjectTracker): parserStack[depth][4] = 0 if depth > 0: if parserStack[depth][2]: - parserStack[depth - 1][5].append( - parserStack[depth][5]) + parserStack[depth - 1][5].append(parserStack[depth][5]) else: - parserStack[depth - 1][5].extend( - parserStack[depth][5]) + 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 + # 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) + logger.debug("Insufficient data %i/%i", self.payloadOffset, self.payloadLength) raise BMProtoInsufficientDataError() def bm_command_error(self): - """Decode an error message and log it""" - err_values = self.decode_payload_content("vvlsls") - fatalStatus = err_values[0] - # banTime = err_values[1] - # inventoryVector = err_values[2] - errorText = err_values[3] - logger.error( - '%s:%i error: %i, %s', self.destination.host, - self.destination.port, fatalStatus, errorText) + 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): - """ - Incoming request for object(s). - If we have them and some other conditions are fulfilled, - append them to the write queue. - """ items = self.decode_payload_content("l32s") # skip? - now = time.time() - if now < self.skipUntil: + if time.time() < self.skipUntil: return True - for i in items: - self.pendingUpload[str(i)] = now + #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): - """ - Common inv announce implementation: - both inv and dinv depending on *dandelion* kwarg - """ items = self.decode_payload_content("l32s") - if len(items) > protocol.MAX_OBJECT_COUNT: - logger.error( - 'Too many items in %sinv message!', 'd' if dandelion else '') + 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: @@ -365,285 +319,229 @@ class BMProto(AdvancedDispatcher, ObjectTracker): return True def bm_command_inv(self): - """Non-dandelion announce""" return self._command_inv(False) def bm_command_dinv(self): - """Dandelion stem announce""" + """ + Dandelion stem announce + """ return self._command_inv(True) def bm_command_object(self): - """Incoming object, process it""" 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) + nonce, expiresTime, objectType, version, streamNumber = self.decode_payload_content("QQIvv") + self.object = BMObject(nonce, expiresTime, objectType, version, streamNumber, self.payload, self.payloadOffset) - payload_len = len(self.payload) - self.payloadOffset - if payload_len > protocol.MAX_OBJECT_PAYLOAD_SIZE: - logger.info( - 'The payload length of this object is too large' - ' (%d bytes). Ignoring it.', payload_len) + 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): + except (BMObjectExpiredError, BMObjectAlreadyHaveError, BMObjectInsufficientPOWError) as e: BMProto.stopDownloadingObject(self.object.inventoryHash) - raise + raise e try: self.object.checkStream() - except BMObjectUnwantedStreamError: - acceptmismatch = config.getboolean( - "inventory", "acceptmismatch") - BMProto.stopDownloadingObject( - self.object.inventoryHash, acceptmismatch) - if not acceptmismatch: - raise - except BMObjectInvalidError: - BMProto.stopDownloadingObject(self.object.inventoryHash) - raise + 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))) # noqa: F821 - except BMObjectInvalidError: + objectProcessorQueue.put((self.object.objectType, buffer(self.object.data))) + except BMObjectInvalidError as e: BMProto.stopDownloadingObject(self.object.inventoryHash, True) else: try: - del missingObjects[self.object.inventoryHash] + 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") + 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, # noqa: F821 - buffer(self.object.tag) # noqa: F821 - ) - self.handleReceivedObject( - self.object.streamNumber, self.object.inventoryHash) - invQueue.put(( - self.object.streamNumber, self.object.inventoryHash, - self.destination)) + 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): - """Incoming addresses, process them""" - # not using services - for seenTime, stream, _, ip, port in self._decode_addr(): - ip = str(ip) - if ( - stream not in state.streamsInWhichIAmParticipating - # FIXME: should check against complete list - or ip.startswith('bootstrap') - ): + 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 - decodedIP = protocol.checkIPAddress(ip) - if ( - decodedIP and time.time() - seenTime > 0 - and seenTime > time.time() - protocol.ADDRESS_ALIVE - and port > 0 - ): - peer = Peer(decodedIP, port) - - with knownnodes.knownNodesLock: - # isnew = - knownnodes.addKnownNode(stream, peer, seenTime) - - # since we don't track peers outside of knownnodes, - # only spread if in knownnodes to prevent flood - # DISABLED TO WORKAROUND FLOOD/LEAK - # if isnew: - # addrQueue.put(( - # stream, peer, seenTime, self.destination)) + 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): - """Incoming port check request, queue it.""" - portCheckerQueue.put(Peer(self.destination, self.peerNode.port)) + portCheckerQueue.put(state.Peer(self.destination, self.peerNode.port)) return True def bm_command_ping(self): - """Incoming ping, respond to it.""" self.append_write_buf(protocol.CreatePacket('pong')) return True - @staticmethod - def bm_command_pong(): - """ - Incoming pong. - Ignore it. PyBitmessage pings connections after about 5 minutes - of inactivity, and leaves it to the TCP stack to handle actual - timeouts. So there is no need to do anything when a pong arrives. - """ + def bm_command_pong(self): # nothing really return True def bm_command_verack(self): - """ - Incoming verack. - If already sent my own verack, handshake is complete (except - potentially waiting for buffers to flush), so we can continue - to the main connection phase. If not sent verack yet, - continue processing. - """ self.verackReceived = True - if not self.verackSent: - return True - self.set_state( - "tls_init" if self.isSSL else "connection_fully_established", - length=self.payloadLength, expectBytes=0) - return False + 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): - """ - Incoming version. - Parse and log, remember important things, like streams, bitfields, etc. - """ - decoded = self.decode_payload_content("IQQiiQlslv") - (self.remoteProtocolVersion, self.services, self.timestamp, - self.sockNode, self.peerNode, self.nonce, self.userAgent - ) = decoded[:7] - self.streams = decoded[7:] + 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.timeOffset) - 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))) + 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(): - # ABORT afterwards + # TODO ABORT return True + #shared.connectedHostsList[self.destination] = self.streams[0] self.append_write_buf(protocol.CreatePacket('verack')) self.verackSent = True - ua_valid = re.match( - r'^/[a-zA-Z]+:[0-9]+\.?[\w\s\(\)\./:;-]*/$', self.userAgent) - if not ua_valid: - self.userAgent = '/INVALID:0/' if not self.isOutbound: - self.append_write_buf(protocol.assembleVersionMessage( - self.destination.host, self.destination.port, - connectionpool.BMConnectionPool().streams, True, - nodeid=self.nodeid)) - logger.debug( - '%(host)s:%(port)i sending version', - self.destination._asdict()) - if ((self.services & protocol.NODE_SSL == protocol.NODE_SSL) - and protocol.haveSSL(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 not self.verackReceived: - return True - self.set_state( - "tls_init" if self.isSSL else "connection_fully_established", - length=self.payloadLength, expectBytes=0) - return False + 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 - # pylint: disable=too-many-return-statements def peerValidityChecks(self): - """Check the validity of the peer""" if self.remoteProtocolVersion < 3: - self.append_write_buf(protocol.assembleErrorMessage( - errorText="Your is using an old protocol. Closing connection.", - fatal=2)) - logger.debug( - 'Closing connection to old protocol version %s, node: %s', - self.remoteProtocolVersion, self.destination) + 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 > protocol.MAX_TIME_OFFSET: - self.append_write_buf(protocol.assembleErrorMessage( - errorText="Your time is too far in the future" - " compared to mine. Closing connection.", fatal=2)) - logger.info( - "%s's time is too far in the future (%s seconds)." - " Closing connection to it.", + 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) - BMProto.timeOffsetWrongCount += 1 + shared.timeOffsetWrongCount += 1 return False - elif self.timeOffset < -protocol.MAX_TIME_OFFSET: - self.append_write_buf(protocol.assembleErrorMessage( - errorText="Your time is too far in the past compared to mine." - " Closing connection.", fatal=2)) - logger.info( - "%s's time is too far in the past" - " (timeOffset %s seconds). Closing connection to it.", + 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) - BMProto.timeOffsetWrongCount += 1 + shared.timeOffsetWrongCount += 1 return False else: - BMProto.timeOffsetWrongCount = 0 + shared.timeOffsetWrongCount = 0 if not self.streams: - self.append_write_buf(protocol.assembleErrorMessage( - errorText="We don't have shared stream interests." - " Closing connection.", fatal=2)) - logger.debug( - 'Closed connection to %s because there is no overlapping' - ' interest in streams.', self.destination) + 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 connectionpool.BMConnectionPool().inboundConnections.get( - self.destination): + if self.destination in network.connectionpool.BMConnectionPool().inboundConnections: try: if not protocol.checkSocksIP(self.destination.host): - self.append_write_buf(protocol.assembleErrorMessage( - errorText="Too many connections from your IP." - " Closing connection.", fatal=2)) - logger.debug( - 'Closed connection to %s because we are already' - ' connected to that IP.', self.destination) + 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 Exception: # nosec B110 # pylint:disable=broad-exception-caught + 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 ( - Peer(self.destination.host, self.peerNode.port) - in connectionpool.BMConnectionPool().inboundConnections - or len(connectionpool.BMConnectionPool()) - > config.safeGetInt( - 'bitmessagesettings', 'maxtotalconnections') - + config.safeGetInt( - 'bitmessagesettings', 'maxbootstrapconnections') - ): - self.append_write_buf(protocol.assembleErrorMessage( - errorText="Server full, please try again later.", fatal=2)) - logger.debug( - 'Closed connection to %s due to server full' - ' or duplicate inbound/outbound.', self.destination) + # 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 connectionpool.BMConnectionPool().isAlreadyConnected(self.nonce): - self.append_write_buf(protocol.assembleErrorMessage( - errorText="I'm connected to myself. Closing connection.", - fatal=2)) - logger.debug( - "Closed connection to %s because I'm connected to myself.", - self.destination) + 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): - """Stop downloading object *hashId*""" - for connection in connectionpool.BMConnectionPool().connections(): + for connection in network.connectionpool.BMConnectionPool().inboundConnections.values() + \ + network.connectionpool.BMConnectionPool().outboundConnections.values(): try: del connection.objectsNewToMe[hashId] except KeyError: @@ -655,25 +553,20 @@ class BMProto(AdvancedDispatcher, ObjectTracker): except KeyError: pass try: - del missingObjects[hashId] + del state.missingObjects[hashId] except KeyError: pass def handle_close(self): - """Handle close""" 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) + 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) + logger.debug("%s:%i: closing", self.destination.host, self.destination.port) except AttributeError: - logger.debug('Disconnected socket closing') + logger.debug("Disconnected socket closing") AdvancedDispatcher.handle_close(self) diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py index d7062d24..819dfeb1 100644 --- a/src/network/connectionchooser.py +++ b/src/network/connectionchooser.py @@ -1,23 +1,15 @@ -""" -Select which node to connect to -""" -# pylint: disable=too-many-branches -import logging +from queues import Queue import random +from bmconfigparser import BMConfigParser import knownnodes import protocol +from queues import portCheckerQueue import state -from bmconfigparser import config -from queues import queue, portCheckerQueue - -logger = logging.getLogger('default') - def getDiscoveredPeer(): - """Get a peer from the local peer discovery list""" try: - peer = random.choice(state.discoveredPeers.keys()) # nosec B311 + peer = random.choice(state.discoveredPeers.keys()) except (IndexError, KeyError): raise ValueError try: @@ -26,44 +18,32 @@ def getDiscoveredPeer(): pass return peer - def chooseConnection(stream): - """Returns an appropriate connection""" - haveOnion = config.safeGet( - "bitmessagesettings", "socksproxytype")[0:5] == 'SOCKS' - onionOnly = config.safeGetBoolean( - "bitmessagesettings", "onionservicesonly") + 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: + except Queue.Empty: pass # with a probability of 0.5, connect to a discovered peer - if random.choice((False, True)) and not haveOnion: # nosec B311 + 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( # nosec B311 - knownnodes.knownNodes[stream].keys()) + peer = random.choice(knownnodes.knownNodes[stream].keys()) try: - peer_info = knownnodes.knownNodes[stream][peer] - if peer_info.get('self'): - continue - rating = peer_info["rating"] + rating = knownnodes.knownNodes[stream][peer]["rating"] except TypeError: - logger.warning('Error in %s', peer) + print "Error in %s" % (peer) rating = 0 if haveOnion: - # do not connect to raw IP addresses - # --keep all traffic within Tor overlay - if onionOnly and not peer.host.endswith('.onion'): - continue # onion addresses have a higher priority when SOCKS if peer.host.endswith('.onion') and rating > 0: rating = 1 - # TODO: need better check - elif not peer.host.startswith('bootstrap'): + else: encodedAddr = protocol.encodeHost(peer.host) # don't connect to local IPs when using SOCKS if not protocol.checkIPAddress(encodedAddr, False): @@ -71,7 +51,7 @@ def chooseConnection(stream): if rating > 1: rating = 1 try: - if 0.05 / (1.0 - rating) > random.random(): # nosec B311 + if 0.05/(1.0-rating) > random.random(): return peer except ZeroDivisionError: return peer diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index 4823b3c8..408d56e0 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -1,123 +1,59 @@ -""" -`BMConnectionPool` class definition -""" +from ConfigParser import NoOptionError, NoSectionError import errno -import logging -import re import socket -import sys import time +import random +import re -import asyncore_pollchoose as asyncore -import helper_random -import knownnodes +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 -import state -from bmconfigparser import config -from connectionchooser import chooseConnection -from node import Peer -from proxy import Proxy from singleton import Singleton -from tcp import ( - bootstrap, Socks4aBMConnection, Socks5BMConnection, - TCPConnection, TCPServer) -from udp import UDPSocket - -logger = logging.getLogger('default') - +import state @Singleton class BMConnectionPool(object): - """Pool of all existing connections""" - # pylint: disable=too-many-instance-attributes - - trustedPeer = None - """ - 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 expected use case is where the user has a trusted server where - they run a Bitmessage daemon permanently. If they then run a second - instance of the client on a local machine periodically when they want - to check for messages it will sync with the network a lot faster - without compromising security. - """ - def __init__(self): asyncore.set_rates( - config.safeGetInt( - "bitmessagesettings", "maxdownloadrate"), - config.safeGetInt( - "bitmessagesettings", "maxuploadrate") - ) + 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 - - trustedPeer = config.safeGet( - 'bitmessagesettings', 'trustedpeer') - try: - if trustedPeer: - host, port = trustedPeer.split(':') - self.trustedPeer = Peer(host, int(port)) - except ValueError: - sys.exit( - 'Bad trustedpeer config setting! It should be set as' - ' trustedpeer=:' - ) - - def __len__(self): - return len(self.outboundConnections) + len(self.inboundConnections) - - def connections(self): - """ - Shortcut for combined list of connections from - `inboundConnections` and `outboundConnections` dicts - """ - return self.inboundConnections.values() + self.outboundConnections.values() - - def establishedConnections(self): - """Shortcut for list of connections having fullyEstablished == True""" - return [ - x for x in self.connections() if x.fullyEstablished] + self.lastSpawned = 0 + self.spawnWait = 2 + self.bootstrapped = False def connectToStream(self, streamNumber): - """Connect to a bitmessage stream""" self.streams.append(streamNumber) - state.streamsInWhichIAmParticipating.append(streamNumber) def getConnectionByAddr(self, addr): - """ - Return an (existing) connection object based on a `Peer` object - (IP and port) - """ - try: + if addr in self.inboundConnections: return self.inboundConnections[addr] - except KeyError: - pass try: - return self.inboundConnections[addr.host] - except (KeyError, AttributeError): + if addr.host in self.inboundConnections: + return self.inboundConnections[addr.host] + except AttributeError: pass - try: + if addr in self.outboundConnections: return self.outboundConnections[addr] - except KeyError: - pass try: - return self.udpSockets[addr.host] - except (KeyError, AttributeError): + if addr.host in self.udpSockets: + return self.udpSockets[addr.host] + except AttributeError: pass raise KeyError def isAlreadyConnected(self, nodeid): - """Check if we're already connected to this peer""" - for i in self.connections(): + for i in self.inboundConnections.values() + self.outboundConnections.values(): try: if nodeid == i.nodeid: return True @@ -126,7 +62,6 @@ class BMConnectionPool(object): return False def addConnection(self, connection): - """Add a connection object to our internal dict""" if isinstance(connection, UDPSocket): return if connection.isOutbound: @@ -135,16 +70,13 @@ class BMConnectionPool(object): if connection.destination.host in self.inboundConnections: self.inboundConnections[connection.destination] = connection else: - self.inboundConnections[connection.destination.host] = \ - connection + self.inboundConnections[connection.destination.host] = connection def removeConnection(self, connection): - """Remove a connection from our internal dict""" if isinstance(connection, UDPSocket): del self.udpSockets[connection.listening.host] elif isinstance(connection, TCPServer): - del self.listeningSockets[Peer( - connection.destination.host, connection.destination.port)] + del self.listeningSockets[state.Peer(connection.destination.host, connection.destination.port)] elif connection.isOutbound: try: del self.outboundConnections[connection.destination] @@ -158,41 +90,29 @@ class BMConnectionPool(object): del self.inboundConnections[connection.destination.host] except KeyError: pass - connection.handle_close() + connection.close() - @staticmethod - def getListeningIP(): - """What IP are we supposed to be listening on?""" - if config.safeGet( - "bitmessagesettings", "onionhostname", "").endswith(".onion"): - host = config.safeGet( - "bitmessagesettings", "onionbindip") + def getListeningIP(self): + if BMConfigParser().safeGet("bitmessagesettings", "onionhostname").endswith(".onion"): + host = BMConfigParser().safeGet("bitmessagesettings", "onionbindip") else: host = '127.0.0.1' - if ( - config.safeGetBoolean("bitmessagesettings", "sockslisten") - or config.safeGet("bitmessagesettings", "socksproxytype") - == "none" - ): + if BMConfigParser().safeGetBoolean("bitmessagesettings", "sockslisten") or \ + BMConfigParser().get("bitmessagesettings", "socksproxytype") == "none": # python doesn't like bind + INADDR_ANY? - # host = socket.INADDR_ANY - host = config.get("network", "bind") + #host = socket.INADDR_ANY + host = BMConfigParser().get("network", "bind") return host def startListening(self, bind=None): - """Open a listening socket and start accepting connections on it""" if bind is None: bind = self.getListeningIP() - port = config.safeGetInt("bitmessagesettings", "port") + 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): - """ - Open an UDP socket. Depending on settings, it can either only - accept incoming UDP packets, or also be able to send them. - """ if bind is None: host = self.getListeningIP() udpSocket = UDPSocket(host=host, announcing=True) @@ -203,96 +123,40 @@ class BMConnectionPool(object): udpSocket = UDPSocket(host=bind, announcing=True) self.udpSockets[udpSocket.listening.host] = udpSocket - def startBootstrappers(self): - """Run the process of resolving bootstrap hostnames""" - proxy_type = config.safeGet( - 'bitmessagesettings', 'socksproxytype') - # A plugins may be added here - hostname = None - if not proxy_type or proxy_type == 'none': - connection_base = TCPConnection - elif proxy_type == 'SOCKS5': - connection_base = Socks5BMConnection - hostname = helper_random.randomchoice([ - 'quzwelsuziwqgpt2.onion', None - ]) - elif proxy_type == 'SOCKS4a': - connection_base = Socks4aBMConnection # FIXME: I cannot test - else: - # This should never happen because socksproxytype setting - # is handled in bitmessagemain before starting the connectionpool - return - - bootstrapper = bootstrap(connection_base) - if not hostname: - port = helper_random.randomchoice([8080, 8444]) - hostname = 'bootstrap%s.bitmessage.org' % port - else: - port = 8444 - self.addConnection(bootstrapper(hostname, port)) - - def loop(self): # pylint: disable=too-many-branches,too-many-statements - """Main Connectionpool's loop""" - # pylint: disable=too-many-locals + def loop(self): # defaults to empty loop if outbound connections are maxed spawnConnections = False acceptConnections = True - if config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect'): + if BMConfigParser().safeGetBoolean('bitmessagesettings', 'dontconnect'): acceptConnections = False - elif config.safeGetBoolean( - 'bitmessagesettings', 'sendoutgoingconnections'): + elif BMConfigParser().safeGetBoolean('bitmessagesettings', 'sendoutgoingconnections'): spawnConnections = True - socksproxytype = config.safeGet( - 'bitmessagesettings', 'socksproxytype', '') - onionsocksproxytype = config.safeGet( - 'bitmessagesettings', 'onionsocksproxytype', '') - if ( - socksproxytype[:5] == 'SOCKS' - and not config.safeGetBoolean( - 'bitmessagesettings', 'sockslisten') - and '.onion' not in config.safeGet( - 'bitmessagesettings', 'onionhostname', '') - ): + if BMConfigParser().get('bitmessagesettings', 'socksproxytype')[0:5] == 'SOCKS' and \ + (not BMConfigParser().getboolean('bitmessagesettings', 'sockslisten') and \ + ".onion" not in BMConfigParser().get('bitmessagesettings', 'onionhostname')): acceptConnections = False - # pylint: disable=too-many-nested-blocks if spawnConnections: - if not knownnodes.knownNodesActual: - self.startBootstrappers() - knownnodes.knownNodesActual = True - if not self._bootstrapped: - self._bootstrapped = True - Proxy.proxy = ( - config.safeGet( - 'bitmessagesettings', 'sockshostname'), - config.safeGetInt( - 'bitmessagesettings', 'socksport') - ) + 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 onionsocksproxytype.startswith("SOCKS"): - raise ValueError - Proxy.onion_proxy = ( - config.safeGet( - 'network', 'onionsockshostname', None), - config.safeGet( - 'network', 'onionsocksport', None) - ) - except ValueError: - Proxy.onion_proxy = None - established = sum( - 1 for c in self.outboundConnections.values() - if (c.connected and c.fullyEstablished)) + 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 < config.safeGetInt( - 'bitmessagesettings', 'maxoutboundconnections'): - for i in range( - state.maximumNumberOfHalfOpenConnections - pending): + if established < BMConfigParser().safeGetInt("bitmessagesettings", "maxoutboundconnections"): + for i in range(state.maximumNumberOfHalfOpenConnections - pending): try: - chosen = self.trustedPeer or chooseConnection( - helper_random.randomchoice(self.streams)) + chosen = chooseConnection(random.choice(self.streams)) except ValueError: continue if chosen in self.outboundConnections: @@ -302,61 +166,55 @@ class BMConnectionPool(object): # don't connect to self if chosen in state.ownAddresses: continue - # don't connect to the hosts from the same - # network group, defense against sibyl attacks - host_network_group = protocol.network_group( - chosen.host) - same_group = False - for j in self.outboundConnections.values(): - if host_network_group == j.network_group: - same_group = True - if chosen.host == j.destination.host: - knownnodes.decreaseRating(chosen) - break - if same_group: - 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.onion_proxy: - if onionsocksproxytype == "SOCKS5": + if chosen.host.endswith(".onion") and Proxy.onionproxy is not None: + if BMConfigParser().get("network", "onionsocksproxytype") == "SOCKS5": self.addConnection(Socks5BMConnection(chosen)) - elif onionsocksproxytype == "SOCKS4a": + elif BMConfigParser().get("network", "onionsocksproxytype") == "SOCKS4a": self.addConnection(Socks4aBMConnection(chosen)) - elif socksproxytype == "SOCKS5": + elif BMConfigParser().safeGet("bitmessagesettings", "socksproxytype") == "SOCKS5": self.addConnection(Socks5BMConnection(chosen)) - elif socksproxytype == "SOCKS4a": + 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() + self.lastSpawned = time.time() else: - for i in self.outboundConnections.values(): + 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 config.safeGet('network', 'bind') == '': + if BMConfigParser().safeGet("network", "bind") == '': self.startListening() else: - for bind in re.sub( - r'[^\w.]+', ' ', - config.safeGet('network', 'bind') - ).split(): + 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 config.safeGet('network', 'bind') == '': + if BMConfigParser().safeGet("network", "bind") == '': self.startUDPSocket() else: - for bind in re.sub( - r'[^\w.]+', ' ', - config.safeGet('network', 'bind') - ).split(): + for bind in re.sub("[^\w.]+", " ", BMConfigParser().safeGet("network", "bind")).split(): self.startUDPSocket(bind) self.startUDPSocket(False) logger.info('Starting UDP socket(s).') @@ -372,13 +230,13 @@ class BMConnectionPool(object): i.accepting = i.connecting = i.connected = False logger.info('Stopped udp sockets.') - loopTime = float(self._spawnWait) - if self._lastSpawned < time.time() - self._spawnWait: + 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.connections(): + for i in self.inboundConnections.values() + self.outboundConnections.values(): minTx = time.time() - 20 if i.fullyEstablished: minTx -= 300 - 20 @@ -386,13 +244,9 @@ class BMConnectionPool(object): if i.fullyEstablished: i.append_write_buf(protocol.CreatePacket('ping')) else: - i.close_reason = "Timeout (%is)" % ( - time.time() - i.lastTx) + i.close_reason = "Timeout (%is)" % (time.time() - i.lastTx) i.set_state("close") - for i in ( - self.connections() - + self.listeningSockets.values() + self.udpSockets.values() - ): + 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: diff --git a/src/network/dandelion.py b/src/network/dandelion.py index 4f3cd07b..06ecca24 100644 --- a/src/network/dandelion.py +++ b/src/network/dandelion.py @@ -1,16 +1,14 @@ -""" -Dandelion class definition, tracks stages -""" -import logging from collections import namedtuple -from random import choice, expovariate, sample +from random import choice, sample, expovariate from threading import RLock from time import time -import connectionpool -import state +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 @@ -23,12 +21,8 @@ MAX_STEMS = 2 Stem = namedtuple('Stem', ['child', 'stream', 'timeout']) -logger = logging.getLogger('default') - - @Singleton -class Dandelion: # pylint: disable=old-style-class - """Dandelion class for tracking stem/fluff stages.""" +class Dandelion(): def __init__(self): # currently assignable child stems self.stem = [] @@ -40,43 +34,32 @@ class Dandelion: # pylint: disable=old-style-class self.refresh = time() + REASSIGN_INTERVAL self.lock = RLock() - @staticmethod - def poissonTimeout(start=None, average=0): - """Generate deadline using Poisson distribution""" + 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 + return start + expovariate(1.0/average) + FLUFF_TRIGGER_FIXED_DELAY def addHash(self, hashId, source=None, stream=1): - """Add inventory vector to dandelion stem""" if not state.dandelion: return with self.lock: self.hashMap[hashId] = Stem( - self.getNodeStem(source), - stream, - self.poissonTimeout()) - - def setHashStream(self, hashId, stream=1): - """ - Update stream for inventory vector (as inv/dinv commands don't - include streams, we only learn this after receiving the object) - """ - with self.lock: - if hashId in self.hashMap: - self.hashMap[hashId] = Stem( - self.hashMap[hashId].child, + 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"): - """Switch inventory vector from stem to fluff mode""" - if logger.isEnabledFor(logging.DEBUG): - logger.debug( - '%s entering fluff mode due to %s.', - ''.join('%02x' % ord(i) for i in hashId), reason) + 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] @@ -84,63 +67,38 @@ class Dandelion: # pylint: disable=old-style-class pass def hasHash(self, hashId): - """Is inventory vector in stem mode?""" return hashId in self.hashMap def objectChildStem(self, hashId): - """Child (i.e. next) node for an inventory vector during stem mode""" return self.hashMap[hashId].child def maybeAddStem(self, connection): - """ - If we had too few outbound connections, add the current one to the - current stem list. Dandelion as designed by the authors should - always have two active stem child connections. - """ # 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()) + 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): - """ - Remove current connection from the stem list (called e.g. when - a connection is closed). - """ # 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 - ): + 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()) + 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): - """ - Pick a random active stem, but not the parent one - (the one where an object came from) - """ try: # pick a random from available stems - stem = choice(range(len(self.stem))) # nosec B311 + stem = choice(range(len(self.stem))) if self.stem[stem] == parent: # one stem available and it's the parent if len(self.stem) == 1: @@ -154,10 +112,6 @@ class Dandelion: # pylint: disable=old-style-class return None def getNodeStem(self, node=None): - """ - Return child stem node for a given parent stem node - (the mapping is static for about 10 minutes, then it reshuffles) - """ with self.lock: try: return self.nodeMap[node] @@ -166,31 +120,22 @@ class Dandelion: # pylint: disable=old-style-class return self.nodeMap[node] def expire(self): - """Switch expired objects from stem to fluff mode""" with self.lock: deadline = time() - toDelete = [ - [v.stream, k, v.child] for k, v in self.hashMap.iteritems() - if v.timeout < deadline - ] - + # 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) - return toDelete + invQueue.put((row[0], row[1], row[2])) def reRandomiseStems(self): - """Re-shuffle stem mapping (parent <-> child pairs)""" with self.lock: try: # random two connections - self.stem = sample( - connectionpool.BMConnectionPool( - ).outboundConnections.values(), MAX_STEMS) + self.stem = sample(network.connectionpool.BMConnectionPool().outboundConnections.values(), MAX_STEMS) # not enough stems available except ValueError: - self.stem = connectionpool.BMConnectionPool( - ).outboundConnections.values() + 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 index 0ae83b5b..7eee2761 100644 --- a/src/network/downloadthread.py +++ b/src/network/downloadthread.py @@ -1,20 +1,17 @@ -""" -`DownloadThread` class definition -""" +import random +import threading import time import addresses -import helper_random -import protocol from dandelion import Dandelion +from debug import logger +from helper_threading import StoppableThread from inventory import Inventory from network.connectionpool import BMConnectionPool -from objectracker import missingObjects -from threads import StoppableThread +import protocol +from state import missingObjects - -class DownloadThread(StoppableThread): - """Thread-based class for downloading from connections""" +class DownloadThread(threading.Thread, StoppableThread): minPending = 200 maxRequestChunk = 1000 requestTimeout = 60 @@ -22,16 +19,16 @@ class DownloadThread(StoppableThread): requestExpires = 3600 def __init__(self): - super(DownloadThread, self).__init__(name="Downloader") + threading.Thread.__init__(self, name="Downloader") + self.initStop() + self.name = "Downloader" + logger.info("init download thread") self.lastCleaned = time.time() def cleanPending(self): - """Expire pending downloads eventually""" - deadline = time.time() - self.requestExpires + deadline = time.time() - DownloadThread.requestExpires try: - toDelete = [ - k for k, v in missingObjects.iteritems() - if v < deadline] + toDelete = [k for k, v in missingObjects.iteritems() if v < deadline] except RuntimeError: pass else: @@ -43,23 +40,20 @@ class DownloadThread(StoppableThread): while not self._stopped: requested = 0 # Choose downloading peers randomly - connections = BMConnectionPool().establishedConnections() - helper_random.randomshuffle(connections) - requestChunk = max(int( - min(self.maxRequestChunk, len(missingObjects)) - / len(connections)), 1) if connections else 1 - + 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() - # avoid unnecessary delay - if i.skipUntil >= now: - continue try: request = i.objectsNewToMe.randomKeys(requestChunk) except KeyError: continue payload = bytearray() - chunkCount = 0 + payload.extend(addresses.encodeVarint(len(request))) for chunk in request: if chunk in Inventory() and not Dandelion().hasHash(chunk): try: @@ -68,17 +62,13 @@ class DownloadThread(StoppableThread): pass continue payload.extend(chunk) - chunkCount += 1 missingObjects[chunk] = now - if not chunkCount: + if not payload: continue - payload[0:0] = addresses.encodeVarint(chunkCount) i.append_write_buf(protocol.CreatePacket('getdata', payload)) - self.logger.debug( - '%s:%i Requesting %i objects', - i.destination.host, i.destination.port, chunkCount) - requested += chunkCount - if time.time() >= self.lastCleaned + self.cleanInterval: + 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 d7a938fa..55cb81a1 100644 --- a/src/network/http.py +++ b/src/network/http.py @@ -2,35 +2,31 @@ import socket from advanceddispatcher import AdvancedDispatcher import asyncore_pollchoose as asyncore -from proxy import ProxyError -from socks5 import Socks5Connection, Socks5Resolver -from socks4a import Socks4aConnection, Socks4aResolver +from proxy import Proxy, ProxyError, GeneralProxyError +from socks5 import Socks5Connection, Socks5Resolver, Socks5AuthError, Socks5Error +from socks4a import Socks4aConnection, Socks4aResolver, Socks4aError - -class HttpError(ProxyError): - pass +class HttpError(ProxyError): pass class HttpConnection(AdvancedDispatcher): - def __init__(self, host, path="/"): # pylint: disable=redefined-outer-name + 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) + print "connecting in background to %s:%i" % (self.destination[0], self.destination[1]) 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.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 state_http_request_sent(self): - if self.read_buf: - print("Received %ib" % len(self.read_buf)) + 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) @@ -38,7 +34,7 @@ class HttpConnection(AdvancedDispatcher): class Socks5HttpConnection(Socks5Connection, HttpConnection): - def __init__(self, host, path="/"): # pylint: disable=super-init-not-called, redefined-outer-name + def __init__(self, host, path="/"): self.path = path Socks5Connection.__init__(self, address=(host, 80)) @@ -48,7 +44,7 @@ class Socks5HttpConnection(Socks5Connection, HttpConnection): class Socks4aHttpConnection(Socks4aConnection, HttpConnection): - def __init__(self, host, path="/"): # pylint: disable=super-init-not-called, redefined-outer-name + def __init__(self, host, path="/"): Socks4aConnection.__init__(self, address=(host, 80)) self.path = path @@ -59,31 +55,32 @@ class Socks4aHttpConnection(Socks4aConnection, HttpConnection): if __name__ == "__main__": # initial fill + for host in ("bootstrap8080.bitmessage.org", "bootstrap8444.bitmessage.org"): proxy = Socks5Resolver(host=host) - while asyncore.socket_map: - print("loop %s, len %i" % (proxy.state, len(asyncore.socket_map))) + 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 asyncore.socket_map: - print("loop %s, len %i" % (proxy.state, len(asyncore.socket_map))) + 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 asyncore.socket_map: - # print "loop, state = %s" % (direct.state) + while len(asyncore.socket_map) > 0: +# print "loop, state = %s" % (direct.state) asyncore.loop(timeout=1, count=1) proxy = Socks5HttpConnection(host) - while asyncore.socket_map: - # print "loop, state = %s" % (proxy.state) + while len(asyncore.socket_map) > 0: +# print "loop, state = %s" % (proxy.state) asyncore.loop(timeout=1, count=1) proxy = Socks4aHttpConnection(host) - while asyncore.socket_map: - # print "loop, state = %s" % (proxy.state) + while len(asyncore.socket_map) > 0: +# print "loop, state = %s" % (proxy.state) asyncore.loop(timeout=1, count=1) diff --git a/src/network/httpd.py b/src/network/httpd.py index b69ffa99..b8b6ba21 100644 --- a/src/network/httpd.py +++ b/src/network/httpd.py @@ -1,34 +1,28 @@ -""" -src/network/httpd.py -======================= -""" import asyncore import socket from tls import TLSHandshake - class HTTPRequestHandler(asyncore.dispatcher): - """Handling HTTP request""" response = """HTTP/1.0 200 OK\r - Date: Sun, 23 Oct 2016 18:02:00 GMT\r - Content-Type: text/html; charset=UTF-8\r - Content-Encoding: UTF-8\r - Content-Length: 136\r - Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT\r - Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)\r - ETag: "3f80f-1b6-3e1cb03b"\r - Accept-Ranges: bytes\r - Connection: close\r - \r - - - An Example Page - - - Hello World, this is a very simple HTML document. - - """ +Date: Sun, 23 Oct 2016 18:02:00 GMT\r +Content-Type: text/html; charset=UTF-8\r +Content-Encoding: UTF-8\r +Content-Length: 136\r +Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT\r +Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)\r +ETag: "3f80f-1b6-3e1cb03b"\r +Accept-Ranges: bytes\r +Connection: close\r +\r + + + An Example Page + + + Hello World, this is a very simple HTML document. + +""" def __init__(self, sock): if not hasattr(self, '_map'): @@ -68,17 +62,11 @@ class HTTPRequestHandler(asyncore.dispatcher): class HTTPSRequestHandler(HTTPRequestHandler, TLSHandshake): - """Handling HTTPS request""" def __init__(self, sock): if not hasattr(self, '_map'): - asyncore.dispatcher.__init__(self, sock) # pylint: disable=non-parent-init-called - # self.tlsDone = False - TLSHandshake.__init__( - self, - sock=sock, - certfile='/home/shurdeek/src/PyBitmessage/src/sslkeys/cert.pem', - keyfile='/home/shurdeek/src/PyBitmessage/src/sslkeys/key.pem', - server_side=True) + asyncore.dispatcher.__init__(self, sock) +# self.tlsDone = False + TLSHandshake.__init__(self, sock=sock, certfile='/home/shurdeek/src/PyBitmessage/src/sslkeys/cert.pem', keyfile='/home/shurdeek/src/PyBitmessage/src/sslkeys/key.pem', server_side=True) HTTPRequestHandler.__init__(self, sock) def handle_connect(self): @@ -93,7 +81,8 @@ class HTTPSRequestHandler(HTTPRequestHandler, TLSHandshake): def readable(self): if self.tlsDone: return HTTPRequestHandler.readable(self) - return TLSHandshake.readable(self) + else: + return TLSHandshake.readable(self) def handle_read(self): if self.tlsDone: @@ -104,7 +93,8 @@ class HTTPSRequestHandler(HTTPRequestHandler, TLSHandshake): def writable(self): if self.tlsDone: return HTTPRequestHandler.writable(self) - return TLSHandshake.writable(self) + else: + return TLSHandshake.writable(self) def handle_write(self): if self.tlsDone: @@ -114,7 +104,6 @@ class HTTPSRequestHandler(HTTPRequestHandler, TLSHandshake): class HTTPServer(asyncore.dispatcher): - """Handling HTTP Server""" port = 12345 def __init__(self): @@ -130,15 +119,14 @@ class HTTPServer(asyncore.dispatcher): pair = self.accept() if pair is not None: sock, addr = pair - # print 'Incoming connection from %s' % repr(addr) +# print 'Incoming connection from %s' % repr(addr) self.connections += 1 - # if self.connections % 1000 == 0: - # print "Processed %i connections, active %i" % (self.connections, len(asyncore.socket_map)) +# if self.connections % 1000 == 0: +# print "Processed %i connections, active %i" % (self.connections, len(asyncore.socket_map)) HTTPRequestHandler(sock) class HTTPSServer(HTTPServer): - """Handling HTTPS Server""" port = 12345 def __init__(self): @@ -149,13 +137,12 @@ class HTTPSServer(HTTPServer): pair = self.accept() if pair is not None: sock, addr = pair - # print 'Incoming connection from %s' % repr(addr) +# print 'Incoming connection from %s' % repr(addr) self.connections += 1 - # if self.connections % 1000 == 0: - # print "Processed %i connections, active %i" % (self.connections, len(asyncore.socket_map)) +# if self.connections % 1000 == 0: +# print "Processed %i connections, active %i" % (self.connections, len(asyncore.socket_map)) HTTPSRequestHandler(sock) - if __name__ == "__main__": client = HTTPSServer() asyncore.loop() diff --git a/src/network/https.py b/src/network/https.py index a7b8b57c..151efcb8 100644 --- a/src/network/https.py +++ b/src/network/https.py @@ -1,18 +1,10 @@ import asyncore from http import HTTPClient +import paths from tls import TLSHandshake -""" -self.sslSock = ssl.wrap_socket( - self.sock, - keyfile=os.path.join(paths.codePath(), 'sslkeys', 'key.pem'), - certfile=os.path.join(paths.codePath(), 'sslkeys', 'cert.pem'), - server_side=not self.initiatedConnection, - ssl_version=ssl.PROTOCOL_TLSv1, - do_handshake_on_connect=False, - ciphers='AECDH-AES256-SHA') -""" +# self.sslSock = ssl.wrap_socket(self.sock, keyfile = os.path.join(paths.codePath(), 'sslkeys', 'key.pem'), certfile = os.path.join(paths.codePath(), 'sslkeys', 'cert.pem'), server_side = not self.initiatedConnection, ssl_version=ssl.PROTOCOL_TLSv1, do_handshake_on_connect=False, ciphers='AECDH-AES256-SHA') class HTTPSClient(HTTPClient, TLSHandshake): @@ -20,15 +12,7 @@ class HTTPSClient(HTTPClient, TLSHandshake): if not hasattr(self, '_map'): asyncore.dispatcher.__init__(self) self.tlsDone = False - """ - TLSHandshake.__init__( - self, - address=(host, 443), - certfile='/home/shurdeek/src/PyBitmessage/sslsrc/keys/cert.pem', - keyfile='/home/shurdeek/src/PyBitmessage/src/sslkeys/key.pem', - server_side=False, - ciphers='AECDH-AES256-SHA') - """ +# TLSHandshake.__init__(self, address=(host, 443), certfile='/home/shurdeek/src/PyBitmessage/sslsrc/keys/cert.pem', keyfile='/home/shurdeek/src/PyBitmessage/src/sslkeys/key.pem', server_side=False, ciphers='AECDH-AES256-SHA') HTTPClient.__init__(self, host, path, connect=False) TLSHandshake.__init__(self, address=(host, 443), server_side=False) @@ -65,7 +49,6 @@ class HTTPSClient(HTTPClient, TLSHandshake): else: TLSHandshake.handle_write(self) - if __name__ == "__main__": client = HTTPSClient('anarchy.economicsofbitcoin.com', '/') asyncore.loop() diff --git a/src/network/invthread.py b/src/network/invthread.py index 14217041..d0d758fb 100644 --- a/src/network/invthread.py +++ b/src/network/invthread.py @@ -1,58 +1,37 @@ -""" -Thread to send inv annoucements -""" import Queue -import random +from random import randint, shuffle +import threading from time import time import addresses -import protocol -import state +from bmconfigparser import BMConfigParser +from helper_threading import StoppableThread from network.connectionpool import BMConnectionPool from network.dandelion import Dandelion from queues import invQueue -from threads import StoppableThread +import protocol +import state +class InvThread(threading.Thread, StoppableThread): + def __init__(self): + threading.Thread.__init__(self, name="InvBroadcaster") + self.initStop() + self.name = "InvBroadcaster" -def handleExpiredDandelion(expired): - """For expired dandelion objects, mark all remotes as not having - the object""" - if not expired: - return - for i in BMConnectionPool().connections(): - if not i.fullyEstablished: - continue - for x in expired: - streamNumber, hashid, _ = x - try: - del i.objectsNewToMe[hashid] - except KeyError: - if streamNumber in i.streams: - with i.objectsNewToThemLock: - i.objectsNewToThem[hashid] = time() - - -class InvThread(StoppableThread): - """Main thread that sends inv annoucements""" - - name = "InvBroadcaster" - - @staticmethod - def handleLocallyGenerated(stream, hashId): - """Locally generated inventory items require special handling""" + def handleLocallyGenerated(self, stream, hashId): Dandelion().addHash(hashId, stream=stream) - for connection in BMConnectionPool().connections(): - if state.dandelion and connection != \ - Dandelion().objectChildStem(hashId): - continue - connection.objectsNewToThem[hashId] = time() + 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): # pylint: disable=too-many-branches - while not state.shutdown: # pylint: disable=too-many-nested-blocks + def run(self): + while not state.shutdown: chunk = [] while True: # Dandelion fluff trigger by expiration - handleExpiredDandelion(Dandelion().expire()) + Dandelion().expire() try: data = invQueue.get(False) chunk.append((data[0], data[1])) @@ -63,7 +42,8 @@ class InvThread(StoppableThread): break if chunk: - for connection in BMConnectionPool().connections(): + for connection in BMConnectionPool().inboundConnections.values() + \ + BMConnectionPool().outboundConnections.values(): fluffs = [] stems = [] for inv in chunk: @@ -78,7 +58,7 @@ class InvThread(StoppableThread): if connection == Dandelion().objectChildStem(inv[1]): # Fluff trigger by RNG # auto-ignore if config set to 0, i.e. dandelion is off - if random.randint(1, 100) >= state.dandelion: # nosec B311 + 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: @@ -89,20 +69,16 @@ class InvThread(StoppableThread): fluffs.append(inv[1]) if fluffs: - random.shuffle(fluffs) - connection.append_write_buf(protocol.CreatePacket( - 'inv', - addresses.encodeVarint( - len(fluffs)) + ''.join(fluffs))) + shuffle(fluffs) + connection.append_write_buf(protocol.CreatePacket('inv', \ + addresses.encodeVarint(len(fluffs)) + "".join(fluffs))) if stems: - random.shuffle(stems) - connection.append_write_buf(protocol.CreatePacket( - 'dinv', - addresses.encodeVarint( - len(stems)) + ''.join(stems))) + shuffle(stems) + connection.append_write_buf(protocol.CreatePacket('dinv', \ + addresses.encodeVarint(len(stems)) + "".join(stems))) invQueue.iterate() - for _ in range(len(chunk)): + for i in range(len(chunk)): invQueue.task_done() if Dandelion().refresh < time(): diff --git a/src/network/knownnodes.py b/src/network/knownnodes.py deleted file mode 100644 index b74c9a15..00000000 --- a/src/network/knownnodes.py +++ /dev/null @@ -1,267 +0,0 @@ -""" -Manipulations with knownNodes dictionary. -""" -# TODO: knownnodes object maybe? -# pylint: disable=global-statement - -import json -import logging -import os -import pickle # nosec B403 -import threading -import time -try: - from collections.abc import Iterable -except ImportError: - from collections import Iterable - -import state -from bmconfigparser import config -from network.node import Peer - -state.Peer = Peer - -knownNodesLock = threading.RLock() -"""Thread lock for knownnodes modification""" -knownNodes = {stream: {} for stream in range(1, 4)} -"""The dict of known nodes for each stream""" - -knownNodesTrimAmount = 2000 -"""trim stream knownnodes dict to this length""" - -knownNodesForgetRating = -0.5 -"""forget a node after rating is this low""" - -knownNodesActual = False - -logger = logging.getLogger('default') - -DEFAULT_NODES = ( - Peer('5.45.99.75', 8444), - Peer('75.167.159.54', 8444), - Peer('95.165.168.168', 8444), - Peer('85.180.139.241', 8444), - Peer('158.222.217.190', 8080), - Peer('178.62.12.187', 8448), - Peer('24.188.198.204', 8111), - Peer('109.147.204.113', 1195), - Peer('178.11.46.221', 8444) -) - - -def json_serialize_knownnodes(output): - """ - Reorganize knownnodes dict and write it as JSON to output - """ - _serialized = [] - for stream, peers in knownNodes.iteritems(): - for peer, info in peers.iteritems(): - info.update(rating=round(info.get('rating', 0), 2)) - _serialized.append({ - 'stream': stream, 'peer': peer._asdict(), 'info': info - }) - json.dump(_serialized, output, indent=4) - - -def json_deserialize_knownnodes(source): - """ - Read JSON from source and make knownnodes dict - """ - global knownNodesActual - for node in json.load(source): - peer = node['peer'] - info = node['info'] - peer = Peer(str(peer['host']), peer.get('port', 8444)) - knownNodes[node['stream']][peer] = info - if not (knownNodesActual - or info.get('self')) and peer not in DEFAULT_NODES: - knownNodesActual = True - - -def pickle_deserialize_old_knownnodes(source): - """ - Unpickle source and reorganize knownnodes dict if it has old format - the old format was {Peer:lastseen, ...} - the new format is {Peer:{"lastseen":i, "rating":f}} - """ - global knownNodes - knownNodes = pickle.load(source) # nosec B301 - for stream in knownNodes.keys(): - for node, params in knownNodes[stream].iteritems(): - if isinstance(params, (float, int)): - addKnownNode(stream, node, params) - - -def saveKnownNodes(dirName=None): - """Save knownnodes to filesystem""" - if dirName is None: - dirName = state.appdata - with knownNodesLock: - with open(os.path.join(dirName, 'knownnodes.dat'), 'wb') as output: - json_serialize_knownnodes(output) - - -def addKnownNode(stream, peer, lastseen=None, is_self=False): - """ - Add a new node to the dict or update lastseen if it already exists. - Do it for each stream number if *stream* is `Iterable`. - Returns True if added a new node. - """ - # pylint: disable=too-many-branches - if isinstance(stream, Iterable): - with knownNodesLock: - for s in stream: - addKnownNode(s, peer, lastseen, is_self) - return - - rating = 0.0 - if not lastseen: - # FIXME: maybe about 28 days? - lastseen = int(time.time()) - else: - lastseen = int(lastseen) - try: - info = knownNodes[stream].get(peer) - if lastseen > info['lastseen']: - info['lastseen'] = lastseen - except (KeyError, TypeError): - pass - else: - return - - if not is_self: - if len(knownNodes[stream]) > config.safeGetInt( - "knownnodes", "maxnodes"): - return - - knownNodes[stream][peer] = { - 'lastseen': lastseen, - 'rating': rating or 1 if is_self else 0, - 'self': is_self, - } - return True - - -def createDefaultKnownNodes(): - """Creating default Knownnodes""" - past = time.time() - 2418600 # 28 days - 10 min - for peer in DEFAULT_NODES: - addKnownNode(1, peer, past) - saveKnownNodes() - - -def readKnownNodes(): - """Load knownnodes from filesystem""" - try: - with open(state.appdata + 'knownnodes.dat', 'rb') as source: - with knownNodesLock: - try: - json_deserialize_knownnodes(source) - except ValueError: - source.seek(0) - pickle_deserialize_old_knownnodes(source) - except (IOError, OSError, KeyError, EOFError): - logger.debug( - 'Failed to read nodes from knownnodes.dat', exc_info=True) - createDefaultKnownNodes() - - # your own onion address, if setup - onionhostname = config.safeGet('bitmessagesettings', 'onionhostname') - if onionhostname and ".onion" in onionhostname: - onionport = config.safeGetInt('bitmessagesettings', 'onionport') - if onionport: - self_peer = Peer(onionhostname, onionport) - addKnownNode(1, self_peer, is_self=True) - state.ownAddresses[self_peer] = True - - -def increaseRating(peer): - """Increase rating of a peer node""" - 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): - """Decrease rating of a peer node""" - 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): - """Triming Knownnodes""" - if len(knownNodes[recAddrStream]) < \ - config.safeGetInt("knownnodes", "maxnodes"): - return - with knownNodesLock: - oldestList = sorted( - knownNodes[recAddrStream], - key=lambda x: x['lastseen'] - )[:knownNodesTrimAmount] - for oldest in oldestList: - del knownNodes[recAddrStream][oldest] - - -def dns(): - """Add DNS names to knownnodes""" - for port in [8080, 8444]: - addKnownNode( - 1, Peer('bootstrap%s.bitmessage.org' % port, port)) - - -def cleanupKnownNodes(): - """ - Cleanup knownnodes: remove old nodes and nodes with low rating - """ - global knownNodesActual - now = int(time.time()) - needToWriteKnownNodesToDisk = False - - with knownNodesLock: - for stream in knownNodes: - if stream not in state.streamsInWhichIAmParticipating: - continue - keys = knownNodes[stream].keys() - for node in keys: - if len(knownNodes[stream]) <= 1: # leave at least one node - if stream == 1: - knownNodesActual = False - break - try: - age = now - knownNodes[stream][node]["lastseen"] - # scrap old nodes (age > 28 days) - if age > 2419200: - needToWriteKnownNodesToDisk = True - del knownNodes[stream][node] - continue - # scrap old nodes (age > 3 hours) with low rating - if (age > 10800 and knownNodes[stream][node]["rating"] - <= knownNodesForgetRating): - needToWriteKnownNodesToDisk = True - del knownNodes[stream][node] - continue - except TypeError: - logger.warning('Error in %s', node) - keys = [] - - # Let us write out the knowNodes to disk - # if there is anything new to write out. - if needToWriteKnownNodesToDisk: - saveKnownNodes() diff --git a/src/network/networkthread.py b/src/network/networkthread.py index ef4f92ba..5a709c8b 100644 --- a/src/network/networkthread.py +++ b/src/network/networkthread.py @@ -1,41 +1,39 @@ -""" -A thread to handle network concerns -""" +import threading + +from bmconfigparser import BMConfigParser +from debug import logger +from helper_threading import StoppableThread import network.asyncore_pollchoose as asyncore -import state from network.connectionpool import BMConnectionPool -from queues import excQueue -from threads import StoppableThread +import state - -class BMNetworkThread(StoppableThread): - """Main network thread""" - name = "Asyncore" +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): - try: - while not self._stopped and state.shutdown == 0: - BMConnectionPool().loop() - except Exception as e: - excQueue.put((self.name, e)) - raise + 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: # nosec B110 # pylint:disable=bare-except + except: pass for i in BMConnectionPool().outboundConnections.values(): try: i.close() - except: # nosec B110 # pylint:disable=bare-except + except: pass for i in BMConnectionPool().inboundConnections.values(): try: i.close() - except: # nosec B110 # pylint:disable=bare-except + except: pass # just in case diff --git a/src/network/node.py b/src/network/node.py index 4c532b81..ab9f5fbe 100644 --- a/src/network/node.py +++ b/src/network/node.py @@ -1,7 +1,3 @@ -""" -Named tuples representing the network peers -""" import collections -Peer = collections.namedtuple('Peer', ['host', 'port']) Node = collections.namedtuple('Node', ['services', 'host', 'port']) diff --git a/src/network/objectracker.py b/src/network/objectracker.py index 65e06de4..66b0685b 100644 --- a/src/network/objectracker.py +++ b/src/network/objectracker.py @@ -1,12 +1,11 @@ -""" -Module for tracking objects -""" 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 @@ -25,12 +24,7 @@ except ImportError: # it isn't actually implemented yet so no point in turning it on haveBloom = False -# tracking pending downloads globally, for stats -missingObjects = {} - - class ObjectTracker(object): - """Object tracker mixin""" invCleanPeriod = 300 invInitialCapacity = 50000 invErrorRate = 0.03 @@ -46,47 +40,38 @@ class ObjectTracker(object): self.lastCleaned = time.time() def initInvBloom(self): - """Init bloom filter for tracking. WIP.""" if haveBloom: # lock? - self.invBloom = BloomFilter( - capacity=ObjectTracker.invInitialCapacity, - error_rate=ObjectTracker.invErrorRate) + self.invBloom = BloomFilter(capacity=ObjectTracker.invInitialCapacity, + error_rate=ObjectTracker.invErrorRate) def initAddrBloom(self): - """Init bloom filter for tracking addrs, WIP. - This either needs to be moved to addrthread.py or removed.""" if haveBloom: # lock? - self.addrBloom = BloomFilter( - capacity=ObjectTracker.invInitialCapacity, - error_rate=ObjectTracker.invErrorRate) + self.addrBloom = BloomFilter(capacity=ObjectTracker.invInitialCapacity, + error_rate=ObjectTracker.invErrorRate) def clean(self): - """Clean up tracking to prevent memory bloat""" if self.lastCleaned < time.time() - ObjectTracker.invCleanPeriod: if haveBloom: - if missingObjects == 0: + # 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.objectsNewToThem = {k: v for k, v in self.objectsNewToThem.iteritems() if v >= deadline} self.lastCleaned = time.time() def hasObj(self, hashid): - """Do we already have object?""" if haveBloom: return hashid in self.invBloom - return hashid in self.objectsNewToMe + else: + return hashid in self.objectsNewToMe def handleReceivedInventory(self, hashId): - """Handling received inventory""" if haveBloom: self.invBloom.add(hashId) try: @@ -99,20 +84,18 @@ class ObjectTracker(object): self.objectsNewToMe[hashId] = True def handleReceivedObject(self, streamNumber, hashid): - """Handling received object""" - for i in network.connectionpool.BMConnectionPool().connections(): + 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): + 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 + # 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) @@ -122,15 +105,25 @@ class ObjectTracker(object): del i.objectsNewToThem[hashid] except KeyError: pass - self.objectsNewToMe.setLastObject() def hasAddr(self, addr): - """WIP, should be moved to addrthread.py or removed""" if haveBloom: return addr in self.invBloom - return None def addAddr(self, hashid): - """WIP, should be moved to addrthread.py or removed""" 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 ed1af127..43298f63 100644 --- a/src/network/proxy.py +++ b/src/network/proxy.py @@ -1,36 +1,26 @@ -""" -Set proxy if avaiable otherwise exception -""" -# pylint: disable=protected-access -import logging import socket import time -import asyncore_pollchoose as asyncore from advanceddispatcher import AdvancedDispatcher -from bmconfigparser import config -from node import Peer - -logger = logging.getLogger('default') - +import asyncore_pollchoose as asyncore +from debug import logger +import network.connectionpool +import state class ProxyError(Exception): - """Base proxy exception class""" - errorCodes = ("Unknown error",) + errorCodes = ("UnknownError") def __init__(self, code=-1): self.code = code try: - self.message = self.errorCodes[code] + self.message = self.__class__.errorCodes[self.code] except IndexError: - self.message = self.errorCodes[-1] + self.message = self.__class__.errorCodes[-1] super(ProxyError, self).__init__(self.message) class GeneralProxyError(ProxyError): - """General proxy error class (not specfic to an implementation)""" - errorCodes = ( - "Success", + errorCodes = ("Success", "Invalid data", "Not connected", "Not available", @@ -39,14 +29,12 @@ class GeneralProxyError(ProxyError): "Timed out", "Network unreachable", "Connection refused", - "Host unreachable" - ) + "Host unreachable") class Proxy(AdvancedDispatcher): - """Base proxy class""" - # these are global, and if you change config during runtime, - # all active/new instances should change too + # these are global, and if you change config during runtime, all active/new + # instances should change too _proxy = ("127.0.0.1", 9050) _auth = None _onion_proxy = None @@ -55,94 +43,65 @@ class Proxy(AdvancedDispatcher): @property def proxy(self): - """Return proxy IP and port""" return self.__class__._proxy @proxy.setter def proxy(self, address): - """Set proxy IP and port""" - if (not isinstance(address, tuple) or len(address) < 2 - or not isinstance(address[0], str) - or not isinstance(address[1], int)): + 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 def auth(self): - """Return proxy authentication settings""" return self.__class__._auth @auth.setter def auth(self, authTuple): - """Set proxy authentication (username and password)""" self.__class__._auth = authTuple @property def onion_proxy(self): - """ - Return separate proxy IP and port for use only with onion - addresses. Untested. - """ return self.__class__._onion_proxy @onion_proxy.setter def onion_proxy(self, address): - """Set onion proxy 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) - ): + 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 proxy authentication settings for onion hosts only""" return self.__class__._onion_auth @onion_auth.setter def onion_auth(self, authTuple): - """Set proxy authentication for onion hosts only. Untested.""" self.__class__._onion_auth = authTuple def __init__(self, address): - if not isinstance(address, Peer): + 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) - if config.safeGetBoolean( - "bitmessagesettings", "socksauthentication"): - self.auth = ( - config.safeGet( - "bitmessagesettings", "socksusername"), - config.safeGet( - "bitmessagesettings", "sockspassword")) + if address.host.endswith(".onion") and self.onion_proxy is not None: + self.connect(self.onion_proxy) else: - self.auth = None - self.connect( - self.onion_proxy - if address.host.endswith(".onion") and self.onion_proxy else - self.proxy - ) + self.connect(self.proxy) def handle_connect(self): - """Handle connection event (to the proxy)""" self.set_state("init") 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, e) + logger.debug("%s:%i: Connection failed: %s", self.destination.host, self.destination.port, str(e)) return self.state_init() def state_proxy_handshake_done(self): - """Handshake is complete at this point""" self.connectedAt = time.time() return False diff --git a/src/network/receivequeuethread.py b/src/network/receivequeuethread.py index 56c01b77..5399b972 100644 --- a/src/network/receivequeuethread.py +++ b/src/network/receivequeuethread.py @@ -1,22 +1,27 @@ -""" -Process data incoming from network -""" import errno import Queue import socket +import sys +import threading +import time -import state -from network.advanceddispatcher import UnknownStateError +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 -from threads import StoppableThread +import protocol +import state - -class ReceiveQueueThread(StoppableThread): - """This thread processes data received from the network - (which is done by the asyncore thread)""" +class ReceiveQueueThread(threading.Thread, StoppableThread): def __init__(self, num=0): - super(ReceiveQueueThread, self).__init__(name="ReceiveQueue_%i" % num) + 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: @@ -29,28 +34,20 @@ class ReceiveQueueThread(StoppableThread): break # cycle as long as there is data - # methods should return False if there isn't enough 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 - # state_* methods should return False if there isn't - # enough data, or the connection is to be aborted - try: - connection = BMConnectionPool().getConnectionByAddr(dest) - # connection object not found - except KeyError: - receiveDataQueue.task_done() - continue - try: - connection.process() - # state isn't implemented - except UnknownStateError: + 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: - connection.set_state("close", 0) + BMConnectionPool().getConnectionByAddr(dest).set_state("close", 0) else: - self.logger.error('Socket error: %s', err) - except: # noqa:E722 - self.logger.error('Error processing', exc_info=True) + logger.error("Socket error: %s", str(err)) receiveDataQueue.task_done() diff --git a/src/network/socks4a.py b/src/network/socks4a.py index e9786168..978ede04 100644 --- a/src/network/socks4a.py +++ b/src/network/socks4a.py @@ -1,43 +1,27 @@ -""" -SOCKS4a proxy module -""" -# pylint: disable=attribute-defined-outside-init -import logging import socket import struct -from proxy import GeneralProxyError, Proxy, ProxyError - -logger = logging.getLogger('default') - +from proxy import Proxy, ProxyError, GeneralProxyError class Socks4aError(ProxyError): - """SOCKS4a error base class""" - errorCodes = ( - "Request granted", + 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" - ) + "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): - """SOCKS4a proxy class""" def __init__(self, address=None): Proxy.__init__(self, address) self.ipaddr = None self.destport = address[1] def state_init(self): - """Protocol initialisation (before connection is established)""" self.set_state("auth_done", 0) return True def state_pre_connect(self): - """Handle feedback from SOCKS4a while it is connecting on our behalf""" # Get the response if self.read_buf[0:1] != chr(0x00).encode(): # bad data @@ -56,32 +40,24 @@ class Socks4a(Proxy): 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]) + 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): - """ - Handle return value when using SOCKS4a for DNS resolving - instead of connecting. - """ - return socket.inet_ntoa(self.__proxysockname[0]) + return socket.inet_ntoa(self.__proxysockname[0]) class Socks4aConnection(Socks4a): - """Child SOCKS4a class used for making outbound connections.""" def __init__(self, address): Socks4a.__init__(self, address=address) def state_auth_done(self): - """Request connection to be made""" # Now we can request the actual connection rmtrslv = False - self.append_write_buf( - struct.pack('>BBH', 0x04, 0x01, self.destination[1])) + 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: @@ -89,16 +65,14 @@ class Socks4aConnection(Socks4a): self.append_write_buf(self.ipaddr) except socket.error: # Well it's not an IP number, so it's probably a DNS name. - if self._remote_dns: + if Proxy._remote_dns: # Resolve remotely rmtrslv = True self.ipaddr = None - self.append_write_buf( - struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)) + 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.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]) @@ -109,7 +83,6 @@ class Socks4aConnection(Socks4a): return True def state_pre_connect(self): - """Tell SOCKS4a to initiate a connection""" try: return Socks4a.state_pre_connect(self) except Socks4aError as e: @@ -118,17 +91,14 @@ class Socks4aConnection(Socks4a): class Socks4aResolver(Socks4a): - """DNS resolver class using SOCKS4a""" def __init__(self, host): self.host = host self.port = 8444 Socks4a.__init__(self, address=(self.host, self.port)) def state_auth_done(self): - """Request connection to be made""" # 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('>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]) @@ -138,10 +108,4 @@ class Socks4aResolver(Socks4a): return True def resolved(self): - """ - Resolving is done, process the return value. To use this within - PyBitmessage, a callback needs to be implemented which hasn't - been done yet. - """ - logger.debug( - 'Resolved %s as %s', self.host, self.proxy_sock_name()) + print "Resolved %s as %s" % (self.host, self.proxy_sock_name()) diff --git a/src/network/socks5.py b/src/network/socks5.py index d1daae42..52050ec9 100644 --- a/src/network/socks5.py +++ b/src/network/socks5.py @@ -1,33 +1,18 @@ -""" -SOCKS5 proxy module -""" -# pylint: disable=attribute-defined-outside-init - -import logging import socket import struct -from node import Peer -from proxy import GeneralProxyError, Proxy, ProxyError - -logger = logging.getLogger('default') - +from proxy import Proxy, ProxyError, GeneralProxyError class Socks5AuthError(ProxyError): - """Rised when the socks5 protocol encounters an authentication error""" - errorCodes = ( - "Succeeded", + errorCodes = ("Succeeded", "Authentication is required", "All offered authentication methods were rejected", "Unknown username or invalid password", - "Unknown error" - ) + "Unknown error") class Socks5Error(ProxyError): - """Rised when socks5 protocol encounters an error""" - errorCodes = ( - "Succeeded", + errorCodes = ("Succeeded", "General SOCKS server failure", "Connection not allowed by ruleset", "Network unreachable", @@ -36,19 +21,16 @@ class Socks5Error(ProxyError): "TTL expired", "Command not supported", "Address type not supported", - "Unknown error" - ) + "Unknown error") class Socks5(Proxy): - """A socks5 proxy base class""" def __init__(self, address=None): Proxy.__init__(self, address) self.ipaddr = None self.destport = address[1] def state_init(self): - """Protocol initialization (before connection is established)""" if self._auth: self.append_write_buf(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) else: @@ -57,8 +39,7 @@ class Socks5(Proxy): return True def state_auth_1(self): - """Perform authentication if peer is requesting it.""" - ret = struct.unpack('BB', self.read_buf[:2]) + ret = struct.unpack('BB', self.read_buf) if ret[0] != 5: # general error raise GeneralProxyError(1) @@ -67,10 +48,9 @@ class Socks5(Proxy): 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.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: @@ -82,7 +62,6 @@ class Socks5(Proxy): return True def state_auth_needed(self): - """Handle response to authentication attempt""" ret = struct.unpack('BB', self.read_buf[0:2]) if ret[0] != 1: # general error @@ -95,7 +74,6 @@ class Socks5(Proxy): return True def state_pre_connect(self): - """Handle feedback from socks5 while it is connecting on our behalf.""" # Get the response if self.read_buf[0:1] != chr(0x05).encode(): self.close() @@ -103,7 +81,7 @@ class Socks5(Proxy): elif self.read_buf[1:2] != chr(0x00).encode(): # Connection failed self.close() - if ord(self.read_buf[1:2]) <= 8: + if ord(self.read_buf[1:2])<=8: raise Socks5Error(ord(self.read_buf[1:2])) else: raise Socks5Error(9) @@ -118,53 +96,39 @@ class Socks5(Proxy): return True def state_proxy_addr_1(self): - """Handle IPv4 address returned for peer""" 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): - """ - Handle other addresses than IPv4 returned for peer - (e.g. IPv6, onion, ...). This is part 1 which retrieves the - length of the data. - """ self.address_length = ord(self.read_buf[0:1]) - self.set_state( - "proxy_addr_2_2", length=1, expectBytes=self.address_length) + self.set_state("proxy_addr_2_2", length=1, expectBytes=self.address_length) return True def state_proxy_addr_2_2(self): - """ - Handle other addresses than IPv4 returned for peer - (e.g. IPv6, onion, ...). This is part 2 which retrieves the data. - """ 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): - """Handle peer's port being returned.""" 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]) + 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): - """Handle return value when using SOCKS5 - for DNS resolving instead of connecting.""" return socket.inet_ntoa(self.__proxysockname[0]) class Socks5Connection(Socks5): - """Child socks5 class used for making outbound connections.""" + def __init__(self, address): + Socks5.__init__(self, address=address) + def state_auth_done(self): - """Request connection to be made""" # 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 @@ -172,24 +136,21 @@ class Socks5Connection(Socks5): try: self.ipaddr = socket.inet_aton(self.destination[0]) self.append_write_buf(chr(0x01).encode() + self.ipaddr) - except socket.error: # may be IPv6! + except socket.error: # Well it's not an IP number, so it's probably a DNS name. - if self._remote_dns: + 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]) + 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.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): - """Tell socks5 to initiate a connection""" try: return Socks5.state_pre_connect(self) except Socks5Error as e: @@ -198,27 +159,18 @@ class Socks5Connection(Socks5): class Socks5Resolver(Socks5): - """DNS resolver class using socks5""" def __init__(self, host): self.host = host self.port = 8444 - Socks5.__init__(self, address=Peer(self.host, self.port)) + Socks5.__init__(self, address=(self.host, self.port)) def state_auth_done(self): - """Perform resolving""" # 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(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): - """ - Resolving is done, process the return value. - To use this within PyBitmessage, a callback needs to be - implemented which hasn't been done yet. - """ - logger.debug( - 'Resolved %s as %s', self.host, self.proxy_sock_name()) + print "Resolved %s as %s" % (self.host, self.proxy_sock_name()) diff --git a/src/network/stats.py b/src/network/stats.py index 82e6c87f..80925f7c 100644 --- a/src/network/stats.py +++ b/src/network/stats.py @@ -1,12 +1,8 @@ -""" -Network statistics -""" import time -import asyncore_pollchoose as asyncore from network.connectionpool import BMConnectionPool -from objectracker import missingObjects - +import asyncore_pollchoose as asyncore +from state import missingObjects lastReceivedTimestamp = time.time() lastReceivedBytes = 0 @@ -15,64 +11,60 @@ lastSentTimestamp = time.time() lastSentBytes = 0 currentSentSpeed = 0 - def connectedHostsList(): - """List of all the connected hosts""" - return BMConnectionPool().establishedConnections() - + 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(): - """Sending Bytes""" return asyncore.sentBytes - def uploadSpeed(): - """Getting upload speed""" - # pylint: disable=global-statement global lastSentTimestamp, lastSentBytes, currentSentSpeed currentTimestamp = time.time() if int(lastSentTimestamp) < int(currentTimestamp): currentSentBytes = asyncore.sentBytes - currentSentSpeed = int( - (currentSentBytes - lastSentBytes) / ( - currentTimestamp - lastSentTimestamp)) + currentSentSpeed = int((currentSentBytes - lastSentBytes) / (currentTimestamp - lastSentTimestamp)) lastSentBytes = currentSentBytes lastSentTimestamp = currentTimestamp return currentSentSpeed - def receivedBytes(): - """Receiving Bytes""" return asyncore.receivedBytes - def downloadSpeed(): - """Getting download speed""" - # pylint: disable=global-statement global lastReceivedTimestamp, lastReceivedBytes, currentReceivedSpeed currentTimestamp = time.time() if int(lastReceivedTimestamp) < int(currentTimestamp): currentReceivedBytes = asyncore.receivedBytes - currentReceivedSpeed = int( - (currentReceivedBytes - lastReceivedBytes) / ( - currentTimestamp - lastReceivedTimestamp)) + currentReceivedSpeed = int((currentReceivedBytes - lastReceivedBytes) / + (currentTimestamp - lastReceivedTimestamp)) lastReceivedBytes = currentReceivedBytes lastReceivedTimestamp = currentTimestamp return currentReceivedSpeed - def pendingDownload(): - """Getting pending downloads""" 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(): - """Getting pending uploads""" - # 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) + #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 index 0bfde3bb..33c4b6ca 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -1,116 +1,83 @@ -""" -TCP protocol handler -""" -# pylint: disable=too-many-ancestors - -import logging +import base64 +from binascii import hexlify +import hashlib import math -import random -import socket import time +from pprint import pprint +import socket +import struct +import random +import traceback -# magic imports! -import addresses -import helper_random -import l10n -import protocol -import state -from bmconfigparser import config +from addresses import calculateInventoryHash +from debug import logger from helper_random import randomBytes from inventory import Inventory -from queues import invQueue, receiveDataQueue, UISignalQueue -from tr import _translate - -import asyncore_pollchoose as asyncore -import connectionpool import knownnodes from network.advanceddispatcher import AdvancedDispatcher -from network.bmproto import BMProto +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.socks4a import Socks4aConnection -from network.socks5 import Socks5Connection +from network.socks5 import Socks5Connection, Socks5Resolver, Socks5AuthError, Socks5Error +from network.socks4a import Socks4aConnection, Socks4aResolver, Socks4aError from network.tls import TLSDispatcher -from node import Peer - - -logger = logging.getLogger('default') - - -maximumAgeOfNodesThatIAdvertiseToOthers = 10800 #: Equals three hours -maximumTimeOffsetWrongCount = 3 #: Connections with wrong time offset +import addresses +from bmconfigparser import BMConfigParser +from queues import invQueue, objectProcessorQueue, portCheckerQueue, UISignalQueue, receiveDataQueue +import shared +import state +import protocol class TCPConnection(BMProto, TLSDispatcher): - # pylint: disable=too-many-instance-attributes - """ - .. todo:: Look to understand and/or fix the non-parent-init-called - """ - 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 = Peer(*sock.getpeername()) + 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) + 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) + logger.debug("Outbound proxy connection to %s:%i", self.destination.host, self.destination.port) else: self.destination = address self.isOutbound = True - self.create_socket( - socket.AF_INET6 if ":" in address.host else socket.AF_INET, - socket.SOCK_STREAM) + 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) - try: - self.local = ( - protocol.checkIPAddress( - protocol.encodeHost(self.destination.host), True) - and not protocol.checkSocksIP(self.destination.host) - ) - except socket.error: - # it's probably a hostname - pass - self.network_group = protocol.network_group(self.destination.host) - ObjectTracker.__init__(self) # pylint: disable=non-parent-init-called + 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): - """ - This is a defense against the so called intersection attacks. - - It is called when you notice peer is requesting non-existing - objects, or right after the connection is established. It will - estimate how long an object will take to propagate across the - network, and skip processing "getdata" requests until then. This - means an attacker only has one shot per IP to perform the attack. - """ - # estimated time for a small object to propagate across the - # whole network - max_known_nodes = max( - len(knownnodes.knownNodes[x]) for x in knownnodes.knownNodes) - delay = math.ceil(math.log(max_known_nodes + 2, 20)) * ( - 0.2 + invQueue.queueCount / 2.0) + 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 @@ -119,134 +86,100 @@ class TCPConnection(BMProto, TLSDispatcher): if initial: self.skipUntil = self.connectedAt + delay if self.skipUntil > time.time(): - logger.debug( - 'Initial skipping processing getdata for %.2fs', - 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) + logger.debug("Skipping processing getdata due to missing object for %.2fs", delay) self.skipUntil = time.time() + delay - def checkTimeOffsetNotification(self): - """ - Check if we have connected to too many nodes which have too high - time offset from us - """ - if BMProto.timeOffsetWrongCount > \ - maximumTimeOffsetWrongCount and \ - not self.fullyEstablished: - UISignalQueue.put(( - 'updateStatusBar', - _translate( - "MainWindow", - "The time on your computer, %1, may be wrong. " - "Please verify your settings." - ).arg(l10n.formatTimestamp()))) - def state_connection_fully_established(self): - """ - State after the bitmessage protocol handshake is completed - (version/verack exchange, and if both side support TLS, - the TLS handshake as well). - """ self.set_connection_fully_established() self.set_state("bm_header") self.bm_proto_reset() return True def set_connection_fully_established(self): - """Initiate inventory synchronisation.""" if not self.isOutbound and not self.local: - state.clientHasReceivedIncomingConnections = True + shared.clientHasReceivedIncomingConnections = True UISignalQueue.put(('setStatusIcon', 'green')) - UISignalQueue.put(( - 'updateNetworkStatusTab', (self.isOutbound, True, self.destination) - )) + UISignalQueue.put(('updateNetworkStatusTab', (self.isOutbound, True, self.destination))) self.antiIntersectionDelay(True) self.fullyEstablished = True - # The connection having host suitable for knownnodes - if self.isOutbound or not self.local and not state.socksIP: + if self.isOutbound: knownnodes.increaseRating(self.destination) - knownnodes.addKnownNode( - self.streams, self.destination, time.time()) + if self.isOutbound: Dandelion().maybeAddStem(self) self.sendAddr() self.sendBigInv() def sendAddr(self): - """Send a partial list of known addresses to peer.""" # 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 = config.safeGetInt( - "bitmessagesettings", "maxaddrperstreamsend", 500) + maxAddrCount = BMConfigParser().safeGetInt("bitmessagesettings", "maxaddrperstreamsend", 500) + + # init + addressCount = 0 + payload = b'' templist = [] addrs = {} for stream in self.streams: with knownnodes.knownNodesLock: - for n, s in enumerate((stream, stream * 2, stream * 2 + 1)): - nodes = knownnodes.knownNodes.get(s) - if not nodes: - continue + 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 - # and having positive or neutral rating - filtered = [ - (k, v) for k, v in nodes.iteritems() - if v["lastseen"] > int(time.time()) - - maximumAgeOfNodesThatIAdvertiseToOthers - and v["rating"] >= 0 and not k.host.endswith('.onion') - ] - # sent 250 only if the remote isn't interested in it - elemCount = min( - len(filtered), - maxAddrCount / 2 if n else maxAddrCount) - addrs[s] = helper_random.randomsample(filtered, elemCount) - for substream in addrs: + 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 templist: - self.append_write_buf(protocol.assembleAddrMessage(templist)) + if len(templist) > 0: + self.append_write_buf(BMProto.assembleAddr(templist)) def sendBigInv(self): - """ - Send hashes of all inventory objects, chunked as the protocol has - a per-command limit. - """ def sendChunk(): - """Send one chunk of inv entries in one command""" 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)) + 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 + # 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 obj_hash, _ in bigInvList.items(): - payload += obj_hash + # 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 - - # Remove -1 below when sufficient time has passed for users to - # upgrade to versions of PyBitmessage that accept inv with 50,000 - # items - if objectCount >= protocol.MAX_OBJECT_COUNT - 1: + if objectCount >= BMProto.maxObjectCount: sendChunk() payload = b'' objectCount = 0 @@ -255,142 +188,74 @@ class TCPConnection(BMProto, TLSDispatcher): sendChunk() def handle_connect(self): - """Callback for TCP connection being established.""" try: AdvancedDispatcher.handle_connect(self) except socket.error as e: - # pylint: disable=protected-access if e.errno in asyncore._DISCONNECTED: - logger.debug( - '%s:%i: Connection failed: %s', - self.destination.host, self.destination.port, e) + 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, - connectionpool.BMConnectionPool().streams, - False, nodeid=self.nodeid)) + 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): - """Callback for reading from a socket""" 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): - """Callback for writing to a socket""" TLSDispatcher.handle_write(self) def handle_close(self): - """Callback for connection being closed.""" - host_is_global = self.isOutbound or not self.local and not state.socksIP + if self.isOutbound and not self.fullyEstablished: + knownnodes.decreaseRating(self.destination) if self.fullyEstablished: - UISignalQueue.put(( - 'updateNetworkStatusTab', - (self.isOutbound, False, self.destination) - )) - if host_is_global: - knownnodes.addKnownNode( - self.streams, self.destination, time.time()) + UISignalQueue.put(('updateNetworkStatusTab', (self.isOutbound, False, self.destination))) + if self.isOutbound: Dandelion().maybeRemoveStem(self) - else: - self.checkTimeOffsetNotification() - if host_is_global: - knownnodes.decreaseRating(self.destination) BMProto.handle_close(self) class Socks5BMConnection(Socks5Connection, TCPConnection): - """SOCKS5 wrapper for TCP connections""" - 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): - """ - State when SOCKS5 connection succeeds, we need to send a - Bitmessage handshake to peer. - """ Socks5Connection.state_proxy_handshake_done(self) self.nodeid = randomBytes(8) - self.append_write_buf( - protocol.assembleVersionMessage( - self.destination.host, self.destination.port, - connectionpool.BMConnectionPool().streams, - False, nodeid=self.nodeid)) + 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): - """SOCKS4a wrapper for TCP connections""" - 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): - """ - State when SOCKS4a connection succeeds, we need to send a - Bitmessage handshake to peer. - """ Socks4aConnection.state_proxy_handshake_done(self) self.nodeid = randomBytes(8) - self.append_write_buf( - protocol.assembleVersionMessage( - self.destination.host, self.destination.port, - connectionpool.BMConnectionPool().streams, - False, nodeid=self.nodeid)) + 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 -def bootstrap(connection_class): - """Make bootstrapper class for connection type (connection_class)""" - class Bootstrapper(connection_class): - """Base class for bootstrappers""" - _connection_base = connection_class - - def __init__(self, host, port): - self._connection_base.__init__(self, Peer(host, port)) - self.close_reason = self._succeed = False - - def bm_command_addr(self): - """ - Got addr message - the bootstrap succeed. - Let BMProto process the addr message and switch state to 'close' - """ - BMProto.bm_command_addr(self) - self._succeed = True - self.close_reason = "Thanks for bootstrapping!" - self.set_state("close") - - def set_connection_fully_established(self): - """Only send addr here""" - # pylint: disable=attribute-defined-outside-init - self.fullyEstablished = True - self.sendAddr() - - def handle_close(self): - """ - After closing the connection switch knownnodes.knownNodesActual - back to False if the bootstrapper failed. - """ - BMProto.handle_close(self) - if not self._succeed: - knownnodes.knownNodesActual = False - - return Bootstrapper - - class TCPServer(AdvancedDispatcher): - """TCP connection server for Bitmessage protocol""" - def __init__(self, host='127.0.0.1', port=8444): if not hasattr(self, '_map'): AdvancedDispatcher.__init__(self) @@ -399,52 +264,62 @@ class TCPServer(AdvancedDispatcher): for attempt in range(50): try: if attempt > 0: - logger.warning('Failed to bind on port %s', port) - port = random.randint(32767, 65535) # nosec B311 + 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: - logger.warning('Setting port to %s', port) - config.set( - 'bitmessagesettings', 'port', str(port)) - config.save() + BMConfigParser().set("bitmessagesettings", "port", str(port)) + BMConfigParser().save() break - self.destination = Peer(host, port) + self.destination = state.Peer(host, port) self.bound = True self.listen(5) def is_bound(self): - """Is the socket bound?""" try: return self.bound except AttributeError: return False def handle_accept(self): - """Incoming connection callback""" - try: - sock = self.accept()[0] - except (TypeError, IndexError): - return + 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 - state.ownAddresses[Peer(*sock.getsockname())] = True - if ( - len(connectionpool.BMConnectionPool()) - > config.safeGetInt( - 'bitmessagesettings', 'maxtotalconnections') - + config.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: - 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/threads.py b/src/network/threads.py deleted file mode 100644 index 9bdaa85d..00000000 --- a/src/network/threads.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Threading primitives for the network package""" - -import logging -import random -import threading -from contextlib import contextmanager - - -class StoppableThread(threading.Thread): - """Base class for application threads with stopThread method""" - name = None - logger = logging.getLogger('default') - - def __init__(self, name=None): - if name: - self.name = name - super(StoppableThread, self).__init__(name=self.name) - self.stop = threading.Event() - self._stopped = False - random.seed() - self.logger.info('Init thread %s', self.name) - - def stopThread(self): - """Stop the thread""" - self._stopped = True - self.stop.set() - - -class BusyError(threading.ThreadError): - """ - Thread error raised when another connection holds the lock - we are trying to acquire. - """ - pass - - -@contextmanager -def nonBlocking(lock): - """ - A context manager which acquires given lock non-blocking - and raises BusyError if failed to acquire. - """ - locked = lock.acquire(False) - if not locked: - raise BusyError - try: - yield - finally: - lock.release() diff --git a/src/network/tls.py b/src/network/tls.py index a3774b44..379dae99 100644 --- a/src/network/tls.py +++ b/src/network/tls.py @@ -1,57 +1,33 @@ """ SSL/TLS negotiation. """ -import logging + import os import socket import ssl import sys -import network.asyncore_pollchoose as asyncore -import paths +from debug import logger from network.advanceddispatcher import AdvancedDispatcher +import network.asyncore_pollchoose as asyncore from queues import receiveDataQueue - -logger = logging.getLogger('default') +import paths +import protocol _DISCONNECTED_SSL = frozenset((ssl.SSL_ERROR_EOF,)) -if sys.version_info >= (2, 7, 13): - # this means TLSv1 or higher - # in the future change to - # ssl.PROTOCOL_TLS1.2 - sslProtocolVersion = ssl.PROTOCOL_TLS # pylint: disable=no-member -elif sys.version_info >= (2, 7, 9): - # this means any SSL/TLS. - # SSLv2 and 3 are excluded with an option after context is created - sslProtocolVersion = ssl.PROTOCOL_SSLv23 -else: - # this means TLSv1, there is no way to set "TLSv1 or higher" - # or "TLSv1.2" in < 2.7.9 - sslProtocolVersion = ssl.PROTOCOL_TLSv1 - - -# ciphers -if ( - ssl.OPENSSL_VERSION_NUMBER >= 0x10100000 - and not ssl.OPENSSL_VERSION.startswith(b"LibreSSL") -): - sslProtocolCiphers = "AECDH-AES256-SHA@SECLEVEL=0" -else: - sslProtocolCiphers = "AECDH-AES256-SHA" - - class TLSDispatcher(AdvancedDispatcher): - """TLS functionality for classes derived from AdvancedDispatcher""" - # pylint: disable=too-many-instance-attributes, too-many-arguments - # pylint: disable=super-init-not-called - def __init__(self, _=None, sock=None, certfile=None, keyfile=None, - server_side=False, ciphers=sslProtocolCiphers): + def __init__(self, address=None, sock=None, + certfile=None, keyfile=None, server_side=False, ciphers=protocol.sslProtocolCiphers): self.want_read = self.want_write = True - self.certfile = certfile or os.path.join( - paths.codePath(), 'sslkeys', 'cert.pem') - self.keyfile = keyfile or os.path.join( - paths.codePath(), 'sslkeys', 'key.pem') + 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 @@ -60,155 +36,132 @@ class TLSDispatcher(AdvancedDispatcher): self.isSSL = False def state_tls_init(self): - """Prepare sockets for TLS handshake""" 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) + # 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.check_hostname = False context.verify_mode = ssl.CERT_NONE # also exclude TLSv1 and TLSv1.1 in the future - 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) + 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, - ssl_version=sslProtocolVersion, - certfile=self.certfile, keyfile=self.keyfile, - ciphers=self.ciphers, do_handshake_on_connect=False) + self.sslSocket = ssl.wrap_socket(self.socket, + server_side=self.server_side, + ssl_version=protocol.sslProtocolVersion, + certfile=self.certfile, + keyfile=self.keyfile, + ciphers=self.ciphers, + 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") - @staticmethod - def state_tls_handshake(): - """ - Do nothing while TLS handshake is pending, as during this phase - we need to react to callbacks instead - """ + def state_tls_handshake(self): return False def writable(self): - """Handle writable checks for TLS-enabled sockets""" try: if self.tlsStarted and not self.tlsDone and not self.write_buf: return self.want_write + return AdvancedDispatcher.writable(self) except AttributeError: - pass - return AdvancedDispatcher.writable(self) + return AdvancedDispatcher.writable(self) def readable(self): - """Handle readable check for TLS-enabled sockets""" try: - # during TLS handshake, and after flushing write buffer, - # return status of last handshake attempt + # 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: - logger.debug('tls readable, %r', self.want_read) + #print "tls readable, %r" % (self.want_read) return self.want_read - # prior to TLS handshake, - # receiveDataThread should emulate synchronous behaviour - if not self.fullyEstablished and ( - self.expectBytes == 0 or not self.write_buf_empty()): + # 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: - pass - return AdvancedDispatcher.readable(self) + return AdvancedDispatcher.readable(self) def handle_read(self): - """ - Handle reads for sockets during TLS handshake. Requires special - treatment as during the handshake, buffers must remain empty - and normal reads must be ignored. - """ 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: - AdvancedDispatcher.handle_read(self) + #logger.debug("%s:%i Not TLS handshaking (read)", self.destination.host, self.destination.port) + return AdvancedDispatcher.handle_read(self) except AttributeError: - AdvancedDispatcher.handle_read(self) + return AdvancedDispatcher.handle_read(self) except ssl.SSLError as err: if err.errno == ssl.SSL_ERROR_WANT_READ: return - if err.errno not in _DISCONNECTED_SSL: - logger.info("SSL Error: %s", err) - self.close_reason = "SSL Error in handle_read" + 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): - """ - Handle writes for sockets during TLS handshake. Requires special - treatment as during the handshake, buffers must remain empty - and normal writes must be ignored. - """ 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: - AdvancedDispatcher.handle_write(self) + #logger.debug("%s:%i Not TLS handshaking (write)", self.destination.host, self.destination.port) + return AdvancedDispatcher.handle_write(self) except AttributeError: - AdvancedDispatcher.handle_write(self) + return AdvancedDispatcher.handle_write(self) except ssl.SSLError as err: if err.errno == ssl.SSL_ERROR_WANT_WRITE: - return - if err.errno not in _DISCONNECTED_SSL: - logger.info("SSL Error: %s", err) - self.close_reason = "SSL Error in handle_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): - """Perform TLS handshake and handle its stages""" # wait for flush if self.write_buf: return False # Perform the handshake. try: - logger.debug("handshaking (internal)") + #print "handshaking (internal)" self.sslSocket.do_handshake() except ssl.SSLError as err: - self.close_reason = "SSL Error in tls_handshake" - logger.info("%s:%i: handshake fail", *self.destination) + #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: - logger.debug("want read") + #print "want read" self.want_read = True if err.args[0] == ssl.SSL_ERROR_WANT_WRITE: - logger.debug("want write") + #print "want write" self.want_write = True if not (self.want_write or self.want_read): raise except socket.error as err: - # pylint: disable=protected-access if err.errno in asyncore._DISCONNECTED: - self.close_reason = "socket.error in tls_handshake" 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.tlsVersion) + 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) + 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) diff --git a/src/network/udp.py b/src/network/udp.py index 1a9891ec..0dba5a3f 100644 --- a/src/network/udp.py +++ b/src/network/udp.py @@ -1,51 +1,51 @@ -""" -UDP protocol handler -""" -import logging -import socket import time +import Queue +import socket -# magic imports! -import protocol +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 -from queues import receiveDataQueue +import protocol -from bmproto import BMProto -from node import Peer -from objectracker import ObjectTracker - - -logger = logging.getLogger('default') - - -class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes - """Bitmessage protocol over UDP (class)""" +class UDPSocket(BMProto): port = 8444 + announceInterval = 60 def __init__(self, host=None, sock=None, announcing=False): - # pylint: disable=bad-super-call super(BMProto, self).__init__(sock=sock) self.verackReceived = True self.verackSent = True - # .. todo:: sort out streams + # 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 = '' - self.create_socket( - socket.AF_INET6 if ":" in host else socket.AF_INET, - socket.SOCK_DGRAM - ) + 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, self.port) - self.socket.bind((host, self.port)) + 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 = Peer(*self.socket.getsockname()) - self.destination = Peer(*self.socket.getsockname()) + 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 @@ -53,7 +53,6 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes self.set_state("bm_header", expectBytes=protocol.Header.size) def set_socket_reuse(self): - """Set socket reuse option""" self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: @@ -61,42 +60,48 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes 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 BMProto.bm_command_getdata(self) return True +# return BMProto.bm_command_getdata(self) def bm_command_inv(self): - # return BMProto.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 + # 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 seenTime, stream, _, ip, port in addresses: + 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() - protocol.MAX_TIME_OFFSET - or seenTime > time.time() + protocol.MAX_TIME_OFFSET): + 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 host's own announcement + # 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) - state.discoveredPeers[Peer(self.destination.host, remoteport)] = \ - time.time() + 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): @@ -121,29 +126,51 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes return self.write_buf def readable(self): - return len(self.read_buf) < self._buf_len + return len(self.read_buf) < AdvancedDispatcher._buf_len def handle_read(self): try: - recdata, addr = self.socket.recvfrom(self._buf_len) - except socket.error: - logger.error("socket error on recvfrom:", exc_info=True) + (recdata, addr) = self.socket.recvfrom(AdvancedDispatcher._buf_len) + except socket.error as e: + logger.error("socket error: %s", str(e)) return - self.destination = Peer(*addr) + self.destination = state.Peer(addr[0], addr[1]) encodedAddr = protocol.encodeHost(addr[0]) - self.local = bool(protocol.checkIPAddress(encodedAddr, True)) - # overwrite the old buffer to avoid mixing data and so that - # self.local works correctly + 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, ('', self.port)) - except socket.error: - logger.error("socket error on sendto:", exc_info=True) - retval = len(self.write_buf) + 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/network/uploadthread.py b/src/network/uploadthread.py deleted file mode 100644 index 7d80d789..00000000 --- a/src/network/uploadthread.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -`UploadThread` class definition -""" -import time - -import helper_random -import protocol -from inventory import Inventory -from network.connectionpool import BMConnectionPool -from network.dandelion import Dandelion -from randomtrackingdict import RandomTrackingDict -from threads import StoppableThread - - -class UploadThread(StoppableThread): - """ - This is a thread that uploads the objects that the peers requested from me - """ - maxBufSize = 2097152 # 2MB - name = "Uploader" - - def run(self): - while not self._stopped: - uploaded = 0 - # Choose uploading peers randomly - connections = BMConnectionPool().establishedConnections() - helper_random.randomshuffle(connections) - for i in connections: - now = time.time() - # avoid unnecessary delay - if i.skipUntil >= now: - continue - if len(i.write_buf) > self.maxBufSize: - continue - try: - request = i.pendingUpload.randomKeys( - RandomTrackingDict.maxPending) - except KeyError: - continue - payload = bytearray() - chunk_count = 0 - for chunk in request: - del i.pendingUpload[chunk] - if Dandelion().hasHash(chunk) and \ - i != Dandelion().objectChildStem(chunk): - i.antiIntersectionDelay() - self.logger.info( - '%s asked for a stem object we didn\'t offer to it.', - i.destination) - break - try: - payload.extend(protocol.CreatePacket( - 'object', Inventory()[chunk].payload)) - chunk_count += 1 - except KeyError: - i.antiIntersectionDelay() - self.logger.info( - '%s asked for an object we don\'t have.', - i.destination) - break - if not chunk_count: - continue - i.append_write_buf(payload) - self.logger.debug( - '%s:%i Uploading %i objects', - i.destination.host, i.destination.port, chunk_count) - uploaded += chunk_count - if not uploaded: - self.stop.wait(1) diff --git a/src/openclpow.py b/src/openclpow.py index 5391590c..894a5b77 100644 --- a/src/openclpow.py +++ b/src/openclpow.py @@ -1,24 +1,16 @@ -""" -Module for Proof of Work using OpenCL -""" -import logging +#!/usr/bin/env python2.7 +from struct import pack, unpack +import time +import hashlib +import random import os -from struct import pack +from bmconfigparser import BMConfigParser import paths -from bmconfigparser import config from state import shutdown +from debug import logger -try: - import numpy - import pyopencl as cl - libAvailable = True -except ImportError: - libAvailable = False - - -logger = logging.getLogger('default') - +libAvailable = True ctx = False queue = False program = False @@ -27,10 +19,14 @@ enabledGpus = [] vendors = [] hash_dt = None +try: + import numpy + import pyopencl as cl +except: + libAvailable = False def initCL(): - """Initlialise OpenCL engine""" - global ctx, queue, program, hash_dt # pylint: disable=global-statement + global ctx, queue, program, hash_dt, libAvailable if libAvailable is False: return del enabledGpus[:] @@ -42,14 +38,13 @@ def initCL(): try: for platform in cl.get_platforms(): gpus.extend(platform.get_devices(device_type=cl.device_type.GPU)) - if config.safeGet("bitmessagesettings", "opencl") == platform.vendor: - enabledGpus.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: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: pass - if enabledGpus: + if (len(enabledGpus) > 0): ctx = cl.Context(devices=enabledGpus) queue = cl.CommandQueue(ctx) f = open(os.path.join(paths.codePath(), "bitmsghash", 'bitmsghash.cl'), 'r') @@ -59,29 +54,23 @@ def initCL(): else: logger.info("No OpenCL GPUs found") del enabledGpus[:] - except Exception: + except Exception as e: logger.error("OpenCL fail: ", exc_info=True) del enabledGpus[:] - def openclAvailable(): - """Are there any OpenCL GPUs available?""" - return bool(gpus) - + return (len(gpus) > 0) def openclEnabled(): - """Is OpenCL enabled (and available)?""" - return bool(enabledGpus) + return (len(enabledGpus) > 0) - -def do_opencl_pow(hash_, target): - """Perform PoW using OpenCL""" +def do_opencl_pow(hash, target): output = numpy.zeros(1, dtype=[('v', numpy.uint64, 1)]) - if not enabledGpus: + 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]['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) @@ -93,19 +82,30 @@ def do_opencl_pow(hash_, target): kernel.set_arg(0, hash_buf) kernel.set_arg(1, dest_buf) + start = time.time() progress = 0 - globamt = worksize * 2000 + 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/pathmagic.py b/src/pathmagic.py deleted file mode 100644 index 3f32c0c1..00000000 --- a/src/pathmagic.py +++ /dev/null @@ -1,10 +0,0 @@ -import os -import sys - - -def setup(): - """Add path to this file to sys.path""" - app_dir = os.path.dirname(os.path.abspath(__file__)) - os.chdir(app_dir) - sys.path.insert(0, app_dir) - return app_dir diff --git a/src/paths.py b/src/paths.py index e0d43334..325fcd8b 100644 --- a/src/paths.py +++ b/src/paths.py @@ -1,87 +1,76 @@ -""" -Path related functions -""" -import logging -import os -import re +from os import environ, path import sys +import re from datetime import datetime -from shutil import move - -logger = logging.getLogger('default') # 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 +# namespace. This can be used to setup a different code path for # binary distributions vs source distributions. -frozen = getattr(sys, 'frozen', None) - +frozen = getattr(sys,'frozen', None) def lookupExeFolder(): - """Returns executable folder path""" if frozen: - exeFolder = ( + if frozen == "macosx_app": # targetdir/Bitmessage.app/Contents/MacOS/Bitmessage - os.path.dirname(sys.executable).split(os.path.sep)[0] - if frozen == "macosx_app" else os.path.dirname(sys.executable)) - elif os.getenv('APPIMAGE'): - exeFolder = os.path.dirname(os.getenv('APPIMAGE')) + exeFolder = path.dirname(path.dirname(path.dirname(path.dirname(sys.executable)))) + path.sep + else: + exeFolder = path.dirname(sys.executable) + path.sep elif __file__: - exeFolder = os.path.dirname(__file__) + exeFolder = path.dirname(__file__) + path.sep else: - return '' - return exeFolder + os.path.sep - + exeFolder = '' + return exeFolder def lookupAppdataFolder(): - """Returns path of the folder where application data is stored""" APPNAME = "PyBitmessage" - dataFolder = os.environ.get('BITMESSAGE_HOME') - if dataFolder: - if dataFolder[-1] not in (os.path.sep, os.path.altsep): - dataFolder += os.path.sep + if "BITMESSAGE_HOME" in environ: + dataFolder = environ["BITMESSAGE_HOME"] + if dataFolder[-1] not in [path.sep, path.altsep]: + dataFolder += path.sep elif sys.platform == 'darwin': - try: - dataFolder = os.path.join( - os.environ['HOME'], - 'Library/Application Support/', APPNAME - ) + '/' + if "HOME" in environ: + dataFolder = path.join(environ["HOME"], "Library/Application Support/", APPNAME) + '/' + else: + stringToLog = 'Could not find home folder, please report this message and your OS X version to the BitMessage Github.' + if 'logger' in globals(): + logger.critical(stringToLog) + else: + print stringToLog + sys.exit() - except KeyError: - sys.exit( - 'Could not find home folder, please report this message' - ' and your OS X version to the BitMessage Github.') - elif sys.platform.startswith('win'): - dataFolder = os.path.join(os.environ['APPDATA'], APPNAME) + os.path.sep + elif 'win32' in sys.platform or 'win64' in sys.platform: + dataFolder = path.join(environ['APPDATA'].decode(sys.getfilesystemencoding(), 'ignore'), APPNAME) + path.sep else: + from shutil import move try: - dataFolder = os.path.join(os.environ['XDG_CONFIG_HOME'], APPNAME) + dataFolder = path.join(environ["XDG_CONFIG_HOME"], APPNAME) except KeyError: - dataFolder = os.path.join(os.environ['HOME'], '.config', APPNAME) + dataFolder = path.join(environ["HOME"], ".config", APPNAME) - # Migrate existing data to the proper location - # if this is an existing install + # Migrate existing data to the proper location if this is an existing install try: - move(os.path.join(os.environ['HOME'], '.%s' % APPNAME), dataFolder) - logger.info('Moving data folder to %s', dataFolder) + move(path.join(environ["HOME"], ".%s" % APPNAME), dataFolder) + stringToLog = "Moving data folder to %s" % (dataFolder) + if 'logger' in globals(): + logger.info(stringToLog) + else: + print stringToLog except IOError: # Old directory may not exist. pass - dataFolder = dataFolder + os.path.sep + dataFolder = dataFolder + '/' return dataFolder - - + def codePath(): - """Returns path to the program sources""" - if not frozen: - return os.path.dirname(__file__) - return ( - os.environ.get('RESOURCEPATH') - # pylint: disable=protected-access - if frozen == "macosx_app" else sys._MEIPASS) - + if frozen == "macosx_app": + codePath = environ.get("RESOURCEPATH") + elif frozen: # windows + codePath = sys._MEIPASS + else: + codePath = path.dirname(__file__) + return codePath def tail(f, lines=20): - """Returns last lines in the f file object""" total_lines_wanted = lines BLOCK_SIZE = 1024 @@ -89,17 +78,16 @@ def tail(f, lines=20): block_end_byte = f.tell() lines_to_go = total_lines_wanted block_number = -1 - # blocks of size BLOCK_SIZE, in reverse order starting - # from the end of the file - blocks = [] + blocks = [] # blocks of size BLOCK_SIZE, in reverse order starting + # from the end of the file while lines_to_go > 0 and block_end_byte > 0: - if block_end_byte - BLOCK_SIZE > 0: + if (block_end_byte - BLOCK_SIZE > 0): # read the last block we haven't yet read - f.seek(block_number * BLOCK_SIZE, 2) + f.seek(block_number*BLOCK_SIZE, 2) blocks.append(f.read(BLOCK_SIZE)) else: # file too small, start from begining - f.seek(0, 0) + f.seek(0,0) # only read what was not read blocks.append(f.read(block_end_byte)) lines_found = blocks[-1].count('\n') @@ -111,12 +99,9 @@ def tail(f, lines=20): def lastCommit(): - """ - Returns last commit information as dict with 'commit' and 'time' keys - """ - githeadfile = os.path.join(codePath(), '..', '.git', 'logs', 'HEAD') + githeadfile = path.join(codePath(), '..', '.git', 'logs', 'HEAD') result = {} - if os.path.isfile(githeadfile): + if path.isfile(githeadfile): try: with open(githeadfile, 'rt') as githead: line = tail(githead, 1) diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py index 285009df..e69de29b 100644 --- a/src/plugins/__init__.py +++ b/src/plugins/__init__.py @@ -1,7 +0,0 @@ -""" -Simple plugin system based on setuptools ----------------------------------------- - - -""" -# .. include:: pybitmessage.plugins.plugin.rst diff --git a/src/plugins/desktop_xdg.py b/src/plugins/desktop_xdg.py deleted file mode 100644 index 0b551e1c..00000000 --- a/src/plugins/desktop_xdg.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- - -import os - -from xdg import BaseDirectory, Menu, Exceptions - - -class DesktopXDG(object): - """pyxdg Freedesktop desktop implementation""" - def __init__(self): - try: - self.desktop = Menu.parse().getMenu('Office').getMenuEntry( - 'pybitmessage.desktop').DesktopEntry - except (AttributeError, Exceptions.ParsingError): - raise TypeError # TypeError disables startonlogon - appimage = os.getenv('APPIMAGE') - if appimage: - self.desktop.set('Exec', appimage) - - def adjust_startonlogon(self, autostart=False): - """Configure autostart according to settings""" - autostart_path = os.path.join( - BaseDirectory.xdg_config_home, 'autostart', 'pybitmessage.desktop') - if autostart: - self.desktop.write(autostart_path) - else: - try: - os.remove(autostart_path) - except OSError: - pass - - -connect_plugin = DesktopXDG diff --git a/src/plugins/indicator_libmessaging.py b/src/plugins/indicator_libmessaging.py index b471d2ef..36178663 100644 --- a/src/plugins/indicator_libmessaging.py +++ b/src/plugins/indicator_libmessaging.py @@ -1,7 +1,4 @@ # -*- coding: utf-8 -*- -""" -Indicator plugin using libmessaging -""" import gi gi.require_version('MessagingMenu', '1.0') # noqa:E402 @@ -12,13 +9,12 @@ from pybitmessage.tr import _translate class IndicatorLibmessaging(object): - """Plugin for libmessage indicator""" def __init__(self, form): try: self.app = MessagingMenu.App(desktop_id='pybitmessage.desktop') self.app.register() self.app.connect('activate-source', self.activate) - except: # noqa:E722 + except: self.app = None return @@ -36,18 +32,15 @@ class IndicatorLibmessaging(object): if self.app: self.app.unregister() - def activate(self, app, source): # pylint: disable=unused-argument - """Activate the libmessaging indicator plugin""" + 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): - """ - show the number of unread messages and subscriptions - on the messaging menu - """ for source, count in zip( ('messages', 'subscriptions'), self.form.getUnread() diff --git a/src/plugins/menu_qrcode.py b/src/plugins/menu_qrcode.py deleted file mode 100644 index ea322a49..00000000 --- a/src/plugins/menu_qrcode.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -""" -A menu plugin showing QR-Code for bitmessage address in modal dialog. -""" - -import urllib - -import qrcode -from PyQt4 import QtCore, QtGui - -from pybitmessage.tr import _translate - - -# http://stackoverflow.com/questions/20452486 -class Image(qrcode.image.base.BaseImage): # pylint: disable=abstract-method - """Image output class for qrcode using QPainter""" - - def __init__(self, border, width, box_size): - # pylint: disable=super-init-not-called - 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): - """Get image pixmap""" - return QtGui.QPixmap.fromImage(self._image) - - def drawrect(self, row, col): - """Draw a single rectangle - implementation""" - 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) - - -class QRCodeDialog(QtGui.QDialog): - """The dialog""" - def __init__(self, parent): - super(QRCodeDialog, self).__init__(parent) - self.image = QtGui.QLabel(self) - self.label = QtGui.QLabel(self) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.label.setFont(font) - self.label.setAlignment( - QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) - buttonBox = QtGui.QDialogButtonBox(self) - buttonBox.setOrientation(QtCore.Qt.Horizontal) - buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) - buttonBox.accepted.connect(self.accept) - layout = QtGui.QVBoxLayout(self) - layout.addWidget(self.image) - layout.addWidget(self.label) - layout.addWidget(buttonBox) - self.retranslateUi() - - def retranslateUi(self): - """A conventional Qt Designer method for dynamic l10n""" - self.setWindowTitle(_translate("QRCodeDialog", "QR-code")) - - def render(self, text): - """Draw QR-code and address in labels""" - pixmap = qrcode.make(text, image_factory=Image).pixmap() - self.image.setPixmap(pixmap) - self.label.setText(text) - self.label.setToolTip(text) - self.label.setFixedWidth(pixmap.width()) - self.setFixedSize(QtGui.QWidget.sizeHint(self)) - - -def connect_plugin(form): - """Plugin entry point""" - def on_action_ShowQR(): - """A slot for popup menu action""" - try: - dialog = form.qrcode_dialog - except AttributeError: - form.qrcode_dialog = dialog = QRCodeDialog(form) - account = form.getContactSelected() - try: - label = account._getLabel() # pylint: disable=protected-access - except AttributeError: - try: - label = account.getLabel() - except AttributeError: - return - dialog.render( - 'bitmessage:%s' % account.address + ( - '?' + urllib.urlencode({'label': label.encode('utf-8')}) - if label != account.address else '') - ) - dialog.exec_() - - return on_action_ShowQR, _translate("MainWindow", "Show QR-code") diff --git a/src/plugins/notification_notify2.py b/src/plugins/notification_notify2.py index f851737d..3fd935c4 100644 --- a/src/plugins/notification_notify2.py +++ b/src/plugins/notification_notify2.py @@ -1,21 +1,15 @@ # -*- coding: utf-8 -*- -""" -Notification plugin using notify2 -""" import gi gi.require_version('Notify', '0.7') -from gi.repository import Notify # noqa:E402 +from gi.repository import Notify Notify.init('pybitmessage') - -def connect_plugin(title, subtitle, category, _, icon): - """Plugin for notify2""" +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 index 629de0a6..6601adaf 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -1,28 +1,16 @@ # -*- coding: utf-8 -*- -""" -Operating with plugins -""" -import logging import pkg_resources -logger = logging.getLogger('default') - - def get_plugins(group, point='', name=None, fallback=None): """ - :param str group: plugin group - :param str point: plugin name prefix - :param name: exact plugin name - :param fallback: fallback plugin name - - 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. + 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 not point or ep.name.startswith(point): + if name and ep.name == name or ep.name.startswith(point): try: plugin = ep.load().connect_plugin if ep.name == fallback: @@ -34,8 +22,6 @@ def get_plugins(group, point='', name=None, fallback=None): ValueError, pkg_resources.DistributionNotFound, pkg_resources.UnknownExtra): - logger.debug( - 'Problem while loading %s', ep.name, exc_info=True) continue try: yield _fallback @@ -44,8 +30,6 @@ def get_plugins(group, point='', name=None, fallback=None): def get_plugin(*args, **kwargs): - """ - :return: first available plugin from :func:`get_plugins` if any. - """ + """Returns first available plugin `from get_plugins()` if any.""" for plugin in get_plugins(*args, **kwargs): return plugin diff --git a/src/plugins/proxyconfig_stem.py b/src/plugins/proxyconfig_stem.py deleted file mode 100644 index 25f75f69..00000000 --- a/src/plugins/proxyconfig_stem.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Configure tor proxy and hidden service with -`stem `_ depending on *bitmessagesettings*: - - * try to start own tor instance on *socksport* if *sockshostname* - is unset or set to localhost; - * if *socksport* is already in use that instance is used only for - hidden service (if *sockslisten* is also set True); - * create ephemeral hidden service v3 if there is already *onionhostname*; - * otherwise use stem's 'BEST' version and save onion keys to the new - section using *onionhostname* as name for future use. -""" -import logging -import os -import random -import sys -import tempfile - -import stem -import stem.control -import stem.process -import stem.version - - -class DebugLogger(object): # pylint: disable=too-few-public-methods - """Safe logger wrapper for tor and plugin's logs""" - def __init__(self): - self._logger = logging.getLogger('default') - self._levels = { - 'err': 40, - 'warn': 30, - 'notice': 20 - } - - def __call__(self, line): - try: - level, line = line.split('[', 1)[1].split(']', 1) - except IndexError: - # Plugin's debug or unexpected log line from tor - self._logger.debug(line) - except ValueError: # some error while splitting - self._logger.warning(line) - else: - self._logger.log(self._levels.get(level, 10), '(tor) %s', line) - - -# pylint: disable=too-many-branches,too-many-statements -def connect_plugin(config): - """ - Run stem proxy configurator - - :param config: current configuration instance - :type config: :class:`pybitmessage.bmconfigparser.BMConfigParser` - :return: True if configuration was done successfully - """ - logwrite = DebugLogger() - if config.safeGet('bitmessagesettings', 'sockshostname', '') not in ( - 'localhost', '127.0.0.1', '' - ): - # remote proxy is choosen for outbound connections, - # nothing to do here, but need to set socksproxytype to SOCKS5! - config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') - logwrite( - 'sockshostname is set to remote address,' - ' aborting stem proxy configuration') - return - - tor_config = {'SocksPort': '9050'} - - datadir = tempfile.mkdtemp() - if sys.platform.startswith('win'): - # no ControlSocket on windows because there is no Unix sockets - tor_config['DataDirectory'] = datadir - else: - control_socket = os.path.join(datadir, 'control') - tor_config['ControlSocket'] = control_socket - - port = config.safeGetInt('bitmessagesettings', 'socksport', 9050) - for attempt in range(50): - if attempt > 0: - port = random.randint(32767, 65535) # nosec B311 - tor_config['SocksPort'] = str(port) - if tor_config.get('DataDirectory'): - control_port = port + 1 - tor_config['ControlPort'] = str(control_port) - # It's recommended to use separate tor instance for hidden services. - # So if there is a system wide tor, use it for outbound connections. - try: - stem.process.launch_tor_with_config( - tor_config, take_ownership=True, - timeout=(None if sys.platform.startswith('win') else 20), - init_msg_handler=logwrite) - except OSError: - if not attempt: - try: - stem.version.get_system_tor_version() - except IOError: - return - continue - else: - logwrite('Started tor on port %s' % port) - break - else: - logwrite('Failed to start tor') - return - - config.setTemp('bitmessagesettings', 'socksproxytype', 'SOCKS5') - - if config.safeGetBoolean('bitmessagesettings', 'sockslisten'): - # need a hidden service for inbound connections - try: - controller = ( - stem.control.Controller.from_port(port=control_port) - if sys.platform.startswith('win') else - stem.control.Controller.from_socket_file(control_socket) - ) - controller.authenticate() - except stem.SocketError: - # something goes wrong way - logwrite('Failed to instantiate or authenticate on controller') - return - - onionhostname = config.safeGet('bitmessagesettings', 'onionhostname') - onionkey = config.safeGet(onionhostname, 'privsigningkey') - if onionhostname and not onionkey: - logwrite('The hidden service found in config ): %s' % - onionhostname) - onionkeytype = config.safeGet(onionhostname, 'keytype') - - response = controller.create_ephemeral_hidden_service( - {config.safeGetInt('bitmessagesettings', 'onionport', 8444): - config.safeGetInt('bitmessagesettings', 'port', 8444)}, - key_type=(onionkeytype or 'NEW'), - key_content=(onionkey or onionhostname and 'ED25519-V3' or 'BEST') - ) - - if not response.is_ok(): - logwrite('Bad response from controller ):') - return - - if not onionkey: - logwrite('Started hidden service %s.onion' % response.service_id) - # only save new service keys - # if onionhostname was not set previously - if not onionhostname: - onionhostname = response.service_id + '.onion' - config.set( - 'bitmessagesettings', 'onionhostname', onionhostname) - config.add_section(onionhostname) - config.set( - onionhostname, 'privsigningkey', response.private_key) - config.set( - onionhostname, 'keytype', response.private_key_type) - config.save() - - return True 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 index 9fea8197..094901ed 100644 --- a/src/plugins/sound_canberra.py +++ b/src/plugins/sound_canberra.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- -""" -Sound theme plugin using pycanberra -""" + +from pybitmessage.bitmessageqt import sound import pycanberra -from pybitmessage.bitmessageqt import sound _canberra = pycanberra.Canberra() @@ -16,8 +14,7 @@ _theme = { } -def connect_plugin(category, label=None): # pylint: disable=unused-argument - """This function implements the entry point.""" +def connect_plugin(category, label=None): try: _canberra.play(0, pycanberra.CA_PROP_EVENT_ID, _theme[category], None) except (KeyError, pycanberra.CanberraException): diff --git a/src/plugins/sound_gstreamer.py b/src/plugins/sound_gstreamer.py index 8f3606dd..062da3f9 100644 --- a/src/plugins/sound_gstreamer.py +++ b/src/plugins/sound_gstreamer.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -""" -Sound notification plugin using gstreamer -""" + import gi gi.require_version('Gst', '1.0') from gi.repository import Gst # noqa: E402 @@ -11,7 +9,6 @@ _player = Gst.ElementFactory.make("playbin", "player") def connect_plugin(sound_file): - """Entry point for 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 index c6b70f66..c8216d07 100644 --- a/src/plugins/sound_playfile.py +++ b/src/plugins/sound_playfile.py @@ -1,28 +1,24 @@ # -*- coding: utf-8 -*- -""" -Sound notification plugin using external executable or winsound (on Windows) -""" + try: import winsound def connect_plugin(sound_file): - """Plugin's entry point""" winsound.PlaySound(sound_file, winsound.SND_FILENAME) except ImportError: import os - import subprocess # nosec B404 + import subprocess play_cmd = {} def _subprocess(*args): FNULL = open(os.devnull, 'wb') subprocess.call( - args, stdout=FNULL, stderr=subprocess.STDOUT, close_fds=True) # nosec B603 + args, stdout=FNULL, stderr=subprocess.STDOUT, close_fds=True) def connect_plugin(sound_file): - """This function implements the entry point.""" - global play_cmd # pylint: disable=global-statement + global play_cmd ext = os.path.splitext(sound_file)[-1] try: diff --git a/src/proofofwork.py b/src/proofofwork.py index f77f455a..df6ed295 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -1,126 +1,70 @@ -# pylint: disable=too-many-branches,too-many-statements,protected-access -""" -Proof of work calculation -""" - -import ctypes +#import shared +#import time +#from multiprocessing import Pool, cpu_count import hashlib -import os +from struct import unpack, pack +from subprocess import call import sys -import tempfile import time -from struct import pack, unpack -from subprocess import call # nosec B404 - -import openclpow -import paths -import queues -import state -import tr -from bmconfigparser import config +from bmconfigparser import BMConfigParser from debug import logger +import paths +import openclpow +import queues +import tr +import os +import ctypes + +import state bitmsglib = 'bitmsghash.so' + bmpow = None - -class LogOutput(object): # pylint: disable=too-few-public-methods - """ - A context manager that block stdout for its scope - and appends it's content to log before exit. Usage:: - - with LogOutput(): - os.system('ls -l') - - https://stackoverflow.com/questions/5081657 - """ - - def __init__(self, prefix='PoW'): - self.prefix = prefix - try: - sys.stdout.flush() - self._stdout = sys.stdout - self._stdout_fno = os.dup(sys.stdout.fileno()) - except AttributeError: - # NullWriter instance has no attribute 'fileno' on Windows - self._stdout = None - else: - self._dst, self._filepath = tempfile.mkstemp() - - def __enter__(self): - if not self._stdout: - return - stdout = os.dup(1) - os.dup2(self._dst, 1) - os.close(self._dst) - sys.stdout = os.fdopen(stdout, 'w') - - def __exit__(self, exc_type, exc_val, exc_tb): - if not self._stdout: - return - sys.stdout.close() - sys.stdout = self._stdout - sys.stdout.flush() - os.dup2(self._stdout_fno, 1) - - with open(self._filepath) as out: - for line in out: - logger.info('%s: %s', self.prefix, line) - os.remove(self._filepath) - - def _set_idle(): if 'linux' in sys.platform: os.nice(20) else: try: - # pylint: disable=no-member,import-error sys.getwindowsversion() - import win32api - import win32process - import win32con + import win32api,win32process,win32con # @UnresolvedImport pid = win32api.GetCurrentProcessId() handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, pid) win32process.SetPriorityClass(handle, win32process.IDLE_PRIORITY_CLASS) - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - # Windows 64-bit + except: + #Windows 64-bit pass - def _pool_worker(nonce, initialHash, target, pool_size): _set_idle() trialValue = float('inf') while trialValue > target: nonce += pool_size - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) return [trialValue, nonce] - def _doSafePoW(target, initialHash): logger.debug("Safe PoW start") nonce = 0 trialValue = float('inf') while trialValue > target and state.shutdown == 0: nonce += 1 - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) if state.shutdown != 0: - raise StopIteration("Interrupted") # pylint: misplaced-bare-raise + raise StopIteration("Interrupted") logger.debug("Safe PoW done") return [trialValue, nonce] - def _doFastPoW(target, initialHash): logger.debug("Fast PoW start") from multiprocessing import Pool, cpu_count try: pool_size = cpu_count() - except: # noqa:E722 + except: pool_size = 4 try: - maxCores = config.getint('bitmessagesettings', 'maxcores') - except: # noqa:E722 + maxCores = BMConfigParser().getint('bitmessagesettings', 'maxcores') + except: maxCores = 99999 if pool_size > maxCores: pool_size = maxCores @@ -135,7 +79,7 @@ def _doFastPoW(target, initialHash): try: pool.terminate() pool.join() - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: pass raise StopIteration("Interrupted") for i in range(pool_size): @@ -152,58 +96,41 @@ def _doFastPoW(target, initialHash): logger.debug("Fast PoW done") return result[0], result[1] time.sleep(0.2) - - + def _doCPoW(target, initialHash): - with LogOutput(): - h = initialHash - m = target - out_h = ctypes.pointer(ctypes.create_string_buffer(h, 64)) - out_m = ctypes.c_ulonglong(m) - logger.debug("C PoW start") - nonce = bmpow(out_h, out_m) - - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + h = initialHash + m = target + out_h = ctypes.pointer(ctypes.create_string_buffer(h, 64)) + out_m = ctypes.c_ulonglong(m) + logger.debug("C PoW start") + nonce = bmpow(out_h, out_m) + trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) if state.shutdown != 0: raise StopIteration("Interrupted") logger.debug("C PoW done") return [trialValue, nonce] - def _doGPUPoW(target, initialHash): logger.debug("GPU PoW start") nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target) - trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + trialValue, = unpack('>Q',hashlib.sha512(hashlib.sha512(pack('>Q',nonce) + initialHash).digest()).digest()[0:8]) + #print "{} - value {} < {}".format(nonce, trialValue, target) if trialValue > target: deviceNames = ", ".join(gpu.name for gpu in openclpow.enabledGpus) - queues.UISignalQueue.put(( - 'updateStatusBar', ( - tr._translate( - "MainWindow", - 'Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.' - ), - 1))) - logger.error( - "Your GPUs (%s) did not calculate correctly, disabling OpenCL. Please report to the developers.", - deviceNames) + queues.UISignalQueue.put(('updateStatusBar', (tr._translate("MainWindow",'Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers.'), 1))) + logger.error("Your GPUs (%s) did not calculate correctly, disabling OpenCL. Please report to the developers.", deviceNames) openclpow.enabledGpus = [] raise Exception("GPU did not calculate correctly.") if state.shutdown != 0: raise StopIteration("Interrupted") logger.debug("GPU PoW done") return [trialValue, nonce] - - -def estimate(difficulty, format=False): # pylint: disable=redefined-builtin - """ - .. todo: fix unused variable - """ + +def estimate(difficulty, format = False): ret = difficulty / 10 if ret < 1: ret = 1 - if format: - # pylint: disable=unused-variable out = str(int(ret)) + " seconds" if ret > 60: ret /= 60 @@ -221,46 +148,25 @@ def estimate(difficulty, format=False): # pylint: disable=redefined-builtin if ret > 366: ret /= 366 out = str(int(ret)) + " years" - ret = None # Ensure legacy behaviour - - return ret - + else: + return ret def getPowType(): - """Get the proof of work implementation""" - if openclpow.openclEnabled(): return "OpenCL" if bmpow: return "C" return "python" - def notifyBuild(tried=False): - """Notify the user of the success or otherwise of building the PoW C module""" - if bmpow: - queues.UISignalQueue.put(('updateStatusBar', (tr._translate( - "proofofwork", "C PoW module built successfully."), 1))) + queues.UISignalQueue.put(('updateStatusBar', (tr._translate("proofofwork", "C PoW module built successfully."), 1))) elif tried: - queues.UISignalQueue.put( - ( - 'updateStatusBar', ( - tr._translate( - "proofofwork", - "Failed to build C PoW module. Please build it manually." - ), - 1 - ) - ) - ) + queues.UISignalQueue.put(('updateStatusBar', (tr._translate("proofofwork", "Failed to build C PoW module. Please build it manually."), 1))) else: - queues.UISignalQueue.put(('updateStatusBar', (tr._translate( - "proofofwork", "C PoW module unavailable. Please build it."), 1))) - + queues.UISignalQueue.put(('updateStatusBar', (tr._translate("proofofwork", "C PoW module unavailable. Please build it."), 1))) def buildCPoW(): - """Attempt to build the PoW C module""" if bmpow is not None: return if paths.frozen is not None: @@ -272,41 +178,41 @@ def buildCPoW(): try: if "bsd" in sys.platform: # BSD make - call(["make", "-C", os.path.join(paths.codePath(), "bitmsghash"), - '-f', 'Makefile.bsd']) # nosec B607, B603 + call(["make", "-C", os.path.join(paths.codePath(), "bitmsghash"), '-f', 'Makefile.bsd']) else: # GNU make - call([ # nosec B607, B603 - "make", "-C", os.path.join(paths.codePath(), "bitmsghash")]) + call(["make", "-C", os.path.join(paths.codePath(), "bitmsghash")]) if os.path.exists(os.path.join(paths.codePath(), "bitmsghash", "bitmsghash.so")): init() notifyBuild(True) else: notifyBuild(True) - except: # noqa:E722 + except: notifyBuild(True) - def run(target, initialHash): - """Run the proof of work thread""" - if state.shutdown != 0: - raise # pylint: disable=misplaced-bare-raise + raise target = int(target) if openclpow.openclEnabled(): +# trialvalue1, nonce1 = _doGPUPoW(target, initialHash) +# trialvalue, nonce = _doFastPoW(target, initialHash) +# print "GPU: %s, %s" % (trialvalue1, nonce1) +# print "Fast: %s, %s" % (trialvalue, nonce) +# return [trialvalue, nonce] try: return _doGPUPoW(target, initialHash) except StopIteration: raise - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - pass # fallback + except: + pass # fallback if bmpow: try: return _doCPoW(target, initialHash) except StopIteration: raise - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - pass # fallback + except: + pass # fallback if paths.frozen == "macosx_app" or not paths.frozen: # on my (Peter Surda) Windows 10, Windows Defender # does not like this and fights with PyBitmessage @@ -317,31 +223,26 @@ def run(target, initialHash): except StopIteration: logger.error("Fast PoW got StopIteration") raise - except: # noqa:E722 # pylint:disable=bare-except + except: logger.error("Fast PoW got exception:", exc_info=True) + pass #fallback try: return _doSafePoW(target, initialHash) except StopIteration: raise - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except - pass # fallback - + except: + pass #fallback def resetPoW(): - """Initialise the OpenCL PoW""" openclpow.initCL() - # init - - def init(): - """Initialise PoW""" - # pylint: disable=global-statement - global bitmsglib, bmpow + global bitmsglib, bso, bmpow openclpow.initCL() - if sys.platform == "win32": + + if "win32" == sys.platform: if ctypes.sizeof(ctypes.c_voidp) == 4: bitmsglib = 'bitmsghash32.dll' else: @@ -354,7 +255,8 @@ def init(): bmpow.restype = ctypes.c_ulonglong _doCPoW(2**63, "") logger.info("Successfully tested C PoW DLL (stdcall) %s", bitmsglib) - except ValueError: + except: + logger.error("C PoW test fail.", exc_info=True) try: # MinGW bso = ctypes.CDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib)) @@ -363,12 +265,9 @@ def init(): bmpow.restype = ctypes.c_ulonglong _doCPoW(2**63, "") logger.info("Successfully tested C PoW DLL (cdecl) %s", bitmsglib) - except Exception as e: - logger.error("Error: %s", e, exc_info=True) + except: + logger.error("C PoW test fail.", exc_info=True) bso = None - except Exception as e: - logger.error("Error: %s", e, exc_info=True) - bso = None else: try: bso = ctypes.CDLL(os.path.join(paths.codePath(), "bitmsghash", bitmsglib)) @@ -380,7 +279,7 @@ def init(): ))[0]) except (OSError, IndexError): bso = None - except: # noqa:E722 + except: bso = None else: logger.info("Loaded C PoW DLL %s", bitmsglib) @@ -388,7 +287,7 @@ def init(): try: bmpow = bso.BitmessagePOW bmpow.restype = ctypes.c_ulonglong - except: # noqa:E722 + except: bmpow = None else: bmpow = None diff --git a/src/protocol.py b/src/protocol.py index 722ce9c1..e5b2c5c2 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -1,408 +1,240 @@ -""" -Low-level protocol-related functions. -""" -# pylint: disable=too-many-boolean-expressions,too-many-return-statements -# pylint: disable=too-many-locals,too-many-statements - import base64 +from binascii import hexlify import hashlib import random import socket +import ssl +from struct import pack, unpack, Struct import sys import time -from binascii import hexlify -from struct import Struct, pack, unpack +import traceback -import defaults -import highlevelcrypto -import state -from addresses import ( - encodeVarint, decodeVarint, decodeAddress, varintDecodeError) -from bmconfigparser import config +from addresses import calculateInventoryHash, encodeVarint, decodeVarint, decodeAddress, varintDecodeError +from bmconfigparser import BMConfigParser from debug import logger -from fallback import RIPEMD160Hash +import defaults from helper_sql import sqlExecute -from network.node import Peer +import highlevelcrypto +from inventory import Inventory +from queues import objectProcessorQueue +import state from version import softwareVersion -# Network constants -magic = 0xE9BEB4D9 -#: protocol specification says max 1000 addresses in one addr command -MAX_ADDR_COUNT = 1000 -#: address is online if online less than this many seconds ago -ADDRESS_ALIVE = 10800 -#: ~1.6 MB which is the maximum possible size of an inv message. -MAX_MESSAGE_SIZE = 1600100 -#: 2**18 = 256kB is the maximum size of an object payload -MAX_OBJECT_PAYLOAD_SIZE = 2**18 -#: protocol specification says max 50000 objects in one inv command -MAX_OBJECT_COUNT = 50000 -#: maximum time offset -MAX_TIME_OFFSET = 3600 - -# Service flags -#: This is a normal network node +#Service flags NODE_NETWORK = 1 -#: This node supports SSL/TLS in the current connect (python < 2.7.9 -#: only supports an SSL client, so in that case it would only have this -#: on when the connection is a client). NODE_SSL = 2 -# (Proposal) This node may do PoW on behalf of some its peers -# (PoW offloading/delegating), but it doesn't have to. Clients may have -# to meet additional requirements (e.g. TLS authentication) -# NODE_POW = 4 -#: Node supports dandelion NODE_DANDELION = 8 -# Bitfield flags +#Bitfield flags BITFIELD_DOESACK = 1 -# Error types +#Error types STATUS_WARNING = 0 STATUS_ERROR = 1 STATUS_FATAL = 2 -# Object types +#Object types OBJECT_GETPUBKEY = 0 OBJECT_PUBKEY = 1 OBJECT_MSG = 2 OBJECT_BROADCAST = 3 -OBJECT_ONIONPEER = 0x746f72 OBJECT_I2P = 0x493250 OBJECT_ADDR = 0x61646472 eightBytesOfRandomDataUsedToDetectConnectionsToSelf = pack( - '>Q', random.randrange(1, 18446744073709551615)) # nosec B311 + '>Q', random.randrange(1, 18446744073709551615)) -# Compiled struct for packing/unpacking headers -# New code should use CreatePacket instead of Header.pack +#Compiled struct for packing/unpacking headers +#New code should use CreatePacket instead of Header.pack Header = Struct('!L12sL4s') VersionPacket = Struct('>LqQ20s4s36sH') # Bitfield - def getBitfield(address): - """Get a bitfield from an address""" # bitfield of features supported by me (see the wiki). bitfield = 0 # send ack - if not config.safeGetBoolean(address, 'dontsendack'): + if not BMConfigParser().safeGetBoolean(address, 'dontsendack'): bitfield |= BITFIELD_DOESACK return pack('>I', bitfield) - def checkBitfield(bitfieldBinary, flags): - """Check if a bitfield matches the given flags""" bitfield, = unpack('>I', bitfieldBinary) return (bitfield & flags) == flags - def isBitSetWithinBitfield(fourByteString, n): - """Check if a particular bit is set in a bitfeld""" # Uses MSB 0 bit numbering across 4 bytes of data n = 31 - n x, = unpack('>L', fourByteString) return x & 2**n != 0 -# Streams - - -MIN_VALID_STREAM = 1 -MAX_VALID_STREAM = 2**63 - 1 - -# IP addresses - +# ip addresses def encodeHost(host): - """Encode a given host to be used in low-level socket operations""" - if host.endswith('.onion'): - return b'\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode( - host.split(".")[0], True) + if host.find('.onion') > -1: + return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(host.split(".")[0], True) elif host.find(':') == -1: - return b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \ + return '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \ socket.inet_aton(host) - return socket.inet_pton(socket.AF_INET6, host) - + else: + return socket.inet_pton(socket.AF_INET6, host) def networkType(host): - """Determine if a host is IPv4, IPv6 or an onion address""" - if host.endswith('.onion'): + if host.find('.onion') > -1: return 'onion' elif host.find(':') == -1: return 'IPv4' - return 'IPv6' - - -def network_group(host): - """Canonical identifier of network group - simplified, borrowed from - GetGroup() in src/netaddresses.cpp in bitcoin core""" - if not isinstance(host, str): - return None - network_type = networkType(host) - try: - raw_host = encodeHost(host) - except socket.error: - return host - if network_type == 'IPv4': - decoded_host = checkIPv4Address(raw_host[12:], True) - if decoded_host: - # /16 subnet - return raw_host[12:14] - elif network_type == 'IPv6': - decoded_host = checkIPv6Address(raw_host, True) - if decoded_host: - # /32 subnet - return raw_host[0:12] else: - # just host, e.g. for tor - return host - # global network type group for local, private, unroutable - return network_type - + return 'IPv6' def checkIPAddress(host, private=False): - """ - Returns hostStandardFormat if it is a valid IP address, - otherwise returns False - """ - if host[0:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF': + 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] == b'\xfd\x87\xd8\x7e\xeb\x43': + 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: - try: - hostStandardFormat = socket.inet_ntop(socket.AF_INET6, host) - except ValueError: - return False - if len(hostStandardFormat) == 0: - # This can happen on Windows systems which are - # not 64-bit compatible so let us drop the IPv6 address. + 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): - """ - Returns hostStandardFormat if it is an IPv4 address, - otherwise returns False - """ - if host[0:1] == b'\x7F': # 127/8 + if host[0] == '\x7F': # 127/8 if not private: - logger.debug( - 'Ignoring IP address in loopback range: %s', - hostStandardFormat) + logger.debug('Ignoring IP address in loopback range: ' + hostStandardFormat) return hostStandardFormat if private else False - if host[0:1] == b'\x0A': # 10/8 + if host[0] == '\x0A': # 10/8 if not private: - logger.debug( - 'Ignoring IP address in private range: %s', hostStandardFormat) + logger.debug('Ignoring IP address in private range: ' + hostStandardFormat) return hostStandardFormat if private else False - if host[0:2] == b'\xC0\xA8': # 192.168/16 + if host[0:2] == '\xC0\xA8': # 192.168/16 if not private: - logger.debug( - 'Ignoring IP address in private range: %s', hostStandardFormat) + logger.debug('Ignoring IP address in private range: ' + hostStandardFormat) return hostStandardFormat if private else False - if host[0:2] >= b'\xAC\x10' and host[0:2] < b'\xAC\x20': # 172.16/12 + 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: %s', hostStandardFormat) + 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): - """ - Returns hostStandardFormat if it is an IPv6 address, - otherwise returns False - """ - if host == b'\x00' * 15 + b'\x01': + if host == ('\x00' * 15) + '\x01': if not private: - logger.debug('Ignoring loopback address: %s', hostStandardFormat) + logger.debug('Ignoring loopback address: ' + hostStandardFormat) return False - try: - host = [ord(c) for c in host[:2]] - except TypeError: # python3 has ints already - pass - if host[0] == 0xfe and host[1] & 0xc0 == 0x80: + if host[0] == '\xFE' and (ord(host[1]) & 0xc0) == 0x80: if not private: - logger.debug('Ignoring local address: %s', hostStandardFormat) + logger.debug ('Ignoring local address: ' + hostStandardFormat) return hostStandardFormat if private else False - if host[0] & 0xfe == 0xfc: + if (ord(host[0]) & 0xfe) == 0xfc: if not private: - logger.debug( - 'Ignoring unique local address: %s', hostStandardFormat) + logger.debug ('Ignoring unique local address: ' + hostStandardFormat) return hostStandardFormat if private else False return False if private else hostStandardFormat +# checks -def haveSSL(server=False): - """ - Predicate to check if ECDSA server support is required and available - - python < 2.7.9's ssl library does not support ECDSA server due to - missing initialisation of available curves, but client works ok - """ - if not server: +def haveSSL(server = False): + # python < 2.7.9's ssl library does not support ECDSA server due to missing initialisation of available curves, but client works ok + if server == False: return True - elif sys.version_info >= (2, 7, 9): + elif sys.version_info >= (2,7,9): return True return False - def checkSocksIP(host): - """Predicate to check if we're using a SOCKS proxy""" - sockshostname = config.safeGet( - 'bitmessagesettings', 'sockshostname') try: - if not state.socksIP: - state.socksIP = socket.gethostbyname(sockshostname) - except NameError: # uninitialised - state.socksIP = socket.gethostbyname(sockshostname) - except (TypeError, socket.gaierror): # None, resolving failure - state.socksIP = sockshostname + if state.socksIP is None or not state.socksIP: + state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname")) + # uninitialised + except NameError: + state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname")) + # resolving failure + except socket.gaierror: + state.socksIP = BMConfigParser().get("bitmessagesettings", "sockshostname") return state.socksIP == host - -def isProofOfWorkSufficient( - data, nonceTrialsPerByte=0, payloadLengthExtraBytes=0, recvTime=0): - """ - Validate an object's Proof of Work using method described - :doc:`here ` - - Arguments: - int nonceTrialsPerByte (default: from `.defaults`) - int payloadLengthExtraBytes (default: from `.defaults`) - float recvTime (optional) UNIX epoch time when object was - received from the network (default: current system time) - Returns: - True if PoW valid and sufficient, False in all other cases - """ +def isProofOfWorkSufficient(data, + nonceTrialsPerByte=0, + payloadLengthExtraBytes=0): if nonceTrialsPerByte < defaults.networkDefaultProofOfWorkNonceTrialsPerByte: nonceTrialsPerByte = defaults.networkDefaultProofOfWorkNonceTrialsPerByte if payloadLengthExtraBytes < defaults.networkDefaultPayloadLengthExtraBytes: payloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes endOfLifeTime, = unpack('>Q', data[8:16]) - TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time())) + TTL = endOfLifeTime - int(time.time()) if TTL < 300: TTL = 300 - POW, = unpack('>Q', hashlib.sha512(hashlib.sha512( - data[:8] + hashlib.sha512(data[8:]).digest() - ).digest()).digest()[0:8]) - return POW <= 2 ** 64 / ( - nonceTrialsPerByte * ( - len(data) + payloadLengthExtraBytes - + ((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16)))) - + POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(data[ + :8] + hashlib.sha512(data[8:]).digest()).digest()).digest()[0:8]) + return POW <= 2 ** 64 / (nonceTrialsPerByte*(len(data) + payloadLengthExtraBytes + ((TTL*(len(data)+payloadLengthExtraBytes))/(2 ** 16)))) # Packet creation - -def CreatePacket(command, payload=b''): - """Construct and return a packet""" +def CreatePacket(command, payload=''): payload_length = len(payload) checksum = hashlib.sha512(payload).digest()[0:4] - + b = bytearray(Header.size + payload_length) - Header.pack_into(b, 0, magic, command, payload_length, checksum) + Header.pack_into(b, 0, 0xE9BEB4D9, command, payload_length, checksum) b[Header.size:] = payload return bytes(b) - -def assembleAddrMessage(peerList): - """Create address command""" - if isinstance(peerList, Peer): - peerList = [peerList] - if not peerList: - return b'' - retval = b'' - for i in range(0, len(peerList), MAX_ADDR_COUNT): - payload = encodeVarint(len(peerList[i:i + MAX_ADDR_COUNT])) - for stream, peer, timestamp in peerList[i:i + MAX_ADDR_COUNT]: - # 64-bit time - payload += pack('>Q', timestamp) - payload += pack('>I', stream) - # service bit flags offered by this node - payload += pack('>q', 1) - payload += encodeHost(peer.host) - # remote port - payload += pack('>H', peer.port) - retval += CreatePacket(b'addr', payload) - return retval - - -def assembleVersionMessage( - remoteHost, remotePort, participatingStreams, server=False, nodeid=None -): - """ - Construct the payload of a version message, - return the resulting bytes of running `CreatePacket` on it - """ - payload = b'' +def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server = False, nodeid = None): + payload = '' payload += pack('>L', 3) # protocol version. # 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', + NODE_NETWORK | + (NODE_SSL if haveSSL(server) else 0) | + (NODE_DANDELION if state.dandelion else 0) + ) payload += pack('>q', int(time.time())) - # boolservices of remote connection; ignored by the remote host. - payload += pack('>q', 1) - if checkSocksIP(remoteHost) and server: - # prevent leaking of tor outbound IP + payload += pack( + '>q', 1) # boolservices of remote connection; ignored by the remote host. + if checkSocksIP(remoteHost) and server: # prevent leaking of tor outbound IP payload += encodeHost('127.0.0.1') payload += pack('>H', 8444) else: - # use first 16 bytes if host data is longer - # for example in case of onion v3 service - try: - payload += encodeHost(remoteHost)[:16] - except socket.error: - payload += encodeHost('127.0.0.1') + payload += encodeHost(remoteHost) payload += pack('>H', remotePort) # remote IPv6 and port # 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) - ) - # = 127.0.0.1. This will be ignored by the remote host. - # The actual remote connected IP will be used. - payload += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack( - '>L', 2130706433) - # we have a separate extPort and incoming over clearnet - # or outgoing through clearnet - extport = config.safeGetInt('bitmessagesettings', 'extport') - if ( - extport and ((server and not checkSocksIP(remoteHost)) or ( - config.get('bitmessagesettings', 'socksproxytype') - == 'none' and not server)) - ): - payload += pack('>H', extport) - elif checkSocksIP(remoteHost) and server: # incoming connection over Tor - payload += pack( - '>H', config.getint('bitmessagesettings', 'onionport')) - else: # no extport and not incoming over Tor - payload += pack( - '>H', config.getint('bitmessagesettings', 'port')) + 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 + # incoming over clearnet or + # outgoing through clearnet + if BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp') and state.extPort \ + and ((server and not checkSocksIP(remoteHost)) or \ + (BMConfigParser().get("bitmessagesettings", "socksproxytype") == "none" and not server)): + payload += pack('>H', state.extPort) + elif checkSocksIP(remoteHost) and server: # incoming connection over Tor + payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'onionport')) + else: # no extPort and not incoming over Tor + payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'port')) + random.seed() if nodeid is not None: payload += nodeid[0:8] else: payload += eightBytesOfRandomDataUsedToDetectConnectionsToSelf - userAgent = ('/PyBitmessage:%s/' % softwareVersion).encode('utf-8') + userAgent = '/PyBitmessage:' + softwareVersion + '/' payload += encodeVarint(len(userAgent)) payload += userAgent @@ -416,112 +248,75 @@ def assembleVersionMessage( if count >= 160000: break - return CreatePacket(b'version', payload) - + return CreatePacket('version', payload) def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''): - """ - Construct the payload of an error message, - return the resulting bytes of running `CreatePacket` on it - """ payload = encodeVarint(fatal) payload += encodeVarint(banTime) payload += encodeVarint(len(inventoryVector)) payload += inventoryVector payload += encodeVarint(len(errorText)) payload += errorText - return CreatePacket(b'error', payload) - + return CreatePacket('error', payload) # Packet decoding - -def decodeObjectParameters(data): - """Decode the parameters of a raw object needed to put it in inventory""" - # BMProto.decode_payload_content("QQIvv") - expiresTime = unpack('>Q', data[8:16])[0] - objectType = unpack('>I', data[16:20])[0] - parserPos = 20 + decodeVarint(data[20:30])[1] - toStreamNumber = decodeVarint(data[parserPos:parserPos + 10])[0] - - return objectType, toStreamNumber, expiresTime - - def decryptAndCheckPubkeyPayload(data, address): """ - Version 4 pubkeys are encrypted. This function is run when we - already have the address to which we want to try to send a message. - The 'data' may come either off of the wire or we might have had it - already in our inventory when we tried to send a msg to this - particular address. + Version 4 pubkeys are encrypted. This function is run when we already have the + address to which we want to try to send a message. The 'data' may come either + off of the wire or we might have had it already in our inventory when we tried + to send a msg to this particular address. """ try: - addressVersion, streamNumber, ripe = decodeAddress(address)[1:] - + status, addressVersion, streamNumber, ripe = decodeAddress(address) + readPosition = 20 # bypass the nonce, time, and object type - embeddedAddressVersion, varintLength = decodeVarint( - data[readPosition:readPosition + 10]) + embeddedAddressVersion, varintLength = decodeVarint(data[readPosition:readPosition + 10]) readPosition += varintLength - embeddedStreamNumber, varintLength = decodeVarint( - data[readPosition:readPosition + 10]) + embeddedStreamNumber, varintLength = decodeVarint(data[readPosition:readPosition + 10]) readPosition += varintLength - # We'll store the address version and stream number - # (and some more) in the pubkeys table. - storedData = data[20:readPosition] - + storedData = data[20:readPosition] # We'll store the address version and stream number (and some more) in the pubkeys table. + if addressVersion != embeddedAddressVersion: - logger.info( - 'Pubkey decryption was UNsuccessful' - ' due to address version mismatch.') + logger.info('Pubkey decryption was UNsuccessful due to address version mismatch.') return 'failed' if streamNumber != embeddedStreamNumber: - logger.info( - 'Pubkey decryption was UNsuccessful' - ' due to stream number mismatch.') + logger.info('Pubkey decryption was UNsuccessful due to stream number mismatch.') return 'failed' - + tag = data[readPosition:readPosition + 32] readPosition += 32 - # the time through the tag. More data is appended onto - # signedData below after the decryption. - signedData = data[8:readPosition] + signedData = data[8:readPosition] # the time through the tag. More data is appended onto signedData below after the decryption. encryptedData = data[readPosition:] - + # Let us try to decrypt the pubkey toAddress, cryptorObject = state.neededPubkeys[tag] if toAddress != address: - logger.critical( - 'decryptAndCheckPubkeyPayload failed due to toAddress' - ' mismatch. This is very peculiar.' - ' toAddress: %s, address %s', - toAddress, address - ) - # the only way I can think that this could happen - # is if someone encodes their address data two different ways. - # That sort of address-malleability should have been caught - # by the UI or API and an error given to the user. + logger.critical('decryptAndCheckPubkeyPayload failed due to toAddress mismatch. This is very peculiar. toAddress: %s, address %s', toAddress, address) + # the only way I can think that this could happen is if someone encodes their address data two different ways. + # That sort of address-malleability should have been caught by the UI or API and an error given to the user. return 'failed' try: decryptedData = cryptorObject.decrypt(encryptedData) - except: # noqa:E722 - # FIXME: use a proper exception after `pyelliptic.ecc` is refactored. + except: # Someone must have encrypted some data with a different key # but tagged it with a tag for which we are watching. logger.info('Pubkey decryption was unsuccessful.') return 'failed' - + readPosition = 0 - # bitfieldBehaviors = decryptedData[readPosition:readPosition + 4] + bitfieldBehaviors = decryptedData[readPosition:readPosition + 4] readPosition += 4 publicSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] readPosition += 64 publicEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64] readPosition += 64 - specifiedNonceTrialsPerByteLength = decodeVarint( - decryptedData[readPosition:readPosition + 10])[1] + specifiedNonceTrialsPerByte, specifiedNonceTrialsPerByteLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) readPosition += specifiedNonceTrialsPerByteLength - specifiedPayloadLengthExtraBytesLength = decodeVarint( - decryptedData[readPosition:readPosition + 10])[1] + specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) readPosition += specifiedPayloadLengthExtraBytesLength storedData += decryptedData[:readPosition] signedData += decryptedData[:readPosition] @@ -529,50 +324,270 @@ def decryptAndCheckPubkeyPayload(data, address): decryptedData[readPosition:readPosition + 10]) readPosition += signatureLengthLength signature = decryptedData[readPosition:readPosition + signatureLength] - - if not highlevelcrypto.verify( - signedData, signature, hexlify(publicSigningKey)): - logger.info( - 'ECDSA verify failed (within decryptAndCheckPubkeyPayload)') + + if highlevelcrypto.verify(signedData, signature, hexlify(publicSigningKey)): + logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload)') + else: + logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload)') return 'failed' - - logger.info( - 'ECDSA verify passed (within decryptAndCheckPubkeyPayload)') - + sha = hashlib.new('sha512') sha.update(publicSigningKey + publicEncryptionKey) - embeddedRipe = RIPEMD160Hash(sha.digest()).digest() - + ripeHasher = hashlib.new('ripemd160') + ripeHasher.update(sha.digest()) + embeddedRipe = ripeHasher.digest() + if embeddedRipe != ripe: - # Although this pubkey object had the tag were were looking for - # and was encrypted with the correct encryption key, - # it doesn't contain the correct pubkeys. Someone is - # either being malicious or using buggy software. - logger.info( - 'Pubkey decryption was UNsuccessful due to RIPE mismatch.') + # Although this pubkey object had the tag were were looking for and was + # encrypted with the correct encryption key, it doesn't contain the + # correct pubkeys. Someone is either being malicious or using buggy software. + logger.info('Pubkey decryption was UNsuccessful due to RIPE mismatch.') return 'failed' - + # Everything checked out. Insert it into the pubkeys table. - - logger.info( - 'within decryptAndCheckPubkeyPayload, ' - 'addressVersion: %s, streamNumber: %s\nripe %s\n' - 'publicSigningKey in hex: %s\npublicEncryptionKey in hex: %s', - addressVersion, streamNumber, hexlify(ripe), - hexlify(publicSigningKey), hexlify(publicEncryptionKey) - ) - + + logger.info('within decryptAndCheckPubkeyPayload, addressVersion: %s, streamNumber: %s \n\ + ripe %s\n\ + publicSigningKey in hex: %s\n\ + publicEncryptionKey in hex: %s', addressVersion, + streamNumber, + hexlify(ripe), + hexlify(publicSigningKey), + hexlify(publicEncryptionKey) + ) + t = (address, addressVersion, storedData, int(time.time()), 'yes') sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) return 'successful' - except varintDecodeError: - logger.info( - 'Pubkey decryption was UNsuccessful due to a malformed varint.') + except varintDecodeError as e: + logger.info('Pubkey decryption was UNsuccessful due to a malformed varint.') return 'failed' - except Exception: - logger.critical( - 'Pubkey decryption was UNsuccessful because of' - ' an unhandled exception! This is definitely a bug!', - exc_info=True - ) + except Exception as e: + logger.critical('Pubkey decryption was UNsuccessful because of an unhandled exception! This is definitely a bug! \n%s', traceback.format_exc()) return 'failed' + +def checkAndShareObjectWithPeers(data): + """ + This function is called after either receiving an object off of the wire + or after receiving one as ackdata. + Returns the length of time that we should reserve to process this message + if we are receiving it off of the wire. + """ + if len(data) > 2 ** 18: + logger.info('The payload length of this object is too large (%s bytes). Ignoring it.', len(data)) + return 0 + # Let us check to make sure that the proof of work is sufficient. + if not isProofOfWorkSufficient(data): + logger.info('Proof of work is insufficient.') + 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) + 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) + return 0 + intObjectType, = unpack('>I', data[16:20]) + try: + if intObjectType == 0: + _checkAndShareGetpubkeyWithPeers(data) + return 0.1 + elif intObjectType == 1: + _checkAndSharePubkeyWithPeers(data) + return 0.1 + elif intObjectType == 2: + _checkAndShareMsgWithPeers(data) + return 0.6 + elif intObjectType == 3: + _checkAndShareBroadcastWithPeers(data) + return 0.6 + else: + _checkAndShareUndefinedObjectWithPeers(data) + return 0.6 + except varintDecodeError as e: + logger.debug("There was a problem with a varint while checking to see whether it was appropriate to share an object with peers. Some details: %s", e) + except Exception as e: + logger.critical('There was a problem while checking to see whether it was appropriate to share an object with peers. This is definitely a bug! \n%s', traceback.format_exc()) + return 0 + + +def _checkAndShareUndefinedObjectWithPeers(data): + embeddedTime, = unpack('>Q', data[8:16]) + readPosition = 20 # bypass nonce, time, and object type + objectVersion, objectVersionLength = decodeVarint( + data[readPosition:readPosition + 9]) + readPosition += objectVersionLength + streamNumber, streamNumberLength = decodeVarint( + data[readPosition:readPosition + 9]) + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) + return + + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this undefined object. Ignoring.') + return + objectType, = unpack('>I', data[16:20]) + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime,'') + logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) + broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + +def _checkAndShareMsgWithPeers(data): + embeddedTime, = unpack('>Q', data[8:16]) + readPosition = 20 # bypass nonce, time, and object type + objectVersion, objectVersionLength = decodeVarint( + data[readPosition:readPosition + 9]) + readPosition += objectVersionLength + streamNumber, streamNumberLength = decodeVarint( + data[readPosition:readPosition + 9]) + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) + return + readPosition += streamNumberLength + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this msg message. Ignoring.') + return + # This msg message is valid. Let's let our peers know about it. + objectType = 2 + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime,'') + logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) + broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + # Now let's enqueue it to be processed ourselves. + objectProcessorQueue.put((objectType,data)) + +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( + data[readPosition:readPosition + 10]) + readPosition += addressVersionLength + streamNumber, streamNumberLength = decodeVarint( + data[readPosition:readPosition + 10]) + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) + return + readPosition += streamNumberLength + + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this getpubkey request. Ignoring it.') + return + + objectType = 0 + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime,'') + # This getpubkey request is valid. Forward to peers. + logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) + broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + # Now let's queue it to be processed ourselves. + objectProcessorQueue.put((objectType,data)) + +def _checkAndSharePubkeyWithPeers(data): + if len(data) < 146 or len(data) > 440: # sanity check + return + embeddedTime, = unpack('>Q', data[8:16]) + readPosition = 20 # bypass the nonce, time, and object type + addressVersion, varintLength = decodeVarint( + data[readPosition:readPosition + 10]) + readPosition += varintLength + streamNumber, varintLength = decodeVarint( + data[readPosition:readPosition + 10]) + readPosition += varintLength + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) + return + if addressVersion >= 4: + tag = data[readPosition:readPosition + 32] + logger.debug('tag in received pubkey is: %s', hexlify(tag)) + else: + tag = '' + + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this pubkey. Ignoring it.') + return + objectType = 1 + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime, tag) + # This object is valid. Forward it to peers. + logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) + broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + + # Now let's queue it to be processed ourselves. + objectProcessorQueue.put((objectType,data)) + + +def _checkAndShareBroadcastWithPeers(data): + if len(data) < 180: + logger.debug('The payload length of this broadcast packet is unreasonably low. Someone is probably trying funny business. Ignoring message.') + return + embeddedTime, = unpack('>Q', data[8:16]) + readPosition = 20 # bypass the nonce, time, and object type + broadcastVersion, broadcastVersionLength = decodeVarint( + data[readPosition:readPosition + 10]) + readPosition += broadcastVersionLength + if broadcastVersion >= 2: + streamNumber, streamNumberLength = decodeVarint(data[readPosition:readPosition + 10]) + readPosition += streamNumberLength + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.', streamNumber) + return + if broadcastVersion >= 3: + tag = data[readPosition:readPosition+32] + else: + tag = '' + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this broadcast object. Ignoring.') + return + # It is valid. Let's let our peers know about it. + objectType = 3 + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime, tag) + # This object is valid. Forward it to peers. + logger.debug('advertising inv with hash: %s', hexlify(inventoryHash)) + broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + # Now let's queue it to be processed ourselves. + objectProcessorQueue.put((objectType,data)) + +# If you want to command all of the sendDataThreads to do something, like shutdown or send some data, this +# function puts your data into the queues for each of the sendDataThreads. The sendDataThreads are +# responsible for putting their queue into (and out of) the sendDataQueues list. +def broadcastToSendDataQueues(data): + # logger.debug('running broadcastToSendDataQueues') + for q in state.sendDataQueues: + q.put(data) + +# sslProtocolVersion +if sys.version_info >= (2,7,13): + # this means TLSv1 or higher + # in the future change to + # ssl.PROTOCOL_TLS1.2 + sslProtocolVersion = ssl.PROTOCOL_TLS +elif sys.version_info >= (2,7,9): + # this means any SSL/TLS. SSLv2 and 3 are excluded with an option after context is created + sslProtocolVersion = ssl.PROTOCOL_SSLv23 +else: + # this means TLSv1, there is no way to set "TLSv1 or higher" or + # "TLSv1.2" in < 2.7.9 + sslProtocolVersion = ssl.PROTOCOL_TLSv1 + +# ciphers +if ssl.OPENSSL_VERSION_NUMBER >= 0x10100000 and not ssl.OPENSSL_VERSION.startswith("LibreSSL"): + sslProtocolCiphers = "AECDH-AES256-SHA@SECLEVEL=0" +else: + sslProtocolCiphers = "AECDH-AES256-SHA" diff --git a/src/pyelliptic/__init__.py b/src/pyelliptic/__init__.py index cafa89c9..761d08af 100644 --- a/src/pyelliptic/__init__.py +++ b/src/pyelliptic/__init__.py @@ -1,30 +1,19 @@ -""" -Copyright (C) 2010 -Author: Yann GUIBET -Contact: - -Python OpenSSL wrapper. -For modern cryptography with ECC, AES, HMAC, Blowfish, ... - -This is an abandoned package maintained inside of the PyBitmessage. -""" - -from .cipher import Cipher -from .ecc import ECC -from .eccblind import ECCBlind -from .eccblindchain import ECCBlindChain -from .hash import hmac_sha256, hmac_sha512, pbkdf2 -from .openssl import OpenSSL +# Copyright (C) 2010 +# Author: Yann GUIBET +# Contact: __version__ = '1.3' __all__ = [ 'OpenSSL', 'ECC', - 'ECCBlind', - 'ECCBlindChain', 'Cipher', 'hmac_sha256', 'hmac_sha512', 'pbkdf2' ] + +from .openssl import OpenSSL +from .ecc import ECC +from .cipher import Cipher +from .hash import hmac_sha256, hmac_sha512, pbkdf2 diff --git a/src/pyelliptic/arithmetic.py b/src/pyelliptic/arithmetic.py index 23c24b5e..1eec381a 100644 --- a/src/pyelliptic/arithmetic.py +++ b/src/pyelliptic/arithmetic.py @@ -1,166 +1,106 @@ -""" -Arithmetic Expressions -""" -import hashlib -import re +import hashlib, re -P = 2**256 - 2**32 - 2**9 - 2**8 - 2**7 - 2**6 - 2**4 - 1 +P = 2**256-2**32-2**9-2**8-2**7-2**6-2**4-1 A = 0 Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240 Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424 -G = (Gx, Gy) - - -def inv(a, n): - """Inversion""" - lm, hm = 1, 0 - low, high = a % n, n - while low > 1: - r = high // low - nm, new = hm - lm * r, high - low * r - lm, low, hm, high = nm, new, lm, low - return lm % n +G = (Gx,Gy) +def inv(a,n): + lm, hm = 1,0 + low, high = a%n,n + while low > 1: + r = high/low + nm, new = hm-lm*r, high-low*r + lm, low, hm, high = nm, new, lm, low + return lm % n def get_code_string(base): - """Returns string according to base value""" - if base == 2: - return b'01' - if base == 10: - return b'0123456789' - if base == 16: - return b'0123456789abcdef' - if base == 58: - return b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' - if base == 256: - try: - return b''.join([chr(x) for x in range(256)]) - except TypeError: - return bytes([x for x in range(256)]) + if base == 2: return '01' + elif base == 10: return '0123456789' + elif base == 16: return "0123456789abcdef" + elif base == 58: return "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + elif base == 256: return ''.join([chr(x) for x in range(256)]) + else: raise ValueError("Invalid base!") - raise ValueError("Invalid base!") +def encode(val,base,minlen=0): + code_string = get_code_string(base) + result = "" + while val > 0: + result = code_string[val % base] + result + val /= base + if len(result) < minlen: + result = code_string[0]*(minlen-len(result))+result + return result +def decode(string,base): + code_string = get_code_string(base) + result = 0 + if base == 16: string = string.lower() + while len(string) > 0: + result *= base + result += code_string.find(string[0]) + string = string[1:] + return result -def encode(val, base, minlen=0): - """Returns the encoded string""" - code_string = get_code_string(base) - result = b'' - while val > 0: - val, i = divmod(val, base) - result = code_string[i:i + 1] + result - if len(result) < minlen: - result = code_string[0:1] * (minlen - len(result)) + result - return result - - -def decode(string, base): - """Returns the decoded string""" - code_string = get_code_string(base) - result = 0 - if base == 16: - string = string.lower() - while string: - result *= base - result += code_string.find(string[0]) - string = string[1:] - return result - - -def changebase(string, frm, to, minlen=0): - """Change base of the string""" - return encode(decode(string, frm), to, minlen) - - -def base10_add(a, b): - """Adding the numbers that are of base10""" - # pylint: disable=too-many-function-args - if a is None: - return b[0], b[1] - if b is None: - return a[0], a[1] - if a[0] == b[0]: - if a[1] == b[1]: - return base10_double(a[0], a[1]) - return None - m = ((b[1] - a[1]) * inv(b[0] - a[0], P)) % P - x = (m * m - a[0] - b[0]) % P - y = (m * (a[0] - x) - a[1]) % P - return (x, y) - +def changebase(string,frm,to,minlen=0): + return encode(decode(string,frm),to,minlen) +def base10_add(a,b): + if a == None: return b[0],b[1] + if b == None: return a[0],a[1] + if a[0] == b[0]: + if a[1] == b[1]: return base10_double(a[0],a[1]) + else: return None + m = ((b[1]-a[1]) * inv(b[0]-a[0],P)) % P + x = (m*m-a[0]-b[0]) % P + y = (m*(a[0]-x)-a[1]) % P + return (x,y) + def base10_double(a): - """Double the numbers that are of base10""" - if a is None: - return None - m = ((3 * a[0] * a[0] + A) * inv(2 * a[1], P)) % P - x = (m * m - 2 * a[0]) % P - y = (m * (a[0] - x) - a[1]) % P - return (x, y) + if a == None: return None + m = ((3*a[0]*a[0]+A)*inv(2*a[1],P)) % P + x = (m*m-2*a[0]) % P + y = (m*(a[0]-x)-a[1]) % P + return (x,y) +def base10_multiply(a,n): + if n == 0: return G + if n == 1: return a + if (n%2) == 0: return base10_double(base10_multiply(a,n/2)) + if (n%2) == 1: return base10_add(base10_double(base10_multiply(a,n/2)),a) -def base10_multiply(a, n): - """Multiply the numbers that are of base10""" - if n == 0: - return G - if n == 1: - return a - n, m = divmod(n, 2) - if m == 0: - return base10_double(base10_multiply(a, n)) - if m == 1: - return base10_add(base10_double(base10_multiply(a, n)), a) - return None +def hex_to_point(h): return (decode(h[2:66],16),decode(h[66:],16)) +def point_to_hex(p): return '04'+encode(p[0],16,64)+encode(p[1],16,64) -def hex_to_point(h): - """Converting hexadecimal to point value""" - return (decode(h[2:66], 16), decode(h[66:], 16)) - - -def point_to_hex(p): - """Converting point value to hexadecimal""" - return b'04' + encode(p[0], 16, 64) + encode(p[1], 16, 64) - - -def multiply(privkey, pubkey): - """Multiplying keys""" - return point_to_hex(base10_multiply( - hex_to_point(pubkey), decode(privkey, 16))) - +def multiply(privkey,pubkey): + return point_to_hex(base10_multiply(hex_to_point(pubkey),decode(privkey,16))) def privtopub(privkey): - """Converting key from private to public""" - return point_to_hex(base10_multiply(G, decode(privkey, 16))) - - -def add(p1, p2): - """Adding two public keys""" - if len(p1) == 32: - return encode(decode(p1, 16) + decode(p2, 16) % P, 16, 32) - return point_to_hex(base10_add(hex_to_point(p1), hex_to_point(p2))) + return point_to_hex(base10_multiply(G,decode(privkey,16))) +def add(p1,p2): + if (len(p1)==32): + return encode(decode(p1,16) + decode(p2,16) % P,16,32) + else: + return point_to_hex(base10_add(hex_to_point(p1),hex_to_point(p2))) def hash_160(string): - """Hashed version of public key""" - intermed = hashlib.sha256(string).digest() - ripemd160 = hashlib.new('ripemd160') - ripemd160.update(intermed) - return ripemd160.digest() - + intermed = hashlib.sha256(string).digest() + ripemd160 = hashlib.new('ripemd160') + ripemd160.update(intermed) + return ripemd160.digest() def dbl_sha256(string): - """Double hashing (SHA256)""" - return hashlib.sha256(hashlib.sha256(string).digest()).digest() - - + return hashlib.sha256(hashlib.sha256(string).digest()).digest() + def bin_to_b58check(inp): - """Convert binary to base58""" - inp_fmtd = '\x00' + inp - leadingzbytes = len(re.match('^\x00*', inp_fmtd).group(0)) - checksum = dbl_sha256(inp_fmtd)[:4] - return '1' * leadingzbytes + changebase(inp_fmtd + checksum, 256, 58) - + inp_fmtd = '\x00' + inp + leadingzbytes = len(re.match('^\x00*',inp_fmtd).group(0)) + checksum = dbl_sha256(inp_fmtd)[:4] + return '1' * leadingzbytes + changebase(inp_fmtd+checksum,256,58) +#Convert a public key (in hex) to a Bitcoin address def pubkey_to_address(pubkey): - """Convert a public key (in hex) to a Bitcoin address""" - return bin_to_b58check(hash_160(changebase(pubkey, 16, 256))) + return bin_to_b58check(hash_160(changebase(pubkey,16,256))) diff --git a/src/pyelliptic/cipher.py b/src/pyelliptic/cipher.py index af6c08ca..b597cafa 100644 --- a/src/pyelliptic/cipher.py +++ b/src/pyelliptic/cipher.py @@ -1,16 +1,15 @@ -""" -Symmetric Encryption -""" +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # Copyright (C) 2011 Yann GUIBET # See LICENSE for details. -from .openssl import OpenSSL +from pyelliptic.openssl import OpenSSL -# pylint: disable=redefined-builtin -class Cipher(object): +class Cipher: """ - Main class for encryption + Symmetric encryption import pyelliptic iv = pyelliptic.Cipher.gen_IV('aes-256-cfb') @@ -45,34 +44,30 @@ class Cipher(object): @staticmethod def get_blocksize(ciphername): - """This Method returns cipher blocksize""" cipher = OpenSSL.get_cipher(ciphername) return cipher.get_blocksize() @staticmethod def gen_IV(ciphername): - """Generate random initialization vector""" cipher = OpenSSL.get_cipher(ciphername) return OpenSSL.rand(cipher.get_blocksize()) def update(self, input): - """Update result with more data""" i = OpenSSL.c_int(0) buffer = OpenSSL.malloc(b"", len(input) + self.cipher.get_blocksize()) inp = OpenSSL.malloc(input, len(input)) if OpenSSL.EVP_CipherUpdate(self.ctx, OpenSSL.byref(buffer), OpenSSL.byref(i), inp, len(input)) == 0: raise Exception("[OpenSSL] EVP_CipherUpdate FAIL ...") - return buffer.raw[0:i.value] # pylint: disable=invalid-slice-index + return buffer.raw[0:i.value] def final(self): - """Returning the final value""" i = OpenSSL.c_int(0) buffer = OpenSSL.malloc(b"", self.cipher.get_blocksize()) if (OpenSSL.EVP_CipherFinal_ex(self.ctx, OpenSSL.byref(buffer), OpenSSL.byref(i))) == 0: raise Exception("[OpenSSL] EVP_CipherFinal_ex FAIL ...") - return buffer.raw[0:i.value] # pylint: disable=invalid-slice-index + return buffer.raw[0:i.value] def ciphering(self, input): """ @@ -82,7 +77,6 @@ class Cipher(object): return buff + self.final() def __del__(self): - # pylint: disable=protected-access if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: OpenSSL.EVP_CIPHER_CTX_reset(self.ctx) else: diff --git a/src/pyelliptic/ecc.py b/src/pyelliptic/ecc.py index c670d023..bea645db 100644 --- a/src/pyelliptic/ecc.py +++ b/src/pyelliptic/ecc.py @@ -1,65 +1,52 @@ -""" -Asymmetric cryptography using elliptic curves -""" -# pylint: disable=protected-access, too-many-branches, too-many-locals +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # Copyright (C) 2011 Yann GUIBET # See LICENSE for details. from hashlib import sha512 +from pyelliptic.openssl import OpenSSL +from pyelliptic.cipher import Cipher +from pyelliptic.hash import hmac_sha256, equals from struct import pack, unpack -from .cipher import Cipher -from .hash import equals, hmac_sha256 -from .openssl import OpenSSL - -class ECC(object): +class ECC: """ Asymmetric encryption with Elliptic Curve Cryptography (ECC) ECDH, ECDSA and ECIES - >>> from binascii import hexlify - >>> import pyelliptic + import pyelliptic - >>> alice = pyelliptic.ECC() # default curve: sect283r1 - >>> bob = pyelliptic.ECC(curve='sect571r1') + alice = pyelliptic.ECC() # default curve: sect283r1 + bob = pyelliptic.ECC(curve='sect571r1') - >>> ciphertext = alice.encrypt("Hello Bob", bob.get_pubkey()) - >>> print(bob.decrypt(ciphertext)) + ciphertext = alice.encrypt("Hello Bob", bob.get_pubkey()) + print bob.decrypt(ciphertext) - >>> signature = bob.sign("Hello Alice") - >>> # alice's job : - >>> print(pyelliptic.ECC( - >>> pubkey=bob.get_pubkey()).verify(signature, "Hello Alice")) + signature = bob.sign("Hello Alice") + # alice's job : + print pyelliptic.ECC( + pubkey=bob.get_pubkey()).verify(signature, "Hello Alice") - >>> # ERROR !!! - >>> try: - >>> key = alice.get_ecdh_key(bob.get_pubkey()) - >>> except: - >>> print( - "For ECDH key agreement, the keys must be defined" - " on the same curve!") + # ERROR !!! + try: + key = alice.get_ecdh_key(bob.get_pubkey()) + except: print("For ECDH key agreement,\ + the keys must be defined on the same curve !") - >>> alice = pyelliptic.ECC(curve='sect571r1') - >>> print(hexlify(alice.get_ecdh_key(bob.get_pubkey()))) - >>> print(hexlify(bob.get_ecdh_key(alice.get_pubkey()))) + alice = pyelliptic.ECC(curve='sect571r1') + print alice.get_ecdh_key(bob.get_pubkey()).encode('hex') + print bob.get_ecdh_key(alice.get_pubkey()).encode('hex') """ - - def __init__( - self, - pubkey=None, - privkey=None, - pubkey_x=None, - pubkey_y=None, - raw_privkey=None, - curve='sect283r1', - ): # pylint: disable=too-many-arguments + def __init__(self, pubkey=None, privkey=None, pubkey_x=None, + pubkey_y=None, raw_privkey=None, curve='sect283r1'): """ - For a normal and high level use, specifie pubkey, + For a normal and High level use, specifie pubkey, privkey (if you need) and the curve """ - if isinstance(curve, str): + if type(curve) == str: self.curve = OpenSSL.get_curve(curve) else: self.curve = curve @@ -67,9 +54,9 @@ class ECC(object): if pubkey_x is not None and pubkey_y is not None: self._set_keys(pubkey_x, pubkey_y, raw_privkey) elif pubkey is not None: - curve, pubkey_x, pubkey_y, _ = ECC._decode_pubkey(pubkey) + curve, pubkey_x, pubkey_y, i = ECC._decode_pubkey(pubkey) if privkey is not None: - curve2, raw_privkey, _ = ECC._decode_privkey(privkey) + curve2, raw_privkey, i = ECC._decode_privkey(privkey) if curve != curve2: raise Exception("Bad ECC keys ...") self.curve = curve @@ -83,23 +70,22 @@ class ECC(object): self.pubkey_y = None self.privkey = None raise Exception("Bad ECC keys ...") - self.pubkey_x = pubkey_x - self.pubkey_y = pubkey_y - self.privkey = privkey + else: + self.pubkey_x = pubkey_x + self.pubkey_y = pubkey_y + self.privkey = privkey @staticmethod def get_curves(): """ - Static method, returns the list of all the curves available + static method, returns the list of all the curves available """ return OpenSSL.curves.keys() def get_curve(self): - """The name of currently used curve""" return OpenSSL.get_curve_by_id(self.curve) def get_curve_id(self): - """Currently used curve""" return self.curve def get_pubkey(self): @@ -107,31 +93,22 @@ class ECC(object): High level function which returns : curve(2) + len_of_pubkeyX(2) + pubkeyX + len_of_pubkeyY + pubkeyY """ - ctx = OpenSSL.BN_CTX_new() - n = OpenSSL.BN_new() - group = OpenSSL.EC_GROUP_new_by_curve_name(self.curve) - OpenSSL.EC_GROUP_get_order(group, n, ctx) - key_len = OpenSSL.BN_num_bytes(n) - pubkey_x = self.pubkey_x.rjust(key_len, b'\x00') - pubkey_y = self.pubkey_y.rjust(key_len, b'\x00') - return b''.join(( - pack('!H', self.curve), - pack('!H', len(pubkey_x)), - pubkey_x, - pack('!H', len(pubkey_y)), - pubkey_y, - )) + return b''.join((pack('!H', self.curve), + pack('!H', len(self.pubkey_x)), + self.pubkey_x, + pack('!H', len(self.pubkey_y)), + self.pubkey_y + )) def get_privkey(self): """ High level function which returns curve(2) + len_of_privkey(2) + privkey """ - return b''.join(( - pack('!H', self.curve), - pack('!H', len(self.privkey)), - self.privkey, - )) + return b''.join((pack('!H', self.curve), + pack('!H', len(self.privkey)), + self.privkey + )) @staticmethod def _decode_pubkey(pubkey): @@ -167,17 +144,19 @@ class ECC(object): key = OpenSSL.EC_KEY_new_by_curve_name(self.curve) if key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") - if OpenSSL.EC_KEY_generate_key(key) == 0: + if (OpenSSL.EC_KEY_generate_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_generate_key FAIL ...") - if OpenSSL.EC_KEY_check_key(key) == 0: + if (OpenSSL.EC_KEY_check_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") priv_key = OpenSSL.EC_KEY_get0_private_key(key) group = OpenSSL.EC_KEY_get0_group(key) pub_key = OpenSSL.EC_KEY_get0_public_key(key) - if OpenSSL.EC_POINT_get_affine_coordinates_GFp( - group, pub_key, pub_key_x, pub_key_y, 0) == 0: + if (OpenSSL.EC_POINT_get_affine_coordinates_GFp(group, pub_key, + pub_key_x, + pub_key_y, 0 + )) == 0: raise Exception( "[OpenSSL] EC_POINT_get_affine_coordinates_GFp FAIL ...") @@ -202,15 +181,14 @@ class ECC(object): def get_ecdh_key(self, pubkey): """ High level function. Compute public key with the local private key - and returns a 512bits shared key. + and returns a 512bits shared key """ - curve, pubkey_x, pubkey_y, _ = ECC._decode_pubkey(pubkey) + curve, pubkey_x, pubkey_y, i = ECC._decode_pubkey(pubkey) if curve != self.curve: raise Exception("ECC keys must be from the same curve !") return sha512(self.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest() def raw_get_ecdh_key(self, pubkey_x, pubkey_y): - """ECDH key as binary data""" try: ecdh_keybuffer = OpenSSL.malloc(0, 32) @@ -218,31 +196,31 @@ class ECC(object): if other_key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") - other_pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), None) - other_pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), None) + other_pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0) + other_pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), 0) other_group = OpenSSL.EC_KEY_get0_group(other_key) other_pub_key = OpenSSL.EC_POINT_new(other_group) - if OpenSSL.EC_POINT_set_affine_coordinates_GFp(other_group, - other_pub_key, - other_pub_key_x, - other_pub_key_y, - 0) == 0: + if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(other_group, + other_pub_key, + other_pub_key_x, + other_pub_key_y, + 0)) == 0: raise Exception( "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...") - if OpenSSL.EC_KEY_set_public_key(other_key, other_pub_key) == 0: + if (OpenSSL.EC_KEY_set_public_key(other_key, other_pub_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...") - if OpenSSL.EC_KEY_check_key(other_key) == 0: + if (OpenSSL.EC_KEY_check_key(other_key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") own_key = OpenSSL.EC_KEY_new_by_curve_name(self.curve) if own_key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") own_priv_key = OpenSSL.BN_bin2bn( - self.privkey, len(self.privkey), None) + self.privkey, len(self.privkey), 0) - if OpenSSL.EC_KEY_set_private_key(own_key, own_priv_key) == 0: + if (OpenSSL.EC_KEY_set_private_key(own_key, own_priv_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_private_key FAIL ...") if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: @@ -268,50 +246,51 @@ class ECC(object): def check_key(self, privkey, pubkey): """ Check the public key and the private key. - The private key is optional (replace by None). + The private key is optional (replace by None) """ - curve, pubkey_x, pubkey_y, _ = ECC._decode_pubkey(pubkey) + curve, pubkey_x, pubkey_y, i = ECC._decode_pubkey(pubkey) if privkey is None: raw_privkey = None curve2 = curve else: - curve2, raw_privkey, _ = ECC._decode_privkey(privkey) + curve2, raw_privkey, i = ECC._decode_privkey(privkey) if curve != curve2: raise Exception("Bad public and private key") return self.raw_check_key(raw_privkey, pubkey_x, pubkey_y, curve) def raw_check_key(self, privkey, pubkey_x, pubkey_y, curve=None): - """Check key validity, key is supplied as binary data""" if curve is None: curve = self.curve - elif isinstance(curve, str): + elif type(curve) == str: curve = OpenSSL.get_curve(curve) + else: + curve = curve try: key = OpenSSL.EC_KEY_new_by_curve_name(curve) if key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") if privkey is not None: - priv_key = OpenSSL.BN_bin2bn(privkey, len(privkey), None) - pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), None) - pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), None) + priv_key = OpenSSL.BN_bin2bn(privkey, len(privkey), 0) + pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0) + pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), 0) if privkey is not None: - if OpenSSL.EC_KEY_set_private_key(key, priv_key) == 0: + if (OpenSSL.EC_KEY_set_private_key(key, priv_key)) == 0: raise Exception( "[OpenSSL] EC_KEY_set_private_key FAIL ...") group = OpenSSL.EC_KEY_get0_group(key) pub_key = OpenSSL.EC_POINT_new(group) - if OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, - pub_key_x, - pub_key_y, - 0) == 0: + if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, + pub_key_x, + pub_key_y, + 0)) == 0: raise Exception( "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...") - if OpenSSL.EC_KEY_set_public_key(key, pub_key) == 0: + if (OpenSSL.EC_KEY_set_public_key(key, pub_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...") - if OpenSSL.EC_KEY_check_key(key) == 0: + if (OpenSSL.EC_KEY_check_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") return 0 @@ -343,27 +322,25 @@ class ECC(object): if key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") - priv_key = OpenSSL.BN_bin2bn(self.privkey, len(self.privkey), None) - pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), - None) - pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), - None) + priv_key = OpenSSL.BN_bin2bn(self.privkey, len(self.privkey), 0) + pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), 0) + pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), 0) - if OpenSSL.EC_KEY_set_private_key(key, priv_key) == 0: + if (OpenSSL.EC_KEY_set_private_key(key, priv_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_private_key FAIL ...") group = OpenSSL.EC_KEY_get0_group(key) pub_key = OpenSSL.EC_POINT_new(group) - if OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, - pub_key_x, - pub_key_y, - 0) == 0: + if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, + pub_key_x, + pub_key_y, + 0)) == 0: raise Exception( "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...") - if OpenSSL.EC_KEY_set_public_key(key, pub_key) == 0: + if (OpenSSL.EC_KEY_set_public_key(key, pub_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...") - if OpenSSL.EC_KEY_check_key(key) == 0: + if (OpenSSL.EC_KEY_check_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: @@ -372,13 +349,12 @@ class ECC(object): OpenSSL.EVP_MD_CTX_init(md_ctx) OpenSSL.EVP_DigestInit_ex(md_ctx, digest_alg(), None) - if OpenSSL.EVP_DigestUpdate(md_ctx, buff, size) == 0: + if (OpenSSL.EVP_DigestUpdate(md_ctx, buff, size)) == 0: raise Exception("[OpenSSL] EVP_DigestUpdate FAIL ...") OpenSSL.EVP_DigestFinal_ex(md_ctx, digest, dgst_len) OpenSSL.ECDSA_sign(0, digest, dgst_len.contents, sig, siglen, key) - if OpenSSL.ECDSA_verify( - 0, digest, dgst_len.contents, sig, siglen.contents, key - ) != 1: + if (OpenSSL.ECDSA_verify(0, digest, dgst_len.contents, sig, + siglen.contents, key)) != 1: raise Exception("[OpenSSL] ECDSA_verify FAIL ...") return sig.raw[:siglen.contents.value] @@ -393,11 +369,12 @@ class ECC(object): OpenSSL.EVP_MD_CTX_free(md_ctx) else: OpenSSL.EVP_MD_CTX_destroy(md_ctx) + pass def verify(self, sig, inputb, digest_alg=OpenSSL.digest_ecdsa_sha1): """ Verify the signature with the input and the local public key. - Returns a boolean. + Returns a boolean """ try: bsig = OpenSSL.malloc(sig, len(sig)) @@ -413,29 +390,27 @@ class ECC(object): if key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") - pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), - None) - pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), - None) + pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), 0) + pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), 0) group = OpenSSL.EC_KEY_get0_group(key) pub_key = OpenSSL.EC_POINT_new(group) - if OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, - pub_key_x, - pub_key_y, - 0) == 0: + if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, + pub_key_x, + pub_key_y, + 0)) == 0: raise Exception( "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...") - if OpenSSL.EC_KEY_set_public_key(key, pub_key) == 0: + if (OpenSSL.EC_KEY_set_public_key(key, pub_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...") - if OpenSSL.EC_KEY_check_key(key) == 0: + if (OpenSSL.EC_KEY_check_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: OpenSSL.EVP_MD_CTX_new(md_ctx) else: OpenSSL.EVP_MD_CTX_init(md_ctx) OpenSSL.EVP_DigestInit_ex(md_ctx, digest_alg(), None) - if OpenSSL.EVP_DigestUpdate(md_ctx, binputb, len(inputb)) == 0: + if (OpenSSL.EVP_DigestUpdate(md_ctx, binputb, len(inputb))) == 0: raise Exception("[OpenSSL] EVP_DigestUpdate FAIL ...") OpenSSL.EVP_DigestFinal_ex(md_ctx, digest, dgst_len) @@ -443,13 +418,13 @@ class ECC(object): 0, digest, dgst_len.contents, bsig, len(sig), key) if ret == -1: - # Fail to Check - return False - if ret == 0: - # Bad signature ! - return False - # Good - return True + return False # Fail to Check + else: + if ret == 0: + return False # Bad signature ! + else: + return True # Good + return False finally: OpenSSL.EC_KEY_free(key) @@ -466,30 +441,22 @@ class ECC(object): """ Encrypt data with ECIES method using the public key of the recipient. """ - curve, pubkey_x, pubkey_y, _ = ECC._decode_pubkey(pubkey) + curve, pubkey_x, pubkey_y, i = ECC._decode_pubkey(pubkey) return ECC.raw_encrypt(data, pubkey_x, pubkey_y, curve=curve, ephemcurve=ephemcurve, ciphername=ciphername) @staticmethod - def raw_encrypt( - data, - pubkey_x, - pubkey_y, - curve='sect283r1', - ephemcurve=None, - ciphername='aes-256-cbc', - ): # pylint: disable=too-many-arguments - """ECDH encryption, keys supplied in binary data format""" - + def raw_encrypt(data, pubkey_x, pubkey_y, curve='sect283r1', + ephemcurve=None, ciphername='aes-256-cbc'): if ephemcurve is None: ephemcurve = curve ephem = ECC(curve=ephemcurve) key = sha512(ephem.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest() key_e, key_m = key[:32], key[32:] pubkey = ephem.get_pubkey() - _iv = Cipher.gen_IV(ciphername) - ctx = Cipher(key_e, _iv, 1, ciphername) - ciphertext = _iv + pubkey + ctx.ciphering(data) + iv = OpenSSL.rand(OpenSSL.get_cipher(ciphername).get_blocksize()) + ctx = Cipher(key_e, iv, 1, ciphername) + ciphertext = iv + pubkey + ctx.ciphering(data) mac = hmac_sha256(key_m, ciphertext) return ciphertext + mac @@ -498,17 +465,16 @@ class ECC(object): Decrypt data with ECIES method using the local private key """ blocksize = OpenSSL.get_cipher(ciphername).get_blocksize() - _iv = data[:blocksize] + iv = data[:blocksize] i = blocksize - _, pubkey_x, pubkey_y, _i2 = ECC._decode_pubkey(data[i:]) - i += _i2 - ciphertext = data[i:len(data) - 32] + curve, pubkey_x, pubkey_y, i2 = ECC._decode_pubkey(data[i:]) + i += i2 + ciphertext = data[i:len(data)-32] i += len(ciphertext) mac = data[i:] key = sha512(self.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest() key_e, key_m = key[:32], key[32:] if not equals(hmac_sha256(key_m, data[:len(data) - 32]), mac): raise RuntimeError("Fail to verify data") - ctx = Cipher(key_e, _iv, 0, ciphername) - retval = ctx.ciphering(ciphertext) - return retval + ctx = Cipher(key_e, iv, 0, ciphername) + return ctx.ciphering(ciphertext) diff --git a/src/pyelliptic/eccblind.py b/src/pyelliptic/eccblind.py deleted file mode 100644 index df987824..00000000 --- a/src/pyelliptic/eccblind.py +++ /dev/null @@ -1,374 +0,0 @@ -""" -ECC blind signature functionality based on -"An Efficient Blind Signature Scheme -Based on the Elliptic CurveDiscrete Logarithm Problem" by Morteza Nikooghadama - and Ali Zakerolhosseini , -http://www.isecure-journal.com/article_39171_47f9ec605dd3918c2793565ec21fcd7a.pdf -""" - -# variable names are based on the math in the paper, so they don't conform -# to PEP8 - -import time -from hashlib import sha256 -from struct import pack, unpack - -from .openssl import OpenSSL - -# first byte in serialisation can contain data -Y_BIT = 0x01 -COMPRESSED_BIT = 0x02 - -# formats -BIGNUM = '!32s' -EC = '!B32s' -PUBKEY = '!BB33s' - - -class Expiration(object): - """Expiration of pubkey""" - @staticmethod - def deserialize(val): - """Create an object out of int""" - year = ((val & 0xF0) >> 4) + 2020 - month = val & 0x0F - assert month < 12 - return Expiration(year, month) - - def __init__(self, year, month): - assert isinstance(year, int) - assert year > 2019 and year < 2036 - assert isinstance(month, int) - assert month < 12 - self.year = year - self.month = month - self.exp = year + month / 12.0 - - def serialize(self): - """Make int out of object""" - return ((self.year - 2020) << 4) + self.month - - def verify(self): - """Check if the pubkey has expired""" - now = time.gmtime() - return self.exp >= now.tm_year + (now.tm_mon - 1) / 12.0 - - -class Value(object): - """Value of a pubkey""" - @staticmethod - def deserialize(val): - """Make object out of int""" - return Value(val) - - def __init__(self, value=0xFF): - assert isinstance(value, int) - self.value = value - - def serialize(self): - """Make int out of object""" - return self.value & 0xFF - - def verify(self, value): - """Verify against supplied value""" - return value <= self.value - - -class ECCBlind(object): # pylint: disable=too-many-instance-attributes - """ - Class for ECC blind signature functionality - """ - - # init - k = None - R = None - F = None - d = None - Q = None - a = None - b = None - c = None - binv = None - r = None - m = None - m_ = None - s_ = None - signature = None - exp = None - val = None - - def ec_get_random(self): - """ - Random integer within the EC order - """ - randomnum = OpenSSL.BN_new() - OpenSSL.BN_rand(randomnum, OpenSSL.BN_num_bits(self.n), 0, 0) - return randomnum - - def ec_invert(self, a): - """ - ECC inversion - """ - inverse = OpenSSL.BN_mod_inverse(None, a, self.n, self.ctx) - return inverse - - def ec_gen_keypair(self): - """ - Generate an ECC keypair - We're using compressed keys - """ - d = self.ec_get_random() - Q = OpenSSL.EC_POINT_new(self.group) - OpenSSL.EC_POINT_mul(self.group, Q, d, None, None, None) - return (d, Q) - - def ec_Ftor(self, F): - """ - x0 coordinate of F - """ - # F = (x0, y0) - x0 = OpenSSL.BN_new() - y0 = OpenSSL.BN_new() - OpenSSL.EC_POINT_get_affine_coordinates(self.group, F, x0, y0, self.ctx) - OpenSSL.BN_free(y0) - return x0 - - def _ec_point_serialize(self, point): - """Make an EC point into a string""" - try: - x = OpenSSL.BN_new() - y = OpenSSL.BN_new() - OpenSSL.EC_POINT_get_affine_coordinates( - self.group, point, x, y, None) - y_byte = (OpenSSL.BN_is_odd(y) & Y_BIT) | COMPRESSED_BIT - l_ = OpenSSL.BN_num_bytes(self.n) - try: - bx = OpenSSL.malloc(0, l_) - OpenSSL.BN_bn2binpad(x, bx, l_) - out = bx.raw - except AttributeError: - # padding manually - bx = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(x)) - OpenSSL.BN_bn2bin(x, bx) - out = bx.raw.rjust(l_, b'\x00') - return pack(EC, y_byte, out) - - finally: - OpenSSL.BN_clear_free(x) - OpenSSL.BN_clear_free(y) - - def _ec_point_deserialize(self, data): - """Make a string into an EC point""" - y_bit, x_raw = unpack(EC, data) - x = OpenSSL.BN_bin2bn(x_raw, OpenSSL.BN_num_bytes(self.n), None) - y_bit &= Y_BIT - retval = OpenSSL.EC_POINT_new(self.group) - OpenSSL.EC_POINT_set_compressed_coordinates(self.group, - retval, - x, - y_bit, - self.ctx) - return retval - - def _bn_serialize(self, bn): - """Make a string out of BigNum""" - l_ = OpenSSL.BN_num_bytes(self.n) - try: - o = OpenSSL.malloc(0, l_) - OpenSSL.BN_bn2binpad(bn, o, l_) - return o.raw - except AttributeError: - o = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(bn)) - OpenSSL.BN_bn2bin(bn, o) - return o.raw.rjust(l_, b'\x00') - - def _bn_deserialize(self, data): - """Make a BigNum out of string""" - x = OpenSSL.BN_bin2bn(data, OpenSSL.BN_num_bytes(self.n), None) - return x - - def _init_privkey(self, privkey): - """Initialise private key out of string/bytes""" - self.d = self._bn_deserialize(privkey) - - def privkey(self): - """Make a private key into a string""" - return pack(BIGNUM, self.d) - - def _init_pubkey(self, pubkey): - """Initialise pubkey out of string/bytes""" - unpacked = unpack(PUBKEY, pubkey) - self.expiration = Expiration.deserialize(unpacked[0]) - self.value = Value.deserialize(unpacked[1]) - self.Q = self._ec_point_deserialize(unpacked[2]) - - def pubkey(self): - """Make a pubkey into a string""" - return pack(PUBKEY, self.expiration.serialize(), - self.value.serialize(), - self._ec_point_serialize(self.Q)) - - def __init__(self, curve="secp256k1", pubkey=None, privkey=None, # pylint: disable=too-many-arguments - year=2025, month=11, value=0xFF): - self.ctx = OpenSSL.BN_CTX_new() - - # ECC group - self.group = OpenSSL.EC_GROUP_new_by_curve_name( - OpenSSL.get_curve(curve)) - - # Order n - self.n = OpenSSL.BN_new() - OpenSSL.EC_GROUP_get_order(self.group, self.n, self.ctx) - - # Generator G - self.G = OpenSSL.EC_GROUP_get0_generator(self.group) - - # Identity O (infinity) - self.iO = OpenSSL.EC_POINT_new(self.group) - OpenSSL.EC_POINT_set_to_infinity(self.group, self.iO) - - if privkey: - assert pubkey - # load both pubkey and privkey from bytes - self._init_privkey(privkey) - self._init_pubkey(pubkey) - elif pubkey: - # load pubkey from bytes - self._init_pubkey(pubkey) - else: - # new keypair - self.d, self.Q = self.ec_gen_keypair() - if not year or not month: - now = time.gmtime() - if now.tm_mon == 12: - self.expiration = Expiration(now.tm_year + 1, 1) - else: - self.expiration = Expiration(now.tm_year, now.tm_mon + 1) - else: - self.expiration = Expiration(year, month) - self.value = Value(value) - - def __del__(self): - OpenSSL.BN_free(self.n) - OpenSSL.BN_CTX_free(self.ctx) - - def signer_init(self): - """ - Init signer - """ - # Signer: Random integer k - self.k = self.ec_get_random() - - # R = kG - self.R = OpenSSL.EC_POINT_new(self.group) - OpenSSL.EC_POINT_mul(self.group, self.R, self.k, None, None, None) - - return self._ec_point_serialize(self.R) - - def create_signing_request(self, R, msg): - """ - Requester creates a new signing request - """ - self.R = self._ec_point_deserialize(R) - msghash = sha256(msg).digest() - - # Requester: 3 random blinding factors - self.F = OpenSSL.EC_POINT_new(self.group) - OpenSSL.EC_POINT_set_to_infinity(self.group, self.F) - temp = OpenSSL.EC_POINT_new(self.group) - abinv = OpenSSL.BN_new() - - # F != O - while OpenSSL.EC_POINT_cmp(self.group, self.F, self.iO, self.ctx) == 0: - self.a = self.ec_get_random() - self.b = self.ec_get_random() - self.c = self.ec_get_random() - - # F = b^-1 * R... - self.binv = self.ec_invert(self.b) - OpenSSL.EC_POINT_mul(self.group, temp, None, self.R, self.binv, - None) - OpenSSL.EC_POINT_copy(self.F, temp) - - # ... + a*b^-1 * Q... - OpenSSL.BN_mul(abinv, self.a, self.binv, self.ctx) - OpenSSL.EC_POINT_mul(self.group, temp, None, self.Q, abinv, None) - OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, None) - - # ... + c*G - OpenSSL.EC_POINT_mul(self.group, temp, None, self.G, self.c, None) - OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, None) - - # F = (x0, y0) - self.r = self.ec_Ftor(self.F) - - # Requester: Blinding (m' = br(m) + a) - self.m = OpenSSL.BN_new() - OpenSSL.BN_bin2bn(msghash, len(msghash), self.m) - - self.m_ = OpenSSL.BN_new() - OpenSSL.BN_mod_mul(self.m_, self.b, self.r, self.n, self.ctx) - OpenSSL.BN_mod_mul(self.m_, self.m_, self.m, self.n, self.ctx) - OpenSSL.BN_mod_add(self.m_, self.m_, self.a, self.n, self.ctx) - return self._bn_serialize(self.m_) - - def blind_sign(self, m_): - """ - Signer blind-signs the request - """ - self.m_ = self._bn_deserialize(m_) - self.s_ = OpenSSL.BN_new() - OpenSSL.BN_mod_mul(self.s_, self.d, self.m_, self.n, self.ctx) - OpenSSL.BN_mod_add(self.s_, self.s_, self.k, self.n, self.ctx) - OpenSSL.BN_free(self.k) - return self._bn_serialize(self.s_) - - def unblind(self, s_): - """ - Requester unblinds the signature - """ - self.s_ = self._bn_deserialize(s_) - s = OpenSSL.BN_new() - OpenSSL.BN_mod_mul(s, self.binv, self.s_, self.n, self.ctx) - OpenSSL.BN_mod_add(s, s, self.c, self.n, self.ctx) - OpenSSL.BN_free(self.a) - OpenSSL.BN_free(self.b) - OpenSSL.BN_free(self.c) - self.signature = (s, self.F) - return self._bn_serialize(s) + self._ec_point_serialize(self.F) - - def verify(self, msg, signature, value=1): - """ - Verify signature with certifier's pubkey - """ - - # convert msg to BIGNUM - self.m = OpenSSL.BN_new() - msghash = sha256(msg).digest() - OpenSSL.BN_bin2bn(msghash, len(msghash), self.m) - - # init - s, self.F = (self._bn_deserialize(signature[0:32]), - self._ec_point_deserialize(signature[32:])) - if self.r is None: - self.r = self.ec_Ftor(self.F) - - lhs = OpenSSL.EC_POINT_new(self.group) - rhs = OpenSSL.EC_POINT_new(self.group) - - OpenSSL.EC_POINT_mul(self.group, lhs, s, None, None, None) - - OpenSSL.EC_POINT_mul(self.group, rhs, None, self.Q, self.m, None) - OpenSSL.EC_POINT_mul(self.group, rhs, None, rhs, self.r, None) - OpenSSL.EC_POINT_add(self.group, rhs, rhs, self.F, self.ctx) - - retval = OpenSSL.EC_POINT_cmp(self.group, lhs, rhs, self.ctx) - if retval == -1: - raise RuntimeError("EC_POINT_cmp returned an error") - elif not self.value.verify(value): - return False - elif not self.expiration.verify(): - return False - elif retval != 0: - return False - return True diff --git a/src/pyelliptic/eccblindchain.py b/src/pyelliptic/eccblindchain.py deleted file mode 100644 index 56e8ce2a..00000000 --- a/src/pyelliptic/eccblindchain.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Blind signature chain with a top level CA -""" - -from .eccblind import ECCBlind - - -class ECCBlindChain(object): # pylint: disable=too-few-public-methods - """ - # Class for ECC Blind Chain signature functionality - """ - - def __init__(self, ca=None, chain=None): - self.chain = [] - self.ca = [] - if ca: - for i in range(0, len(ca), 35): - self.ca.append(ca[i:i + 35]) - if chain: - self.chain.append(chain[0:35]) - for i in range(35, len(chain), 100): - if len(chain[i:]) == 65: - self.chain.append(chain[i:i + 65]) - else: - self.chain.append(chain[i:i + 100]) - - def verify(self, msg, value): - """Verify a chain provides supplied message and value""" - parent = None - l_ = 0 - for level in self.chain: - l_ += 1 - pubkey = None - signature = None - if len(level) == 100: - pubkey, signature = (level[0:35], level[35:]) - elif len(level) == 35: - if level not in self.ca: - return False - parent = level - continue - else: - signature = level - verifier_obj = ECCBlind(pubkey=parent) - if pubkey: - if not verifier_obj.verify(pubkey, signature, value): - return False - parent = pubkey - else: - return verifier_obj.verify(msg=msg, signature=signature, - value=value) - return None diff --git a/src/pyelliptic/hash.py b/src/pyelliptic/hash.py index 70c9a6ce..fb910dd4 100644 --- a/src/pyelliptic/hash.py +++ b/src/pyelliptic/hash.py @@ -1,10 +1,10 @@ -""" -Wrappers for hash functions from OpenSSL. -""" +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # Copyright (C) 2011 Yann GUIBET # See LICENSE for details. -from .openssl import OpenSSL +from pyelliptic.openssl import OpenSSL # For python3 @@ -27,10 +27,10 @@ def _equals_str(a, b): def equals(a, b): - """Compare two strings or bytearrays""" if isinstance(a, str): return _equals_str(a, b) - return _equals_bytes(a, b) + else: + return _equals_bytes(a, b) def hmac_sha256(k, m): @@ -58,7 +58,6 @@ def hmac_sha512(k, m): def pbkdf2(password, salt=None, i=10000, keylen=64): - """Key derivation function using SHA256""" if salt is None: salt = OpenSSL.rand(8) p_password = OpenSSL.malloc(password, len(password)) diff --git a/src/pyelliptic/openssl.py b/src/pyelliptic/openssl.py index deb81644..7af4fd18 100644 --- a/src/pyelliptic/openssl.py +++ b/src/pyelliptic/openssl.py @@ -1,52 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + # Copyright (C) 2011 Yann GUIBET # See LICENSE for details. # # Software slightly changed by Jonathan Warren -""" -This module loads openssl libs with ctypes and incapsulates -needed openssl functionality in class _OpenSSL. -""" -import ctypes -import sys -# pylint: disable=protected-access +import sys +import ctypes OpenSSL = None -class CipherName(object): - """Class returns cipher name, pointer and blocksize""" - +class CipherName: def __init__(self, name, pointer, blocksize): self._name = name self._pointer = pointer self._blocksize = blocksize def __str__(self): - return "Cipher : " + self._name + \ - " | Blocksize : " + str(self._blocksize) + \ - " | Function pointer : " + str(self._pointer) + return "Cipher : " + self._name + " | Blocksize : " + str(self._blocksize) + " | Fonction pointer : " + str(self._pointer) def get_pointer(self): - """This method returns cipher pointer""" return self._pointer() def get_name(self): - """This method returns cipher name""" return self._name def get_blocksize(self): - """This method returns cipher blocksize""" return self._blocksize def get_version(library): - """This function return version, hexversion and cflages""" version = None hexversion = None cflags = None try: - # OpenSSL 1.1 + #OpenSSL 1.1 OPENSSL_VERSION = 0 OPENSSL_CFLAGS = 1 library.OpenSSL_version.argtypes = [ctypes.c_int] @@ -57,7 +47,7 @@ def get_version(library): hexversion = library.OpenSSL_version_num() except AttributeError: try: - # OpenSSL 1.0 + #OpenSSL 1.0 SSLEAY_VERSION = 0 SSLEAY_CFLAGS = 2 library.SSLeay.restype = ctypes.c_long @@ -67,46 +57,22 @@ def get_version(library): cflags = library.SSLeay_version(SSLEAY_CFLAGS) hexversion = library.SSLeay() except AttributeError: - # raise NotImplementedError('Cannot determine version of this OpenSSL library.') + #raise NotImplementedError('Cannot determine version of this OpenSSL library.') pass return (version, hexversion, cflags) -class BIGNUM(ctypes.Structure): # pylint: disable=too-few-public-methods - """OpenSSL's BIGNUM struct""" - _fields_ = [ - ('d', ctypes.POINTER(ctypes.c_ulong)), - ('top', ctypes.c_int), - ('dmax', ctypes.c_int), - ('neg', ctypes.c_int), - ('flags', ctypes.c_int), - ] - - -class EC_POINT(ctypes.Structure): # pylint: disable=too-few-public-methods - """OpenSSL's EC_POINT struct""" - _fields_ = [ - ('meth', ctypes.c_void_p), - ('curve_name', ctypes.c_int), - ('X', ctypes.POINTER(BIGNUM)), - ('Y', ctypes.POINTER(BIGNUM)), - ('Z', ctypes.POINTER(BIGNUM)), - ('Z_is_one', ctypes.c_int), - ] - - -class _OpenSSL(object): +class _OpenSSL: """ Wrapper for OpenSSL using ctypes """ - # pylint: disable=too-many-statements, too-many-instance-attributes def __init__(self, library): """ Build the wrapper """ self._lib = ctypes.CDLL(library) self._version, self._hexversion, self._cflags = get_version(self._lib) - self._libreSSL = self._version.startswith(b"LibreSSL") + self._libreSSL = self._version.startswith("LibreSSL") self.pointer = ctypes.pointer self.c_int = ctypes.c_int @@ -114,38 +80,25 @@ class _OpenSSL(object): self.create_string_buffer = ctypes.create_string_buffer self.BN_new = self._lib.BN_new - self.BN_new.restype = ctypes.POINTER(BIGNUM) + self.BN_new.restype = ctypes.c_void_p self.BN_new.argtypes = [] self.BN_free = self._lib.BN_free self.BN_free.restype = None - self.BN_free.argtypes = [ctypes.POINTER(BIGNUM)] - - self.BN_clear_free = self._lib.BN_clear_free - self.BN_clear_free.restype = None - self.BN_clear_free.argtypes = [ctypes.POINTER(BIGNUM)] + self.BN_free.argtypes = [ctypes.c_void_p] self.BN_num_bits = self._lib.BN_num_bits self.BN_num_bits.restype = ctypes.c_int - self.BN_num_bits.argtypes = [ctypes.POINTER(BIGNUM)] + self.BN_num_bits.argtypes = [ctypes.c_void_p] self.BN_bn2bin = self._lib.BN_bn2bin self.BN_bn2bin.restype = ctypes.c_int - self.BN_bn2bin.argtypes = [ctypes.POINTER(BIGNUM), ctypes.c_void_p] - - try: - self.BN_bn2binpad = self._lib.BN_bn2binpad - self.BN_bn2binpad.restype = ctypes.c_int - self.BN_bn2binpad.argtypes = [ctypes.POINTER(BIGNUM), ctypes.c_void_p, - ctypes.c_int] - except AttributeError: - # optional, we have a workaround - pass + self.BN_bn2bin.argtypes = [ctypes.c_void_p, ctypes.c_void_p] self.BN_bin2bn = self._lib.BN_bin2bn - self.BN_bin2bn.restype = ctypes.POINTER(BIGNUM) + self.BN_bin2bn.restype = ctypes.c_void_p self.BN_bin2bn.argtypes = [ctypes.c_void_p, ctypes.c_int, - ctypes.POINTER(BIGNUM)] + ctypes.c_void_p] self.EC_KEY_free = self._lib.EC_KEY_free self.EC_KEY_free.restype = None @@ -164,127 +117,68 @@ class _OpenSSL(object): self.EC_KEY_check_key.argtypes = [ctypes.c_void_p] self.EC_KEY_get0_private_key = self._lib.EC_KEY_get0_private_key - self.EC_KEY_get0_private_key.restype = ctypes.POINTER(BIGNUM) + self.EC_KEY_get0_private_key.restype = ctypes.c_void_p self.EC_KEY_get0_private_key.argtypes = [ctypes.c_void_p] self.EC_KEY_get0_public_key = self._lib.EC_KEY_get0_public_key - self.EC_KEY_get0_public_key.restype = ctypes.POINTER(EC_POINT) + self.EC_KEY_get0_public_key.restype = ctypes.c_void_p self.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p] self.EC_KEY_get0_group = self._lib.EC_KEY_get0_group self.EC_KEY_get0_group.restype = ctypes.c_void_p self.EC_KEY_get0_group.argtypes = [ctypes.c_void_p] - self.EC_POINT_get_affine_coordinates_GFp = \ - self._lib.EC_POINT_get_affine_coordinates_GFp + self.EC_POINT_get_affine_coordinates_GFp = self._lib.EC_POINT_get_affine_coordinates_GFp self.EC_POINT_get_affine_coordinates_GFp.restype = ctypes.c_int - self.EC_POINT_get_affine_coordinates_GFp.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] - - try: - self.EC_POINT_get_affine_coordinates = \ - self._lib.EC_POINT_get_affine_coordinates - except AttributeError: - # OpenSSL docs say only use this for backwards compatibility - self.EC_POINT_get_affine_coordinates = \ - self._lib.EC_POINT_get_affine_coordinates_GF2m - self.EC_POINT_get_affine_coordinates.restype = ctypes.c_int - self.EC_POINT_get_affine_coordinates.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] + self.EC_POINT_get_affine_coordinates_GFp.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] self.EC_KEY_set_private_key = self._lib.EC_KEY_set_private_key self.EC_KEY_set_private_key.restype = ctypes.c_int self.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, - ctypes.POINTER(BIGNUM)] + ctypes.c_void_p] self.EC_KEY_set_public_key = self._lib.EC_KEY_set_public_key self.EC_KEY_set_public_key.restype = ctypes.c_int self.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT)] + ctypes.c_void_p] self.EC_KEY_set_group = self._lib.EC_KEY_set_group self.EC_KEY_set_group.restype = ctypes.c_int - self.EC_KEY_set_group.argtypes = [ctypes.c_void_p, - ctypes.c_void_p] + self.EC_KEY_set_group.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - self.EC_POINT_set_affine_coordinates_GFp = \ - self._lib.EC_POINT_set_affine_coordinates_GFp + self.EC_POINT_set_affine_coordinates_GFp = self._lib.EC_POINT_set_affine_coordinates_GFp self.EC_POINT_set_affine_coordinates_GFp.restype = ctypes.c_int - self.EC_POINT_set_affine_coordinates_GFp.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] - - try: - self.EC_POINT_set_affine_coordinates = \ - self._lib.EC_POINT_set_affine_coordinates - except AttributeError: - # OpenSSL docs say only use this for backwards compatibility - self.EC_POINT_set_affine_coordinates = \ - self._lib.EC_POINT_set_affine_coordinates_GF2m - self.EC_POINT_set_affine_coordinates.restype = ctypes.c_int - self.EC_POINT_set_affine_coordinates.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] - - try: - self.EC_POINT_set_compressed_coordinates = \ - self._lib.EC_POINT_set_compressed_coordinates - except AttributeError: - # OpenSSL docs say only use this for backwards compatibility - self.EC_POINT_set_compressed_coordinates = \ - self._lib.EC_POINT_set_compressed_coordinates_GFp - self.EC_POINT_set_compressed_coordinates.restype = ctypes.c_int - self.EC_POINT_set_compressed_coordinates.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.c_int, - ctypes.c_void_p] + self.EC_POINT_set_affine_coordinates_GFp.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] self.EC_POINT_new = self._lib.EC_POINT_new - self.EC_POINT_new.restype = ctypes.POINTER(EC_POINT) + self.EC_POINT_new.restype = ctypes.c_void_p self.EC_POINT_new.argtypes = [ctypes.c_void_p] self.EC_POINT_free = self._lib.EC_POINT_free self.EC_POINT_free.restype = None - self.EC_POINT_free.argtypes = [ctypes.POINTER(EC_POINT)] + self.EC_POINT_free.argtypes = [ctypes.c_void_p] self.BN_CTX_free = self._lib.BN_CTX_free self.BN_CTX_free.restype = None self.BN_CTX_free.argtypes = [ctypes.c_void_p] self.EC_POINT_mul = self._lib.EC_POINT_mul - self.EC_POINT_mul.restype = ctypes.c_int - self.EC_POINT_mul.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] + self.EC_POINT_mul.restype = None + self.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] self.EC_KEY_set_private_key = self._lib.EC_KEY_set_private_key self.EC_KEY_set_private_key.restype = ctypes.c_int self.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, - ctypes.POINTER(BIGNUM)] + ctypes.c_void_p] if self._hexversion >= 0x10100000 and not self._libreSSL: self.EC_KEY_OpenSSL = self._lib.EC_KEY_OpenSSL self._lib.EC_KEY_OpenSSL.restype = ctypes.c_void_p self._lib.EC_KEY_OpenSSL.argtypes = [] - + self.EC_KEY_set_method = self._lib.EC_KEY_set_method self._lib.EC_KEY_set_method.restype = ctypes.c_int - self._lib.EC_KEY_set_method.argtypes = [ctypes.c_void_p, - ctypes.c_void_p] + self._lib.EC_KEY_set_method.argtypes = [ctypes.c_void_p, ctypes.c_void_p] else: self.ECDH_OpenSSL = self._lib.ECDH_OpenSSL self._lib.ECDH_OpenSSL.restype = ctypes.c_void_p @@ -292,21 +186,21 @@ class _OpenSSL(object): self.ECDH_set_method = self._lib.ECDH_set_method self._lib.ECDH_set_method.restype = ctypes.c_int - self._lib.ECDH_set_method.argtypes = [ctypes.c_void_p, - ctypes.c_void_p] + self._lib.ECDH_set_method.argtypes = [ctypes.c_void_p, ctypes.c_void_p] + + self.BN_CTX_new = self._lib.BN_CTX_new + self._lib.BN_CTX_new.restype = ctypes.c_void_p + self._lib.BN_CTX_new.argtypes = [] self.ECDH_compute_key = self._lib.ECDH_compute_key self.ECDH_compute_key.restype = ctypes.c_int self.ECDH_compute_key.argtypes = [ctypes.c_void_p, - ctypes.c_int, - ctypes.c_void_p, - ctypes.c_void_p] + ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p] self.EVP_CipherInit_ex = self._lib.EVP_CipherInit_ex self.EVP_CipherInit_ex.restype = ctypes.c_int self.EVP_CipherInit_ex.argtypes = [ctypes.c_void_p, - ctypes.c_void_p, - ctypes.c_void_p] + ctypes.c_void_p, ctypes.c_void_p] self.EVP_CIPHER_CTX_new = self._lib.EVP_CIPHER_CTX_new self.EVP_CIPHER_CTX_new.restype = ctypes.c_void_p @@ -329,13 +223,13 @@ class _OpenSSL(object): self.EVP_aes_256_cbc.restype = ctypes.c_void_p self.EVP_aes_256_cbc.argtypes = [] - # self.EVP_aes_128_ctr = self._lib.EVP_aes_128_ctr - # self.EVP_aes_128_ctr.restype = ctypes.c_void_p - # self.EVP_aes_128_ctr.argtypes = [] + #self.EVP_aes_128_ctr = self._lib.EVP_aes_128_ctr + #self.EVP_aes_128_ctr.restype = ctypes.c_void_p + #self.EVP_aes_128_ctr.argtypes = [] - # self.EVP_aes_256_ctr = self._lib.EVP_aes_256_ctr - # self.EVP_aes_256_ctr.restype = ctypes.c_void_p - # self.EVP_aes_256_ctr.argtypes = [] + #self.EVP_aes_256_ctr = self._lib.EVP_aes_256_ctr + #self.EVP_aes_256_ctr.restype = ctypes.c_void_p + #self.EVP_aes_256_ctr.argtypes = [] self.EVP_aes_128_ofb = self._lib.EVP_aes_128_ofb self.EVP_aes_128_ofb.restype = ctypes.c_void_p @@ -356,7 +250,7 @@ class _OpenSSL(object): self.EVP_rc4 = self._lib.EVP_rc4 self.EVP_rc4.restype = ctypes.c_void_p self.EVP_rc4.argtypes = [] - + if self._hexversion >= 0x10100000 and not self._libreSSL: self.EVP_CIPHER_CTX_reset = self._lib.EVP_CIPHER_CTX_reset self.EVP_CIPHER_CTX_reset.restype = ctypes.c_int @@ -373,8 +267,7 @@ class _OpenSSL(object): self.EVP_CipherUpdate = self._lib.EVP_CipherUpdate self.EVP_CipherUpdate.restype = ctypes.c_int self.EVP_CipherUpdate.argtypes = [ctypes.c_void_p, - ctypes.c_void_p, ctypes.c_void_p, - ctypes.c_void_p, ctypes.c_int] + ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] self.EVP_CipherFinal_ex = self._lib.EVP_CipherFinal_ex self.EVP_CipherFinal_ex.restype = ctypes.c_int @@ -388,11 +281,11 @@ class _OpenSSL(object): self.EVP_DigestInit_ex = self._lib.EVP_DigestInit_ex self.EVP_DigestInit_ex.restype = ctypes.c_int self._lib.EVP_DigestInit_ex.argtypes = 3 * [ctypes.c_void_p] - + self.EVP_DigestUpdate = self._lib.EVP_DigestUpdate self.EVP_DigestUpdate.restype = ctypes.c_int self.EVP_DigestUpdate.argtypes = [ctypes.c_void_p, - ctypes.c_void_p, ctypes.c_size_t] + ctypes.c_void_p, ctypes.c_int] self.EVP_DigestFinal = self._lib.EVP_DigestFinal self.EVP_DigestFinal.restype = ctypes.c_int @@ -403,24 +296,22 @@ class _OpenSSL(object): self.EVP_DigestFinal_ex.restype = ctypes.c_int self.EVP_DigestFinal_ex.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - + self.ECDSA_sign = self._lib.ECDSA_sign self.ECDSA_sign.restype = ctypes.c_int self.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p, - ctypes.c_int, ctypes.c_void_p, - ctypes.c_void_p, ctypes.c_void_p] + ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] self.ECDSA_verify = self._lib.ECDSA_verify self.ECDSA_verify.restype = ctypes.c_int self.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p, - ctypes.c_int, ctypes.c_void_p, - ctypes.c_int, ctypes.c_void_p] + ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p] if self._hexversion >= 0x10100000 and not self._libreSSL: self.EVP_MD_CTX_new = self._lib.EVP_MD_CTX_new self.EVP_MD_CTX_new.restype = ctypes.c_void_p self.EVP_MD_CTX_new.argtypes = [] - + self.EVP_MD_CTX_reset = self._lib.EVP_MD_CTX_reset self.EVP_MD_CTX_reset.restype = None self.EVP_MD_CTX_reset.argtypes = [ctypes.c_void_p] @@ -438,11 +329,11 @@ class _OpenSSL(object): self.EVP_MD_CTX_create = self._lib.EVP_MD_CTX_create self.EVP_MD_CTX_create.restype = ctypes.c_void_p self.EVP_MD_CTX_create.argtypes = [] - + self.EVP_MD_CTX_init = self._lib.EVP_MD_CTX_init self.EVP_MD_CTX_init.restype = None self.EVP_MD_CTX_init.argtypes = [ctypes.c_void_p] - + self.EVP_MD_CTX_destroy = self._lib.EVP_MD_CTX_destroy self.EVP_MD_CTX_destroy.restype = None self.EVP_MD_CTX_destroy.argtypes = [ctypes.c_void_p] @@ -472,174 +363,36 @@ class _OpenSSL(object): self.HMAC = self._lib.HMAC self.HMAC.restype = ctypes.c_void_p self.HMAC.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, - ctypes.c_void_p, ctypes.c_size_t, - ctypes.c_void_p, ctypes.c_void_p] + ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p] try: self.PKCS5_PBKDF2_HMAC = self._lib.PKCS5_PBKDF2_HMAC - except Exception: + except: # The above is not compatible with all versions of OSX. self.PKCS5_PBKDF2_HMAC = self._lib.PKCS5_PBKDF2_HMAC_SHA1 - + self.PKCS5_PBKDF2_HMAC.restype = ctypes.c_int self.PKCS5_PBKDF2_HMAC.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p] - # Blind signature requirements - self.BN_CTX_new = self._lib.BN_CTX_new - self.BN_CTX_new.restype = ctypes.c_void_p - self.BN_CTX_new.argtypes = [] - - self.BN_dup = self._lib.BN_dup - self.BN_dup.restype = ctypes.POINTER(BIGNUM) - self.BN_dup.argtypes = [ctypes.POINTER(BIGNUM)] - - self.BN_rand = self._lib.BN_rand - self.BN_rand.restype = ctypes.c_int - self.BN_rand.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.c_int, - ctypes.c_int, - ctypes.c_int] - - self.BN_set_word = self._lib.BN_set_word - self.BN_set_word.restype = ctypes.c_int - self.BN_set_word.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.c_ulong] - - self.BN_mul = self._lib.BN_mul - self.BN_mul.restype = ctypes.c_int - self.BN_mul.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] - - self.BN_mod_add = self._lib.BN_mod_add - self.BN_mod_add.restype = ctypes.c_int - self.BN_mod_add.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] - - self.BN_mod_inverse = self._lib.BN_mod_inverse - self.BN_mod_inverse.restype = ctypes.POINTER(BIGNUM) - self.BN_mod_inverse.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] - - self.BN_mod_mul = self._lib.BN_mod_mul - self.BN_mod_mul.restype = ctypes.c_int - self.BN_mod_mul.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] - - self.BN_lshift = self._lib.BN_lshift - self.BN_lshift.restype = ctypes.c_int - self.BN_lshift.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.c_int] - - self.BN_sub_word = self._lib.BN_sub_word - self.BN_sub_word.restype = ctypes.c_int - self.BN_sub_word.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.c_ulong] - - self.BN_cmp = self._lib.BN_cmp - self.BN_cmp.restype = ctypes.c_int - self.BN_cmp.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM)] - - try: - self.BN_is_odd = self._lib.BN_is_odd - self.BN_is_odd.restype = ctypes.c_int - self.BN_is_odd.argtypes = [ctypes.POINTER(BIGNUM)] - except AttributeError: - # OpenSSL 1.1.0 implements this as a function, but earlier - # versions as macro, so we need to workaround - self.BN_is_odd = self.BN_is_odd_compatible - - self.BN_bn2dec = self._lib.BN_bn2dec - self.BN_bn2dec.restype = ctypes.c_char_p - self.BN_bn2dec.argtypes = [ctypes.POINTER(BIGNUM)] - - self.EC_GROUP_new_by_curve_name = self._lib.EC_GROUP_new_by_curve_name - self.EC_GROUP_new_by_curve_name.restype = ctypes.c_void_p - self.EC_GROUP_new_by_curve_name.argtypes = [ctypes.c_int] - - self.EC_GROUP_get_order = self._lib.EC_GROUP_get_order - self.EC_GROUP_get_order.restype = ctypes.c_int - self.EC_GROUP_get_order.argtypes = [ctypes.c_void_p, - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] - - self.EC_GROUP_get_cofactor = self._lib.EC_GROUP_get_cofactor - self.EC_GROUP_get_cofactor.restype = ctypes.c_int - self.EC_GROUP_get_cofactor.argtypes = [ctypes.c_void_p, - ctypes.POINTER(BIGNUM), - ctypes.c_void_p] - - self.EC_GROUP_get0_generator = self._lib.EC_GROUP_get0_generator - self.EC_GROUP_get0_generator.restype = ctypes.POINTER(EC_POINT) - self.EC_GROUP_get0_generator.argtypes = [ctypes.c_void_p] - - self.EC_POINT_copy = self._lib.EC_POINT_copy - self.EC_POINT_copy.restype = ctypes.c_int - self.EC_POINT_copy.argtypes = [ctypes.POINTER(EC_POINT), - ctypes.POINTER(EC_POINT)] - - self.EC_POINT_add = self._lib.EC_POINT_add - self.EC_POINT_add.restype = ctypes.c_int - self.EC_POINT_add.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(EC_POINT), - ctypes.POINTER(EC_POINT), - ctypes.c_void_p] - - self.EC_POINT_cmp = self._lib.EC_POINT_cmp - self.EC_POINT_cmp.restype = ctypes.c_int - self.EC_POINT_cmp.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(EC_POINT), - ctypes.c_void_p] - - self.EC_POINT_set_to_infinity = self._lib.EC_POINT_set_to_infinity - self.EC_POINT_set_to_infinity.restype = ctypes.c_int - self.EC_POINT_set_to_infinity.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT)] - self._set_ciphers() self._set_curves() def _set_ciphers(self): self.cipher_algo = { - 'aes-128-cbc': CipherName( - 'aes-128-cbc', self.EVP_aes_128_cbc, 16), - 'aes-256-cbc': CipherName( - 'aes-256-cbc', self.EVP_aes_256_cbc, 16), - 'aes-128-cfb': CipherName( - 'aes-128-cfb', self.EVP_aes_128_cfb128, 16), - 'aes-256-cfb': CipherName( - 'aes-256-cfb', self.EVP_aes_256_cfb128, 16), - 'aes-128-ofb': CipherName( - 'aes-128-ofb', self._lib.EVP_aes_128_ofb, 16), - 'aes-256-ofb': CipherName( - 'aes-256-ofb', self._lib.EVP_aes_256_ofb, 16), - # 'aes-128-ctr': CipherName( - # 'aes-128-ctr', self._lib.EVP_aes_128_ctr, 16), - # 'aes-256-ctr': CipherName( - # 'aes-256-ctr', self._lib.EVP_aes_256_ctr, 16), - 'bf-cfb': CipherName( - 'bf-cfb', self.EVP_bf_cfb64, 8), - 'bf-cbc': CipherName( - 'bf-cbc', self.EVP_bf_cbc, 8), - # 128 is the initialisation size not block size - 'rc4': CipherName( - 'rc4', self.EVP_rc4, 128), + 'aes-128-cbc': CipherName('aes-128-cbc', self.EVP_aes_128_cbc, 16), + 'aes-256-cbc': CipherName('aes-256-cbc', self.EVP_aes_256_cbc, 16), + 'aes-128-cfb': CipherName('aes-128-cfb', self.EVP_aes_128_cfb128, 16), + 'aes-256-cfb': CipherName('aes-256-cfb', self.EVP_aes_256_cfb128, 16), + 'aes-128-ofb': CipherName('aes-128-ofb', self._lib.EVP_aes_128_ofb, 16), + 'aes-256-ofb': CipherName('aes-256-ofb', self._lib.EVP_aes_256_ofb, 16), + #'aes-128-ctr': CipherName('aes-128-ctr', self._lib.EVP_aes_128_ctr, 16), + #'aes-256-ctr': CipherName('aes-256-ctr', self._lib.EVP_aes_256_ctr, 16), + 'bf-cfb': CipherName('bf-cfb', self.EVP_bf_cfb64, 8), + 'bf-cbc': CipherName('bf-cbc', self.EVP_bf_cbc, 8), + 'rc4': CipherName('rc4', self.EVP_rc4, 128), # 128 is the initialisation size not block size } def _set_curves(self): @@ -683,16 +436,6 @@ class _OpenSSL(object): """ return int((self.BN_num_bits(x) + 7) / 8) - def BN_is_odd_compatible(self, x): - """ - returns if BN is odd - we assume big endianness, and that BN is initialised - """ - length = self.BN_num_bytes(x) - data = self.malloc(0, length) - OpenSSL.BN_bn2bin(x, data) - return ord(data[length - 1]) & 1 - def get_cipher(self, name): """ returns the OpenSSL cipher instance @@ -709,13 +452,13 @@ class _OpenSSL(object): raise Exception("Unknown curve") return self.curves[name] - def get_curve_by_id(self, id_): + def get_curve_by_id(self, id): """ returns the name of a elliptic curve with his id """ res = None for i in self.curves: - if self.curves[i] == id_: + if self.curves[i] == id: res = i break if res is None: @@ -726,63 +469,47 @@ class _OpenSSL(object): """ OpenSSL random function """ - buffer_ = self.malloc(0, size) - # This pyelliptic library, by default, didn't check the return value - # of RAND_bytes. It is evidently possible that it returned an error - # and not-actually-random data. However, in tests on various - # operating systems, while generating hundreds of gigabytes of random - # strings of various sizes I could not get an error to occur. - # Also Bitcoin doesn't check the return value of RAND_bytes either. + buffer = self.malloc(0, size) + # This pyelliptic library, by default, didn't check the return value of RAND_bytes. It is + # evidently possible that it returned an error and not-actually-random data. However, in + # tests on various operating systems, while generating hundreds of gigabytes of random + # strings of various sizes I could not get an error to occur. Also Bitcoin doesn't check + # the return value of RAND_bytes either. # Fixed in Bitmessage version 0.4.2 (in source code on 2013-10-13) - while self.RAND_bytes(buffer_, size) != 1: + while self.RAND_bytes(buffer, size) != 1: import time time.sleep(1) - return buffer_.raw + return buffer.raw def malloc(self, data, size): """ returns a create_string_buffer (ctypes) """ - buffer_ = None + buffer = None if data != 0: if sys.version_info.major == 3 and isinstance(data, type('')): data = data.encode() - buffer_ = self.create_string_buffer(data, size) + buffer = self.create_string_buffer(data, size) else: - buffer_ = self.create_string_buffer(size) - return buffer_ - + buffer = self.create_string_buffer(size) + return buffer def loadOpenSSL(): - """This function finds and load the OpenSSL library""" - # pylint: disable=global-statement global OpenSSL from os import path, environ from ctypes.util import find_library - + libdir = [] - if getattr(sys, 'frozen', None): + if getattr(sys,'frozen', None): if 'darwin' in sys.platform: libdir.extend([ - path.join( - environ['RESOURCEPATH'], '..', - 'Frameworks', 'libcrypto.dylib'), - path.join( - environ['RESOURCEPATH'], '..', - 'Frameworks', 'libcrypto.1.1.0.dylib'), - path.join( - environ['RESOURCEPATH'], '..', - 'Frameworks', 'libcrypto.1.0.2.dylib'), - path.join( - environ['RESOURCEPATH'], '..', - 'Frameworks', 'libcrypto.1.0.1.dylib'), - path.join( - environ['RESOURCEPATH'], '..', - 'Frameworks', 'libcrypto.1.0.0.dylib'), - path.join( - environ['RESOURCEPATH'], '..', - 'Frameworks', 'libcrypto.0.9.8.dylib'), - ]) + path.join(environ['RESOURCEPATH'], '..', 'Frameworks','libcrypto.dylib'), + path.join(environ['RESOURCEPATH'], '..', 'Frameworks','libcrypto.1.1.0.dylib'), + path.join(environ['RESOURCEPATH'], '..', 'Frameworks','libcrypto.1.0.2.dylib'), + path.join(environ['RESOURCEPATH'], '..', 'Frameworks','libcrypto.1.0.1.dylib'), + path.join(environ['RESOURCEPATH'], '..', 'Frameworks','libcrypto.1.0.0.dylib'), + path.join(environ['RESOURCEPATH'], '..', 'Frameworks','libcrypto.0.9.8.dylib'), + ]) elif 'win32' in sys.platform or 'win64' in sys.platform: libdir.append(path.join(sys._MEIPASS, 'libeay32.dll')) else: @@ -801,17 +528,13 @@ def loadOpenSSL(): path.join(sys._MEIPASS, 'libssl.so.0.9.8'), ]) if 'darwin' in sys.platform: - libdir.extend([ - 'libcrypto.dylib', '/usr/local/opt/openssl/lib/libcrypto.dylib']) + libdir.extend(['libcrypto.dylib', '/usr/local/opt/openssl/lib/libcrypto.dylib']) elif 'win32' in sys.platform or 'win64' in sys.platform: libdir.append('libeay32.dll') else: libdir.append('libcrypto.so') libdir.append('libssl.so') - libdir.append('libcrypto.so.1.0.0') - libdir.append('libssl.so.1.0.0') - if 'linux' in sys.platform or 'darwin' in sys.platform \ - or 'bsd' in sys.platform: + if 'linux' in sys.platform or 'darwin' in sys.platform or 'bsd' in sys.platform: libdir.append(find_library('ssl')) elif 'win32' in sys.platform or 'win64' in sys.platform: libdir.append(find_library('libeay32')) @@ -819,10 +542,8 @@ def loadOpenSSL(): try: OpenSSL = _OpenSSL(library) return - except Exception: # nosec B110 + except: pass - raise Exception( - "Couldn't find and load the OpenSSL library. You must install it.") - + raise Exception("Couldn't find and load the OpenSSL library. You must install it.") loadOpenSSL() diff --git a/src/pyelliptic/tests/__init__.py b/src/pyelliptic/tests/__init__.py deleted file mode 100644 index b53ef881..00000000 --- a/src/pyelliptic/tests/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys - -if getattr(sys, 'frozen', None): - from test_arithmetic import TestArithmetic - from test_blindsig import TestBlindSig - from test_ecc import TestECC - from test_openssl import TestOpenSSL - - __all__ = ["TestArithmetic", "TestBlindSig", "TestECC", "TestOpenSSL"] diff --git a/src/pyelliptic/tests/samples.py b/src/pyelliptic/tests/samples.py deleted file mode 100644 index d6d71c02..00000000 --- a/src/pyelliptic/tests/samples.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Testing samples""" - -from binascii import unhexlify - -# pubkey K -sample_pubkey = unhexlify( - '0409d4e5c0ab3d25fe' - '048c64c9da1a242c' - '7f19417e9517cd26' - '6950d72c75571358' - '5c6178e97fe092fc' - '897c9a1f1720d577' - '0ae8eaad2fa8fcbd' - '08e9324a5dde1857' -) - -sample_iv = unhexlify( - 'bddb7c2829b08038' - '753084a2f3991681' -) - -# Private key r -sample_ephem_privkey = unhexlify( - '5be6facd941b76e9' - 'd3ead03029fbdb6b' - '6e0809293f7fb197' - 'd0c51f84e96b8ba4' -) -# Public key R -sample_ephem_pubkey = unhexlify( - '040293213dcf1388b6' - '1c2ae5cf80fee6ff' - 'ffc049a2f9fe7365' - 'fe3867813ca81292' - 'df94686c6afb565a' - 'c6149b153d61b3b2' - '87ee2c7f997c1423' - '8796c12b43a3865a' -) - -# First 32 bytes of H called key_e -sample_enkey = unhexlify( - '1705438282678671' - '05263d4828efff82' - 'd9d59cbf08743b69' - '6bcc5d69fa1897b4' -) - -# Last 32 bytes of H called key_m -sample_mackey = unhexlify( - 'f83f1e9cc5d6b844' - '8d39dc6a9d5f5b7f' - '460e4a78e9286ee8' - 'd91ce1660a53eacd' -) - -# No padding of input! -sample_data = b'The quick brown fox jumps over the lazy dog.' - -sample_ciphertext = unhexlify( - '64203d5b24688e25' - '47bba345fa139a5a' - '1d962220d4d48a0c' - 'f3b1572c0d95b616' - '43a6f9a0d75af7ea' - 'cc1bd957147bf723' -) - -sample_mac = unhexlify( - 'f2526d61b4851fb2' - '3409863826fd2061' - '65edc021368c7946' - '571cead69046e619' -) diff --git a/src/pyelliptic/tests/test_arithmetic.py b/src/pyelliptic/tests/test_arithmetic.py deleted file mode 100644 index 7b5c59b1..00000000 --- a/src/pyelliptic/tests/test_arithmetic.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Test the arithmetic functions -""" - -from binascii import unhexlify -import unittest - -try: - from pyelliptic import arithmetic -except ImportError: - from pybitmessage.pyelliptic import arithmetic - - -# These keys are from addresses test script -sample_pubsigningkey = ( - b'044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09d' - b'16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') -sample_pubencryptionkey = ( - b'044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3c' - b'e7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') -sample_privsigningkey = \ - b'93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' -sample_privencryptionkey = \ - b'4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a' - -sample_factor = \ - 66858749573256452658262553961707680376751171096153613379801854825275240965733 -# G * sample_factor -sample_point = ( - 33567437183004486938355437500683826356288335339807546987348409590129959362313, - 94730058721143827257669456336351159718085716196507891067256111928318063085006 -) - - -class TestArithmetic(unittest.TestCase): - """Test arithmetic functions""" - def test_base10_multiply(self): - """Test arithmetic.base10_multiply""" - self.assertEqual( - sample_point, - arithmetic.base10_multiply(arithmetic.G, sample_factor)) - - def test_decode(self): - """Decode sample privsigningkey from hex to int and compare to factor""" - self.assertEqual( - arithmetic.decode(sample_privsigningkey, 16), sample_factor) - - def test_encode(self): - """Encode sample factor into hex and compare to privsigningkey""" - self.assertEqual( - arithmetic.encode(sample_factor, 16), sample_privsigningkey) - - def test_changebase(self): - """Check the results of changebase()""" - self.assertEqual( - arithmetic.changebase(sample_privsigningkey, 16, 256, minlen=32), - unhexlify(sample_privsigningkey)) - self.assertEqual( - arithmetic.changebase(sample_pubsigningkey, 16, 256, minlen=64), - unhexlify(sample_pubsigningkey)) - self.assertEqual( - 32, # padding - len(arithmetic.changebase(sample_privsigningkey[:5], 16, 256, 32))) - - def test_hex_to_point(self): - """Check that sample_pubsigningkey is sample_point encoded in hex""" - self.assertEqual( - arithmetic.hex_to_point(sample_pubsigningkey), sample_point) - - def test_point_to_hex(self): - """Check that sample_point is sample_pubsigningkey decoded from hex""" - self.assertEqual( - arithmetic.point_to_hex(sample_point), sample_pubsigningkey) - - def test_privtopub(self): - """Generate public keys and check the result""" - self.assertEqual( - arithmetic.privtopub(sample_privsigningkey), - sample_pubsigningkey - ) - self.assertEqual( - arithmetic.privtopub(sample_privencryptionkey), - sample_pubencryptionkey - ) diff --git a/src/pyelliptic/tests/test_blindsig.py b/src/pyelliptic/tests/test_blindsig.py deleted file mode 100644 index 8c4b2b9d..00000000 --- a/src/pyelliptic/tests/test_blindsig.py +++ /dev/null @@ -1,277 +0,0 @@ -""" -Test for ECC blind signatures -""" -import os -import unittest -from hashlib import sha256 - -try: - from pyelliptic import ECCBlind, ECCBlindChain, OpenSSL -except ImportError: - from pybitmessage.pyelliptic import ECCBlind, ECCBlindChain, OpenSSL - -# pylint: disable=protected-access - - -class TestBlindSig(unittest.TestCase): - """ - Test case for ECC blind signature - """ - def test_blind_sig(self): - """Test full sequence using a random certifier key and a random message""" - # See page 127 of the paper - # (1) Initialization - signer_obj = ECCBlind() - point_r = signer_obj.signer_init() - self.assertEqual(len(signer_obj.pubkey()), 35) - - # (2) Request - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - # only 64 byte messages are planned to be used in Bitmessage - msg = os.urandom(64) - msg_blinded = requester_obj.create_signing_request(point_r, msg) - self.assertEqual(len(msg_blinded), 32) - - # check - self.assertNotEqual(sha256(msg).digest(), msg_blinded) - - # (3) Signature Generation - signature_blinded = signer_obj.blind_sign(msg_blinded) - assert isinstance(signature_blinded, bytes) - self.assertEqual(len(signature_blinded), 32) - - # (4) Extraction - signature = requester_obj.unblind(signature_blinded) - assert isinstance(signature, bytes) - self.assertEqual(len(signature), 65) - - self.assertNotEqual(signature, signature_blinded) - - # (5) Verification - verifier_obj = ECCBlind(pubkey=signer_obj.pubkey()) - self.assertTrue(verifier_obj.verify(msg, signature)) - - def test_is_odd(self): - """Test our implementation of BN_is_odd""" - for _ in range(1024): - obj = ECCBlind() - x = OpenSSL.BN_new() - y = OpenSSL.BN_new() - OpenSSL.EC_POINT_get_affine_coordinates( - obj.group, obj.Q, x, y, None) - self.assertEqual(OpenSSL.BN_is_odd(y), - OpenSSL.BN_is_odd_compatible(y)) - - def test_serialize_ec_point(self): - """Test EC point serialization/deserialization""" - for _ in range(1024): - try: - obj = ECCBlind() - obj2 = ECCBlind() - randompoint = obj.Q - serialized = obj._ec_point_serialize(randompoint) - secondpoint = obj2._ec_point_deserialize(serialized) - x0 = OpenSSL.BN_new() - y0 = OpenSSL.BN_new() - OpenSSL.EC_POINT_get_affine_coordinates(obj.group, - randompoint, x0, - y0, obj.ctx) - x1 = OpenSSL.BN_new() - y1 = OpenSSL.BN_new() - OpenSSL.EC_POINT_get_affine_coordinates(obj2.group, - secondpoint, x1, - y1, obj2.ctx) - - self.assertEqual(OpenSSL.BN_cmp(y0, y1), 0) - self.assertEqual(OpenSSL.BN_cmp(x0, x1), 0) - self.assertEqual(OpenSSL.EC_POINT_cmp(obj.group, randompoint, - secondpoint, None), 0) - finally: - OpenSSL.BN_free(x0) - OpenSSL.BN_free(x1) - OpenSSL.BN_free(y0) - OpenSSL.BN_free(y1) - del obj - del obj2 - - def test_serialize_bn(self): - """Test Bignum serialization/deserialization""" - for _ in range(1024): - obj = ECCBlind() - obj2 = ECCBlind() - randomnum = obj.d - serialized = obj._bn_serialize(randomnum) - secondnum = obj2._bn_deserialize(serialized) - self.assertEqual(OpenSSL.BN_cmp(randomnum, secondnum), 0) - - def test_blind_sig_many(self): - """Test a lot of blind signatures""" - for _ in range(1024): - self.test_blind_sig() - - def test_blind_sig_value(self): - """Test blind signature value checking""" - signer_obj = ECCBlind(value=5) - point_r = signer_obj.signer_init() - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - msg = os.urandom(64) - msg_blinded = requester_obj.create_signing_request(point_r, msg) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - verifier_obj = ECCBlind(pubkey=signer_obj.pubkey()) - self.assertFalse(verifier_obj.verify(msg, signature, value=8)) - - def test_blind_sig_expiration(self): - """Test blind signature expiration checking""" - signer_obj = ECCBlind(year=2020, month=1) - point_r = signer_obj.signer_init() - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - msg = os.urandom(64) - msg_blinded = requester_obj.create_signing_request(point_r, msg) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - verifier_obj = ECCBlind(pubkey=signer_obj.pubkey()) - self.assertFalse(verifier_obj.verify(msg, signature)) - - def test_blind_sig_chain(self): # pylint: disable=too-many-locals - """Test blind signature chain using a random certifier key and a random message""" - - test_levels = 4 - msg = os.urandom(1024) - - ca = ECCBlind() - signer_obj = ca - - output = bytearray() - - for level in range(test_levels): - if not level: - output.extend(ca.pubkey()) - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - child_obj = ECCBlind() - point_r = signer_obj.signer_init() - pubkey = child_obj.pubkey() - - if level == test_levels - 1: - msg_blinded = requester_obj.create_signing_request(point_r, - msg) - else: - msg_blinded = requester_obj.create_signing_request(point_r, - pubkey) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - if level != test_levels - 1: - output.extend(pubkey) - output.extend(signature) - signer_obj = child_obj - verifychain = ECCBlindChain(ca=ca.pubkey(), chain=bytes(output)) - self.assertTrue(verifychain.verify(msg=msg, value=1)) - - def test_blind_sig_chain_wrong_ca(self): # pylint: disable=too-many-locals - """Test blind signature chain with an unlisted ca""" - - test_levels = 4 - msg = os.urandom(1024) - - ca = ECCBlind() - fake_ca = ECCBlind() - signer_obj = fake_ca - - output = bytearray() - - for level in range(test_levels): - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - child_obj = ECCBlind() - if not level: - # unlisted CA, but a syntactically valid pubkey - output.extend(fake_ca.pubkey()) - point_r = signer_obj.signer_init() - pubkey = child_obj.pubkey() - - if level == test_levels - 1: - msg_blinded = requester_obj.create_signing_request(point_r, - msg) - else: - msg_blinded = requester_obj.create_signing_request(point_r, - pubkey) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - if level != test_levels - 1: - output.extend(pubkey) - output.extend(signature) - signer_obj = child_obj - verifychain = ECCBlindChain(ca=ca.pubkey(), chain=bytes(output)) - self.assertFalse(verifychain.verify(msg, 1)) - - def test_blind_sig_chain_wrong_msg(self): # pylint: disable=too-many-locals - """Test blind signature chain with a fake message""" - - test_levels = 4 - msg = os.urandom(1024) - fake_msg = os.urandom(1024) - - ca = ECCBlind() - signer_obj = ca - - output = bytearray() - - for level in range(test_levels): - if not level: - output.extend(ca.pubkey()) - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - child_obj = ECCBlind() - point_r = signer_obj.signer_init() - pubkey = child_obj.pubkey() - - if level == test_levels - 1: - msg_blinded = requester_obj.create_signing_request(point_r, - msg) - else: - msg_blinded = requester_obj.create_signing_request(point_r, - pubkey) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - if level != test_levels - 1: - output.extend(pubkey) - output.extend(signature) - signer_obj = child_obj - verifychain = ECCBlindChain(ca=ca.pubkey(), chain=bytes(output)) - self.assertFalse(verifychain.verify(fake_msg, 1)) - - def test_blind_sig_chain_wrong_intermediary(self): # pylint: disable=too-many-locals - """Test blind signature chain using a fake intermediary pubkey""" - - test_levels = 4 - msg = os.urandom(1024) - wrong_level = 2 - - ca = ECCBlind() - signer_obj = ca - fake_intermediary = ECCBlind() - - output = bytearray() - - for level in range(test_levels): - if not level: - output.extend(ca.pubkey()) - requester_obj = ECCBlind(pubkey=signer_obj.pubkey()) - child_obj = ECCBlind() - point_r = signer_obj.signer_init() - pubkey = child_obj.pubkey() - - if level == test_levels - 1: - msg_blinded = requester_obj.create_signing_request(point_r, - msg) - else: - msg_blinded = requester_obj.create_signing_request(point_r, - pubkey) - signature_blinded = signer_obj.blind_sign(msg_blinded) - signature = requester_obj.unblind(signature_blinded) - if level == wrong_level: - output.extend(fake_intermediary.pubkey()) - elif level != test_levels - 1: - output.extend(pubkey) - output.extend(signature) - signer_obj = child_obj - verifychain = ECCBlindChain(ca=ca.pubkey(), chain=bytes(output)) - self.assertFalse(verifychain.verify(msg, 1)) diff --git a/src/pyelliptic/tests/test_ecc.py b/src/pyelliptic/tests/test_ecc.py deleted file mode 100644 index e87d1c21..00000000 --- a/src/pyelliptic/tests/test_ecc.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Tests for ECC object""" - -import os -import unittest -from hashlib import sha512 - -try: - import pyelliptic -except ImportError: - from pybitmessage import pyelliptic - -from .samples import ( - sample_pubkey, sample_iv, sample_ephem_privkey, sample_ephem_pubkey, - sample_enkey, sample_mackey, sample_data, sample_ciphertext, sample_mac) - - -sample_pubkey_x = sample_ephem_pubkey[1:-32] -sample_pubkey_y = sample_ephem_pubkey[-32:] -sample_pubkey_bin = ( - b'\x02\xca\x00\x20' + sample_pubkey_x + b'\x00\x20' + sample_pubkey_y) -sample_privkey_bin = b'\x02\xca\x00\x20' + sample_ephem_privkey - - -class TestECC(unittest.TestCase): - """The test case for ECC""" - - def test_random_keys(self): - """A dummy test for random keys in ECC object""" - eccobj = pyelliptic.ECC(curve='secp256k1') - self.assertTrue(len(eccobj.privkey) <= 32) - pubkey = eccobj.get_pubkey() - self.assertEqual(pubkey[:4], b'\x02\xca\x00\x20') - - def test_short_keys(self): - """Check formatting of the keys with leading zeroes""" - # pylint: disable=protected-access - def sample_key(_): - """Fake ECC keypair""" - return os.urandom(32), os.urandom(31), os.urandom(30) - - try: - gen_orig = pyelliptic.ECC._generate - pyelliptic.ECC._generate = sample_key - eccobj = pyelliptic.ECC(curve='secp256k1') - pubkey = eccobj.get_pubkey() - self.assertEqual(pubkey[:4], b'\x02\xca\x00\x20') - self.assertEqual(pubkey[36:38], b'\x00\x20') - self.assertEqual(len(pubkey[38:]), 32) - finally: - pyelliptic.ECC._generate = gen_orig - - def test_decode_keys(self): - """Check keys decoding""" - # pylint: disable=protected-access - curve_secp256k1 = pyelliptic.OpenSSL.get_curve('secp256k1') - curve, raw_privkey, _ = pyelliptic.ECC._decode_privkey( - sample_privkey_bin) - self.assertEqual(curve, curve_secp256k1) - self.assertEqual( - pyelliptic.OpenSSL.get_curve_by_id(curve), 'secp256k1') - self.assertEqual(sample_ephem_privkey, raw_privkey) - - curve, pubkey_x, pubkey_y, _ = pyelliptic.ECC._decode_pubkey( - sample_pubkey_bin) - self.assertEqual(curve, curve_secp256k1) - self.assertEqual(sample_pubkey_x, pubkey_x) - self.assertEqual(sample_pubkey_y, pubkey_y) - - def test_encode_keys(self): - """Check keys encoding""" - cryptor = pyelliptic.ECC( - pubkey_x=sample_pubkey_x, - pubkey_y=sample_pubkey_y, - raw_privkey=sample_ephem_privkey, curve='secp256k1') - self.assertEqual(cryptor.get_privkey(), sample_privkey_bin) - self.assertEqual(cryptor.get_pubkey(), sample_pubkey_bin) - - def test_encryption_parts(self): - """Check results of the encryption steps against samples in the Spec""" - ephem = pyelliptic.ECC( - pubkey_x=sample_pubkey_x, - pubkey_y=sample_pubkey_y, - raw_privkey=sample_ephem_privkey, curve='secp256k1') - key = sha512(ephem.raw_get_ecdh_key( - sample_pubkey[1:-32], sample_pubkey[-32:])).digest() - self.assertEqual(sample_enkey, key[:32]) - self.assertEqual(sample_mackey, key[32:]) - - ctx = pyelliptic.Cipher(sample_enkey, sample_iv, 1) - self.assertEqual(ctx.ciphering(sample_data), sample_ciphertext) - self.assertEqual( - sample_mac, - pyelliptic.hash.hmac_sha256( - sample_mackey, - sample_iv + sample_pubkey_bin + sample_ciphertext)) - - def test_decryption(self): - """Check decription of a message by random cryptor""" - random_recipient = pyelliptic.ECC(curve='secp256k1') - payload = pyelliptic.ECC.encrypt( - sample_data, random_recipient.get_pubkey()) - self.assertEqual(random_recipient.decrypt(payload), sample_data) diff --git a/src/pyelliptic/tests/test_openssl.py b/src/pyelliptic/tests/test_openssl.py deleted file mode 100644 index cb789277..00000000 --- a/src/pyelliptic/tests/test_openssl.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Test if OpenSSL is working correctly -""" -import unittest - -try: - from pyelliptic.openssl import OpenSSL -except ImportError: - from pybitmessage.pyelliptic import OpenSSL - -try: - OpenSSL.BN_bn2binpad - have_pad = True -except AttributeError: - have_pad = None - - -class TestOpenSSL(unittest.TestCase): - """ - Test cases for OpenSSL - """ - def test_is_odd(self): - """Test BN_is_odd implementation""" - ctx = OpenSSL.BN_CTX_new() - a = OpenSSL.BN_new() - group = OpenSSL.EC_GROUP_new_by_curve_name( - OpenSSL.get_curve("secp256k1")) - OpenSSL.EC_GROUP_get_order(group, a, ctx) - - bad = 0 - for _ in range(1024): - OpenSSL.BN_rand(a, OpenSSL.BN_num_bits(a), 0, 0) - if not OpenSSL.BN_is_odd(a) == OpenSSL.BN_is_odd_compatible(a): - bad += 1 - self.assertEqual(bad, 0) - - @unittest.skipUnless(have_pad, 'Skipping OpenSSL pad test') - def test_padding(self): - """Test an alternative implementation of bn2binpad""" - - ctx = OpenSSL.BN_CTX_new() - a = OpenSSL.BN_new() - n = OpenSSL.BN_new() - group = OpenSSL.EC_GROUP_new_by_curve_name( - OpenSSL.get_curve("secp256k1")) - OpenSSL.EC_GROUP_get_order(group, n, ctx) - - bad = 0 - for _ in range(1024): - OpenSSL.BN_rand(a, OpenSSL.BN_num_bits(n), 0, 0) - b = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(n)) - c = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(a)) - OpenSSL.BN_bn2binpad(a, b, OpenSSL.BN_num_bytes(n)) - OpenSSL.BN_bn2bin(a, c) - if b.raw != c.raw.rjust(OpenSSL.BN_num_bytes(n), b'\x00'): - bad += 1 - self.assertEqual(bad, 0) diff --git a/src/qidenticon.py b/src/qidenticon.py index 13be3578..cc3af6b3 100644 --- a/src/qidenticon.py +++ b/src/qidenticon.py @@ -1,276 +1,255 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- + ### # qidenticon.py is Licesensed under FreeBSD License. # (http://www.freebsd.org/copyright/freebsd-license.html) # -# Copyright 1994-2009 Shin Adachi. All rights reserved. # Copyright 2013 "Sendiulo". All rights reserved. -# Copyright 2018-2021 The Bitmessage Developers. All rights reserved. # -# Redistribution and use in source and binary forms, -# with or without modification, are permitted provided that the following -# conditions are met: +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: # -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. # -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS -# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +### + +### +# identicon.py is Licesensed under FreeBSD License. +# (http://www.freebsd.org/copyright/freebsd-license.html) +# +# Copyright 1994-2009 Shin Adachi. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### -# pylint: disable=too-many-locals,too-many-arguments,too-many-function-args """ -Usage ------ +qidenticon.py +identicon python implementation with QPixmap output +by sendiulo ->>> import qidenticon ->>> qidenticon.render_identicon(code, size) +based on +identicon.py +identicon python implementation. +by Shin Adachi -Returns an instance of :class:`QPixmap` which have generated identicon image. -``size`` specifies `patch size`. Generated image size is 3 * ``size``. += usage = + +== python == +>>> import qtidenticon +>>> qtidenticon.render_identicon(code, size) + +Return a PIL Image class instance which have generated identicon image. +```size``` specifies `patch size`. Generated image size is 3 * ```size```. """ -from six.moves import range - -try: - from PyQt5 import QtCore, QtGui -except (ImportError, RuntimeError): - from PyQt4 import QtCore, QtGui +# we probably don't need all of them, but i don't want to check now +from PyQt4 import QtCore, QtGui +from PyQt4.QtCore import * +from PyQt4.QtGui import * +__all__ = ['render_identicon', 'IdenticonRendererBase'] class IdenticonRendererBase(object): - """Encapsulate methods around rendering identicons""" - PATH_SET = [] - + def __init__(self, code): """ - :param code: code for icon + @param code code for icon """ if not isinstance(code, int): code = int(code) self.code = code - + def render(self, size, twoColor, opacity, penwidth): """ - render identicon to QPixmap - - :param size: identicon patchsize. (image size is 3 * [size]) - :returns: :class:`QPixmap` + render identicon to QPicture + + @param size identicon patchsize. (image size is 3 * [size]) + @return QPicture """ - + # decode the code - middle, corner, side, foreColor, secondColor, swap_cross = \ - self.decode(self.code, twoColor) + middle, corner, side, foreColor, secondColor, swap_cross = self.decode(self.code, twoColor) # make image - image = QtGui.QPixmap( - QtCore.QSize(size * 3 + penwidth, size * 3 + penwidth)) - + image = QPixmap(QSize(size * 3 +penwidth, size * 3 +penwidth)) + # fill background - backColor = QtGui.QColor(255, 255, 255, opacity) + backColor = QtGui.QColor(255,255,255,opacity) image.fill(backColor) - + kwds = { 'image': image, 'size': size, 'foreColor': foreColor if swap_cross else secondColor, 'penwidth': penwidth, 'backColor': backColor} - + # middle patch - image = self.drawPatchQt( - (1, 1), middle[2], middle[1], middle[0], **kwds) - + image = self.drawPatchQt((1, 1), middle[2], middle[1], middle[0], **kwds) + # side patch kwds['foreColor'] = foreColor - kwds['patch_type'] = side[0] - for i in range(4): + kwds['type'] = side[0] + for i in xrange(4): pos = [(1, 0), (2, 1), (1, 2), (0, 1)][i] image = self.drawPatchQt(pos, side[2] + 1 + i, side[1], **kwds) - + # corner patch kwds['foreColor'] = secondColor - kwds['patch_type'] = corner[0] - for i in range(4): + kwds['type'] = corner[0] + for i in xrange(4): pos = [(0, 0), (2, 0), (2, 2), (0, 2)][i] image = self.drawPatchQt(pos, corner[2] + 1 + i, corner[1], **kwds) - + return image + - def drawPatchQt( - self, pos, turn, invert, patch_type, image, size, foreColor, - backColor, penwidth): # pylint: disable=unused-argument + def drawPatchQt(self, pos, turn, invert, type, image, size, foreColor, + backColor, penwidth): """ - :param size: patch size + @param size patch size """ - path = self.PATH_SET[patch_type] + path = self.PATH_SET[type] if not path: # blank patch invert = not invert path = [(0., 0.), (1., 0.), (1., 1.), (0., 1.), (0., 0.)] - polygon = QtGui.QPolygonF([ - QtCore.QPointF(x * size, y * size) for x, y in path]) - + + polygon = QPolygonF([QPointF(x*size,y*size) for x,y in path]) + rot = turn % 4 - rect = [ - QtCore.QPointF(0., 0.), QtCore.QPointF(size, 0.), - QtCore.QPointF(size, size), QtCore.QPointF(0., size)] - rotation = [0, 90, 180, 270] - - nopen = QtGui.QPen(foreColor, QtCore.Qt.NoPen) - foreBrush = QtGui.QBrush(foreColor, QtCore.Qt.SolidPattern) + rect = [QPointF(0.,0.), QPointF(size, 0.), QPointF(size, size), QPointF(0., size)] + rotation = [0,90,180,270] + + nopen = QtGui.QPen(foreColor, Qt.NoPen) + foreBrush = QtGui.QBrush(foreColor, Qt.SolidPattern) if penwidth > 0: pen_color = QtGui.QColor(255, 255, 255) - pen = QtGui.QPen(pen_color, QtCore.Qt.SolidPattern) + pen = QtGui.QPen(pen_color, Qt.SolidPattern) pen.setWidth(penwidth) - - painter = QtGui.QPainter() + + painter = QPainter() painter.begin(image) painter.setPen(nopen) - - painter.translate( - pos[0] * size + penwidth / 2, pos[1] * size + penwidth / 2) + + painter.translate(pos[0]*size +penwidth/2, pos[1]*size +penwidth/2) painter.translate(rect[rot]) painter.rotate(rotation[rot]) - + if invert: # subtract the actual polygon from a rectangle to invert it - poly_rect = QtGui.QPolygonF(rect) + poly_rect = QPolygonF(rect) polygon = poly_rect.subtracted(polygon) painter.setBrush(foreBrush) if penwidth > 0: # draw the borders painter.setPen(pen) - painter.drawPolygon(polygon, QtCore.Qt.WindingFill) + painter.drawPolygon(polygon, Qt.WindingFill) # draw the fill painter.setPen(nopen) - painter.drawPolygon(polygon, QtCore.Qt.WindingFill) - + painter.drawPolygon(polygon, Qt.WindingFill) + painter.end() - + return image - def decode(self, code, twoColor): - """virtual functions""" + ### virtual functions + def decode(self, code): raise NotImplementedError - - + class DonRenderer(IdenticonRendererBase): """ - Don Park's implementation of identicon, see: - https://blog.docuverse.com/2007/01/18/identicon-updated-and-source-released + Don Park's implementation of identicon + see : http://www.docuverse.com/blog/donpark/2007/01/19/identicon-updated-and-source-released """ - + PATH_SET = [ - # [0] full square: + #[0] full square: [(0, 0), (4, 0), (4, 4), (0, 4)], - # [1] right-angled triangle pointing top-left: + #[1] right-angled triangle pointing top-left: [(0, 0), (4, 0), (0, 4)], - # [2] upwardy triangle: + #[2] upwardy triangle: [(2, 0), (4, 4), (0, 4)], - # [3] left half of square, standing rectangle: + #[3] left half of square, standing rectangle: [(0, 0), (2, 0), (2, 4), (0, 4)], - # [4] square standing on diagonale: + #[4] square standing on diagonale: [(2, 0), (4, 2), (2, 4), (0, 2)], - # [5] kite pointing topleft: + #[5] kite pointing topleft: [(0, 0), (4, 2), (4, 4), (2, 4)], - # [6] Sierpinski triangle, fractal triangles: + #[6] Sierpinski triangle, fractal triangles: [(2, 0), (4, 4), (2, 4), (3, 2), (1, 2), (2, 4), (0, 4)], - # [7] sharp angled lefttop pointing triangle: + #[7] sharp angled lefttop pointing triangle: [(0, 0), (4, 2), (2, 4)], - # [8] small centered square: + #[8] small centered square: [(1, 1), (3, 1), (3, 3), (1, 3)], - # [9] two small triangles: + #[9] two small triangles: [(2, 0), (4, 0), (0, 4), (0, 2), (2, 2)], - # [10] small topleft square: + #[10] small topleft square: [(0, 0), (2, 0), (2, 2), (0, 2)], - # [11] downpointing right-angled triangle on bottom: + #[11] downpointing right-angled triangle on bottom: [(0, 2), (4, 2), (2, 4)], - # [12] uppointing right-angled triangle on bottom: + #[12] uppointing right-angled triangle on bottom: [(2, 2), (4, 4), (0, 4)], - # [13] small rightbottom pointing right-angled triangle on topleft: + #[13] small rightbottom pointing right-angled triangle on topleft: [(2, 0), (2, 2), (0, 2)], - # [14] small lefttop pointing right-angled triangle on topleft: + #[14] small lefttop pointing right-angled triangle on topleft: [(0, 0), (2, 0), (0, 2)], - # [15] empty: + #[15] empty: []] - # get the [0] full square, [4] square standing on diagonale, - # [8] small centered square, or [15] empty tile: + # get the [0] full square, [4] square standing on diagonale, [8] small centered square, or [15] empty tile: MIDDLE_PATCH_SET = [0, 4, 8, 15] - + # modify path set - for idx, path in enumerate(PATH_SET): - if path: - p = [(vec[0] / 4.0, vec[1] / 4.0) for vec in path] + for idx in xrange(len(PATH_SET)): + if PATH_SET[idx]: + p = map(lambda vec: (vec[0] / 4.0, vec[1] / 4.0), PATH_SET[idx]) PATH_SET[idx] = p + p[:1] - + def decode(self, code, twoColor): - """decode the code""" - - shift = 0 - middleType = (code >> shift) & 0x03 - shift += 2 - middleInvert = (code >> shift) & 0x01 - shift += 1 - cornerType = (code >> shift) & 0x0F - shift += 4 - cornerInvert = (code >> shift) & 0x01 - shift += 1 - cornerTurn = (code >> shift) & 0x03 - shift += 2 - sideType = (code >> shift) & 0x0F - shift += 4 - sideInvert = (code >> shift) & 0x01 - shift += 1 - sideTurn = (code >> shift) & 0x03 - shift += 2 - blue = (code >> shift) & 0x1F - shift += 5 - green = (code >> shift) & 0x1F - shift += 5 - red = (code >> shift) & 0x1F - shift += 5 - second_blue = (code >> shift) & 0x1F - shift += 5 - second_green = (code >> shift) & 0x1F - shift += 5 - second_red = (code >> shift) & 0x1F - shift += 1 - swap_cross = (code >> shift) & 0x01 - + # decode the code + shift = 0; middleType = (code >> shift) & 0x03 + shift += 2; middleInvert= (code >> shift) & 0x01 + shift += 1; cornerType = (code >> shift) & 0x0F + shift += 4; cornerInvert= (code >> shift) & 0x01 + shift += 1; cornerTurn = (code >> shift) & 0x03 + shift += 2; sideType = (code >> shift) & 0x0F + shift += 4; sideInvert = (code >> shift) & 0x01 + shift += 1; sideTurn = (code >> shift) & 0x03 + shift += 2; blue = (code >> shift) & 0x1F + shift += 5; green = (code >> shift) & 0x1F + shift += 5; red = (code >> shift) & 0x1F + shift += 5; second_blue = (code >> shift) & 0x1F + shift += 5; second_green= (code >> shift) & 0x1F + shift += 5; second_red = (code >> shift) & 0x1F + shift += 1; swap_cross = (code >> shift) & 0x01 + middleType = self.MIDDLE_PATCH_SET[middleType] - + foreColor = (red << 3, green << 3, blue << 3) foreColor = QtGui.QColor(*foreColor) - + if twoColor: - secondColor = ( - second_blue << 3, second_green << 3, second_red << 3) + secondColor = (second_blue << 3, second_green << 3, second_red << 3) secondColor = QtGui.QColor(*secondColor) else: secondColor = foreColor - + return (middleType, middleInvert, 0),\ (cornerType, cornerInvert, cornerTurn),\ (sideType, sideInvert, sideTurn),\ - foreColor, secondColor, swap_cross + foreColor, secondColor, swap_cross -def render_identicon( - code, size, twoColor=False, opacity=255, penwidth=0, renderer=None): - """Render an image""" +def render_identicon(code, size, twoColor=False, opacity=255, penwidth=0, renderer=None): if not renderer: renderer = DonRenderer - return renderer(code).render(size, twoColor, opacity, penwidth) + return renderer(code).render(size, twoColor, opacity, penwidth) \ No newline at end of file diff --git a/src/queues.py b/src/queues.py index 4a9b98d2..e8923dbd 100644 --- a/src/queues.py +++ b/src/queues.py @@ -1,55 +1,16 @@ -"""Most of the queues used by bitmessage threads are defined here.""" +import Queue -import threading -import time +from class_objectProcessorQueue import ObjectProcessorQueue +from multiqueue import MultiQueue -from six.moves import queue - -try: - from multiqueue import MultiQueue -except ImportError: - from .multiqueue import MultiQueue - - -class ObjectProcessorQueue(queue.Queue): - """Special queue class using lock for `.threads.objectProcessor`""" - - maxSize = 32000000 - - def __init__(self): - queue.Queue.__init__(self) - self.sizeLock = threading.Lock() - #: in Bytes. We maintain this to prevent nodes from flooding us - #: with objects which take up too much memory. If this gets - #: too big we'll sleep before asking for further objects. - self.curSize = 0 - - def put(self, item, block=True, timeout=None): - while self.curSize >= self.maxSize: - time.sleep(1) - with self.sizeLock: - self.curSize += len(item[1]) - queue.Queue.put(self, item, block, timeout) - - def get(self, block=True, timeout=None): - item = queue.Queue.get(self, block, timeout) - with self.sizeLock: - self.curSize -= len(item[1]) - return item - - -workerQueue = queue.Queue() -UISignalQueue = queue.Queue() -addressGeneratorQueue = queue.Queue() -#: `.network.ReceiveQueueThread` instances dump objects they hear -#: on the network into this queue to be processed. +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() -#: The address generator thread uses this queue to get information back -#: to the API thread. -apiAddressGeneratorReturnQueue = queue.Queue() -#: for exceptions -excQueue = queue.Queue() +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 index 5bf19181..83d35cdf 100644 --- a/src/randomtrackingdict.py +++ b/src/randomtrackingdict.py @@ -1,37 +1,16 @@ -""" -Track randomize ordered dict -""" +import random from threading import RLock from time import time -try: - import helper_random -except ImportError: - from . import helper_random - - class RandomTrackingDict(object): - """ - Dict with randomised order and tracking. - - Keeps a track of how many items have been requested from the dict, - and timeouts. Resets after all objects have been retrieved and timed out. - The main purpose of this isn't as much putting related code together - as performance optimisation and anonymisation of downloading of objects - from other peers. If done using a standard dict or array, it takes - too much CPU (and looks convoluted). Randomisation helps with anonymity. - """ - # pylint: disable=too-many-instance-attributes maxPending = 10 pendingTimeout = 60 - - def __init__(self): + def __init__(self): # O(1) self.dictionary = {} self.indexDict = [] self.len = 0 self.pendingLen = 0 self.lastPoll = 0 - self.lastObject = 0 self.lock = RLock() def __len__(self): @@ -65,7 +44,7 @@ class RandomTrackingDict(object): self.len += 1 def __delitem__(self, key): - if key not in self.dictionary: + if not key in self.dictionary: raise KeyError with self.lock: index = self.dictionary[key][0] @@ -86,42 +65,24 @@ class RandomTrackingDict(object): self.len -= 1 def setMaxPending(self, maxPending): - """ - Sets maximum number of objects that can be retrieved from the class - simultaneously as long as there is no timeout - """ self.maxPending = maxPending def setPendingTimeout(self, pendingTimeout): - """Sets how long to wait for a timeout if max pending is reached - (or all objects have been retrieved)""" self.pendingTimeout = pendingTimeout - def setLastObject(self): - """Update timestamp for tracking of received objects""" - self.lastObject = time() - def randomKeys(self, count=1): - """Retrieve count random keys from the dict - that haven't already been retrieved""" - if self.len == 0 or ( - (self.pendingLen >= self.maxPending or self.pendingLen == self.len) - and self.lastPoll + self.pendingTimeout > time()): + if self.len == 0 or ((self.pendingLen >= self.maxPending or + self.pendingLen == self.len) and self.lastPoll + + self.pendingTimeout > time()): raise KeyError - - # pylint: disable=redefined-outer-name + # reset if we've requested all with self.lock: - # reset if we've requested all - # and if last object received too long time ago - if self.pendingLen == self.len and self.lastObject + \ - self.pendingTimeout < time(): + if self.pendingLen == self.len: self.pendingLen = 0 - self.setLastObject() available = self.len - self.pendingLen if count > available: count = available - randomIndex = helper_random.randomsample( - range(self.len - self.pendingLen), count) + randomIndex = random.sample(range(self.len - self.pendingLen), count) retval = [self.indexDict[i] for i in randomIndex] for i in sorted(randomIndex, reverse=True): @@ -130,3 +91,44 @@ class RandomTrackingDict(object): 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 cb7968f6..e2f3c1cc 100644 --- a/src/shared.py +++ b/src/shared.py @@ -1,206 +1,177 @@ -""" -Some shared functions +from __future__ import division + +verbose = 1 +maximumAgeOfAnObjectThatIAmWillingToAccept = 216000 # This is obsolete with the change to protocol v3 but the singleCleaner thread still hasn't been updated so we need this a little longer. +lengthOfTimeToHoldOnToAllPubkeys = 2419200 # Equals 4 weeks. You could make this longer if you want but making it shorter would not be advisable because there is a very small possibility that it could keep you from obtaining a needed pubkey for a period of time. +maximumAgeOfNodesThatIAdvertiseToOthers = 10800 # Equals three hours +useVeryEasyProofOfWorkForTesting = False # If you set this to True while on the normal network, you won't be able to send or sometimes receive messages. -.. deprecated:: 0.6.3 - Should be moved to different places and this file removed, - but it needs refactoring. -""" -from __future__ import division # Libraries. -import hashlib import os -import stat -import subprocess # nosec B404 import sys +import stat +import threading +import time +import traceback from binascii import hexlify # Project imports. +from addresses import * +from bmconfigparser import BMConfigParser import highlevelcrypto +#import helper_startup +from helper_sql import * +from inventory import Inventory +from queues import objectProcessorQueue +import protocol import state -from addresses import decodeAddress, encodeVarint -from bmconfigparser import config -from debug import logger -from helper_sql import sqlQuery - -from pyelliptic import arithmetic myECCryptorObjects = {} MyECSubscriptionCryptorObjects = {} -# The key in this dictionary is the RIPE hash which is encoded -# in an address and value is the address itself. -myAddressesByHash = {} -# The key in this dictionary is the tag generated from the address. -myAddressesByTag = {} +myAddressesByHash = {} #The key in this dictionary is the RIPE hash which is encoded in an address and value is the address itself. +myAddressesByTag = {} # The key in this dictionary is the tag generated from the address. broadcastSendersForWhichImWatching = {} +printLock = threading.Lock() +statusIconColor = 'red' +connectedHostsList = {} #List of hosts to which we are connected. Used to guarantee that the outgoingSynSender threads won't connect to the same remote node twice. +thisapp = None # singleton lock instance +alreadyAttemptedConnectionsList = { +} # This is a list of nodes to which we have already attempted a connection +alreadyAttemptedConnectionsListLock = threading.Lock() +alreadyAttemptedConnectionsListResetTime = int( + time.time()) # used to clear out the alreadyAttemptedConnectionsList periodically so that we will retry connecting to hosts to which we have already tried to connect. +successfullyDecryptMessageTimings = [ + ] # A list of the amounts of time it took to successfully decrypt msg messages +ackdataForWhichImWatching = {} +clientHasReceivedIncomingConnections = False #used by API command clientStatus +numberOfMessagesProcessed = 0 +numberOfBroadcastsProcessed = 0 +numberOfPubkeysProcessed = 0 +needToWriteKnownNodesToDisk = False # If True, the singleCleaner will write it to disk eventually. +maximumLengthOfTimeToBotherResendingMessages = 0 +timeOffsetWrongCount = 0 def isAddressInMyAddressBook(address): - """Is address in my addressbook?""" queryreturn = sqlQuery( '''select address from addressbook where address=?''', address) return queryreturn != [] - -# At this point we should really just have a isAddressInMy(book, address)... +#At this point we should really just have a isAddressInMy(book, address)... def isAddressInMySubscriptionsList(address): - """Am I subscribed to this address?""" queryreturn = sqlQuery( '''select * from subscriptions where address=?''', str(address)) return queryreturn != [] - def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): - """ - Am I subscribed to this address, is it in my addressbook or whitelist? - """ if isAddressInMyAddressBook(address): return True - queryreturn = sqlQuery( - '''SELECT address FROM whitelist where address=?''' - ''' and enabled = '1' ''', - address) - if queryreturn != []: + queryreturn = sqlQuery('''SELECT address FROM whitelist where address=? and enabled = '1' ''', address) + if queryreturn <> []: return True queryreturn = sqlQuery( - '''select address from subscriptions where address=?''' - ''' and enabled = '1' ''', + '''select address from subscriptions where address=? and enabled = '1' ''', address) - if queryreturn != []: + if queryreturn <> []: return True return False - def decodeWalletImportFormat(WIFstring): - # pylint: disable=inconsistent-return-statements - """ - Convert private key from base58 that's used in the config file to - 8-bit binary string - """ - fullString = arithmetic.changebase(WIFstring, 58, 256) + fullString = arithmetic.changebase(WIFstring,58,256) privkey = fullString[:-4] - if fullString[-4:] != \ - hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: - logger.critical( - 'Major problem! When trying to decode one of your' - ' private keys, the checksum failed. Here are the first' - ' 6 characters of the PRIVATE key: %s', - str(WIFstring)[:6] - ) - os._exit(0) # pylint: disable=protected-access - # return "" - elif privkey[0] == '\x80': # checksum passed - return privkey[1:] - - logger.critical( - 'Major problem! When trying to decode one of your private keys,' - ' the checksum passed but the key doesn\'t begin with hex 80.' - ' Here is the PRIVATE key: %s', WIFstring - ) - os._exit(0) # pylint: disable=protected-access + if fullString[-4:] != hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: + logger.critical('Major problem! When trying to decode one of your private keys, the checksum ' + 'failed. Here are the first 6 characters of the PRIVATE key: %s' % str(WIFstring)[:6]) + os._exit(0) + return "" + else: + #checksum passed + if privkey[0] == '\x80': + return privkey[1:] + else: + logger.critical('Major problem! When trying to decode one of your private keys, the ' + 'checksum passed but the key doesn\'t begin with hex 80. Here is the ' + 'PRIVATE key: %s' % str(WIFstring)) + os._exit(0) + return "" def reloadMyAddressHashes(): - """Reload keys for user's addresses from the config file""" logger.debug('reloading keys from keys.dat file') myECCryptorObjects.clear() myAddressesByHash.clear() myAddressesByTag.clear() - # myPrivateKeys.clear() + #myPrivateKeys.clear() - keyfileSecure = checkSensitiveFilePermissions(os.path.join( - state.appdata, 'keys.dat')) + keyfileSecure = checkSensitiveFilePermissions(state.appdata + 'keys.dat') hasEnabledKeys = False - for addressInKeysFile in config.addresses(): - isEnabled = config.getboolean(addressInKeysFile, 'enabled') + for addressInKeysFile in BMConfigParser().addresses(): + isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled') if isEnabled: hasEnabledKeys = True - # status - addressVersionNumber, streamNumber, hashobj = decodeAddress(addressInKeysFile)[1:] - if addressVersionNumber in (2, 3, 4): - # Returns a simple 32 bytes of information encoded - # in 64 Hex characters, or null if there was an error. + 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( - config.get(addressInKeysFile, 'privencryptionkey'))) - # It is 32 bytes encoded as 64 hex characters - if len(privEncryptionKey) == 64: - myECCryptorObjects[hashobj] = \ - highlevelcrypto.makeCryptor(privEncryptionKey) - myAddressesByHash[hashobj] = addressInKeysFile - tag = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj).digest()).digest()[32:] + 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 + else: - logger.error( - 'Error in reloadMyAddressHashes: Can\'t handle' - ' address versions other than 2, 3, or 4.' - ) + logger.error('Error in reloadMyAddressHashes: Can\'t handle address versions other than 2, 3, or 4.\n') if not keyfileSecure: - fixSensitiveFilePermissions(os.path.join( - state.appdata, 'keys.dat'), hasEnabledKeys) - + fixSensitiveFilePermissions(state.appdata + 'keys.dat', hasEnabledKeys) def reloadBroadcastSendersForWhichImWatching(): - """ - Reinitialize runtime data for the broadcasts I'm subscribed to - from the config file - """ broadcastSendersForWhichImWatching.clear() MyECSubscriptionCryptorObjects.clear() queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1') logger.debug('reloading subscriptions...') for row in queryreturn: address, = row - # status - addressVersionNumber, streamNumber, hashobj = decodeAddress(address)[1:] + status,addressVersionNumber,streamNumber,hash = decodeAddress(address) if addressVersionNumber == 2: - broadcastSendersForWhichImWatching[hashobj] = 0 - # Now, for all addresses, even version 2 addresses, - # we should create Cryptor objects in a dictionary which we will - # use to attempt to decrypt encrypted broadcast messages. - + broadcastSendersForWhichImWatching[hash] = 0 + #Now, for all addresses, even version 2 addresses, we should create Cryptor objects in a dictionary which we will use to attempt to decrypt encrypted broadcast messages. + if addressVersionNumber <= 3: - privEncryptionKey = hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj - ).digest()[:32] - MyECSubscriptionCryptorObjects[hashobj] = \ - highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) + privEncryptionKey = hashlib.sha512(encodeVarint(addressVersionNumber)+encodeVarint(streamNumber)+hash).digest()[:32] + MyECSubscriptionCryptorObjects[hash] = highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) else: - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj - ).digest()).digest() + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512(encodeVarint( + addressVersionNumber) + encodeVarint(streamNumber) + hash).digest()).digest() tag = doubleHashOfAddressData[32:] privEncryptionKey = doubleHashOfAddressData[:32] - MyECSubscriptionCryptorObjects[tag] = \ - highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) - + MyECSubscriptionCryptorObjects[tag] = highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) def fixPotentiallyInvalidUTF8Data(text): - """Sanitise invalid UTF-8 strings""" try: - text.decode('utf-8') + unicode(text,'utf-8') return text - except UnicodeDecodeError: - return 'Part of the message is corrupt. The message cannot be' \ - ' displayed the normal way.\n\n' + repr(text) - + except: + output = 'Part of the message is corrupt. The message cannot be displayed the normal way.\n\n' + repr(text) + return output +# Checks sensitive file permissions for inappropriate umask during keys.dat creation. +# (Or unwise subsequent chmod.) +# +# Returns true iff file appears to have appropriate permissions. def checkSensitiveFilePermissions(filename): - """ - :param str filename: path to the file - :return: True if file appears to have appropriate permissions. - """ if sys.platform == 'win32': - # .. todo:: This might deserve extra checks by someone familiar with + # TODO: This might deserve extra checks by someone familiar with # Windows systems. return True elif sys.platform[:7] == 'freebsd': @@ -208,47 +179,359 @@ def checkSensitiveFilePermissions(filename): present_permissions = os.stat(filename)[0] disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO return present_permissions & disallowed_permissions == 0 - try: - # Skip known problems for non-Win32 filesystems - # without POSIX permissions. - fstype = subprocess.check_output( - ['/usr/bin/stat', '-f', '-c', '%T', filename], - stderr=subprocess.STDOUT - ) # nosec B603 - if 'fuseblk' in fstype: - logger.info( - 'Skipping file permissions check for %s.' - ' Filesystem fuseblk detected.', filename) - return True - except: # noqa:E722 - # Swallow exception here, but we might run into trouble later! - logger.error('Could not determine filesystem type. %s', filename) - present_permissions = os.stat(filename)[0] - disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO - return present_permissions & disallowed_permissions == 0 - + else: + try: + # Skip known problems for non-Win32 filesystems without POSIX permissions. + import subprocess + fstype = subprocess.check_output('stat -f -c "%%T" %s' % (filename), + shell=True, + stderr=subprocess.STDOUT) + if 'fuseblk' in fstype: + logger.info('Skipping file permissions check for %s. Filesystem fuseblk detected.', + filename) + return True + except: + # Swallow exception here, but we might run into trouble later! + logger.error('Could not determine filesystem type. %s', filename) + present_permissions = os.stat(filename)[0] + disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO + return present_permissions & disallowed_permissions == 0 # Fixes permissions on a sensitive file. def fixSensitiveFilePermissions(filename, hasEnabledKeys): - """Try to change file permissions to be more restrictive""" if hasEnabledKeys: - logger.warning( - 'Keyfile had insecure permissions, and there were enabled' - ' keys. The truly paranoid should stop using them immediately.') + logger.warning('Keyfile had insecure permissions, and there were enabled keys. ' + 'The truly paranoid should stop using them immediately.') else: - logger.warning( - 'Keyfile had insecure permissions, but there were no enabled keys.' - ) + logger.warning('Keyfile had insecure permissions, but there were no enabled keys.') try: present_permissions = os.stat(filename)[0] disallowed_permissions = stat.S_IRWXG | stat.S_IRWXO - allowed_permissions = ((1 << 32) - 1) ^ disallowed_permissions + allowed_permissions = ((1<<32)-1) ^ disallowed_permissions new_permissions = ( allowed_permissions & present_permissions) os.chmod(filename, new_permissions) logger.info('Keyfile permissions automatically fixed.') - except Exception: + except Exception, e: logger.exception('Keyfile permissions could not be fixed.') raise + +def isBitSetWithinBitfield(fourByteString, n): + # Uses MSB 0 bit numbering across 4 bytes of data + n = 31 - n + x, = unpack('>L', fourByteString) + return x & 2**n != 0 + + +def decryptAndCheckPubkeyPayload(data, address): + """ + Version 4 pubkeys are encrypted. This function is run when we already have the + address to which we want to try to send a message. The 'data' may come either + off of the wire or we might have had it already in our inventory when we tried + to send a msg to this particular address. + """ + try: + status, addressVersion, streamNumber, ripe = decodeAddress(address) + + readPosition = 20 # bypass the nonce, time, and object type + embeddedAddressVersion, varintLength = decodeVarint(data[readPosition:readPosition + 10]) + readPosition += varintLength + embeddedStreamNumber, varintLength = decodeVarint(data[readPosition:readPosition + 10]) + readPosition += varintLength + storedData = data[20:readPosition] # We'll store the address version and stream number (and some more) in the pubkeys table. + + if addressVersion != embeddedAddressVersion: + logger.info('Pubkey decryption was UNsuccessful due to address version mismatch.') + return 'failed' + if streamNumber != embeddedStreamNumber: + logger.info('Pubkey decryption was UNsuccessful due to stream number mismatch.') + return 'failed' + + tag = data[readPosition:readPosition + 32] + readPosition += 32 + signedData = data[8:readPosition] # the time through the tag. More data is appended onto signedData below after the decryption. + encryptedData = data[readPosition:] + + # Let us try to decrypt the pubkey + toAddress, cryptorObject = state.neededPubkeys[tag] + if toAddress != address: + logger.critical('decryptAndCheckPubkeyPayload failed due to toAddress mismatch. This is very peculiar. toAddress: %s, address %s' % (toAddress, address)) + # the only way I can think that this could happen is if someone encodes their address data two different ways. + # That sort of address-malleability should have been caught by the UI or API and an error given to the user. + return 'failed' + try: + decryptedData = cryptorObject.decrypt(encryptedData) + except: + # Someone must have encrypted some data with a different key + # but tagged it with a tag for which we are watching. + logger.info('Pubkey decryption was unsuccessful.') + return 'failed' + + readPosition = 0 + bitfieldBehaviors = decryptedData[readPosition:readPosition + 4] + readPosition += 4 + publicSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] + readPosition += 64 + publicEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64] + readPosition += 64 + specifiedNonceTrialsPerByte, specifiedNonceTrialsPerByteLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) + readPosition += specifiedNonceTrialsPerByteLength + specifiedPayloadLengthExtraBytes, specifiedPayloadLengthExtraBytesLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) + readPosition += specifiedPayloadLengthExtraBytesLength + storedData += decryptedData[:readPosition] + signedData += decryptedData[:readPosition] + signatureLength, signatureLengthLength = decodeVarint( + decryptedData[readPosition:readPosition + 10]) + readPosition += signatureLengthLength + signature = decryptedData[readPosition:readPosition + signatureLength] + + if highlevelcrypto.verify(signedData, signature, hexlify(publicSigningKey)): + logger.info('ECDSA verify passed (within decryptAndCheckPubkeyPayload)') + else: + logger.info('ECDSA verify failed (within decryptAndCheckPubkeyPayload)') + return 'failed' + + sha = hashlib.new('sha512') + sha.update(publicSigningKey + publicEncryptionKey) + ripeHasher = hashlib.new('ripemd160') + ripeHasher.update(sha.digest()) + embeddedRipe = ripeHasher.digest() + + if embeddedRipe != ripe: + # Although this pubkey object had the tag were were looking for and was + # encrypted with the correct encryption key, it doesn't contain the + # correct pubkeys. Someone is either being malicious or using buggy software. + logger.info('Pubkey decryption was UNsuccessful due to RIPE mismatch.') + return 'failed' + + # Everything checked out. Insert it into the pubkeys table. + + logger.info('within decryptAndCheckPubkeyPayload, addressVersion: %s, streamNumber: %s \n\ + ripe %s\n\ + publicSigningKey in hex: %s\n\ + publicEncryptionKey in hex: %s' % (addressVersion, + streamNumber, + hexlify(ripe), + hexlify(publicSigningKey), + hexlify(publicEncryptionKey) + ) + ) + + t = (address, addressVersion, storedData, int(time.time()), 'yes') + sqlExecute('''INSERT INTO pubkeys VALUES (?,?,?,?,?)''', *t) + return 'successful' + except varintDecodeError as e: + logger.info('Pubkey decryption was UNsuccessful due to a malformed varint.') + return 'failed' + except Exception as e: + logger.critical('Pubkey decryption was UNsuccessful because of an unhandled exception! This is definitely a bug! \n%s' % traceback.format_exc()) + return 'failed' + +def checkAndShareObjectWithPeers(data): + """ + This function is called after either receiving an object off of the wire + or after receiving one as ackdata. + Returns the length of time that we should reserve to process this message + if we are receiving it off of the wire. + """ + if len(data) > 2 ** 18: + logger.info('The payload length of this object is too large (%s bytes). Ignoring it.' % len(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.') + 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) + 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) + return 0 + intObjectType, = unpack('>I', data[16:20]) + try: + if intObjectType == 0: + _checkAndShareGetpubkeyWithPeers(data) + return 0.1 + elif intObjectType == 1: + _checkAndSharePubkeyWithPeers(data) + return 0.1 + elif intObjectType == 2: + _checkAndShareMsgWithPeers(data) + return 0.6 + elif intObjectType == 3: + _checkAndShareBroadcastWithPeers(data) + return 0.6 + else: + _checkAndShareUndefinedObjectWithPeers(data) + return 0.6 + except varintDecodeError as e: + logger.debug("There was a problem with a varint while checking to see whether it was appropriate to share an object with peers. Some details: %s" % e) + except Exception as e: + logger.critical('There was a problem while checking to see whether it was appropriate to share an object with peers. This is definitely a bug! \n%s' % traceback.format_exc()) + return 0 + + +def _checkAndShareUndefinedObjectWithPeers(data): + embeddedTime, = unpack('>Q', data[8:16]) + readPosition = 20 # bypass nonce, time, and object type + objectVersion, objectVersionLength = decodeVarint( + data[readPosition:readPosition + 9]) + readPosition += objectVersionLength + streamNumber, streamNumberLength = decodeVarint( + data[readPosition:readPosition + 9]) + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber) + return + + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this undefined object. Ignoring.') + return + objectType, = unpack('>I', data[16:20]) + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime,'') + logger.debug('advertising inv with hash: %s' % hexlify(inventoryHash)) + protocol.broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + +def _checkAndShareMsgWithPeers(data): + embeddedTime, = unpack('>Q', data[8:16]) + readPosition = 20 # bypass nonce, time, and object type + objectVersion, objectVersionLength = decodeVarint( + data[readPosition:readPosition + 9]) + readPosition += objectVersionLength + streamNumber, streamNumberLength = decodeVarint( + data[readPosition:readPosition + 9]) + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber) + return + readPosition += streamNumberLength + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this msg message. Ignoring.') + return + # This msg message is valid. Let's let our peers know about it. + objectType = 2 + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime,'') + logger.debug('advertising inv with hash: %s' % hexlify(inventoryHash)) + protocol.broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + # Now let's enqueue it to be processed ourselves. + objectProcessorQueue.put((objectType,data)) + +def _checkAndShareGetpubkeyWithPeers(data): + if len(data) < 42: + logger.info('getpubkey message doesn\'t contain enough data. Ignoring.') + return + embeddedTime, = unpack('>Q', data[8:16]) + readPosition = 20 # bypass the nonce, time, and object type + requestedAddressVersionNumber, addressVersionLength = decodeVarint( + data[readPosition:readPosition + 10]) + readPosition += addressVersionLength + streamNumber, streamNumberLength = decodeVarint( + data[readPosition:readPosition + 10]) + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber) + return + readPosition += streamNumberLength + + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this getpubkey request. Ignoring it.') + return + + objectType = 0 + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime,'') + # This getpubkey request is valid. Forward to peers. + logger.debug('advertising inv with hash: %s' % hexlify(inventoryHash)) + protocol.broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + # Now let's queue it to be processed ourselves. + objectProcessorQueue.put((objectType,data)) + +def _checkAndSharePubkeyWithPeers(data): + if len(data) < 146 or len(data) > 440: # sanity check + return + embeddedTime, = unpack('>Q', data[8:16]) + readPosition = 20 # bypass the nonce, time, and object type + addressVersion, varintLength = decodeVarint( + data[readPosition:readPosition + 10]) + readPosition += varintLength + streamNumber, varintLength = decodeVarint( + data[readPosition:readPosition + 10]) + readPosition += varintLength + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber) + return + if addressVersion >= 4: + tag = data[readPosition:readPosition + 32] + logger.debug('tag in received pubkey is: %s' % hexlify(tag)) + else: + tag = '' + + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this pubkey. Ignoring it.') + return + objectType = 1 + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime, tag) + # This object is valid. Forward it to peers. + logger.debug('advertising inv with hash: %s' % hexlify(inventoryHash)) + protocol.broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + + # Now let's queue it to be processed ourselves. + objectProcessorQueue.put((objectType,data)) + + +def _checkAndShareBroadcastWithPeers(data): + if len(data) < 180: + logger.debug('The payload length of this broadcast packet is unreasonably low. Someone is probably trying funny business. Ignoring message.') + return + embeddedTime, = unpack('>Q', data[8:16]) + readPosition = 20 # bypass the nonce, time, and object type + broadcastVersion, broadcastVersionLength = decodeVarint( + data[readPosition:readPosition + 10]) + readPosition += broadcastVersionLength + if broadcastVersion >= 2: + streamNumber, streamNumberLength = decodeVarint(data[readPosition:readPosition + 10]) + readPosition += streamNumberLength + if not streamNumber in state.streamsInWhichIAmParticipating: + logger.debug('The streamNumber %s isn\'t one we are interested in.' % streamNumber) + return + if broadcastVersion >= 3: + tag = data[readPosition:readPosition+32] + else: + tag = '' + inventoryHash = calculateInventoryHash(data) + if inventoryHash in Inventory(): + logger.debug('We have already received this broadcast object. Ignoring.') + return + # It is valid. Let's let our peers know about it. + objectType = 3 + Inventory()[inventoryHash] = ( + objectType, streamNumber, data, embeddedTime, tag) + # This object is valid. Forward it to peers. + logger.debug('advertising inv with hash: %s' % hexlify(inventoryHash)) + protocol.broadcastToSendDataQueues((streamNumber, 'advertiseobject', inventoryHash)) + + # Now let's queue it to be processed ourselves. + objectProcessorQueue.put((objectType,data)) + +def openKeysFile(): + if 'linux' in sys.platform: + import subprocess + subprocess.call(["xdg-open", state.appdata + 'keys.dat']) + else: + os.startfile(state.appdata + 'keys.dat') + +from debug import logger diff --git a/src/shutdown.py b/src/shutdown.py index 3e2b8ca8..f447148b 100644 --- a/src/shutdown.py +++ b/src/shutdown.py @@ -1,91 +1,69 @@ -"""shutdown function""" - import os +import Queue import threading import time -from six.moves import queue - -import state from debug import logger from helper_sql import sqlQuery, sqlStoredProcedure +from helper_threading import StoppableThread +from knownnodes import saveKnownNodes from inventory import Inventory -from network import StoppableThread -from network.knownnodes import saveKnownNodes -from queues import ( - addressGeneratorQueue, objectProcessorQueue, UISignalQueue, workerQueue) - +from queues import addressGeneratorQueue, objectProcessorQueue, UISignalQueue, workerQueue +import shared +import state def doCleanShutdown(): - """ - Used to tell all the treads to finish work and exit. - """ - state.shutdown = 1 - + state.shutdown = 1 #Used to tell proof of work worker threads and the objectProcessorThread to exit. objectProcessorQueue.put(('checkShutdownVariable', 'no data')) for thread in threading.enumerate(): if thread.isAlive() and isinstance(thread, StoppableThread): thread.stopThread() - - UISignalQueue.put(( - 'updateStatusBar', - 'Saving the knownNodes list of peers to disk...')) + + UISignalQueue.put(('updateStatusBar','Saving the knownNodes list of peers to disk...')) logger.info('Saving knownNodes list of peers to disk') saveKnownNodes() logger.info('Done saving knownNodes list of peers to disk') - UISignalQueue.put(( - 'updateStatusBar', - 'Done saving the knownNodes list of peers to disk.')) + UISignalQueue.put(('updateStatusBar','Done saving the knownNodes list of peers to disk.')) logger.info('Flushing inventory in memory out to disk...') UISignalQueue.put(( 'updateStatusBar', - 'Flushing inventory in memory out to disk.' - ' This should normally only take a second...')) + 'Flushing inventory in memory out to disk. This should normally only take a second...')) Inventory().flush() - # Verify that the objectProcessor has finished exiting. It should have - # incremented the shutdown variable from 1 to 2. This must finish before - # we command the sqlThread to exit. + # Verify that the objectProcessor has finished exiting. It should have incremented the + # shutdown variable from 1 to 2. This must finish before we command the sqlThread to exit. while state.shutdown == 1: time.sleep(.1) - - # Wait long enough to guarantee that any running proof of work worker - # threads will check the shutdown variable and exit. If the main thread - # closes before they do then they won't stop. - time.sleep(.25) - - for thread in threading.enumerate(): - if ( - thread is not threading.currentThread() - and isinstance(thread, StoppableThread) - and thread.name != 'SQL' - ): - logger.debug("Waiting for thread %s", thread.name) - thread.join() - - # This one last useless query will guarantee that the previous flush - # committed and that the + + # This one last useless query will guarantee that the previous flush committed and that the # objectProcessorThread committed before we close the program. sqlQuery('SELECT address FROM subscriptions') logger.info('Finished flushing inventory.') sqlStoredProcedure('exit') + + # Wait long enough to guarantee that any running proof of work worker threads will check the + # shutdown variable and exit. If the main thread closes before they do then they won't stop. + time.sleep(.25) - # flush queues - for q in ( - workerQueue, UISignalQueue, addressGeneratorQueue, - objectProcessorQueue): + for thread in threading.enumerate(): + if thread is not threading.currentThread() and isinstance(thread, StoppableThread): + logger.debug("Waiting for thread %s", thread.name) + thread.join() + + # flush queued + for queue in (workerQueue, UISignalQueue, addressGeneratorQueue, objectProcessorQueue): while True: try: - q.get(False) - q.task_done() - except queue.Empty: + queue.get(False) + queue.task_done() + except Queue.Empty: break - if state.thisapp.daemon or not state.enableGUI: + if shared.thisapp.daemon: logger.info('Clean shutdown complete.') - state.thisapp.cleanup() - os._exit(0) # pylint: disable=protected-access + shared.thisapp.cleanup() + os._exit(0) else: logger.info('Core shutdown complete.') for thread in threading.enumerate(): - logger.debug('Thread %s still running', thread.name) + logger.debug("Thread %s still running", thread.name) diff --git a/src/singleinstance.py b/src/singleinstance.py index cff9d794..ed1048ba 100644 --- a/src/singleinstance.py +++ b/src/singleinstance.py @@ -1,35 +1,32 @@ -""" -This is based upon the singleton class from -`tendo `_ -which is under the Python Software Foundation License version 2 -""" +#! /usr/bin/env python import atexit +import errno +from multiprocessing import Process import os import sys - import state try: import fcntl # @UnresolvedImport -except ImportError: +except: pass - -class singleinstance(object): +class singleinstance: """ - Implements a single instance application by creating a lock file - at appdata. + Implements a single instance application by creating a lock file at appdata. + + This is based upon the singleton class from tendo https://github.com/pycontribs/tendo + which is under the Python Software Foundation License version 2 """ def __init__(self, flavor_id="", daemon=False): self.initialized = False self.counter = 0 self.daemon = daemon self.lockPid = None - self.lockfile = os.path.normpath( - os.path.join(state.appdata, 'singleton%s.lock' % flavor_id)) + self.lockfile = os.path.normpath(os.path.join(state.appdata, 'singleton%s.lock' % flavor_id)) - if state.enableGUI and not self.daemon and not state.curses: + if not self.daemon and not state.curses: # Tells the already running (if any) application to get focus. import bitmessageqt bitmessageqt.init() @@ -40,24 +37,20 @@ class singleinstance(object): atexit.register(self.cleanup) def lock(self): - """Obtain single instance lock""" if self.lockPid is None: self.lockPid = os.getpid() if sys.platform == 'win32': try: - # file already exists, we try to remove - # (in case previous execution was interrupted) + # 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 | os.O_TRUNC - ) - except OSError as e: + 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: - sys.exit( - 'Another instance of this application is' - ' already running') + print 'Another instance of this application is already running' + sys.exit(-1) + print(e.errno) raise else: pidLine = "%i\n" % self.lockPid @@ -66,15 +59,13 @@ class singleinstance(object): self.fp = open(self.lockfile, 'a+') try: if self.daemon and self.lockPid != os.getpid(): - # wait for parent to finish - fcntl.lockf(self.fp, fcntl.LOCK_EX) + fcntl.lockf(self.fp, fcntl.LOCK_EX) # wait for parent to finish else: fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB) self.lockPid = os.getpid() except IOError: - sys.exit( - 'Another instance of this application is' - ' already running') + print 'Another instance of this application is already running' + sys.exit(-1) else: pidLine = "%i\n" % self.lockPid self.fp.truncate(0) @@ -82,7 +73,6 @@ class singleinstance(object): self.fp.flush() def cleanup(self): - """Release single instance lock""" if not self.initialized: return if self.daemon and self.lockPid == os.getpid(): @@ -93,11 +83,11 @@ class singleinstance(object): os.close(self.fd) else: fcntl.lockf(self.fp, fcntl.LOCK_UN) - except (IOError, OSError): + except Exception, e: pass return - + print "Cleaning up lockfile" try: if sys.platform == 'win32': if hasattr(self, 'fd'): @@ -107,5 +97,5 @@ class singleinstance(object): fcntl.lockf(self.fp, fcntl.LOCK_UN) if os.path.isfile(self.lockfile): os.unlink(self.lockfile) - except (IOError, OSError): + except Exception, e: pass diff --git a/src/singleton.py b/src/singleton.py index 5c6c43be..1eef08e1 100644 --- a/src/singleton.py +++ b/src/singleton.py @@ -1,21 +1,6 @@ -""" -Singleton decorator definition -""" - -from functools import wraps - - def Singleton(cls): - """ - Decorator implementing the singleton pattern: - it restricts the instantiation of a class to one "single" instance. - """ instances = {} - - # https://github.com/sphinx-doc/sphinx/issues/3783 - @wraps(cls) def getinstance(): - """Find an instance or save newly created one""" if cls not in instances: instances[cls] = cls() return instances[cls] diff --git a/src/socks/BUGS b/src/socks/BUGS new file mode 100644 index 00000000..fa8ccfad --- /dev/null +++ b/src/socks/BUGS @@ -0,0 +1,25 @@ +SocksiPy version 1.00 +A Python SOCKS module. +(C) 2006 Dan-Haim. All rights reserved. +See LICENSE file for details. + + +KNOWN BUGS AND ISSUES +---------------------- + +There are no currently known bugs in this module. +There are some limits though: + +1) Only outgoing connections are supported - This module currently only supports +outgoing TCP connections, though some servers may support incoming connections +as well. UDP is not supported either. + +2) GSSAPI Socks5 authenticaion is not supported. + + +If you find any new bugs, please contact the author at: + +negativeiq@users.sourceforge.net + + +Thank you! diff --git a/src/socks/LICENSE b/src/socks/LICENSE new file mode 100644 index 00000000..04b6b1f3 --- /dev/null +++ b/src/socks/LICENSE @@ -0,0 +1,22 @@ +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. diff --git a/src/socks/README b/src/socks/README new file mode 100644 index 00000000..a52f55f3 --- /dev/null +++ b/src/socks/README @@ -0,0 +1,201 @@ +SocksiPy version 1.00 +A Python SOCKS module. +(C) 2006 Dan-Haim. All rights reserved. +See LICENSE file for details. + + +WHAT IS A SOCKS PROXY? +A SOCKS proxy is a proxy server at the TCP level. In other words, it acts as +a tunnel, relaying all traffic going through it without modifying it. +SOCKS proxies can be used to relay traffic using any network protocol that +uses TCP. + +WHAT IS SOCKSIPY? +This Python module allows you to create TCP connections through a SOCKS +proxy without any special effort. + +PROXY COMPATIBILITY +SocksiPy is compatible with three different types of proxies: +1. SOCKS Version 4 (Socks4), including the Socks4a extension. +2. SOCKS Version 5 (Socks5). +3. HTTP Proxies which support tunneling using the CONNECT method. + +SYSTEM REQUIREMENTS +Being written in Python, SocksiPy can run on any platform that has a Python +interpreter and TCP/IP support. +This module has been tested with Python 2.3 and should work with greater versions +just as well. + + +INSTALLATION +------------- + +Simply copy the file "socks.py" to your Python's lib/site-packages directory, +and you're ready to go. + + +USAGE +------ + +First load the socks module with the command: + +>>> import socks +>>> + +The socks module provides a class called "socksocket", which is the base to +all of the module's functionality. +The socksocket object has the same initialization parameters as the normal socket +object to ensure maximal compatibility, however it should be noted that socksocket +will only function with family being AF_INET and type being SOCK_STREAM. +Generally, it is best to initialize the socksocket object with no parameters + +>>> s = socks.socksocket() +>>> + +The socksocket object has an interface which is very similiar to socket's (in fact +the socksocket class is derived from socket) with a few extra methods. +To select the proxy server you would like to use, use the setproxy method, whose +syntax is: + +setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + +Explaination of the parameters: + +proxytype - The type of the proxy server. This can be one of three possible +choices: PROXY_TYPE_SOCKS4, PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP for Socks4, +Socks5 and HTTP servers respectively. + +addr - The IP address or DNS name of the proxy server. + +port - The port of the proxy server. Defaults to 1080 for socks and 8080 for http. + +rdns - This is a boolean flag than modifies the behavior regarding DNS resolving. +If it is set to True, DNS resolving will be preformed remotely, on the server. +If it is set to False, DNS resolving will be preformed locally. Please note that +setting this to True with Socks4 servers actually use an extension to the protocol, +called Socks4a, which may not be supported on all servers (Socks5 and http servers +always support DNS). The default is True. + +username - For Socks5 servers, this allows simple username / password authentication +with the server. For Socks4 servers, this parameter will be sent as the userid. +This parameter is ignored if an HTTP server is being used. If it is not provided, +authentication will not be used (servers may accept unauthentication requests). + +password - This parameter is valid only for Socks5 servers and specifies the +respective password for the username provided. + +Example of usage: + +>>> s.setproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com") +>>> + +After the setproxy method has been called, simply call the connect method with the +traditional parameters to establish a connection through the proxy: + +>>> s.connect(("www.sourceforge.net",80)) +>>> + +Connection will take a bit longer to allow negotiation with the proxy server. +Please note that calling connect without calling setproxy earlier will connect +without a proxy (just like a regular socket). + +Errors: Any errors in the connection process will trigger exceptions. The exception +may either be generated by the underlying socket layer or may be custom module +exceptions, whose details follow: + +class ProxyError - This is a base exception class. It is not raised directly but +rather all other exception classes raised by this module are derived from it. +This allows an easy way to catch all proxy-related errors. + +class GeneralProxyError - When thrown, it indicates a problem which does not fall +into another category. The parameter is a tuple containing an error code and a +description of the error, from the following list: +1 - invalid data - This error means that unexpected data has been received from +the server. The most common reason is that the server specified as the proxy is +not really a Socks4/Socks5/HTTP proxy, or maybe the proxy type specified is wrong. +4 - bad proxy type - This will be raised if the type of the proxy supplied to the +setproxy function was not PROXY_TYPE_SOCKS4/PROXY_TYPE_SOCKS5/PROXY_TYPE_HTTP. +5 - bad input - This will be raised if the connect method is called with bad input +parameters. + +class Socks5AuthError - This indicates that the connection through a Socks5 server +failed due to an authentication problem. The parameter is a tuple containing a +code and a description message according to the following list: + +1 - authentication is required - This will happen if you use a Socks5 server which +requires authentication without providing a username / password at all. +2 - all offered authentication methods were rejected - This will happen if the proxy +requires a special authentication method which is not supported by this module. +3 - unknown username or invalid password - Self descriptive. + +class Socks5Error - This will be raised for Socks5 errors which are not related to +authentication. The parameter is a tuple containing a code and a description of the +error, as given by the server. The possible errors, according to the RFC are: + +1 - General SOCKS server failure - If for any reason the proxy server is unable to +fulfill your request (internal server error). +2 - connection not allowed by ruleset - If the address you're trying to connect to +is blacklisted on the server or requires authentication. +3 - Network unreachable - The target could not be contacted. A router on the network +had replied with a destination net unreachable error. +4 - Host unreachable - The target could not be contacted. A router on the network +had replied with a destination host unreachable error. +5 - Connection refused - The target server has actively refused the connection +(the requested port is closed). +6 - TTL expired - The TTL value of the SYN packet from the proxy to the target server +has expired. This usually means that there are network problems causing the packet +to be caught in a router-to-router "ping-pong". +7 - Command not supported - The client has issued an invalid command. When using this +module, this error should not occur. +8 - Address type not supported - The client has provided an invalid address type. +When using this module, this error should not occur. + +class Socks4Error - This will be raised for Socks4 errors. The parameter is a tuple +containing a code and a description of the error, as given by the server. The +possible error, according to the specification are: + +1 - Request rejected or failed - Will be raised in the event of an failure for any +reason other then the two mentioned next. +2 - request rejected because SOCKS server cannot connect to identd on the client - +The Socks server had tried an ident lookup on your computer and has failed. In this +case you should run an identd server and/or configure your firewall to allow incoming +connections to local port 113 from the remote server. +3 - request rejected because the client program and identd report different user-ids - +The Socks server had performed an ident lookup on your computer and has received a +different userid than the one you have provided. Change your userid (through the +username parameter of the setproxy method) to match and try again. + +class HTTPError - This will be raised for HTTP errors. The parameter is a tuple +containing the HTTP status code and the description of the server. + + +After establishing the connection, the object behaves like a standard socket. +Call the close method to close the connection. + +In addition to the socksocket class, an additional function worth mentioning is the +setdefaultproxy function. The parameters are the same as the setproxy method. +This function will set default proxy settings for newly created socksocket objects, +in which the proxy settings haven't been changed via the setproxy method. +This is quite useful if you wish to force 3rd party modules to use a socks proxy, +by overriding the socket object. +For example: + +>>> socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5,"socks.example.com") +>>> socket.socket = socks.socksocket +>>> urllib.urlopen("http://www.sourceforge.net/") + + +PROBLEMS +--------- + +If you have any problems using this module, please first refer to the BUGS file +(containing current bugs and issues). If your problem is not mentioned you may +contact the author at the following E-Mail address: + +negativeiq@users.sourceforge.net + +Please allow some time for your question to be received and handled. + + +Dan-Haim, +Author. diff --git a/src/socks/__init__.py b/src/socks/__init__.py new file mode 100644 index 00000000..0bfa18f5 --- /dev/null +++ b/src/socks/__init__.py @@ -0,0 +1,462 @@ +"""SocksiPy - Python SOCKS module. +Version 1.00 + +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. + + +This module provides a standard socket-like interface for Python +for tunneling connections through SOCKS proxies. + +""" + +""" + +Minor modifications made by Christopher Gilbert (http://motomastyle.com/) +for use in PyLoris (http://pyloris.sourceforge.net/) + +Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) +mainly to merge bug fixes found in Sourceforge + +""" + +import socket +import struct +import sys + +PROXY_TYPE_SOCKS4 = 1 +PROXY_TYPE_SOCKS5 = 2 +PROXY_TYPE_HTTP = 3 + +_defaultproxy = None +_orgsocket = socket.socket + +class ProxyError(Exception): pass +class GeneralProxyError(ProxyError): pass +class Socks5AuthError(ProxyError): pass +class Socks5Error(ProxyError): pass +class Socks4Error(ProxyError): pass +class HTTPError(ProxyError): pass + +_generalerrors = ("success", + "invalid data", + "not connected", + "not available", + "bad proxy type", + "bad input", + "timed out", + "network unreachable", + "connection refused", + "host unreachable") + +_socks5errors = ("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") + +_socks5autherrors = ("succeeded", + "authentication is required", + "all offered authentication methods were rejected", + "unknown username or invalid password", + "unknown error") + +_socks4errors = ("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") + +def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): + """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets a default proxy which all further socksocket objects will use, + unless explicitly changed. + """ + global _defaultproxy + _defaultproxy = (proxytype, addr, port, rdns, username, password) + +def wrapmodule(module): + """wrapmodule(module) + Attempts to replace a module's socket library with a SOCKS socket. Must set + a default proxy using setdefaultproxy(...) first. + This will only work on modules that import socket directly into the namespace; + most of the Python Standard Library falls into this category. + """ + if _defaultproxy != None: + module.socket.socket = socksocket + else: + raise GeneralProxyError((4, "no proxy specified")) + +class socksocket(socket.socket): + """socksocket([family[, type[, proto]]]) -> socket object + Open a SOCKS enabled socket. The parameters are the same as + those of the standard socket init. In order for SOCKS to work, + you must specify family=AF_INET, type=SOCK_STREAM and proto=0. + """ + + def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): + _orgsocket.__init__(self, family, type, proto, _sock) + if _defaultproxy != None: + self.__proxy = _defaultproxy + else: + self.__proxy = (None, None, None, None, None, None) + self.__proxysockname = None + self.__proxypeername = None + + def __recvall(self, count): + """__recvall(count) -> data + Receive EXACTLY the number of bytes requested from the socket. + Blocks until the required number of bytes have been received. + """ + try: + data = self.recv(count) + except socket.timeout: + raise GeneralProxyError((6, "timed out")) + while len(data) < count: + d = self.recv(count-len(data)) + if not d: raise GeneralProxyError((0, "connection closed unexpectedly")) + data = data + d + return data + + def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): + """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets the proxy to be used. + proxytype - The type of the proxy to be used. Three types + are supported: PROXY_TYPE_SOCKS4 (including socks4a), + PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP + addr - The address of the server (IP or DNS). + port - The port of the server. Defaults to 1080 for SOCKS + servers and 8080 for HTTP proxy servers. + rdns - Should DNS queries be preformed on the remote side + (rather than the local side). The default is True. + Note: This has no effect with SOCKS4 servers. + username - Username to authenticate with to the server. + The default is no authentication. + password - Password to authenticate with to the server. + Only relevant when username is also provided. + """ + self.__proxy = (proxytype, addr, port, rdns, username, password) + + def __negotiatesocks5(self): + """__negotiatesocks5(self,destaddr,destport) + Negotiates a connection through a SOCKS5 server. + """ + # First we'll send the authentication packages we support. + if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): + # The username/password details were supplied to the + # setproxy method so we support the USERNAME/PASSWORD + # authentication (in addition to the standard none). + self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) + else: + # No username/password were entered, therefore we + # only support connections with no authentication. + self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00)) + # We'll receive the server's response to determine which + # method was selected + chosenauth = self.__recvall(2) + if chosenauth[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + # Check the chosen authentication method + if chosenauth[1:2] == chr(0x00).encode(): + # No authentication is required + pass + elif chosenauth[1:2] == chr(0x02).encode(): + # Okay, we need to perform a basic username/password + # authentication. + self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5]) + authstat = self.__recvall(2) + if authstat[0:1] != chr(0x01).encode(): + # Bad response + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if authstat[1:2] != chr(0x00).encode(): + # Authentication failed + self.close() + raise Socks5AuthError((3, _socks5autherrors[3])) + # Authentication succeeded + else: + # Reaching here is always bad + self.close() + if chosenauth[1] == chr(0xFF).encode(): + raise Socks5AuthError((2, _socks5autherrors[2])) + else: + raise GeneralProxyError((1, _generalerrors[1])) + + def __connectsocks5(self, destaddr, destport): + # Now we can request the actual connection + req = 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: + ipaddr = socket.inet_aton(destaddr) + req = req + chr(0x01).encode() + ipaddr + except socket.error: + # Well it's not an IP number, so it's probably a DNS name. + if self.__proxy[3]: + # Resolve remotely + ipaddr = None + req = req + chr(0x03).encode() + chr(len(destaddr)).encode() + destaddr + else: + # Resolve locally + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + req = req + chr(0x01).encode() + ipaddr + req = req + struct.pack(">H", destport) + self.sendall(req) + # Get the response + resp = self.__recvall(4) + if resp[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + elif resp[1:2] != chr(0x00).encode(): + # Connection failed + self.close() + if ord(resp[1:2])<=8: + raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) + else: + raise Socks5Error((9, _socks5errors[9])) + # Get the bound address/port + elif resp[3:4] == chr(0x01).encode(): + boundaddr = self.__recvall(4) + elif resp[3:4] == chr(0x03).encode(): + resp = resp + self.recv(1) + boundaddr = self.__recvall(ord(resp[4:5])) + else: + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + boundport = struct.unpack(">H", self.__recvall(2))[0] + self.__proxysockname = (boundaddr, boundport) + if ipaddr != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def __resolvesocks5(self, host): + # Now we can request the actual connection + req = struct.pack('BBB', 0x05, 0xF0, 0x00) + req += chr(0x03).encode() + chr(len(host)).encode() + host + req = req + struct.pack(">H", 8444) + self.sendall(req) + # Get the response + ip = "" + resp = self.__recvall(4) + if resp[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + elif resp[1:2] != chr(0x00).encode(): + # Connection failed + self.close() + if ord(resp[1:2])<=8: + raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) + else: + raise Socks5Error((9, _socks5errors[9])) + # Get the bound address/port + elif resp[3:4] == chr(0x01).encode(): + ip = socket.inet_ntoa(self.__recvall(4)) + elif resp[3:4] == chr(0x03).encode(): + resp = resp + self.recv(1) + ip = self.__recvall(ord(resp[4:5])) + else: + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + boundport = struct.unpack(">H", self.__recvall(2))[0] + return ip + + def getproxysockname(self): + """getsockname() -> address info + Returns the bound IP address and port number at the proxy. + """ + return self.__proxysockname + + def getproxypeername(self): + """getproxypeername() -> address info + Returns the IP and port number of the proxy. + """ + return _orgsocket.getpeername(self) + + def getpeername(self): + """getpeername() -> address info + Returns the IP address and port number of the destination + machine (note: getproxypeername returns the proxy) + """ + return self.__proxypeername + + def getproxytype(self): + return self.__proxy[0] + + def __negotiatesocks4(self,destaddr,destport): + """__negotiatesocks4(self,destaddr,destport) + Negotiates a connection through a SOCKS4 server. + """ + # Check if the destination address provided is an IP address + rmtrslv = False + try: + ipaddr = socket.inet_aton(destaddr) + except socket.error: + # It's a DNS name. Check where it should be resolved. + if self.__proxy[3]: + ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) + rmtrslv = True + else: + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + # Construct the request packet + req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr + # The username parameter is considered userid for SOCKS4 + if self.__proxy[4] != None: + req = req + self.__proxy[4] + req = req + chr(0x00).encode() + # DNS name if remote resolving is required + # NOTE: This is actually an extension to the SOCKS4 protocol + # called SOCKS4A and may not be supported in all cases. + if rmtrslv: + req = req + destaddr + chr(0x00).encode() + self.sendall(req) + # Get the response from the server + resp = self.__recvall(8) + if resp[0:1] != chr(0x00).encode(): + # Bad data + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + if resp[1:2] != chr(0x5A).encode(): + # Server returned an error + self.close() + if ord(resp[1:2]) in (91, 92, 93): + self.close() + raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) + else: + raise Socks4Error((94, _socks4errors[4])) + # Get the bound address/port + self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0]) + if rmtrslv != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def __negotiatehttp(self, destaddr, destport): + """__negotiatehttp(self,destaddr,destport) + Negotiates a connection through an HTTP server. + """ + # If we need to resolve locally, we do this now + if not self.__proxy[3]: + addr = socket.gethostbyname(destaddr) + else: + addr = destaddr + self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode()) + # We read the response until we get the string "\r\n\r\n" + resp = self.recv(1) + while resp.find("\r\n\r\n".encode()) == -1: + resp = resp + self.recv(1) + # We just need the first line to check if the connection + # was successful + statusline = resp.splitlines()[0].split(" ".encode(), 2) + if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + try: + statuscode = int(statusline[1]) + except ValueError: + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if statuscode != 200: + self.close() + raise HTTPError((statuscode, statusline[2])) + self.__proxysockname = ("0.0.0.0", 0) + self.__proxypeername = (addr, destport) + + def connect(self, destpair): + """connect(self, despair) + Connects to the specified destination through a proxy. + destpar - A tuple of the IP/DNS address and the port number. + (identical to socket's connect). + To select the proxy server use setproxy(). + """ + # Do a minimal input check first + if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int): + raise GeneralProxyError((5, _generalerrors[5])) + if self.__proxy[0] == PROXY_TYPE_SOCKS5: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + try: + _orgsocket.connect(self, (self.__proxy[1], portnum)) + except socket.error as e: + # ENETUNREACH, WSAENETUNREACH + if e[0] in [101, 10051]: + raise GeneralProxyError((7, _generalerrors[7])) + # ECONNREFUSED, WSAECONNREFUSED + if e[0] in [111, 10061]: + raise GeneralProxyError((8, _generalerrors[8])) + # EHOSTUNREACH, WSAEHOSTUNREACH + if e[0] in [113, 10065]: + raise GeneralProxyError((9, _generalerrors[9])) + raise + self.__negotiatesocks5() + self.__connectsocks5(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_SOCKS4: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self,(self.__proxy[1], portnum)) + self.__negotiatesocks4(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_HTTP: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 8080 + try: + _orgsocket.connect(self,(self.__proxy[1], portnum)) + except socket.error as e: + # ENETUNREACH, WSAENETUNREACH + if e[0] in [101, 10051]: + raise GeneralProxyError((7, _generalerrors[7])) + # ECONNREFUSED, WSAECONNREFUSED + if e[0] in [111, 10061]: + raise GeneralProxyError((8, _generalerrors[8])) + # EHOSTUNREACH, WSAEHOSTUNREACH + if e[0] in [113, 10065]: + raise GeneralProxyError((9, _generalerrors[9])) + raise + self.__negotiatehttp(destpair[0], destpair[1]) + elif self.__proxy[0] == None: + _orgsocket.connect(self, (destpair[0], destpair[1])) + else: + raise GeneralProxyError((4, _generalerrors[4])) + + def resolve(self, host): + if self.__proxy[0] == PROXY_TYPE_SOCKS5: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatesocks5() + return self.__resolvesocks5(host) + else: + return None diff --git a/src/sql/config_setting_ver_2.sql b/src/sql/config_setting_ver_2.sql deleted file mode 100644 index 087d297a..00000000 --- a/src/sql/config_setting_ver_2.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE pubkeys ADD usedpersonally text DEFAULT 'no'; diff --git a/src/sql/config_setting_ver_3.sql b/src/sql/config_setting_ver_3.sql deleted file mode 100644 index 4bdcccc8..00000000 --- a/src/sql/config_setting_ver_3.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE inbox ADD encodingtype int DEFAULT '2'; - -ALTER TABLE inbox ADD read bool DEFAULT '1'; - -ALTER TABLE sent ADD encodingtype int DEFAULT '2'; diff --git a/src/sql/init_version_10.sql b/src/sql/init_version_10.sql deleted file mode 100644 index 8bd8b0b3..00000000 --- a/src/sql/init_version_10.sql +++ /dev/null @@ -1,15 +0,0 @@ --- -- --- -- Update the address colunm to unique in addressbook table --- -- - -ALTER TABLE addressbook RENAME TO old_addressbook; - -CREATE TABLE `addressbook` ( - `label` text , - `address` text , - UNIQUE(address) ON CONFLICT IGNORE -) ; - -INSERT INTO addressbook SELECT label, address FROM old_addressbook; - -DROP TABLE old_addressbook; diff --git a/src/sql/init_version_2.sql b/src/sql/init_version_2.sql deleted file mode 100644 index ea42df4c..00000000 --- a/src/sql/init_version_2.sql +++ /dev/null @@ -1,29 +0,0 @@ --- --- Let's get rid of the first20bytesofencryptedmessage field in the inventory table. --- - -CREATE TEMP TABLE `inventory_backup` ( - `hash` blob , - `objecttype` text , - `streamnumber` int , - `payload` blob , - `receivedtime` int , - UNIQUE(hash) ON CONFLICT REPLACE -) ; - -INSERT INTO `inventory_backup` SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory; - -DROP TABLE inventory; - -CREATE TABLE `inventory` ( - `hash` blob , - `objecttype` text , - `streamnumber` int , - `payload` blob , - `receivedtime` int , - UNIQUE(hash) ON CONFLICT REPLACE -) ; - -INSERT INTO inventory SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory_backup; - -DROP TABLE inventory_backup; diff --git a/src/sql/init_version_3.sql b/src/sql/init_version_3.sql deleted file mode 100644 index 9de784a5..00000000 --- a/src/sql/init_version_3.sql +++ /dev/null @@ -1,5 +0,0 @@ --- --- Add a new column to the inventory table to store tags. --- - -ALTER TABLE inventory ADD tag blob DEFAULT ''; diff --git a/src/sql/init_version_4.sql b/src/sql/init_version_4.sql deleted file mode 100644 index d2fd393d..00000000 --- a/src/sql/init_version_4.sql +++ /dev/null @@ -1,17 +0,0 @@ - -- - -- Add a new column to the pubkeys table to store the address version. - -- We're going to trash all of our pubkeys and let them be redownloaded. - -- - -DROP TABLE pubkeys; - -CREATE TABLE `pubkeys` ( - `hash` blob , - `addressversion` int , - `transmitdata` blob , - `time` int , - `usedpersonally` text , - UNIQUE(hash, addressversion) ON CONFLICT REPLACE -) ; - -DELETE FROM inventory WHERE objecttype = 'pubkey'; diff --git a/src/sql/init_version_5.sql b/src/sql/init_version_5.sql deleted file mode 100644 index a13fa8cf..00000000 --- a/src/sql/init_version_5.sql +++ /dev/null @@ -1,12 +0,0 @@ - -- - -- Add a new table: objectprocessorqueue with which to hold objects - -- that have yet to be processed if the user shuts down Bitmessage. - -- - -DROP TABLE knownnodes; - -CREATE TABLE `objectprocessorqueue` ( - `objecttype` text, - `data` blob, - UNIQUE(objecttype, data) ON CONFLICT REPLACE -) ; diff --git a/src/sql/init_version_6.sql b/src/sql/init_version_6.sql deleted file mode 100644 index b9a03669..00000000 --- a/src/sql/init_version_6.sql +++ /dev/null @@ -1,25 +0,0 @@ --- --- changes related to protocol v3 --- In table inventory and objectprocessorqueue, objecttype is now --- an integer (it was a human-friendly string previously) --- - -DROP TABLE inventory; - -CREATE TABLE `inventory` ( - `hash` blob, - `objecttype` int, - `streamnumber` int, - `payload` blob, - `expirestime` integer, - `tag` blob, - UNIQUE(hash) ON CONFLICT REPLACE -) ; - -DROP TABLE objectprocessorqueue; - -CREATE TABLE `objectprocessorqueue` ( - `objecttype` int, - `data` blob, - UNIQUE(objecttype, data) ON CONFLICT REPLACE -) ; diff --git a/src/sql/init_version_7.sql b/src/sql/init_version_7.sql deleted file mode 100644 index a2f6f6e3..00000000 --- a/src/sql/init_version_7.sql +++ /dev/null @@ -1,11 +0,0 @@ --- --- The format of data stored in the pubkeys table has changed. Let's --- clear it, and the pubkeys from inventory, so that they'll --- be re-downloaded. --- - -DELETE FROM inventory WHERE objecttype = 1; - -DELETE FROM pubkeys; - -UPDATE sent SET status='msgqueued' WHERE status='doingmsgpow' or status='badkey'; diff --git a/src/sql/init_version_8.sql b/src/sql/init_version_8.sql deleted file mode 100644 index 0c1813d3..00000000 --- a/src/sql/init_version_8.sql +++ /dev/null @@ -1,7 +0,0 @@ --- --- Add a new column to the inbox table to store the hash of --- the message signature. We'll use this as temporary message UUID --- in order to detect duplicates. --- - -ALTER TABLE inbox ADD sighash blob DEFAULT ''; diff --git a/src/sql/init_version_9.sql b/src/sql/init_version_9.sql deleted file mode 100644 index bc8296b9..00000000 --- a/src/sql/init_version_9.sql +++ /dev/null @@ -1,74 +0,0 @@ -CREATE TEMPORARY TABLE `sent_backup` ( - `msgid` blob, - `toaddress` text, - `toripe` blob, - `fromaddress` text, - `subject` text, - `message` text, - `ackdata` blob, - `lastactiontime` integer, - `status` text, - `retrynumber` integer, - `folder` text, - `encodingtype` int -) ; - -INSERT INTO sent_backup SELECT msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, status, 0, folder, encodingtype FROM sent; - -DROP TABLE sent; - -CREATE TABLE `sent` ( - `msgid` blob, - `toaddress` text, - `toripe` blob, - `fromaddress` text, - `subject` text, - `message` text, - `ackdata` blob, - `senttime` integer, - `lastactiontime` integer, - `sleeptill` int, - `status` text, - `retrynumber` integer, - `folder` text, - `encodingtype` int, - `ttl` int -) ; - -INSERT INTO sent SELECT msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, lastactiontime, 0, status, 0, folder, encodingtype, 216000 FROM sent_backup; - -DROP TABLE sent_backup; - -ALTER TABLE pubkeys ADD address text DEFAULT '' ; - --- --- replica for loop to update hashed address --- - -UPDATE pubkeys SET address=(enaddr(pubkeys.addressversion, 1, hash)); - -CREATE TEMPORARY TABLE `pubkeys_backup` ( - `address` text, - `addressversion` int, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(address) ON CONFLICT REPLACE -) ; - -INSERT INTO pubkeys_backup SELECT address, addressversion, transmitdata, `time`, usedpersonally FROM pubkeys; - -DROP TABLE pubkeys; - -CREATE TABLE `pubkeys` ( - `address` text, - `addressversion` int, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(address) ON CONFLICT REPLACE -) ; - -INSERT INTO pubkeys SELECT address, addressversion, transmitdata, `time`, usedpersonally FROM pubkeys_backup; - -DROP TABLE pubkeys_backup; diff --git a/src/sql/initialize_schema.sql b/src/sql/initialize_schema.sql deleted file mode 100644 index 8413aa0a..00000000 --- a/src/sql/initialize_schema.sql +++ /dev/null @@ -1,100 +0,0 @@ -CREATE TABLE `inbox` ( - `msgid` blob, - `toaddress` text, - `fromaddress` text, - `subject` text, - `received` text, - `message` text, - `folder` text, - `encodingtype` int, - `read` bool, - `sighash` blob, -UNIQUE(msgid) ON CONFLICT REPLACE -) ; - -CREATE TABLE `sent` ( - `msgid` blob, - `toaddress` text, - `toripe` blob, - `fromaddress` text, - `subject` text, - `message` text, - `ackdata` blob, - `senttime` integer, - `lastactiontime` integer, - `sleeptill` integer, - `status` text, - `retrynumber` integer, - `folder` text, - `encodingtype` int, - `ttl` int -) ; - - -CREATE TABLE `subscriptions` ( - `label` text, - `address` text, - `enabled` bool -) ; - - -CREATE TABLE `addressbook` ( - `label` text, - `address` text, - UNIQUE(address) ON CONFLICT IGNORE -) ; - - - CREATE TABLE `blacklist` ( - `label` text, - `address` text, - `enabled` bool - ) ; - - - CREATE TABLE `whitelist` ( - `label` text, - `address` text, - `enabled` bool - ) ; - - -CREATE TABLE `pubkeys` ( - `address` text, - `addressversion` int, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(address) ON CONFLICT REPLACE -) ; - - -CREATE TABLE `inventory` ( - `hash` blob, - `objecttype` int, - `streamnumber` int, - `payload` blob, - `expirestime` integer, - `tag` blob, - UNIQUE(hash) ON CONFLICT REPLACE -) ; - - -INSERT INTO subscriptions VALUES ('Bitmessage new releases/announcements', 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw', 1); - - - CREATE TABLE `settings` ( - `key` blob, - `value` blob, - UNIQUE(key) ON CONFLICT REPLACE - ) ; - -INSERT INTO settings VALUES('version','11'); - -INSERT INTO settings VALUES('lastvacuumtime', CAST(strftime('%s', 'now') AS STR) ); - -CREATE TABLE `objectprocessorqueue` ( - `objecttype` int, - `data` blob, - UNIQUE(objecttype, data) ON CONFLICT REPLACE -) ; diff --git a/src/sql/upg_sc_if_old_ver_1.sql b/src/sql/upg_sc_if_old_ver_1.sql deleted file mode 100644 index 18a5ecfc..00000000 --- a/src/sql/upg_sc_if_old_ver_1.sql +++ /dev/null @@ -1,30 +0,0 @@ -CREATE TEMPORARY TABLE `pubkeys_backup` ( - `hash` blob, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(hash) ON CONFLICT REPLACE -) ; - -INSERT INTO `pubkeys_backup` SELECT hash, transmitdata, `time`, usedpersonally FROM `pubkeys`; - -DROP TABLE `pubkeys` - -CREATE TABLE `pubkeys` ( - `hash` blob, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(hash) ON CONFLICT REPLACE -) ; - - -INSERT INTO `pubkeys` SELECT hash, transmitdata, `time`, usedpersonally FROM `pubkeys_backup`; - -DROP TABLE `pubkeys_backup`; - -DELETE FROM inventory WHERE objecttype = 'pubkey'; - -DELETE FROM subscriptions WHERE address='BM-BbkPSZbzPwpVcYZpU4yHwf9ZPEapN5Zx' - -INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1) diff --git a/src/sql/upg_sc_if_old_ver_2.sql b/src/sql/upg_sc_if_old_ver_2.sql deleted file mode 100644 index 1fde0098..00000000 --- a/src/sql/upg_sc_if_old_ver_2.sql +++ /dev/null @@ -1,7 +0,0 @@ -UPDATE `sent` SET status='doingmsgpow' WHERE status='doingpow'; - -UPDATE `sent` SET status='msgsent' WHERE status='sentmessage'; - -UPDATE `sent` SET status='doingpubkeypow' WHERE status='findingpubkey'; - -UPDATE `sent` SET status='broadcastqueued' WHERE status='broadcastpending'; diff --git a/src/state.py b/src/state.py index cac5db19..73e4f789 100644 --- a/src/state.py +++ b/src/state.py @@ -1,73 +1,57 @@ -""" -Global runtime variables. -""" +import collections neededPubkeys = {} streamsInWhichIAmParticipating = [] +sendDataQueues = [] #each sendData thread puts its queue in this list. +# For UPnP extPort = None -"""For UPnP""" +# for Tor hidden service socksIP = None -"""for Tor hidden service""" -appdata = '' -"""holds the location of the application data storage directory""" +# Network protocols availability, initialised below +networkProtocolAvailability = None -shutdown = 0 -""" -Set to 1 by the `.shutdown.doCleanShutdown` function. -Used to tell the threads to exit. -""" +appdata = '' #holds the location of the application data storage directory + +shutdown = 0 #Set to 1 by the doCleanShutdown function. Used to tell the proof of work worker threads to exit. -# Component control flags - set on startup, do not change during runtime -# The defaults are for standalone GUI (default operating mode) -enableNetwork = True -"""enable network threads""" -enableObjProc = True -"""enable object processing thread""" -enableAPI = True -"""enable API (if configured)""" -enableGUI = True -"""enable GUI (QT or ncurses)""" -enableSTDIO = False -"""enable STDIO threads""" -enableKivy = False -"""enable kivy app and test cases""" curses = False +sqlReady = False # set to true by sqlTread when ready for processing + maximumNumberOfHalfOpenConnections = 0 -maximumLengthOfTimeToBotherResendingMessages = 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 +# this peer and the timing attack mitigation will be disabled in order +# to download data faster. The expected use case is where the user has +# a fast connection to a trusted server where they run a BitMessage +# daemon permanently. If they then run a second instance of the client +# on a local machine periodically when they want to check for messages +# it will sync with the network a lot faster without compromising +# security. +trustedPeer = None + discoveredPeers = {} +# tracking pending downloads globally, for stats +missingObjects = {} + +Peer = collections.namedtuple('Peer', ['host', 'port']) + +def resetNetworkProtocolAvailability(): + global networkProtocolAvailability + networkProtocolAvailability = {'IPv4': None, 'IPv6': None, 'onion': None} + +resetNetworkProtocolAvailability() + dandelion = 0 - -kivy = False - -kivyapp = None - -testmode = False - -clientHasReceivedIncomingConnections = False -"""used by API command clientStatus""" - -numberOfMessagesProcessed = 0 -numberOfBroadcastsProcessed = 0 -numberOfPubkeysProcessed = 0 - -statusIconColor = 'red' -""" -GUI status icon color -.. note:: bad style, refactor it -""" - -ackdataForWhichImWatching = {} - -thisapp = None -"""Singleton instance""" - -backend_py3_compatible = False diff --git a/src/storage/filesystem.py b/src/storage/filesystem.py index e756a820..d64894a9 100644 --- a/src/storage/filesystem.py +++ b/src/storage/filesystem.py @@ -1,149 +1,94 @@ -""" -Module for using filesystem (directory with files) for inventory storage -""" -import logging -import os -import time 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 InventoryItem, InventoryStorage - -logger = logging.getLogger('default') - +from storage import InventoryStorage, InventoryItem class FilesystemInventory(InventoryStorage): - """Filesystem for inventory storage""" topDir = "inventory" objectDir = "objects" metadataFilename = "metadata" dataFilename = "data" def __init__(self): - super(FilesystemInventory, self).__init__() - self.baseDir = os.path.join( - lookupAppdataFolder(), FilesystemInventory.topDir) - for createDir in [self.baseDir, os.path.join(self.baseDir, "objects")]: - if os.path.exists(createDir): - if not os.path.isdir(createDir): - raise IOError( - "%s exists but it's not a directory" % createDir) + 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: - os.makedirs(createDir) - # Guarantees that two receiveDataThreads - # don't receive and process the same message - # concurrently (probably sent by a malicious individual) - self.lock = RLock() + 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, hashval): + def __contains__(self, hash): + retval = False for streamDict in self._inventory.values(): - if hashval in streamDict: + if hash in streamDict: return True return False - def __delitem__(self, hash_): - raise NotImplementedError - - def __getitem__(self, hashval): + def __getitem__(self, hash): for streamDict in self._inventory.values(): try: - retval = streamDict[hashval] + retval = streamDict[hash] except KeyError: continue if retval.payload is None: - retval = InventoryItem( - retval.type, - retval.stream, - self.getData(hashval), - retval.expires, - retval.tag) + retval = InventoryItem(retval.type, retval.stream, self.getData(hash), retval.expires, retval.tag) return retval - raise KeyError(hashval) + raise KeyError(hash) - def __setitem__(self, hashval, value): + def __setitem__(self, hash, value): with self.lock: value = InventoryItem(*value) try: - os.makedirs(os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode())) + makedirs(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash))) except OSError: pass try: - with open( - os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode(), - FilesystemInventory.metadataFilename, - ), - "w", - ) as f: - f.write("%s,%s,%s,%s," % ( - value.type, - value.stream, - value.expires, - hexlify(value.tag).decode())) - with open( - os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode(), - FilesystemInventory.dataFilename, - ), - "wb", - ) as f: + 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][hashval] = value + self._inventory[value.stream][hash] = value except KeyError: self._inventory[value.stream] = {} - self._inventory[value.stream][hashval] = value + self._inventory[value.stream][hash] = value - def delHashId(self, hashval): - """Remove object from inventory""" - for stream in self._inventory: + def delHashId(self, hash): + for stream in self._inventory.keys(): try: - del self._inventory[stream][hashval] + del self._inventory[stream][hash] except KeyError: pass with self.lock: try: - os.remove( - os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode(), - FilesystemInventory.metadataFilename)) + remove(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.metadataFilename)) except IOError: pass try: - os.remove( - os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode(), - FilesystemInventory.dataFilename)) + remove(path.join(self.baseDir, FilesystemInventory.objectDir, hexlify(hash), FilesystemInventory.dataFilename)) except IOError: pass try: - os.rmdir(os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashval).decode())) + 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()) + elems.extend (streamDict.keys()) return elems.__iter__() def __len__(self): @@ -156,111 +101,73 @@ class FilesystemInventory(InventoryStorage): newInventory = {} for hashId in self.object_list(): try: - objectType, streamNumber, expiresTime, tag = self.getMetadata( - hashId) + objectType, streamNumber, expiresTime, tag = self.getMetadata(hashId) try: - newInventory[streamNumber][hashId] = InventoryItem( - objectType, streamNumber, None, expiresTime, tag) + newInventory[streamNumber][hashId] = InventoryItem(objectType, streamNumber, None, expiresTime, tag) except KeyError: newInventory[streamNumber] = {} - newInventory[streamNumber][hashId] = InventoryItem( - objectType, streamNumber, None, expiresTime, tag) + newInventory[streamNumber][hashId] = InventoryItem(objectType, streamNumber, None, expiresTime, tag) except KeyError: - logger.debug( - 'error loading %s', hexlify(hashId), exc_info=True) + 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 list of streams""" return self._inventory.keys() def object_list(self): - """Return inventory vectors (hashes) from a directory""" - return [unhexlify(x) for x in os.listdir(os.path.join( - self.baseDir, FilesystemInventory.objectDir))] + return [unhexlify(x) for x in listdir(path.join(self.baseDir, FilesystemInventory.objectDir))] def getData(self, hashId): - """Get object data""" try: - with open( - os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashId).decode(), - FilesystemInventory.dataFilename, - ), - "r", - ) as f: + 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): - """Get object metadata""" try: - with open( - os.path.join( - self.baseDir, - FilesystemInventory.objectDir, - hexlify(hashId).decode(), - FilesystemInventory.metadataFilename, - ), - "r", - ) as f: - objectType, streamNumber, expiresTime, tag = f.read().split( - ",", 4)[:4] - return [ - int(objectType), - int(streamNumber), - int(expiresTime), - unhexlify(tag)] + 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): - """Get a list of objects filtered by object type and tag""" retval = [] - for streamDict in self._inventory.values(): + for stream, streamDict in self._inventory: for hashId, item in streamDict: if item.type == objectType and item.tag == tag: - try: + 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)) + retval.append(InventoryItem(item.type, item.stream, item.payload, item.expires, item.tag)) return retval def hashes_by_stream(self, stream): - """Return inventory vectors (hashes) for a stream""" try: return self._inventory[stream].keys() except KeyError: return [] def unexpired_hashes_by_stream(self, stream): - """Return unexpired hashes in the inventory for a particular stream""" + t = int(time.time()) try: - return [ - x for x, value in self._inventory[stream].items() - if value.expires > int(time.time())] + return [x for x, value in self._inventory[stream].items() if value.expires > t] except KeyError: return [] def flush(self): - """Flush the inventory and create a new, empty one""" self._load() def clean(self): - """Clean out old items from the inventory""" - minTime = int(time.time()) - 60 * 60 * 30 + minTime = int(time.time()) - (60 * 60 * 30) deletes = [] - for streamDict in self._inventory.values(): + for stream, streamDict in self._inventory.items(): for hashId, item in streamDict.items(): if item.expires < minTime: deletes.append(hashId) diff --git a/src/storage/sqlite.py b/src/storage/sqlite.py index eb5df098..438cbdcb 100644 --- a/src/storage/sqlite.py +++ b/src/storage/sqlite.py @@ -1,62 +1,45 @@ -""" -Sqlite Inventory -""" +import collections +from threading import current_thread, enumerate as threadingEnumerate, RLock +import Queue import sqlite3 import time -from threading import RLock - -from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery -from .storage import InventoryItem, InventoryStorage +from helper_sql import * +from storage import InventoryStorage, InventoryItem class SqliteInventory(InventoryStorage): - """Inventory using SQLite""" def __init__(self): - super(SqliteInventory, self).__init__() - # of objects (like msg payloads and pubkey payloads) - # Does not include protocol headers (the first 24 bytes of each packet). - self._inventory = {} - # 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._objects = {} - # Guarantees that two receiveDataThreads don't receive - # and process the same message concurrently - # (probably sent by a malicious individual) - self.lock = RLock() + 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_): + def __contains__(self, hash): with self.lock: - if hash_ in self._objects: + if hash in self._objects: return True - rows = sqlQuery( - 'SELECT streamnumber FROM inventory WHERE hash=?', - sqlite3.Binary(hash_)) + rows = sqlQuery('SELECT streamnumber FROM inventory WHERE hash=?', sqlite3.Binary(hash)) if not rows: return False - self._objects[hash_] = rows[0][0] + self._objects[hash] = rows[0][0] return True - def __getitem__(self, 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=?', sqlite3.Binary(hash_)) + 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_) + raise KeyError(hash) return InventoryItem(*rows[0]) - def __setitem__(self, hash_, value): + def __setitem__(self, hash, value): with self.lock: value = InventoryItem(*value) - self._inventory[hash_] = value - self._objects[hash_] = value.stream + self._inventory[hash] = value + self._objects[hash] = value.stream - def __delitem__(self, hash_): + def __delitem__(self, hash): raise NotImplementedError def __iter__(self): @@ -67,57 +50,32 @@ class SqliteInventory(InventoryStorage): def __len__(self): with self.lock: - return len(self._inventory) + sqlQuery( - 'SELECT count(*) FROM inventory')[0][0] + return len(self._inventory) + sqlQuery('SELECT count(*) FROM inventory')[0][0] - def by_type_and_tag(self, objectType, tag=None): - """ - Get all inventory items of certain *objectType* - with *tag* if given. - """ - query = [ - 'SELECT objecttype, streamnumber, payload, expirestime, tag' - ' FROM inventory WHERE objecttype=?', objectType] - if tag: - query[0] += ' AND tag=?' - query.append(sqlite3.Binary(tag)) + def by_type_and_tag(self, objectType, tag): with self.lock: - values = [ - value for value in self._inventory.values() - if value.type == objectType - and tag is None or value.tag == tag - ] + [InventoryItem(*value) for value in sqlQuery(*query)] + 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): - """Return unexpired inventory vectors filtered by 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)) + 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): - """Flush cache""" - with self.lock: - # If you use both the inventoryLock and the sqlLock, - # always use the inventoryLock OUTSIDE of the sqlLock. + 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) + sql.execute('INSERT INTO inventory VALUES (?, ?, ?, ?, ?, ?)', sqlite3.Binary(objectHash), *value) self._inventory.clear() def clean(self): - """Free memory / perform garbage collection""" with self.lock: - sqlExecute( - 'DELETE FROM inventory WHERE expirestime= 0x3000000: - raise unittest.SkipTest('Module is not ported to python3') - - -def put_signal_file(path, filename): - """Creates file, presence of which is a signal about some event.""" - with open(os.path.join(path, filename), 'wb') as outfile: - outfile.write(b'%i' % time.time()) diff --git a/src/tests/core.py b/src/tests/core.py deleted file mode 100644 index 868efe2b..00000000 --- a/src/tests/core.py +++ /dev/null @@ -1,436 +0,0 @@ -""" -Tests for core and those that do not work outside -(because of import error for example) -""" - -import atexit -import os -import pickle # nosec -import Queue -import random # nosec -import shutil -import socket -import string -import sys -import threading -import time -import unittest - -import protocol -import state -import helper_sent -import helper_addressbook - -from bmconfigparser import config -from helper_msgcoding import MsgEncode, MsgDecode -from helper_sql import sqlQuery -from network import asyncore_pollchoose as asyncore, knownnodes -from network.bmproto import BMProto -from network.connectionpool import BMConnectionPool -from network.node import Node, Peer -from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection -from queues import excQueue -from version import softwareVersion - -from common import cleanup - -try: - socket.socket().bind(('127.0.0.1', 9050)) - tor_port_free = True -except (OSError, socket.error): - tor_port_free = False - -frozen = getattr(sys, 'frozen', None) -knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat') - - -def pickle_knownnodes(): - """Generate old style pickled knownnodes.dat""" - now = time.time() - with open(knownnodes_file, 'wb') as dst: - pickle.dump({ - stream: { - Peer( - '%i.%i.%i.%i' % tuple([ - random.randint(1, 255) for i in range(4)]), - 8444): {'lastseen': now, 'rating': 0.1} - for i in range(1, 4) # 3 test nodes - } - for stream in range(1, 4) # 3 test streams - }, dst) - - -class TestCore(unittest.TestCase): - """Test case, which runs in main pybitmessage thread""" - addr = 'BM-2cVvkzJuQDsQHLqxRXc6HZGPLZnkBLzEZY' - - def tearDown(self): - """Reset possible unexpected settings after test""" - knownnodes.addKnownNode(1, Peer('127.0.0.1', 8444), is_self=True) - config.remove_option('bitmessagesettings', 'dontconnect') - config.remove_option('bitmessagesettings', 'onionservicesonly') - config.set('bitmessagesettings', 'socksproxytype', 'none') - - def test_msgcoding(self): - """test encoding and decoding (originally from helper_msgcoding)""" - msg_data = { - 'subject': ''.join( - random.choice(string.ascii_lowercase + string.digits) # nosec - for _ in range(40)), - 'body': ''.join( - random.choice(string.ascii_lowercase + string.digits) # nosec - for _ in range(10000)) - } - - obj1 = MsgEncode(msg_data, 1) - obj2 = MsgEncode(msg_data, 2) - obj3 = MsgEncode(msg_data, 3) - # print "1: %i 2: %i 3: %i" % ( - # len(obj1.data), len(obj2.data), len(obj3.data)) - - obj1e = MsgDecode(1, obj1.data) - # no subject in trivial encoding - self.assertEqual(msg_data['body'], obj1e.body) - - obj2e = MsgDecode(2, obj2.data) - self.assertEqual(msg_data['subject'], obj2e.subject) - self.assertEqual(msg_data['body'], obj2e.body) - - obj3e = MsgDecode(3, obj3.data) - self.assertEqual(msg_data['subject'], obj3e.subject) - self.assertEqual(msg_data['body'], obj3e.body) - - try: - MsgEncode({'body': 'A msg with no subject'}, 3) - except Exception as e: - self.fail( - 'Exception %s while trying to encode message' - ' with no subject!' % e - ) - - @unittest.skip('Bad environment for asyncore.loop') - def test_tcpconnection(self): - """initial fill script from network.tcp""" - config.set('bitmessagesettings', 'dontconnect', 'true') - try: - for peer in (Peer("127.0.0.1", 8448),): - direct = TCPConnection(peer) - while asyncore.socket_map: - print("loop, state = %s" % direct.state) - asyncore.loop(timeout=10, count=1) - except: # noqa:E722 - self.fail('Exception in test loop') - - def _load_knownnodes(self, filepath): - with knownnodes.knownNodesLock: - shutil.copyfile(filepath, knownnodes_file) - try: - knownnodes.readKnownNodes() - except AttributeError as e: - self.fail('Failed to load knownnodes: %s' % e) - - @staticmethod - def _wipe_knownnodes(): - with knownnodes.knownNodesLock: - knownnodes.knownNodes = {stream: {} for stream in range(1, 4)} - - @staticmethod - def _outdate_knownnodes(): - with knownnodes.knownNodesLock: - for nodes in knownnodes.knownNodes.itervalues(): - for node in nodes.itervalues(): - node['lastseen'] -= 2419205 # older than 28 days - - def test_knownnodes_pickle(self): - """ensure that 3 nodes was imported for each stream""" - pickle_knownnodes() - self._wipe_knownnodes() - knownnodes.readKnownNodes() - for nodes in knownnodes.knownNodes.itervalues(): - self_count = n = 0 - for n, node in enumerate(nodes.itervalues()): - if node.get('self'): - self_count += 1 - self.assertEqual(n - self_count, 2) - - def test_knownnodes_default(self): - """test adding default knownnodes if nothing loaded""" - cleanup(files=('knownnodes.dat',)) - self._wipe_knownnodes() - knownnodes.readKnownNodes() - self.assertGreaterEqual( - len(knownnodes.knownNodes[1]), len(knownnodes.DEFAULT_NODES)) - - def test_0_cleaner(self): - """test knownnodes starvation leading to IndexError in Asyncore""" - self._outdate_knownnodes() - # time.sleep(303) # singleCleaner wakes up every 5 min - knownnodes.cleanupKnownNodes() - self.assertTrue(knownnodes.knownNodes[1]) - while True: - try: - thread, exc = excQueue.get(block=False) - except Queue.Empty: - return - if thread == 'Asyncore' and isinstance(exc, IndexError): - self.fail("IndexError because of empty knownNodes!") - - def _initiate_bootstrap(self): - config.set('bitmessagesettings', 'dontconnect', 'true') - self._wipe_knownnodes() - knownnodes.addKnownNode(1, Peer('127.0.0.1', 8444), is_self=True) - knownnodes.cleanupKnownNodes() - time.sleep(5) - - def _check_connection(self, full=False): - """ - Check if there is at least one outbound connection to remote host - with name not starting with "bootstrap" in 6 minutes at most, - fail otherwise. - """ - _started = time.time() - config.remove_option('bitmessagesettings', 'dontconnect') - proxy_type = config.safeGet( - 'bitmessagesettings', 'socksproxytype') - if proxy_type == 'SOCKS5': - connection_base = Socks5BMConnection - elif proxy_type == 'SOCKS4a': - connection_base = Socks4aBMConnection - else: - connection_base = TCPConnection - c = 360 - while c > 0: - time.sleep(1) - c -= 2 - for peer, con in BMConnectionPool().outboundConnections.iteritems(): - if ( - peer.host.startswith('bootstrap') - or peer.host == 'quzwelsuziwqgpt2.onion' - ): - if c < 60: - self.fail( - 'Still connected to bootstrap node %s after %.2f' - ' seconds' % (peer, time.time() - _started)) - c += 1 - break - else: - self.assertIsInstance(con, connection_base) - self.assertNotEqual(peer.host, '127.0.0.1') - if full and not con.fullyEstablished: - continue - return - self.fail( - 'Failed to connect during %.2f sec' % (time.time() - _started)) - - def _check_knownnodes(self): - for stream in knownnodes.knownNodes.itervalues(): - for peer in stream: - if peer.host.startswith('bootstrap'): - self.fail( - 'Bootstrap server in knownnodes: %s' % peer.host) - - def test_dontconnect(self): - """all connections are closed 5 seconds after setting dontconnect""" - self._initiate_bootstrap() - self.assertEqual(len(BMConnectionPool().connections()), 0) - - def test_connection(self): - """test connection to bootstrap servers""" - self._initiate_bootstrap() - for port in [8080, 8444]: - for item in socket.getaddrinfo( - 'bootstrap%s.bitmessage.org' % port, 80): - try: - addr = item[4][0] - socket.inet_aton(item[4][0]) - except (TypeError, socket.error): - continue - else: - knownnodes.addKnownNode(1, Peer(addr, port)) - self._check_connection(True) - - def test_bootstrap(self): - """test bootstrapping""" - config.set('bitmessagesettings', 'socksproxytype', 'none') - self._initiate_bootstrap() - self._check_connection() - self._check_knownnodes() - # backup potentially enough knownnodes - knownnodes.saveKnownNodes() - with knownnodes.knownNodesLock: - shutil.copyfile(knownnodes_file, knownnodes_file + '.bak') - - @unittest.skipIf(tor_port_free, 'no running tor detected') - def test_bootstrap_tor(self): - """test bootstrapping with tor""" - config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') - self._initiate_bootstrap() - self._check_connection() - self._check_knownnodes() - - @unittest.skipIf(tor_port_free, 'no running tor detected') - def test_onionservicesonly(self): - """ensure bitmessage doesn't try to connect to non-onion nodes - if onionservicesonly set, wait at least 3 onion nodes - """ - config.set('bitmessagesettings', 'socksproxytype', 'SOCKS5') - config.set('bitmessagesettings', 'onionservicesonly', 'true') - self._load_knownnodes(knownnodes_file + '.bak') - if len([ - node for node in knownnodes.knownNodes[1] - if node.host.endswith('.onion') - ]) < 3: # generate fake onion nodes if have not enough - with knownnodes.knownNodesLock: - for f in ('a', 'b', 'c', 'd'): - knownnodes.addKnownNode(1, Peer(f * 16 + '.onion', 8444)) - config.remove_option('bitmessagesettings', 'dontconnect') - tried_hosts = set() - for _ in range(360): - time.sleep(1) - for peer in BMConnectionPool().outboundConnections: - if peer.host.endswith('.onion'): - tried_hosts.add(peer.host) - else: - if not peer.host.startswith('bootstrap'): - self.fail( - 'Found non onion hostname %s in outbound' - 'connections!' % peer.host) - if len(tried_hosts) > 2: - return - self.fail('Failed to find at least 3 nodes to connect within 360 sec') - - @unittest.skipIf(frozen, 'skip fragile test') - def test_udp(self): - """check default udp setting and presence of Announcer thread""" - self.assertTrue( - config.safeGetBoolean('bitmessagesettings', 'udp')) - for thread in threading.enumerate(): - if thread.name == 'Announcer': # find Announcer thread - break - else: - return self.fail('No Announcer thread found') - - @staticmethod - def _decode_msg(data, pattern): - proto = BMProto() - proto.bm_proto_reset() - proto.payload = data[protocol.Header.size:] - return proto.decode_payload_content(pattern) - - def test_version(self): - """check encoding/decoding of the version message""" - # with single stream - msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1]) - decoded = self._decode_msg(msg, "IQQiiQlsLv") - peer, _, ua, streams = self._decode_msg(msg, "IQQiiQlsLv")[4:] - self.assertEqual( - peer, Node(11 if state.dandelion else 3, '127.0.0.1', 8444)) - self.assertEqual(ua, '/PyBitmessage:' + softwareVersion + '/') - self.assertEqual(streams, [1]) - # with multiple streams - msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1, 2, 3]) - decoded = self._decode_msg(msg, "IQQiiQlslv") - peer, _, ua = decoded[4:7] - streams = decoded[7:] - self.assertEqual(streams, [1, 2, 3]) - - def test_insert_method_msgid(self): - """Test insert method of helper_sent module with message sending""" - fromAddress = 'BM-2cTrmD22fLRrumi3pPLg1ELJ6PdAaTRTdfg' - toAddress = 'BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTpEgU9U' - message = 'test message' - subject = 'test subject' - result = helper_sent.insert( - toAddress=toAddress, fromAddress=fromAddress, - subject=subject, message=message - ) - queryreturn = sqlQuery( - '''select msgid from sent where ackdata=?''', result) - self.assertNotEqual(queryreturn[0][0] if queryreturn else '', '') - - column_type = sqlQuery( - '''select typeof(msgid) from sent where ackdata=?''', result) - self.assertEqual(column_type[0][0] if column_type else '', 'text') - - @unittest.skipIf(frozen, 'not packed test_pattern into the bundle') - def test_old_knownnodes_pickle(self): - """Testing old (v0.6.2) version knownnodes.dat file""" - try: - self._load_knownnodes( - os.path.join( - os.path.abspath(os.path.dirname(__file__)), - 'test_pattern', 'knownnodes.dat')) - except self.failureException: - raise - finally: - cleanup(files=('knownnodes.dat',)) - - @staticmethod - def delete_address_from_addressbook(address): - """Clean up addressbook""" - sqlQuery('''delete from addressbook where address=?''', address) - - def test_add_same_address_twice_in_addressbook(self): - """checking same address is added twice in addressbook""" - self.assertTrue( - helper_addressbook.insert(label='test1', address=self.addr)) - self.assertFalse( - helper_addressbook.insert(label='test1', address=self.addr)) - self.delete_address_from_addressbook(self.addr) - - def test_is_address_present_in_addressbook(self): - """checking is address added in addressbook or not""" - helper_addressbook.insert(label='test1', address=self.addr) - queryreturn = sqlQuery( - 'select count(*) from addressbook where address=?', self.addr) - self.assertEqual(queryreturn[0][0], 1) - self.delete_address_from_addressbook(self.addr) - - def test_adding_two_same_case_sensitive_addresses(self): - """Testing same case sensitive address store in addressbook""" - address1 = 'BM-2cVWtdUzPwF7UNGDrZftWuHWiJ6xxBpiSP' - address2 = 'BM-2CvwTDuZpWf7ungdRzFTwUhwIj6XXbPIsp' - self.assertTrue( - helper_addressbook.insert(label='test1', address=address1)) - self.assertTrue( - helper_addressbook.insert(label='test2', address=address2)) - self.delete_address_from_addressbook(address1) - self.delete_address_from_addressbook(address2) - - -def run(): - """Starts all tests intended for core run""" - loader = unittest.defaultTestLoader - loader.sortTestMethodsUsing = None - suite = loader.loadTestsFromTestCase(TestCore) - if frozen: - try: - from pybitmessage import tests - suite.addTests(loader.loadTestsFromModule(tests)) - except ImportError: - pass - try: - from pyelliptic import tests - suite.addTests(loader.loadTestsFromModule(tests)) - except ImportError: - pass - try: - import bitmessageqt.tests - from xvfbwrapper import Xvfb - except ImportError: - Xvfb = None - else: - qt_tests = loader.loadTestsFromModule(bitmessageqt.tests) - suite.addTests(qt_tests) - - def keep_exc(ex_cls, exc, tb): # pylint: disable=unused-argument - """Own exception hook for test cases""" - excQueue.put(('tests', exc)) - - sys.excepthook = keep_exc - - if Xvfb: - vdisplay = Xvfb(width=1024, height=768) - vdisplay.start() - atexit.register(vdisplay.stop) - return unittest.TextTestRunner(verbosity=2).run(suite) diff --git a/src/tests/mockbm/__init__.py b/src/tests/mockbm/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/tests/mockbm/bitmessagemock.py b/src/tests/mockbm/bitmessagemock.py deleted file mode 100644 index d9ee857b..00000000 --- a/src/tests/mockbm/bitmessagemock.py +++ /dev/null @@ -1,32 +0,0 @@ -# pylint: disable=no-name-in-module, import-error - -""" -Bitmessage mock -""" - -from pybitmessage.class_addressGenerator import addressGenerator -from pybitmessage.inventory import Inventory -from pybitmessage.mpybit import NavigateApp -from pybitmessage import state - - -class MockMain(object): # pylint: disable=too-few-public-methods - """Mock main function""" - - def __init__(self): - """Start main application""" - addressGeneratorThread = addressGenerator() - # close the main program even if there are threads left - addressGeneratorThread.start() - Inventory() - state.kivyapp = NavigateApp() - state.kivyapp.run() - - -def main(): - """Triggers main module""" - MockMain() - - -if __name__ == "__main__": - main() diff --git a/src/tests/mockbm/images b/src/tests/mockbm/images deleted file mode 120000 index 847b03ed..00000000 --- a/src/tests/mockbm/images +++ /dev/null @@ -1 +0,0 @@ -../../images/ \ No newline at end of file diff --git a/src/tests/mockbm/kivy_main.py b/src/tests/mockbm/kivy_main.py deleted file mode 100644 index 79bb413e..00000000 --- a/src/tests/mockbm/kivy_main.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Mock kivy app with mock threads.""" -from pybitmessage import state - -if __name__ == '__main__': - state.kivy = True - print("Kivy Loading......") - from bitmessagemock import main - main() diff --git a/src/tests/mockbm/pybitmessage/__init__.py b/src/tests/mockbm/pybitmessage/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/tests/mockbm/pybitmessage/addresses.py b/src/tests/mockbm/pybitmessage/addresses.py deleted file mode 120000 index 88fcee82..00000000 --- a/src/tests/mockbm/pybitmessage/addresses.py +++ /dev/null @@ -1 +0,0 @@ -../../../addresses.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/bmconfigparser.py b/src/tests/mockbm/pybitmessage/bmconfigparser.py deleted file mode 120000 index da05040e..00000000 --- a/src/tests/mockbm/pybitmessage/bmconfigparser.py +++ /dev/null @@ -1 +0,0 @@ -../../../bmconfigparser.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/class_addressGenerator.py b/src/tests/mockbm/pybitmessage/class_addressGenerator.py deleted file mode 100644 index 34258bbc..00000000 --- a/src/tests/mockbm/pybitmessage/class_addressGenerator.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -A thread for creating addresses -""" - -from six.moves import queue - -from pybitmessage import state -from pybitmessage import queues - -from pybitmessage.bmconfigparser import BMConfigParser - -from pybitmessage.network.threads import StoppableThread - - -fake_addresses = { - 'BM-2cUgQGcTLWAkC6dNsv2Bc8XB3Y1GEesVLV': { - 'privsigningkey': '5KWXwYq1oJMzghUSJaJoWPn8VdeBbhDN8zFot1cBd6ezKKReqBd', - 'privencryptionkey': '5JaeFJs8iPcQT3N8676r3gHKvJ5mTWXy1VLhGCEDqRs4vpvpxV8' - }, - 'BM-2cUd2dm8MVMokruMTcGhhteTpyRZCAMhnA': { - 'privsigningkey': '5JnJ79nkcwjo4Aj7iG8sFMkzYoQqWfpUjTcitTuFJZ1YKHZz98J', - 'privencryptionkey': '5JXgNzTRouFLqSRFJvuHMDHCYPBvTeMPBiHt4Jeb6smNjhUNTYq' - }, - 'BM-2cWyvL54WytfALrJHZqbsDHca5QkrtByAW': { - 'privsigningkey': '5KVE4gLmcfYVicLdgyD4GmnbBTFSnY7Yj2UCuytQqgBBsfwDhpi', - 'privencryptionkey': '5JTw48CGm5CP8fyJUJQMq8HQANQMHDHp2ETUe1dgm6EFpT1egD7' - }, - 'BM-2cTE65PK9Y4AQEkCZbazV86pcQACocnRXd': { - 'privsigningkey': '5KCuyReHx9MB4m5hhEyCWcLEXqc8rxhD1T2VWk8CicPFc8B6LaZ', - 'privencryptionkey': '5KBRpwXdX3n2tP7f583SbFgfzgs6Jemx7qfYqhdH7B1Vhe2jqY6' - }, - 'BM-2cX5z1EgmJ87f2oKAwXdv4VQtEVwr2V3BG': { - 'privsigningkey': '5K5UK7qED7F1uWCVsehudQrszLyMZxFVnP6vN2VDQAjtn5qnyRK', - 'privencryptionkey': '5J5coocoJBX6hy5DFTWKtyEgPmADpSwfQTazMpU7QPeART6oMAu' - } -} - - -class addressGenerator(StoppableThread): - """A thread for creating fake addresses""" - name = "addressGenerator" - address_list = list(fake_addresses.keys()) - - def stopThread(self): - """"To stop address generator thread""" - try: - queues.addressGeneratorQueue.put(("stopThread", "data")) - except queue.Full: - self.logger.warning('addressGeneratorQueue is Full') - super(addressGenerator, self).stopThread() # pylint: disable=super-with-arguments - - def run(self): - """ - Process the requests for addresses generation - from `.queues.addressGeneratorQueue` - """ - while state.shutdown == 0: - queueValue = queues.addressGeneratorQueue.get() - try: - address = self.address_list.pop(0) - except IndexError: - self.logger.error( - 'Program error: you can only create 5 fake addresses') - continue - - if len(queueValue) >= 3: - label = queueValue[3] - else: - label = '' - - BMConfigParser().add_section(address) - BMConfigParser().set(address, 'label', label) - BMConfigParser().set(address, 'enabled', 'true') - BMConfigParser().set( - address, 'privsigningkey', fake_addresses[address]['privsigningkey']) - BMConfigParser().set( - address, 'privencryptionkey', fake_addresses[address]['privencryptionkey']) - BMConfigParser().save() - - queues.addressGeneratorQueue.task_done() diff --git a/src/tests/mockbm/pybitmessage/inventory.py b/src/tests/mockbm/pybitmessage/inventory.py deleted file mode 100644 index 6173c3cd..00000000 --- a/src/tests/mockbm/pybitmessage/inventory.py +++ /dev/null @@ -1,15 +0,0 @@ -"""The Inventory singleton""" - -# TODO make this dynamic, and watch out for frozen, like with messagetypes -from pybitmessage.singleton import Singleton - - -# pylint: disable=old-style-class,too-few-public-methods -@Singleton -class Inventory(): - """ - Inventory singleton class which uses storage backends - to manage the inventory. - """ - def __init__(self): - self.numberOfInventoryLookupsPerformed = 0 diff --git a/src/tests/mockbm/pybitmessage/network/__init__.py b/src/tests/mockbm/pybitmessage/network/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/tests/mockbm/pybitmessage/network/threads.py b/src/tests/mockbm/pybitmessage/network/threads.py deleted file mode 120000 index c95b4c36..00000000 --- a/src/tests/mockbm/pybitmessage/network/threads.py +++ /dev/null @@ -1 +0,0 @@ -../../../../network/threads.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/queues.py b/src/tests/mockbm/pybitmessage/queues.py deleted file mode 120000 index 8c556015..00000000 --- a/src/tests/mockbm/pybitmessage/queues.py +++ /dev/null @@ -1 +0,0 @@ -../../../queues.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/shutdown.py b/src/tests/mockbm/pybitmessage/shutdown.py deleted file mode 100644 index 08c885d8..00000000 --- a/src/tests/mockbm/pybitmessage/shutdown.py +++ /dev/null @@ -1,11 +0,0 @@ -# pylint: disable=invalid-name -"""shutdown function""" - -from pybitmessage import state - - -def doCleanShutdown(): - """ - Used to exit Kivy UI. - """ - state.shutdown = 1 diff --git a/src/tests/mockbm/pybitmessage/singleton.py b/src/tests/mockbm/pybitmessage/singleton.py deleted file mode 120000 index 5e112567..00000000 --- a/src/tests/mockbm/pybitmessage/singleton.py +++ /dev/null @@ -1 +0,0 @@ -../../../singleton.py \ No newline at end of file diff --git a/src/tests/mockbm/pybitmessage/state.py b/src/tests/mockbm/pybitmessage/state.py deleted file mode 120000 index 117203f5..00000000 --- a/src/tests/mockbm/pybitmessage/state.py +++ /dev/null @@ -1 +0,0 @@ -../../../state.py \ No newline at end of file diff --git a/src/tests/partial.py b/src/tests/partial.py deleted file mode 100644 index 870f6626..00000000 --- a/src/tests/partial.py +++ /dev/null @@ -1,41 +0,0 @@ -"""A test case for partial run class definition""" - -import os -import sys -import unittest - -from pybitmessage import pathmagic - - -class TestPartialRun(unittest.TestCase): - """ - A base class for test cases running some parts of the app, - e.g. separate threads or packages. - """ - - @classmethod - def setUpClass(cls): - # pylint: disable=import-outside-toplevel,unused-import - cls.dirs = (os.path.abspath(os.curdir), pathmagic.setup()) - - import bmconfigparser - import state - - from debug import logger # noqa:F401 pylint: disable=unused-variable - if sys.hexversion >= 0x3000000: - # pylint: disable=no-name-in-module,relative-import - from mockbm import network as network_mock - import network - network.stats = network_mock.stats - - state.shutdown = 0 - cls.state = state - bmconfigparser.config = cls.config = bmconfigparser.BMConfigParser() - cls.config.read() - - @classmethod - def tearDownClass(cls): - cls.state.shutdown = 1 - # deactivate pathmagic - os.chdir(cls.dirs[0]) - sys.path.remove(cls.dirs[1]) diff --git a/src/tests/samples.py b/src/tests/samples.py deleted file mode 100644 index e33c5f50..00000000 --- a/src/tests/samples.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Various sample data""" - -from binascii import unhexlify - - -# 500 identical peers: -# 1626611891, 1, 1, 127.0.0.1, 8444 -sample_addr_data = unhexlify( - 'fd01f4' + ( - '0000000060f420b30000000' - '1000000000000000100000000000000000000ffff7f00000120fc' - ) * 500 -) - -# These keys are from addresses test script -sample_pubsigningkey = unhexlify( - '044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09d' - '16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') -sample_pubencryptionkey = unhexlify( - '044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3c' - 'e7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') -sample_privsigningkey = \ - b'93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' -sample_privencryptionkey = \ - b'4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a' - -sample_ripe = b'003cd097eb7f35c87b5dc8b4538c22cb55312a9f' -# stream: 1, version: 2 -sample_address = 'BM-onkVu1KKL2UaUss5Upg9vXmqd3esTmV79' - -sample_factor = \ - 66858749573256452658262553961707680376751171096153613379801854825275240965733 -# G * sample_factor -sample_point = ( - 33567437183004486938355437500683826356288335339807546987348409590129959362313, - 94730058721143827257669456336351159718085716196507891067256111928318063085006 -) - -sample_seed = b'TIGER, tiger, burning bright. In the forests of the night' -# Deterministic addresses with stream 1 and versions 3, 4 -sample_deterministic_ripe = b'00cfb69416ae76f68a81c459de4e13460c7d17eb' -sample_deterministic_addr3 = 'BM-2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN' -sample_deterministic_addr4 = 'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK' -sample_daddr3_512 = 18875720106589866286514488037355423395410802084648916523381 -sample_daddr4_512 = 25152821841976547050350277460563089811513157529113201589004 - -sample_statusbar_msg = 'new status bar message' -sample_inbox_msg_ids = [ - '27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', - '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] -# second address in sample_subscription_addresses is -# for the announcement broadcast, but is it matter? -sample_subscription_addresses = [ - 'BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', - 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] -sample_subscription_name = 'test sub' - - -sample_object_expires = 1712271487 -# from minode import structure -# obj = structure.Object( -# b'\x00' * 8, sample_object_expires, 42, 1, 2, b'HELLO') -# .. do pow and obj.to_bytes() -sample_object_data = unhexlify( - '00000000001be7fc00000000660f307f0000002a010248454c4c4f') - -sample_msg = unhexlify( - '0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff' - '1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200') -sample_sig = unhexlify( - '304402202302475351db6b822de15d922e29397541f10d8a19780ba2ca4a920b1035f075' - '02205e5bba40d5f07a24c23a89ba5f01a3828371dfbb685dd5375fa1c29095fd232b') -sample_sig_sha1 = unhexlify( - '30460221008ad234687d1bdc259932e28ea6ee091b88b0900d8134902aa8c2fd7f016b96e' - 'd022100dafb94e28322c2fa88878f9dcbf0c2d33270466ab3bbffaec3dca0a2d1ef9354') diff --git a/src/tests/sql/init_version_10.sql b/src/tests/sql/init_version_10.sql deleted file mode 100644 index b1764e76..00000000 --- a/src/tests/sql/init_version_10.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `addressbook` VALUES ('test', "BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz"), ('testone', "BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz"); diff --git a/src/tests/sql/init_version_2.sql b/src/tests/sql/init_version_2.sql deleted file mode 100644 index 133284ec..00000000 --- a/src/tests/sql/init_version_2.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `inventory` VALUES ('hash', 1, 1,1, 1,'test'); diff --git a/src/tests/sql/init_version_3.sql b/src/tests/sql/init_version_3.sql deleted file mode 100644 index 875d859d..00000000 --- a/src/tests/sql/init_version_3.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `settings` VALUES ('version','3'); diff --git a/src/tests/sql/init_version_4.sql b/src/tests/sql/init_version_4.sql deleted file mode 100644 index ea3f1768..00000000 --- a/src/tests/sql/init_version_4.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `pubkeys` VALUES ('hash', 1, 1, 1,'test'); diff --git a/src/tests/sql/init_version_5.sql b/src/tests/sql/init_version_5.sql deleted file mode 100644 index b894c038..00000000 --- a/src/tests/sql/init_version_5.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `objectprocessorqueue` VALUES ('hash', 1); diff --git a/src/tests/sql/init_version_6.sql b/src/tests/sql/init_version_6.sql deleted file mode 100644 index 7cd30571..00000000 --- a/src/tests/sql/init_version_6.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `inventory` VALUES ('hash', 1, 1, 1,'test','test'); diff --git a/src/tests/sql/init_version_7.sql b/src/tests/sql/init_version_7.sql deleted file mode 100644 index bd87f8d8..00000000 --- a/src/tests/sql/init_version_7.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `sent` VALUES -(1,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test1 subject','message test 1','ackdata',1638176409,1638176409,1638176423,'msgqueued',1,'testfolder',1,2), -(2,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test2 subject','message test 2','ackdata',1638176423,1638176423,1638176423,'msgqueued',1,'testfolder',1,2); diff --git a/src/tests/sql/init_version_8.sql b/src/tests/sql/init_version_8.sql deleted file mode 100644 index 9d9b6f3a..00000000 --- a/src/tests/sql/init_version_8.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `inbox` VALUES (1, "poland", "malasia", "test", "yes", "test message", "folder", 1, 1, 1); diff --git a/src/tests/sql/init_version_9.sql b/src/tests/sql/init_version_9.sql deleted file mode 100644 index 764634d2..00000000 --- a/src/tests/sql/init_version_9.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO `sent` VALUES -(1,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test1 subject','message test 1','ackdata',1638176409,1638176409,1638176423,'msgqueued',1,'testfolder',1,2); diff --git a/src/tests/test_addresses.py b/src/tests/test_addresses.py deleted file mode 100644 index 8f9c283d..00000000 --- a/src/tests/test_addresses.py +++ /dev/null @@ -1,61 +0,0 @@ - -import unittest -from binascii import unhexlify - -from pybitmessage import addresses - -from .samples import ( - sample_address, sample_daddr3_512, sample_daddr4_512, - sample_deterministic_addr4, sample_deterministic_addr3, - sample_deterministic_ripe, sample_ripe) - -sample_addr3 = sample_deterministic_addr3.split('-')[1] -sample_addr4 = sample_deterministic_addr4.split('-')[1] - - -class TestAddresses(unittest.TestCase): - """Test addresses manipulations""" - - def test_decode(self): - """Decode some well known addresses and check the result""" - self.assertEqual( - addresses.decodeAddress(sample_address), - ('success', 2, 1, unhexlify(sample_ripe))) - - status, version, stream, ripe1 = addresses.decodeAddress( - sample_deterministic_addr4) - self.assertEqual(status, 'success') - self.assertEqual(stream, 1) - self.assertEqual(version, 4) - status, version, stream, ripe2 = addresses.decodeAddress(sample_addr3) - self.assertEqual(status, 'success') - self.assertEqual(stream, 1) - self.assertEqual(version, 3) - self.assertEqual(ripe1, ripe2) - self.assertEqual(ripe1, unhexlify(sample_deterministic_ripe)) - - def test_encode(self): - """Encode sample ripe and compare the result to sample address""" - self.assertEqual( - sample_address, - addresses.encodeAddress(2, 1, unhexlify(sample_ripe))) - ripe = unhexlify(sample_deterministic_ripe) - self.assertEqual( - addresses.encodeAddress(3, 1, ripe), - 'BM-%s' % addresses.encodeBase58(sample_daddr3_512)) - - def test_base58(self): - """Check Base58 encoding and decoding""" - self.assertEqual(addresses.decodeBase58('1'), 0) - self.assertEqual(addresses.decodeBase58('!'), 0) - self.assertEqual( - addresses.decodeBase58(sample_addr4), sample_daddr4_512) - self.assertEqual( - addresses.decodeBase58(sample_addr3), sample_daddr3_512) - - self.assertEqual(addresses.encodeBase58(0), '1') - self.assertEqual(addresses.encodeBase58(-1), None) - self.assertEqual( - sample_addr4, addresses.encodeBase58(sample_daddr4_512)) - self.assertEqual( - sample_addr3, addresses.encodeBase58(sample_daddr3_512)) diff --git a/src/tests/test_addressgenerator.py b/src/tests/test_addressgenerator.py deleted file mode 100644 index 236b4d8b..00000000 --- a/src/tests/test_addressgenerator.py +++ /dev/null @@ -1,87 +0,0 @@ -"""Tests for AddressGenerator (with thread or not)""" - -from binascii import unhexlify - -from six.moves import queue - -from .partial import TestPartialRun -from .samples import ( - sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, - sample_deterministic_ripe) - -TEST_LABEL = 'test' - - -class TestAddressGenerator(TestPartialRun): - """Test case for AddressGenerator thread""" - - @classmethod - def setUpClass(cls): - super(TestAddressGenerator, cls).setUpClass() - - import defaults - import queues - from class_addressGenerator import addressGenerator - - cls.state.enableGUI = False - - cls.command_queue = queues.addressGeneratorQueue - cls.return_queue = queues.apiAddressGeneratorReturnQueue - cls.worker_queue = queues.workerQueue - - cls.config.set( - 'bitmessagesettings', 'defaultnoncetrialsperbyte', - str(defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) - cls.config.set( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes', - str(defaults.networkDefaultPayloadLengthExtraBytes)) - - thread = addressGenerator() - thread.daemon = True - thread.start() - - def _execute(self, command, *args): - self.command_queue.put((command,) + args) - try: - return self.return_queue.get(timeout=30)[0] - except (IndexError, queue.Empty): - self.fail('Failed to execute command %s' % command) - - def test_deterministic(self): - """Test deterministic commands""" - self.command_queue.put(( - 'getDeterministicAddress', 3, 1, - TEST_LABEL, 1, sample_seed, False)) - self.assertEqual(sample_deterministic_addr3, self.return_queue.get()) - - self.assertEqual( - sample_deterministic_addr3, - self._execute( - 'createDeterministicAddresses', 3, 1, TEST_LABEL, 2, - sample_seed, False, 0, 0)) - - try: - self.assertEqual( - self.worker_queue.get(timeout=30), - ('sendOutOrStoreMyV3Pubkey', - unhexlify(sample_deterministic_ripe))) - - self.worker_queue.get(timeout=30) # get the next addr's task - except queue.Empty: - self.fail('No commands in the worker queue') - - self.assertEqual( - sample_deterministic_addr4, - self._execute('createChan', 4, 1, TEST_LABEL, sample_seed, True)) - try: - self.assertEqual( - self.worker_queue.get(), - ('sendOutOrStoreMyV4Pubkey', sample_deterministic_addr4)) - except queue.Empty: - self.fail('No commands in the worker queue') - self.assertEqual( - self.config.get(sample_deterministic_addr4, 'label'), TEST_LABEL) - self.assertTrue( - self.config.getboolean(sample_deterministic_addr4, 'chan')) - self.assertTrue( - self.config.getboolean(sample_deterministic_addr4, 'enabled')) diff --git a/src/tests/test_api.py b/src/tests/test_api.py deleted file mode 100644 index 2a4640fa..00000000 --- a/src/tests/test_api.py +++ /dev/null @@ -1,441 +0,0 @@ -""" -Tests using API. -""" - -import base64 -import json -import time - -from binascii import hexlify -from six.moves import xmlrpc_client # nosec - -import psutil - -from .samples import ( - sample_deterministic_addr3, sample_deterministic_addr4, sample_seed, - sample_inbox_msg_ids, - sample_subscription_addresses, sample_subscription_name -) - -from .test_process import TestProcessProto - - -class TestAPIProto(TestProcessProto): - """Test case logic for testing API""" - _process_cmd = ['pybitmessage', '-t'] - - @classmethod - def setUpClass(cls): - """Setup XMLRPC proxy for pybitmessage API""" - super(TestAPIProto, cls).setUpClass() - cls.addresses = [] - cls.api = xmlrpc_client.ServerProxy( - "http://username:password@127.0.0.1:8442/") - for _ in range(5): - if cls._get_readline('.api_started'): - return - time.sleep(1) - - -class TestAPIShutdown(TestAPIProto): - """Separate test case for API command 'shutdown'""" - def test_shutdown(self): - """Shutdown the pybitmessage""" - self.assertEqual(self.api.shutdown(), 'done') - try: - self.process.wait(20) - except psutil.TimeoutExpired: - self.fail( - '%s has not stopped in 20 sec' % ' '.join(self._process_cmd)) - - -# TODO: uncovered API commands -# disseminatePreEncryptedMsg -# disseminatePubkey -# getMessageDataByDestinationHash - - -class TestAPI(TestAPIProto): - """Main API test case""" - _seed = base64.encodestring(sample_seed) - - def _add_random_address(self, label): - addr = self.api.createRandomAddress(base64.encodestring(label)) - return addr - - def test_user_password(self): - """Trying to connect with wrong username/password""" - api_wrong = xmlrpc_client.ServerProxy( - "http://test:wrong@127.0.0.1:8442/") - with self.assertRaises(xmlrpc_client.ProtocolError): - api_wrong.clientStatus() - - def test_connection(self): - """API command 'helloWorld'""" - self.assertEqual( - self.api.helloWorld('hello', 'world'), - 'hello-world' - ) - - def test_arithmetic(self): - """API command 'add'""" - self.assertEqual(self.api.add(69, 42), 111) - - def test_invalid_method(self): - """Issuing nonexistent command 'test'""" - self.assertEqual( - self.api.test(), - 'API Error 0020: Invalid method: test' - ) - - def test_message_inbox(self): - """Test message inbox methods""" - self.assertEqual( - len(json.loads( - self.api.getAllInboxMessages())["inboxMessages"]), - 4, - # Custom AssertError message for details - json.loads(self.api.getAllInboxMessages())["inboxMessages"] - ) - self.assertEqual( - len(json.loads( - self.api.getAllInboxMessageIds())["inboxMessageIds"]), - 4 - ) - self.assertEqual( - len(json.loads( - self.api.getInboxMessageById( - hexlify(sample_inbox_msg_ids[2])))["inboxMessage"]), - 1 - ) - self.assertEqual( - len(json.loads( - self.api.getInboxMessagesByReceiver( - sample_deterministic_addr4))["inboxMessages"]), - 4 - ) - - def test_message_trash(self): - """Test message inbox methods""" - - messages_before_delete = len( - json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]) - for msgid in sample_inbox_msg_ids[:2]: - self.assertEqual( - self.api.trashMessage(hexlify(msgid)), - 'Trashed message (assuming message existed).' - ) - self.assertEqual(len( - json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"] - ), messages_before_delete - 2) - for msgid in sample_inbox_msg_ids[:2]: - self.assertEqual( - self.api.undeleteMessage(hexlify(msgid)), - 'Undeleted message' - ) - self.assertEqual(len( - json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"] - ), messages_before_delete) - - def test_clientstatus_consistency(self): - """If networkStatus is notConnected networkConnections should be 0""" - status = json.loads(self.api.clientStatus()) - if status["networkStatus"] == "notConnected": - self.assertEqual(status["networkConnections"], 0) - else: - self.assertGreater(status["networkConnections"], 0) - - def test_listconnections_consistency(self): - """Checking the return of API command 'listConnections'""" - result = json.loads(self.api.listConnections()) - self.assertGreaterEqual(len(result["inbound"]), 0) - self.assertGreaterEqual(len(result["outbound"]), 0) - - def test_list_addresses(self): - """Checking the return of API command 'listAddresses'""" - self.assertEqual( - json.loads(self.api.listAddresses()).get('addresses'), - self.addresses - ) - - def test_decode_address(self): - """Checking the return of API command 'decodeAddress'""" - result = json.loads( - self.api.decodeAddress(sample_deterministic_addr4)) - self.assertEqual(result.get('status'), 'success') - self.assertEqual(result['addressVersion'], 4) - self.assertEqual(result['streamNumber'], 1) - - def test_create_deterministic_addresses(self): - """Test creation of deterministic addresses""" - self.assertEqual( - self.api.getDeterministicAddress(self._seed, 4, 1), - sample_deterministic_addr4) - self.assertEqual( - self.api.getDeterministicAddress(self._seed, 3, 1), - sample_deterministic_addr3) - self.assertRegexpMatches( - self.api.getDeterministicAddress(self._seed, 2, 1), - r'^API Error 0002:') - - # This is here until the streams will be implemented - self.assertRegexpMatches( - self.api.getDeterministicAddress(self._seed, 3, 2), - r'API Error 0003:') - self.assertRegexpMatches( - self.api.createDeterministicAddresses(self._seed, 1, 4, 2), - r'API Error 0003:') - - self.assertRegexpMatches( - self.api.createDeterministicAddresses('', 1), - r'API Error 0001:') - self.assertRegexpMatches( - self.api.createDeterministicAddresses(self._seed, 1, 2), - r'API Error 0002:') - self.assertRegexpMatches( - self.api.createDeterministicAddresses(self._seed, 0), - r'API Error 0004:') - self.assertRegexpMatches( - self.api.createDeterministicAddresses(self._seed, 1000), - r'API Error 0005:') - - addresses = json.loads( - self.api.createDeterministicAddresses(self._seed, 2, 4) - )['addresses'] - self.assertEqual(len(addresses), 2) - self.assertEqual(addresses[0], sample_deterministic_addr4) - for addr in addresses: - self.assertEqual(self.api.deleteAddress(addr), 'success') - - def test_create_random_address(self): - """API command 'createRandomAddress': basic BM-address validation""" - addr = self._add_random_address('random_1') - self.assertRegexpMatches(addr, r'^BM-') - self.assertRegexpMatches(addr[3:], r'[a-zA-Z1-9]+$') - # Whitepaper says "around 36 character" - self.assertLessEqual(len(addr[3:]), 40) - self.assertEqual(self.api.deleteAddress(addr), 'success') - - def test_addressbook(self): - """Testing API commands for addressbook manipulations""" - # Initially it's empty - self.assertEqual( - json.loads(self.api.listAddressBookEntries()).get('addresses'), - [] - ) - # Add known address - self.api.addAddressBookEntry( - sample_deterministic_addr4, - base64.encodestring('tiger_4') - ) - # Check addressbook entry - entries = json.loads( - self.api.listAddressBookEntries()).get('addresses')[0] - self.assertEqual( - entries['address'], sample_deterministic_addr4) - self.assertEqual( - base64.decodestring(entries['label']), 'tiger_4') - # Try sending to this address (#1898) - addr = self._add_random_address('random_2') - # TODO: it was never deleted - msg = base64.encodestring('test message') - msg_subject = base64.encodestring('test_subject') - result = self.api.sendMessage( - sample_deterministic_addr4, addr, msg_subject, msg) - self.assertNotRegexpMatches(result, r'^API Error') - self.api.deleteAddress(addr) - # Remove known address - self.api.deleteAddressBookEntry(sample_deterministic_addr4) - # Addressbook should be empty again - self.assertEqual( - json.loads(self.api.listAddressBookEntries()).get('addresses'), - [] - ) - - def test_subscriptions(self): - """Testing the API commands related to subscriptions""" - - self.assertEqual( - self.api.addSubscription( - sample_subscription_addresses[0], - sample_subscription_name.encode('base64')), - 'Added subscription.' - ) - - added_subscription = {'label': None, 'enabled': False} - # check_address - for sub in json.loads(self.api.listSubscriptions())['subscriptions']: - # special address, added when sqlThread starts - if sub['address'] == sample_subscription_addresses[0]: - added_subscription = sub - self.assertEqual( - base64.decodestring(sub['label']), sample_subscription_name - ) - self.assertTrue(sub['enabled']) - break - - self.assertEqual( - base64.decodestring(added_subscription['label']) - if added_subscription['label'] else None, - sample_subscription_name) - self.assertTrue(added_subscription['enabled']) - - for s in json.loads(self.api.listSubscriptions())['subscriptions']: - # special address, added when sqlThread starts - if s['address'] == sample_subscription_addresses[1]: - self.assertEqual( - base64.decodestring(s['label']), - 'Bitmessage new releases/announcements') - self.assertTrue(s['enabled']) - break - else: - self.fail( - 'Could not find Bitmessage new releases/announcements' - ' in subscriptions') - self.assertEqual( - self.api.deleteSubscription(sample_subscription_addresses[0]), - 'Deleted subscription if it existed.') - self.assertEqual( - self.api.deleteSubscription(sample_subscription_addresses[1]), - 'Deleted subscription if it existed.') - self.assertEqual( - json.loads(self.api.listSubscriptions())['subscriptions'], []) - - def test_send(self): - """Test message sending""" - addr = self._add_random_address('random_2') - msg = base64.encodestring('test message') - msg_subject = base64.encodestring('test_subject') - ackdata = self.api.sendMessage( - sample_deterministic_addr4, addr, msg_subject, msg) - try: - # Check ackdata and message status - int(ackdata, 16) - status = self.api.getStatus(ackdata) - if status == 'notfound': - raise KeyError - self.assertIn( - status, ( - 'msgqueued', 'awaitingpubkey', 'msgsent', 'ackreceived', - 'doingpubkeypow', 'doingmsgpow', 'msgsentnoackexpected' - )) - # Find the message in sent - for m in json.loads( - self.api.getSentMessagesByAddress(addr))['sentMessages']: - if m['ackData'] == ackdata: - sent_msg = m['message'] - break - else: - raise KeyError - except ValueError: - self.fail('sendMessage returned error or ackData is not hex') - except KeyError: - self.fail('Could not find sent message in sent messages') - else: - # Check found message - try: - self.assertEqual(sent_msg, msg.strip()) - except UnboundLocalError: - self.fail('Could not find sent message in sent messages') - # self.assertEqual(inbox_msg, msg.strip()) - self.assertEqual(json.loads( - self.api.getSentMessageByAckData(ackdata) - )['sentMessage'][0]['message'], sent_msg) - # Trash the message - self.assertEqual( - self.api.trashSentMessageByAckData(ackdata), - 'Trashed sent message (assuming message existed).') - # Empty trash - self.assertEqual(self.api.deleteAndVacuum(), 'done') - # The message should disappear - self.assertIsNone(json.loads( - self.api.getSentMessageByAckData(ackdata))) - finally: - self.assertEqual(self.api.deleteAddress(addr), 'success') - - def test_send_broadcast(self): - """Test broadcast sending""" - addr = self._add_random_address('random_2') - msg = base64.encodestring('test broadcast') - ackdata = self.api.sendBroadcast( - addr, base64.encodestring('test_subject'), msg) - - try: - int(ackdata, 16) - status = self.api.getStatus(ackdata) - if status == 'notfound': - raise KeyError - self.assertIn(status, ( - 'doingbroadcastpow', 'broadcastqueued', 'broadcastsent')) - - start = time.time() - while status != 'broadcastsent': - spent = int(time.time() - start) - if spent > 30: - self.fail('PoW is taking too much time: %ss' % spent) - time.sleep(1) # wait for PoW to get final msgid on next step - status = self.api.getStatus(ackdata) - - # Find the message and its ID in sent - for m in json.loads(self.api.getAllSentMessages())['sentMessages']: - if m['ackData'] == ackdata: - sent_msg = m['message'] - sent_msgid = m['msgid'] - break - else: - raise KeyError - except ValueError: - self.fail('sendBroadcast returned error or ackData is not hex') - except KeyError: - self.fail('Could not find sent broadcast in sent messages') - else: - # Check found message and its ID - try: - self.assertEqual(sent_msg, msg.strip()) - except UnboundLocalError: - self.fail('Could not find sent message in sent messages') - self.assertEqual(json.loads( - self.api.getSentMessageById(sent_msgid) - )['sentMessage'][0]['message'], sent_msg) - self.assertIn( - {'msgid': sent_msgid}, json.loads( - self.api.getAllSentMessageIds())['sentMessageIds']) - # Trash the message by ID - self.assertEqual( - self.api.trashSentMessage(sent_msgid), - 'Trashed sent message (assuming message existed).') - self.assertEqual(self.api.deleteAndVacuum(), 'done') - self.assertIsNone(json.loads( - self.api.getSentMessageById(sent_msgid))) - # Try sending from disabled address - self.assertEqual(self.api.enableAddress(addr, False), 'success') - result = self.api.sendBroadcast( - addr, base64.encodestring('test_subject'), msg) - self.assertRegexpMatches(result, r'^API Error 0014:') - finally: - self.assertEqual(self.api.deleteAddress(addr), 'success') - - # sending from an address without private key - # (Bitmessage new releases/announcements) - result = self.api.sendBroadcast( - 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw', - base64.encodestring('test_subject'), msg) - self.assertRegexpMatches(result, r'^API Error 0013:') - - def test_chan(self): - """Testing chan creation/joining""" - # Create chan with known address - self.assertEqual( - self.api.createChan(self._seed), sample_deterministic_addr4) - # cleanup - self.assertEqual( - self.api.leaveChan(sample_deterministic_addr4), 'success') - # Join chan with addresses of version 3 or 4 - for addr in (sample_deterministic_addr4, sample_deterministic_addr3): - self.assertEqual(self.api.joinChan(self._seed, addr), 'success') - self.assertEqual(self.api.leaveChan(addr), 'success') - # Joining with wrong address should fail - self.assertRegexpMatches( - self.api.joinChan(self._seed, 'BM-2cWzSnwjJ7yRP3nLEW'), - r'^API Error 0008:' - ) diff --git a/src/tests/test_api_thread.py b/src/tests/test_api_thread.py deleted file mode 100644 index 5b004066..00000000 --- a/src/tests/test_api_thread.py +++ /dev/null @@ -1,94 +0,0 @@ -"""TestAPIThread class definition""" - -import sys -import time -from binascii import hexlify, unhexlify -from struct import pack - -from six.moves import queue, xmlrpc_client - -from pybitmessage import protocol -from pybitmessage.defaults import ( - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes) - -from .partial import TestPartialRun -from .samples import sample_statusbar_msg, sample_object_data - - -class TestAPIThread(TestPartialRun): - """Test case running the API thread""" - - @classmethod - def setUpClass(cls): - super(TestAPIThread, cls).setUpClass() - - import helper_sql - import queues - - # pylint: disable=too-few-public-methods - class SqlReadyMock(object): - """Mock helper_sql.sql_ready event with dummy class""" - @staticmethod - def wait(): - """Don't wait, return immediately""" - return - - helper_sql.sql_ready = SqlReadyMock - cls.queues = queues - - cls.config.set('bitmessagesettings', 'apiusername', 'username') - cls.config.set('bitmessagesettings', 'apipassword', 'password') - cls.config.set('inventory', 'storage', 'filesystem') - - import api - cls.thread = api.singleAPI() - cls.thread.daemon = True - cls.thread.start() - time.sleep(3) - cls.api = xmlrpc_client.ServerProxy( - "http://username:password@127.0.0.1:8442/") - - def test_connection(self): - """API command 'helloWorld'""" - self.assertEqual( - self.api.helloWorld('hello', 'world'), 'hello-world') - - def test_statusbar(self): - """Check UISignalQueue after issuing the 'statusBar' command""" - self.queues.UISignalQueue.queue.clear() - self.assertEqual( - self.api.statusBar(sample_statusbar_msg), 'success') - try: - cmd, data = self.queues.UISignalQueue.get(block=False) - except queue.Empty: - self.fail('UISignalQueue is empty!') - - self.assertEqual(cmd, 'updateStatusBar') - self.assertEqual(data, sample_statusbar_msg) - - def test_client_status(self): - """Ensure the reply of clientStatus corresponds to mock""" - status = self.api.clientStatus() - if sys.hexversion >= 0x3000000: - self.assertEqual(status["networkConnections"], 4) - self.assertEqual(status["pendingDownload"], 0) - - def test_disseminate_preencrypted(self): - """Call disseminatePreEncryptedMsg API command and check inventory""" - import proofofwork - from inventory import Inventory - - proofofwork.init() - update_object = pack( - '>Q', int(time.time() + 7200)) + sample_object_data[16:] - invhash = unhexlify(self.api.disseminatePreEncryptedMsg( - hexlify(update_object).decode(), - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes - )) - obj_type, obj_stream, obj_data = Inventory()[invhash][:3] - self.assertEqual(obj_type, 42) - self.assertEqual(obj_stream, 2) - self.assertEqual(sample_object_data[16:], obj_data[16:]) - self.assertTrue(protocol.isProofOfWorkSufficient(obj_data)) diff --git a/src/tests/test_config.py b/src/tests/test_config.py deleted file mode 100644 index 44db7c8a..00000000 --- a/src/tests/test_config.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Various tests for config -""" -import unittest - -from six import StringIO -from pybitmessage.bmconfigparser import BMConfigParser - -test_config = """[bitmessagesettings] -maxaddrperstreamsend = 100 -maxbootstrapconnections = 10 -maxdownloadrate = 0 -maxoutboundconnections = 8 -maxtotalconnections = 100 -maxuploadrate = 0 -apiinterface = 127.0.0.1 -apiport = 8442 -udp = True - -[threads] -receive = 3 - -[network] -bind = None -dandelion = 90 - -[inventory] -storage = sqlite -acceptmismatch = False - -[knownnodes] -maxnodes = 15000 - -[zlib] -maxsize = 1048576""" - - -# pylint: disable=protected-access -class TestConfig(unittest.TestCase): - """A test case for bmconfigparser""" - - def setUp(self): - self.config = BMConfigParser() - self.config.add_section('bitmessagesettings') - - def test_safeGet(self): - """safeGet retuns provided default for nonexistent option or None""" - self.assertIs( - self.config.safeGet('nonexistent', 'nonexistent'), None) - self.assertEqual( - self.config.safeGet('nonexistent', 'nonexistent', 42), 42) - - def test_safeGetBoolean(self): - """safeGetBoolean returns False for nonexistent option, no default""" - self.assertIs( - self.config.safeGetBoolean('nonexistent', 'nonexistent'), False) - # no arg for default - # pylint: disable=too-many-function-args - with self.assertRaises(TypeError): - self.config.safeGetBoolean('nonexistent', 'nonexistent', True) - - def test_safeGetInt(self): - """safeGetInt retuns provided default for nonexistent option or 0""" - self.assertEqual( - self.config.safeGetInt('nonexistent', 'nonexistent'), 0) - self.assertEqual( - self.config.safeGetInt('nonexistent', 'nonexistent', 42), 42) - - def test_safeGetFloat(self): - """ - safeGetFloat retuns provided default for nonexistent option or 0.0 - """ - self.assertEqual( - self.config.safeGetFloat('nonexistent', 'nonexistent'), 0.0) - self.assertEqual( - self.config.safeGetFloat('nonexistent', 'nonexistent', 42.0), 42.0) - - def test_set(self): - """Check exceptions in set()""" - with self.assertRaises(TypeError): - self.config.set('bitmessagesettings', 'any', 42) - with self.assertRaises(ValueError): - self.config.set( - 'bitmessagesettings', 'maxoutboundconnections', 'none') - with self.assertRaises(ValueError): - self.config.set( - 'bitmessagesettings', 'maxoutboundconnections', '9') - - def test_validate(self): - """Check validation""" - self.assertTrue( - self.config.validate('nonexistent', 'nonexistent', 'any')) - for val in range(9): - self.assertTrue(self.config.validate( - 'bitmessagesettings', 'maxoutboundconnections', str(val))) - - def test_setTemp(self): - """Set a temporary value and ensure it's returned by get()""" - self.config.setTemp('bitmessagesettings', 'connect', 'true') - self.assertIs( - self.config.safeGetBoolean('bitmessagesettings', 'connect'), True) - written_fp = StringIO('') - self.config.write(written_fp) - self.config._reset() - self.config.read_file(written_fp) - self.assertIs( - self.config.safeGetBoolean('bitmessagesettings', 'connect'), False) - - def test_addresses(self): - """Check the addresses() method""" - self.config.read() - for num in range(1, 4): - addr = 'BM-%s' % num - self.config.add_section(addr) - self.config.set(addr, 'label', 'account %s' % (4 - num)) - self.assertEqual(self.config.addresses(), ['BM-1', 'BM-2', 'BM-3']) - self.assertEqual(self.config.addresses(True), ['BM-3', 'BM-2', 'BM-1']) - - def test_reset(self): - """Some logic for testing _reset()""" - test_config_object = StringIO(test_config) - self.config.read_file(test_config_object) - self.assertEqual( - self.config.safeGetInt( - 'bitmessagesettings', 'maxaddrperstreamsend'), 100) - self.config._reset() - self.assertEqual(self.config.sections(), []) - - def test_defaults(self): - """Loading defaults""" - self.config.set('bitmessagesettings', 'maxaddrperstreamsend', '100') - self.config.read() - self.assertEqual( - self.config.safeGetInt( - 'bitmessagesettings', 'maxaddrperstreamsend'), 500) diff --git a/src/tests/test_config_address.py b/src/tests/test_config_address.py deleted file mode 100644 index b76df7ec..00000000 --- a/src/tests/test_config_address.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Various tests to Enable and Disable the identity -""" - -import unittest -from six import StringIO -from six.moves import configparser -from pybitmessage.bmconfigparser import BMConfigParser - - -address_obj = """[BM-enabled_identity] -label = Test_address_1 -enabled = true - -[BM-disabled_identity] -label = Test_address_2 -enabled = false -""" - - -# pylint: disable=protected-access -class TestAddressEnableDisable(unittest.TestCase): - """A test case for bmconfigparser""" - - def setUp(self): - self.config = BMConfigParser() - self.config.read_file(StringIO(address_obj)) - - def test_enable_enabled_identity(self): - """Test enabling already enabled identity""" - self.config.enable_address('BM-enabled_identity') - self.assertEqual(self.config.safeGet('BM-enabled_identity', 'enabled'), 'true') - - def test_enable_disabled_identity(self): - """Test enabling the Disabled identity""" - self.config.enable_address('BM-disabled_identity') - self.assertEqual(self.config.safeGet('BM-disabled_identity', 'enabled'), 'true') - - def test_enable_non_existent_identity(self): - """Test enable non-existent address""" - with self.assertRaises(configparser.NoSectionError): - self.config.enable_address('non_existent_address') - - def test_disable_disabled_identity(self): - """Test disabling already disabled identity""" - self.config.disable_address('BM-disabled_identity') - self.assertEqual(self.config.safeGet('BM-disabled_identity', 'enabled'), 'false') - - def test_disable_enabled_identity(self): - """Test Disabling the Enabled identity""" - self.config.disable_address('BM-enabled_identity') - self.assertEqual(self.config.safeGet('BM-enabled_identity', 'enabled'), 'false') - - def test_disable_non_existent_identity(self): - """Test dsiable non-existent address""" - with self.assertRaises(configparser.NoSectionError): - self.config.disable_address('non_existent_address') diff --git a/src/tests/test_config_process.py b/src/tests/test_config_process.py deleted file mode 100644 index 9322a2f0..00000000 --- a/src/tests/test_config_process.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Various tests for config -""" - -import os -import tempfile -from pybitmessage.bmconfigparser import config -from .test_process import TestProcessProto -from .common import skip_python3 - -skip_python3() - - -class TestProcessConfig(TestProcessProto): - """A test case for keys.dat""" - home = tempfile.mkdtemp() - - def test_config_defaults(self): - """Test settings in the generated config""" - self._stop_process() - self._kill_process() - config.read(os.path.join(self.home, 'keys.dat')) - - self.assertEqual(config.safeGetInt( - 'bitmessagesettings', 'settingsversion'), 10) - self.assertEqual(config.safeGetInt( - 'bitmessagesettings', 'port'), 8444) - # don't connect - self.assertTrue(config.safeGetBoolean( - 'bitmessagesettings', 'dontconnect')) - # API disabled - self.assertFalse(config.safeGetBoolean( - 'bitmessagesettings', 'apienabled')) - - # extralowdifficulty is false - self.assertEqual(config.safeGetInt( - 'bitmessagesettings', 'defaultnoncetrialsperbyte'), 1000) - self.assertEqual(config.safeGetInt( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes'), 1000) diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py deleted file mode 100644 index b3f2484e..00000000 --- a/src/tests/test_crypto.py +++ /dev/null @@ -1,100 +0,0 @@ -""" -Test the alternatives for crypto primitives -""" - -import hashlib -import ssl -import unittest -from abc import ABCMeta, abstractmethod -from binascii import hexlify - -from pybitmessage import highlevelcrypto - - -try: - from Crypto.Hash import RIPEMD160 -except ImportError: - RIPEMD160 = None - -from .samples import ( - sample_msg, sample_pubsigningkey, sample_pubencryptionkey, - sample_privsigningkey, sample_privencryptionkey, sample_ripe, - sample_sig, sample_sig_sha1 -) - - -_sha = hashlib.new('sha512') -_sha.update(sample_pubsigningkey + sample_pubencryptionkey) - -pubkey_sha = _sha.digest() - - -class RIPEMD160TestCase(object): - """Base class for RIPEMD160 test case""" - # pylint: disable=too-few-public-methods,no-member - __metaclass__ = ABCMeta - - @abstractmethod - def _hashdigest(self, data): - """RIPEMD160 digest implementation""" - pass - - def test_hash_string(self): - """Check RIPEMD160 hash function on string""" - self.assertEqual(hexlify(self._hashdigest(pubkey_sha)), sample_ripe) - - -@unittest.skipIf( - ssl.OPENSSL_VERSION.startswith('OpenSSL 3'), 'no ripemd160 in openssl 3') -class TestHashlib(RIPEMD160TestCase, unittest.TestCase): - """RIPEMD160 test case for hashlib""" - @staticmethod - def _hashdigest(data): - hasher = hashlib.new('ripemd160') - hasher.update(data) - return hasher.digest() - - -@unittest.skipUnless(RIPEMD160, 'pycrypto package not found') -class TestCrypto(RIPEMD160TestCase, unittest.TestCase): - """RIPEMD160 test case for Crypto""" - @staticmethod - def _hashdigest(data): - return RIPEMD160.new(data).digest() - - -class TestHighlevelcrypto(unittest.TestCase): - """Test highlevelcrypto public functions""" - - def test_signatures(self): - """Verify sample signatures and newly generated ones""" - pubkey_hex = hexlify(sample_pubsigningkey) - # pregenerated signatures - self.assertTrue(highlevelcrypto.verify( - sample_msg, sample_sig, pubkey_hex, "sha256")) - self.assertFalse(highlevelcrypto.verify( - sample_msg, sample_sig, pubkey_hex, "sha1")) - self.assertTrue(highlevelcrypto.verify( - sample_msg, sample_sig_sha1, pubkey_hex, "sha1")) - self.assertTrue(highlevelcrypto.verify( - sample_msg, sample_sig_sha1, pubkey_hex)) - # new signatures - sig256 = highlevelcrypto.sign(sample_msg, sample_privsigningkey) - sig1 = highlevelcrypto.sign(sample_msg, sample_privsigningkey, "sha1") - self.assertTrue( - highlevelcrypto.verify(sample_msg, sig256, pubkey_hex)) - self.assertTrue( - highlevelcrypto.verify(sample_msg, sig256, pubkey_hex, "sha256")) - self.assertTrue( - highlevelcrypto.verify(sample_msg, sig1, pubkey_hex)) - - def test_privtopub(self): - """Generate public keys and check the result""" - self.assertEqual( - highlevelcrypto.privToPub(sample_privsigningkey), - hexlify(sample_pubsigningkey) - ) - self.assertEqual( - highlevelcrypto.privToPub(sample_privencryptionkey), - hexlify(sample_pubencryptionkey) - ) diff --git a/src/tests/test_helper_inbox.py b/src/tests/test_helper_inbox.py deleted file mode 100644 index a0b6de1b..00000000 --- a/src/tests/test_helper_inbox.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Test cases for Helper Inbox""" - -import time -import unittest -from pybitmessage.helper_inbox import ( - insert, - trash, - delete, - isMessageAlreadyInInbox, - undeleteMessage, -) -from pybitmessage.helper_ackPayload import genAckPayload - -try: - # Python 3 - from unittest.mock import patch -except ImportError: - # Python 2 - from mock import patch - - -class TestHelperInbox(unittest.TestCase): - """Test class for Helper Inbox""" - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_insert(self, mock_sql_execute): # pylint: disable=no-self-use - """Test to perform an insert into the "inbox" table""" - mock_message_data = ( - "ruyv87bv", - "BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTpEgU9U", - "BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTp5g99U", - "Test subject", - int(time.time()), - "Test message", - "inbox", - 2, - 0, - "658gvjhtghv", - ) - insert(t=mock_message_data) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_trash(self, mock_sql_execute): # pylint: disable=no-self-use - """Test marking a message in the `inbox` as `trash`""" - mock_msg_id = "fefkosghsbse92" - trash(msgid=mock_msg_id) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_delete(self, mock_sql_execute): # pylint: disable=no-self-use - """Test for permanent deletion of message from trash""" - mock_ack_data = genAckPayload() - delete(mock_ack_data) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_undeleteMessage(self, mock_sql_execute): # pylint: disable=no-self-use - """Test for Undelete the message""" - mock_msg_id = "fefkosghsbse92" - undeleteMessage(msgid=mock_msg_id) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlQuery") - def test_isMessageAlreadyInInbox(self, mock_sql_query): - """Test for check for previous instances of this message""" - fake_sigHash = "h4dkn54546" - # if Message is already in Inbox - mock_sql_query.return_value = [(1,)] - result = isMessageAlreadyInInbox(sigHash=fake_sigHash) - self.assertTrue(result) - - # if Message is not in Inbox - mock_sql_query.return_value = [(0,)] - result = isMessageAlreadyInInbox(sigHash=fake_sigHash) - self.assertFalse(result) diff --git a/src/tests/test_helper_sent.py b/src/tests/test_helper_sent.py deleted file mode 100644 index 9227e43a..00000000 --- a/src/tests/test_helper_sent.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Test cases for helper_sent class""" - -import unittest -from pybitmessage.helper_sent import insert, delete, trash, retrieve_message_details - -try: - # Python 3 - from unittest.mock import patch -except ImportError: - # Python 2 - from mock import patch - - -class TestHelperSent(unittest.TestCase): - """Test class for helper_sent""" - - @patch("pybitmessage.helper_sent.sqlExecute") - def test_insert_valid_address(self, mock_sql_execute): - """Test insert with valid address""" - VALID_ADDRESS = "BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTpEgU9U" - ackdata = insert( - msgid="123456", - toAddress="[Broadcast subscribers]", - fromAddress=VALID_ADDRESS, - subject="Test Subject", - message="Test Message", - status="msgqueued", - sentTime=1234567890, - lastActionTime=1234567890, - sleeptill=0, - retryNumber=0, - encoding=2, - ttl=3600, - folder="sent", - ) - mock_sql_execute.assert_called_once() - self.assertIsNotNone(ackdata) - - def test_insert_invalid_address(self): - """Test insert with invalid address""" - INVALID_ADDRESS = "TEST@1245.780" - ackdata = insert(toAddress=INVALID_ADDRESS) - self.assertIsNone(ackdata) - - @patch("pybitmessage.helper_sent.sqlExecute") - def test_delete(self, mock_sql_execute): - """Test delete function""" - delete("ack_data") - self.assertTrue(mock_sql_execute.called) - mock_sql_execute.assert_called_once_with( - "DELETE FROM sent WHERE ackdata = ?", "ack_data" - ) - - @patch("pybitmessage.helper_sent.sqlQuery") - def test_retrieve_valid_message_details(self, mock_sql_query): - """Test retrieving valid message details""" - return_data = [ - ( - "to@example.com", - "from@example.com", - "Test Subject", - "Test Message", - "2022-01-01", - ) - ] - mock_sql_query.return_value = return_data - result = retrieve_message_details("12345") - self.assertEqual(result, return_data) - - @patch("pybitmessage.helper_sent.sqlExecute") - def test_trash(self, mock_sql_execute): - """Test marking a message as 'trash'""" - ackdata = "ack_data" - mock_sql_execute.return_value = 1 - rowcount = trash(ackdata) - self.assertEqual(rowcount, 1) diff --git a/src/tests/test_helper_sql.py b/src/tests/test_helper_sql.py deleted file mode 100644 index 036bd2c9..00000000 --- a/src/tests/test_helper_sql.py +++ /dev/null @@ -1,131 +0,0 @@ -"""Test cases for helper_sql""" - -import unittest - -try: - # Python 3 - from unittest.mock import patch -except ImportError: - # Python 2 - from mock import patch - -import pybitmessage.helper_sql as helper_sql - - -class TestHelperSql(unittest.TestCase): - """Test class for helper_sql""" - - @classmethod - def setUpClass(cls): - helper_sql.sql_available = True - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlquery_no_args(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlQuery with no additional arguments""" - mock_sqlreturnqueue_get.return_value = ("dummy_result", None) - result = helper_sql.sqlQuery( - "SELECT msgid FROM inbox where folder='inbox' ORDER BY received" - ) - self.assertEqual(mock_sqlsubmitqueue_put.call_count, 2) - self.assertEqual(result, "dummy_result") - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlquery_with_args(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlQuery with additional arguments""" - mock_sqlreturnqueue_get.return_value = ("dummy_result", None) - result = helper_sql.sqlQuery( - "SELECT address FROM addressbook WHERE address=?", "PB-5yfds868gbkj" - ) - self.assertEqual(mock_sqlsubmitqueue_put.call_count, 2) - self.assertEqual(result, "dummy_result") - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlexecute(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlExecute with valid arguments""" - mock_sqlreturnqueue_get.return_value = (None, 1) - rowcount = helper_sql.sqlExecute( - "UPDATE sent SET status = 'msgqueued'" - "WHERE ackdata = ? AND folder = 'sent'", - "1710652313", - ) - self.assertEqual(mock_sqlsubmitqueue_put.call_count, 3) - self.assertEqual(rowcount, 1) - - @patch("pybitmessage.helper_sql.SqlBulkExecute.execute") - def test_sqlexecute_script(self, mock_execute): - """Test sqlExecuteScript with a SQL script""" - helper_sql.sqlExecuteScript( - "CREATE TABLE test (id INTEGER); INSERT INTO test VALUES (1);" - ) - self.assertTrue(mock_execute.assert_called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch( - "pybitmessage.helper_sql.sqlReturnQueue.get", - ) - def test_sqlexecute_chunked(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlExecuteChunked with valid arguments""" - # side_effect is list of return value (_, rowcount) - # of sqlReturnQueue.get for each chunk - CHUNK_COUNT = 6 - CHUNK_SIZE = 999 - ID_COUNT = CHUNK_COUNT * CHUNK_SIZE - CHUNKS_ROWCOUNT_LIST = [50, 29, 28, 18, 678, 900] - TOTAL_ROW_COUNT = sum(CHUNKS_ROWCOUNT_LIST) - mock_sqlreturnqueue_get.side_effect = [(None, rowcount) for rowcount in CHUNKS_ROWCOUNT_LIST] - args = [] - for i in range(0, ID_COUNT): - args.append("arg{}".format(i)) - total_row_count_return = helper_sql.sqlExecuteChunked( - "INSERT INTO table VALUES {}", ID_COUNT, *args - ) - self.assertEqual(TOTAL_ROW_COUNT, total_row_count_return) - self.assertTrue(mock_sqlsubmitqueue_put.called) - self.assertTrue(mock_sqlreturnqueue_get.called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlexecute_chunked_with_idcount_zero( - self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put - ): - """Test sqlExecuteChunked with id count 0""" - ID_COUNT = 0 - args = list() - for i in range(0, ID_COUNT): - args.append("arg{}".format(i)) - total_row_count = helper_sql.sqlExecuteChunked( - "INSERT INTO table VALUES {}", ID_COUNT, *args - ) - self.assertEqual(total_row_count, 0) - self.assertFalse(mock_sqlsubmitqueue_put.called) - self.assertFalse(mock_sqlreturnqueue_get.called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlexecute_chunked_with_args_less( - self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put - ): - """Test sqlExecuteChunked with length of args less than idcount""" - ID_COUNT = 12 - args = ["args0", "arg1"] - total_row_count = helper_sql.sqlExecuteChunked( - "INSERT INTO table VALUES {}", ID_COUNT, *args - ) - self.assertEqual(total_row_count, 0) - self.assertFalse(mock_sqlsubmitqueue_put.called) - self.assertFalse(mock_sqlreturnqueue_get.called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlSubmitQueue.task_done") - def test_sqlstored_procedure(self, mock_task_done, mock_sqlsubmitqueue_put): - """Test sqlStoredProcedure with a stored procedure name""" - helper_sql.sqlStoredProcedure("exit") - self.assertTrue(mock_task_done.called_once) - mock_sqlsubmitqueue_put.assert_called_with("terminate") - - @classmethod - def tearDownClass(cls): - helper_sql.sql_available = False diff --git a/src/tests/test_identicon.py b/src/tests/test_identicon.py deleted file mode 100644 index 4c6be32d..00000000 --- a/src/tests/test_identicon.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Tests for qidenticon""" - -import atexit -import unittest - -try: - from PyQt5 import QtGui, QtWidgets - from xvfbwrapper import Xvfb - from pybitmessage import qidenticon -except ImportError: - Xvfb = None - # raise unittest.SkipTest( - # 'Skipping graphical test, because of no PyQt or xvfbwrapper') -else: - vdisplay = Xvfb(width=1024, height=768) - vdisplay.start() - atexit.register(vdisplay.stop) - - -sample_code = 0x3fd4bf901b9d4ea1394f0fb358725b28 -sample_size = 48 - - -@unittest.skipUnless( - Xvfb, 'Skipping graphical test, because of no PyQt or xvfbwrapper') -class TestIdenticon(unittest.TestCase): - """QIdenticon implementation test case""" - - @classmethod - def setUpClass(cls): - """Instantiate QtWidgets.QApplication""" - cls.app = QtWidgets.QApplication([]) - - def test_qidenticon_samples(self): - """Generate 4 qidenticon samples and check their properties""" - icon_simple = qidenticon.render_identicon(sample_code, sample_size) - self.assertIsInstance(icon_simple, QtGui.QPixmap) - self.assertEqual(icon_simple.height(), sample_size * 3) - self.assertEqual(icon_simple.width(), sample_size * 3) - self.assertFalse(icon_simple.hasAlphaChannel()) - - # icon_sample = QtGui.QPixmap() - # icon_sample.load('../images/qidenticon.png') - # self.assertFalse( - # icon_simple.toImage(), icon_sample.toImage()) - - icon_x = qidenticon.render_identicon( - sample_code, sample_size, opacity=0) - self.assertTrue(icon_x.hasAlphaChannel()) diff --git a/src/tests/test_inventory.py b/src/tests/test_inventory.py deleted file mode 100644 index b6d0cc85..00000000 --- a/src/tests/test_inventory.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Tests for inventory""" - -import os -import shutil -import struct -import tempfile -import time -import unittest - -from pybitmessage.storage import storage -from pybitmessage.addresses import calculateInventoryHash - -from .partial import TestPartialRun - - -class TestFilesystemInventory(TestPartialRun): - """A test case for the inventory using filesystem backend""" - - @classmethod - def setUpClass(cls): - cls.home = os.environ['BITMESSAGE_HOME'] = tempfile.mkdtemp() - super(TestFilesystemInventory, cls).setUpClass() - - from inventory import create_inventory_instance - cls.inventory = create_inventory_instance('filesystem') - - def test_consistency(self): - """Ensure the inventory is of proper class""" - if os.path.isfile(os.path.join(self.home, 'messages.dat')): - # this will likely never happen - self.fail("Failed to configure filesystem inventory!") - - def test_appending(self): - """Add a sample message to the inventory""" - TTL = 24 * 60 * 60 - embedded_time = int(time.time() + TTL) - msg = struct.pack('>Q', embedded_time) + os.urandom(166) - invhash = calculateInventoryHash(msg) - self.inventory[invhash] = (2, 1, msg, embedded_time, b'') - - @classmethod - def tearDownClass(cls): - super(TestFilesystemInventory, cls).tearDownClass() - cls.inventory.flush() - shutil.rmtree(os.path.join(cls.home, cls.inventory.topDir)) - - -class TestStorageAbstract(unittest.TestCase): - """A test case for refactoring of the storage abstract classes""" - - def test_inventory_storage(self): - """Check inherited abstract methods""" - with self.assertRaisesRegexp( - TypeError, "^Can't instantiate abstract class.*" - "methods __contains__, __delitem__, __getitem__, __iter__," - " __len__, __setitem__" - ): # pylint: disable=abstract-class-instantiated - storage.InventoryStorage() diff --git a/src/tests/test_l10n.py b/src/tests/test_l10n.py deleted file mode 100644 index c6988827..00000000 --- a/src/tests/test_l10n.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Tests for l10n module""" - -import re -import sys -import time -import unittest - -from pybitmessage import l10n - - -class TestL10n(unittest.TestCase): - """A test case for L10N""" - - def test_l10n_assumptions(self): - """Check the assumptions made while rewriting the l10n""" - self.assertFalse(re.search(r'\d', time.strftime("wrong"))) - timestring_type = type(time.strftime(l10n.DEFAULT_TIME_FORMAT)) - self.assertEqual(timestring_type, str) - if sys.version_info[0] == 2: - self.assertEqual(timestring_type, bytes) - - def test_getWindowsLocale(self): - """Check the getWindowsLocale() docstring example""" - self.assertEqual(l10n.getWindowsLocale("en_EN.UTF-8"), "english") diff --git a/src/tests/test_log.py b/src/tests/test_log.py deleted file mode 100644 index 4e74e50d..00000000 --- a/src/tests/test_log.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Tests for logging""" - -import subprocess -import sys -import unittest - -from pybitmessage import proofofwork - - -class TestLog(unittest.TestCase): - """A test case for logging""" - - @unittest.skipIf( - sys.hexversion < 0x3000000, 'assertLogs is new in version 3.4') - def test_LogOutput(self): - """Use proofofwork.LogOutput to log output of a shell command""" - with self.assertLogs('default') as cm: # pylint: disable=no-member - with proofofwork.LogOutput('+'): - subprocess.call(['echo', 'HELLO']) - - self.assertEqual(cm.output, ['INFO:default:+: HELLO\n']) diff --git a/src/tests/test_logger.py b/src/tests/test_logger.py deleted file mode 100644 index d6bf33ed..00000000 --- a/src/tests/test_logger.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Testing the logger configuration -""" - -import os -import tempfile - -from .test_process import TestProcessProto - - -class TestLogger(TestProcessProto): - """A test case for logger configuration""" - - pattern = r' <===> ' - conf_template = ''' -[loggers] -keys=root - -[handlers] -keys=default - -[formatters] -keys=default - -[formatter_default] -format=%(asctime)s {1} %(message)s - -[handler_default] -class=FileHandler -level=NOTSET -formatter=default -args=({0!r}, 'w') - -[logger_root] -level=DEBUG -handlers=default -''' - - @classmethod - def setUpClass(cls): - cls.home = tempfile.mkdtemp() - cls._files = cls._files[2:] + ('logging.dat',) - cls.log_file = os.path.join(cls.home, 'debug.log') - - with open(os.path.join(cls.home, 'logging.dat'), 'wb') as dst: - dst.write(cls.conf_template.format(cls.log_file, cls.pattern)) - - super(TestLogger, cls).setUpClass() - - def test_fileConfig(self): - """Check that our logging.dat was used""" - - self._stop_process() - data = open(self.log_file).read() - self.assertRegexpMatches(data, self.pattern) - self.assertRegexpMatches(data, 'Loaded logger configuration') diff --git a/src/tests/test_msg.py b/src/tests/test_msg.py deleted file mode 100644 index cb586fa5..00000000 --- a/src/tests/test_msg.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Tests for messagetypes module""" -import unittest - -from six import text_type - -from pybitmessage import messagetypes - -sample_data = {"": "message", "subject": "subject", "body": "body"} -invalid_data = {"": "message", "subject": b"\x01\x02\x03", "body": b"\x01\x02\x03\x04"} - - -class TestMessageTypes(unittest.TestCase): - """A test case for messagetypes""" - - def test_msg_encode(self): - """Test msg encode""" - msgObj = messagetypes.message.Message() - encoded_message = msgObj.encode(sample_data) - self.assertEqual(type(encoded_message), dict) - self.assertEqual(encoded_message["subject"], sample_data["subject"]) - self.assertEqual(encoded_message["body"], sample_data["body"]) - - def test_msg_decode(self): - """Test msg decode""" - msgObj = messagetypes.constructObject(sample_data) - self.assertEqual(msgObj.subject, sample_data["subject"]) - self.assertEqual(msgObj.body, sample_data["body"]) - - def test_invalid_data_type(self): - """Test invalid data type""" - msgObj = messagetypes.constructObject(invalid_data) - self.assertTrue(isinstance(msgObj.subject, text_type)) - self.assertTrue(isinstance(msgObj.body, text_type)) - - def test_msg_process(self): - """Test msg process""" - msgObj = messagetypes.constructObject(sample_data) - self.assertTrue(isinstance(msgObj, messagetypes.message.Message)) - self.assertIsNone(msgObj.process()) diff --git a/src/tests/test_multiqueue.py b/src/tests/test_multiqueue.py deleted file mode 100644 index 87149d56..00000000 --- a/src/tests/test_multiqueue.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Test cases for multiqueue""" - -import unittest -from pybitmessage.multiqueue import MultiQueue - - -class TestMultiQueue(unittest.TestCase): - """Test cases for multiqueue""" - - def test_queue_creation(self): - """Check if the queueCount matches the specified value""" - mqsize = 3 - multiqueue = MultiQueue(count=mqsize) - self.assertEqual(multiqueue.queueCount, mqsize) - - def test_empty_queue(self): - """Check for empty queue""" - multiqueue = MultiQueue(count=5) - self.assertEqual(multiqueue.totalSize(), 0) - - def test_put_get_count(self): - """check if put & get count is equal""" - multiqueue = MultiQueue(count=5) - put_count = 6 - for i in range(put_count): - multiqueue.put(i) - - get_count = 0 - while multiqueue.totalSize() != 0: - if multiqueue.qsize() > 0: - multiqueue.get() - get_count += 1 - multiqueue.iterate() - - self.assertEqual(get_count, put_count) - - def test_put_and_get(self): - """Testing Put and Get""" - item = 400 - multiqueue = MultiQueue(count=3) - multiqueue.put(item) - result = None - for _ in multiqueue.queues: - if multiqueue.qsize() > 0: - result = multiqueue.get() - break - multiqueue.iterate() - self.assertEqual(result, item) - - def test_iteration(self): - """Check if the iteration wraps around correctly""" - mqsize = 3 - iteroffset = 1 - multiqueue = MultiQueue(count=mqsize) - for _ in range(mqsize + iteroffset): - multiqueue.iterate() - self.assertEqual(multiqueue.iter, iteroffset) - - def test_total_size(self): - """Check if the total size matches the expected value""" - multiqueue = MultiQueue(count=3) - put_count = 5 - for i in range(put_count): - multiqueue.put(i) - self.assertEqual(multiqueue.totalSize(), put_count) diff --git a/src/tests/test_network.py b/src/tests/test_network.py deleted file mode 100644 index 08cd95ce..00000000 --- a/src/tests/test_network.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Test network module""" - -import threading -import time - -from .common import skip_python3 -from .partial import TestPartialRun - -skip_python3() - - -class TestNetwork(TestPartialRun): - """A test case for running the network subsystem""" - - @classmethod - def setUpClass(cls): - super(TestNetwork, cls).setUpClass() - - cls.state.maximumNumberOfHalfOpenConnections = 4 - - cls.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') - cls.config.set('bitmessagesettings', 'udp', 'True') - - # config variable is still used inside of the network ): - import network - from network import connectionpool, stats - - # beware of singleton - connectionpool.config = cls.config - cls.pool = network.BMConnectionPool() - cls.stats = stats - - network.start(cls.config, cls.state) - - def test_threads(self): - """Ensure all the network threads started""" - threads = { - "AddrBroadcaster", "Announcer", "Asyncore", "Downloader", - "InvBroadcaster", "Uploader"} - extra = self.config.getint('threads', 'receive') - for thread in threading.enumerate(): - try: - threads.remove(thread.name) - except KeyError: - extra -= thread.name.startswith("ReceiveQueue_") - - self.assertEqual(len(threads), 0) - self.assertEqual(extra, 0) - - def test_stats(self): - """Check that network starts connections and updates stats""" - pl = 0 - for _ in range(30): - if pl == 0: - pl = len(self.pool) - if ( - self.stats.receivedBytes() > 0 and self.stats.sentBytes() > 0 - and pl > 0 - # and len(self.stats.connectedHostsList()) > 0 - ): - break - time.sleep(1) - else: - self.fail('Have not started any connection in 30 sec') - - def test_udp(self): - """Invoke AnnounceThread.announceSelf() and check discovered peers""" - for _ in range(20): - if self.pool.udpSockets: - break - time.sleep(1) - else: - self.fail('No UDP sockets found in 20 sec') - - for _ in range(10): - try: - self.state.announceThread.announceSelf() - except AttributeError: - self.fail('state.announceThread is not set properly') - time.sleep(1) - try: - peer = self.state.discoveredPeers.popitem()[0] - except KeyError: - continue - else: - self.assertEqual(peer.port, 8444) - break - else: - self.fail('No self in discovered peers') - - @classmethod - def tearDownClass(cls): - super(TestNetwork, cls).tearDownClass() - for thread in threading.enumerate(): - if thread.name == "Asyncore": - thread.stopThread() diff --git a/src/tests/test_openclpow.py b/src/tests/test_openclpow.py deleted file mode 100644 index 341beec9..00000000 --- a/src/tests/test_openclpow.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Tests for openclpow module -""" -import hashlib -import unittest -from struct import pack, unpack -from pybitmessage import openclpow - - -class TestOpenClPow(unittest.TestCase): - """ - Main opencl test case - """ - - @classmethod - def setUpClass(cls): - openclpow.initCL() - - @unittest.skipUnless(openclpow.enabledGpus, "No GPUs found / enabled") - def test_openclpow(self): - """Check the working of openclpow module""" - target_ = 54227212183 - initialHash = ( - "3758f55b5a8d902fd3597e4ce6a2d3f23daff735f65d9698c270987f4e67ad590" - "b93f3ffeba0ef2fd08a8dc2f87b68ae5a0dc819ab57f22ad2c4c9c8618a43b3" - ).decode("hex") - nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target_) - trialValue, = unpack( - '>Q', hashlib.sha512(hashlib.sha512( - pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) - self.assertLess((nonce - trialValue), target_) diff --git a/src/tests/test_packets.py b/src/tests/test_packets.py deleted file mode 100644 index 9dfb1d23..00000000 --- a/src/tests/test_packets.py +++ /dev/null @@ -1,87 +0,0 @@ -"""Test packets creation and parsing""" - -from binascii import unhexlify -from struct import pack - -from pybitmessage import addresses, protocol - -from .samples import ( - sample_addr_data, sample_object_data, sample_object_expires) -from .test_protocol import TestSocketInet - - -class TestSerialize(TestSocketInet): - """Test serializing and deserializing packet data""" - - def test_varint(self): - """Test varint encoding and decoding""" - data = addresses.encodeVarint(0) - self.assertEqual(data, b'\x00') - data = addresses.encodeVarint(42) - self.assertEqual(data, b'*') - data = addresses.encodeVarint(252) - self.assertEqual(data, unhexlify('fc')) - data = addresses.encodeVarint(253) - self.assertEqual(data, unhexlify('fd00fd')) - data = addresses.encodeVarint(100500) - self.assertEqual(data, unhexlify('fe00018894')) - data = addresses.encodeVarint(65535) - self.assertEqual(data, unhexlify('fdffff')) - data = addresses.encodeVarint(4294967295) - self.assertEqual(data, unhexlify('feffffffff')) - data = addresses.encodeVarint(4294967296) - self.assertEqual(data, unhexlify('ff0000000100000000')) - data = addresses.encodeVarint(18446744073709551615) - self.assertEqual(data, unhexlify('ffffffffffffffffff')) - - with self.assertRaises(addresses.varintEncodeError): - addresses.encodeVarint(18446744073709551616) - - value, length = addresses.decodeVarint(b'\xfeaddr') - self.assertEqual(value, protocol.OBJECT_ADDR) - self.assertEqual(length, 5) - value, length = addresses.decodeVarint(b'\xfe\x00tor') - self.assertEqual(value, protocol.OBJECT_ONIONPEER) - self.assertEqual(length, 5) - - def test_packet(self): - """Check the packet created by protocol.CreatePacket()""" - head = unhexlify(b'%x' % protocol.magic) - self.assertEqual( - protocol.CreatePacket(b'ping')[:len(head)], head) - - def test_decode_obj_parameters(self): - """Check parameters decoded from a sample object""" - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(sample_object_data) - self.assertEqual(objectType, 42) - self.assertEqual(toStreamNumber, 2) - self.assertEqual(expiresTime, sample_object_expires) - - def test_encodehost(self): - """Check the result of protocol.encodeHost()""" - self.assertEqual( - protocol.encodeHost('127.0.0.1'), - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' - + pack('>L', 2130706433)) - self.assertEqual( - protocol.encodeHost('191.168.1.1'), - unhexlify('00000000000000000000ffffbfa80101')) - self.assertEqual( - protocol.encodeHost('1.1.1.1'), - unhexlify('00000000000000000000ffff01010101')) - self.assertEqual( - protocol.encodeHost('0102:0304:0506:0708:090A:0B0C:0D0E:0F10'), - unhexlify('0102030405060708090a0b0c0d0e0f10')) - self.assertEqual( - protocol.encodeHost('quzwelsuziwqgpt2.onion'), - unhexlify('fd87d87eeb438533622e54ca2d033e7a')) - - def test_assemble_addr(self): - """Assemble addr packet and compare it to pregenerated sample""" - self.assertEqual( - sample_addr_data, - protocol.assembleAddrMessage([ - (1, protocol.Peer('127.0.0.1', 8444), 1626611891) - for _ in range(500) - ])[protocol.Header.size:]) diff --git a/src/tests/test_pattern/knownnodes.dat b/src/tests/test_pattern/knownnodes.dat deleted file mode 100644 index a78a4434..00000000 --- a/src/tests/test_pattern/knownnodes.dat +++ /dev/null @@ -1,104 +0,0 @@ -(dp0 -I1 -(dp1 -ccopy_reg -_reconstructor -p2 -(cstate -Peer -p3 -c__builtin__ -tuple -p4 -(S'85.180.139.241' -p5 -I8444 -tp6 -tp7 -Rp8 -I1608398841 -sg2 -(g3 -g4 -(S'158.222.211.81' -p9 -I8080 -tp10 -tp11 -Rp12 -I1608398841 -sg2 -(g3 -g4 -(S'178.62.12.187' -p13 -I8448 -tp14 -tp15 -Rp16 -I1608398841 -sg2 -(g3 -g4 -(S'109.147.204.113' -p17 -I1195 -tp18 -tp19 -Rp20 -I1608398841 -sg2 -(g3 -g4 -(S'5.45.99.75' -p21 -I8444 -tp22 -tp23 -Rp24 -I1608398841 -sg2 -(g3 -g4 -(S'178.11.46.221' -p25 -I8444 -tp26 -tp27 -Rp28 -I1608398841 -sg2 -(g3 -g4 -(S'95.165.168.168' -p29 -I8444 -tp30 -tp31 -Rp32 -I1608398841 -sg2 -(g3 -g4 -(S'24.188.198.204' -p33 -I8111 -tp34 -tp35 -Rp36 -I1608398841 -sg2 -(g3 -g4 -(S'75.167.159.54' -p37 -I8444 -tp38 -tp39 -Rp40 -I1608398841 -ssI2 -(dp41 -sI3 -(dp42 -s. \ No newline at end of file diff --git a/src/tests/test_process.py b/src/tests/test_process.py deleted file mode 100644 index 37b34541..00000000 --- a/src/tests/test_process.py +++ /dev/null @@ -1,229 +0,0 @@ -""" -Common reusable code for tests and tests for pybitmessage process. -""" - -import os -import signal -import subprocess # nosec -import sys -import tempfile -import time -import unittest - -import psutil - -from .common import cleanup, put_signal_file, skip_python3 - - -skip_python3() - - -class TestProcessProto(unittest.TestCase): - """Test case implementing common logic for external testing: - it starts pybitmessage in setUpClass() and stops it in tearDownClass() - """ - _process_cmd = ['pybitmessage', '-d'] - _threads_count_min = 15 - _threads_count_max = 16 - _threads_names = [ - 'PyBitmessage', - 'addressGenerato', - 'singleWorker', - 'SQL', - 'objectProcessor', - 'singleCleaner', - 'singleAPI', - 'Asyncore', - 'ReceiveQueue_0', - 'ReceiveQueue_1', - 'ReceiveQueue_2', - 'Announcer', - 'InvBroadcaster', - 'AddrBroadcaster', - 'Downloader', - 'Uploader' - ] - _files = ( - 'keys.dat', 'debug.log', 'messages.dat', 'knownnodes.dat', - '.api_started', 'unittest.lock' - ) - home = None - - @classmethod - def setUpClass(cls): - """Setup environment and start pybitmessage""" - cls.flag = False - if not cls.home: - cls.home = tempfile.gettempdir() - cls._cleanup_files() - os.environ['BITMESSAGE_HOME'] = cls.home - put_signal_file(cls.home, 'unittest.lock') - starttime = int(time.time()) - 0.5 - cls.process = psutil.Popen( - cls._process_cmd, stderr=subprocess.STDOUT) # nosec - - pidfile = os.path.join(cls.home, 'singleton.lock') - for _ in range(10): - time.sleep(1) - try: - pstat = os.stat(pidfile) - if starttime <= pstat.st_mtime and pstat.st_size > 0: - break # the pidfile is suitable - except OSError: - continue - - try: - pid = int(cls._get_readline('singleton.lock')) - cls.process = psutil.Process(pid) - time.sleep(5) - except (psutil.NoSuchProcess, TypeError): - cls.flag = True - - def setUp(self): - if self.flag: - self.fail("%s is not started ):" % self._process_cmd) - - @classmethod - def _get_readline(cls, pfile): - pfile = os.path.join(cls.home, pfile) - try: - with open(pfile, 'rb') as p: - return p.readline().strip() - except (OSError, IOError): - pass - - @classmethod - def _stop_process(cls, timeout=5): - cls.process.send_signal(signal.SIGTERM) - try: - cls.process.wait(timeout) - except psutil.TimeoutExpired: - return False - return True - - @classmethod - def _kill_process(cls, timeout=5): - try: - cls.process.send_signal(signal.SIGKILL) - cls.process.wait(timeout) - # Windows or already dead - except (AttributeError, psutil.NoSuchProcess): - return True - # except psutil.TimeoutExpired propagates, it means something is very - # wrong - return True - - @classmethod - def _cleanup_files(cls): - cleanup(cls.home, cls._files) - - @classmethod - def tearDownClass(cls): - """Ensures that pybitmessage stopped and removes files""" - try: - if not cls._stop_process(10): - processes = cls.process.children(recursive=True) - processes.append(cls.process) - for p in processes: - try: - p.kill() - except psutil.NoSuchProcess: - pass - except psutil.NoSuchProcess: - pass - finally: - cls._cleanup_files() - - def _test_threads(self): - """Test number and names of threads""" - - # pylint: disable=invalid-name - self.longMessage = True - - try: - # using ps for posix platforms - # because of https://github.com/giampaolo/psutil/issues/613 - thread_names = subprocess.check_output([ - "ps", "-L", "-o", "comm=", "--pid", - str(self.process.pid) - ]).split() - except subprocess.CalledProcessError: - thread_names = [] - except: # noqa:E722 - thread_names = [] - - running_threads = len(thread_names) - if 0 < running_threads < 30: # adequacy check - extra_threads = [] - missing_threads = [] - for thread_name in thread_names: - if thread_name not in self._threads_names: - extra_threads.append(thread_name) - for thread_name in self._threads_names: - if thread_name not in thread_names: - missing_threads.append(thread_name) - - msg = "Missing threads: {}, Extra threads: {}".format( - ",".join(missing_threads), ",".join(extra_threads)) - else: - running_threads = self.process.num_threads() - if sys.platform.startswith('win'): - running_threads -= 1 # one extra thread on Windows! - msg = "Unexpected running thread count" - - self.assertGreaterEqual( - running_threads, - self._threads_count_min, - msg) - - self.assertLessEqual( - running_threads, - self._threads_count_max, - msg) - - -class TestProcessShutdown(TestProcessProto): - """Separate test case for SIGTERM""" - def test_shutdown(self): - """Send to pybitmessage SIGTERM and ensure it stopped""" - # longer wait time because it's not a benchmark - self.assertTrue( - self._stop_process(20), - '%s has not stopped in 20 sec' % ' '.join(self._process_cmd)) - - -class TestProcess(TestProcessProto): - """A test case for pybitmessage process""" - @unittest.skipIf(sys.platform[:5] != 'linux', 'probably needs prctl') - def test_process_name(self): - """Check PyBitmessage process name""" - self.assertEqual(self.process.name(), 'PyBitmessage') - - @unittest.skipIf(psutil.version_info < (4, 0), 'psutil is too old') - def test_home(self): - """Ensure BITMESSAGE_HOME is used by process""" - self.assertEqual( - self.process.environ().get('BITMESSAGE_HOME'), self.home) - - @unittest.skipIf( - os.getenv('WINEPREFIX'), "process.connections() doesn't work on wine") - def test_listening(self): - """Check that pybitmessage listens on port 8444""" - for c in self.process.connections(): - if c.status == 'LISTEN': - self.assertEqual(c.laddr[1], 8444) - break - - def test_files(self): - """Check existence of PyBitmessage files""" - for pfile in self._files: - if pfile.startswith('.'): - continue - self.assertIsNot( - self._get_readline(pfile), None, - 'Failed to read file %s' % pfile - ) - - def test_threads(self): - """Testing PyBitmessage threads""" - self._test_threads() diff --git a/src/tests/test_protocol.py b/src/tests/test_protocol.py deleted file mode 100644 index e3137b25..00000000 --- a/src/tests/test_protocol.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Tests for common protocol functions -""" - -import sys -import unittest - -from pybitmessage import protocol, state -from pybitmessage.helper_startup import fixSocket - - -class TestSocketInet(unittest.TestCase): - """Base class for test cases using protocol.encodeHost()""" - - @classmethod - def setUpClass(cls): - """Execute fixSocket() before start. Only for Windows?""" - fixSocket() - - -class TestProtocol(TestSocketInet): - """Main protocol test case""" - - def test_checkIPv4Address(self): - """Check the results of protocol.checkIPv4Address()""" - token = 'HELLO' - # checking protocol.encodeHost()[12:] - self.assertEqual( # 127.0.0.1 - token, protocol.checkIPv4Address(b'\x7f\x00\x00\x01', token, True)) - self.assertFalse( - protocol.checkIPv4Address(b'\x7f\x00\x00\x01', token)) - self.assertEqual( # 10.42.43.1 - token, protocol.checkIPv4Address(b'\n*+\x01', token, True)) - self.assertFalse( - protocol.checkIPv4Address(b'\n*+\x01', token, False)) - self.assertEqual( # 192.168.0.254 - token, protocol.checkIPv4Address(b'\xc0\xa8\x00\xfe', token, True)) - self.assertEqual( # 172.31.255.254 - token, protocol.checkIPv4Address(b'\xac\x1f\xff\xfe', token, True)) - # self.assertEqual( # 169.254.1.1 - # token, protocol.checkIPv4Address(b'\xa9\xfe\x01\x01', token, True)) - # self.assertEqual( # 254.128.1.1 - # token, protocol.checkIPv4Address(b'\xfe\x80\x01\x01', token, True)) - self.assertFalse( # 8.8.8.8 - protocol.checkIPv4Address(b'\x08\x08\x08\x08', token, True)) - - def test_checkIPv6Address(self): - """Check the results of protocol.checkIPv6Address()""" - test_ip = '2001:db8::ff00:42:8329' - self.assertEqual( - 'test', protocol.checkIPv6Address( - protocol.encodeHost(test_ip), 'test')) - self.assertFalse( - protocol.checkIPv6Address( - protocol.encodeHost(test_ip), 'test', True)) - for test_ip in ('fe80::200:5aee:feaa:20a2', 'fdf8:f53b:82e4::53'): - self.assertEqual( - 'test', protocol.checkIPv6Address( - protocol.encodeHost(test_ip), 'test', True)) - self.assertFalse( - protocol.checkIPv6Address( - protocol.encodeHost(test_ip), 'test')) - - def test_check_local(self): - """Check the logic of TCPConnection.local""" - self.assertTrue( - protocol.checkIPAddress(protocol.encodeHost('127.0.0.1'), True)) - self.assertTrue( - protocol.checkIPAddress(protocol.encodeHost('192.168.0.1'), True)) - self.assertTrue( - protocol.checkIPAddress(protocol.encodeHost('10.42.43.1'), True)) - self.assertTrue( - protocol.checkIPAddress(protocol.encodeHost('172.31.255.2'), True)) - self.assertFalse(protocol.checkIPAddress( - protocol.encodeHost('2001:db8::ff00:42:8329'), True)) - - globalhost = protocol.encodeHost('8.8.8.8') - self.assertFalse(protocol.checkIPAddress(globalhost, True)) - self.assertEqual(protocol.checkIPAddress(globalhost), '8.8.8.8') - - @unittest.skipIf( - sys.hexversion >= 0x3000000, 'this is still not working with python3') - def test_check_local_socks(self): - """The SOCKS part of the local check""" - self.assertTrue( - not protocol.checkSocksIP('127.0.0.1') - or state.socksIP) - - def test_network_group(self): - """Test various types of network groups""" - - test_ip = '1.2.3.4' - self.assertEqual(b'\x01\x02', protocol.network_group(test_ip)) - - test_ip = '127.0.0.1' - self.assertEqual('IPv4', protocol.network_group(test_ip)) - - self.assertEqual( - protocol.network_group('8.8.8.8'), - protocol.network_group('8.8.4.4')) - self.assertNotEqual( - protocol.network_group('1.1.1.1'), - protocol.network_group('8.8.8.8')) - - test_ip = '0102:0304:0506:0708:090A:0B0C:0D0E:0F10' - self.assertEqual( - b'\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C', - protocol.network_group(test_ip)) - - for test_ip in ( - 'bootstrap8444.bitmessage.org', 'quzwelsuziwqgpt2.onion', None): - self.assertEqual( - test_ip, protocol.network_group(test_ip)) diff --git a/src/tests/test_randomtrackingdict.py b/src/tests/test_randomtrackingdict.py deleted file mode 100644 index 2db3c423..00000000 --- a/src/tests/test_randomtrackingdict.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Tests for RandomTrackingDict Class -""" -import random -import unittest - -from time import time - - -class TestRandomTrackingDict(unittest.TestCase): - """ - Main protocol test case - """ - - @staticmethod - def randString(): - """helper function for tests, generates a random string""" - retval = '' - for _ in range(32): - retval += chr(random.randint(0, 255)) - return retval - - def test_check_randomtrackingdict(self): - """Check the logic of RandomTrackingDict class""" - from pybitmessage.randomtrackingdict import RandomTrackingDict - a = [] - k = RandomTrackingDict() - - a.append(time()) - for i in range(50000): - k[self.randString()] = True - a.append(time()) - - while k: - retval = k.randomKeys(1000) - if not retval: - self.fail("error getting random keys") - - try: - k.randomKeys(100) - self.fail("bad") - except KeyError: - pass - for i in retval: - del k[i] - a.append(time()) - - for x in range(len(a) - 1): - self.assertLess(a[x + 1] - a[x], 10) diff --git a/src/tests/test_sqlthread.py b/src/tests/test_sqlthread.py deleted file mode 100644 index a612df3a..00000000 --- a/src/tests/test_sqlthread.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Tests for SQL thread""" -# flake8: noqa:E402 -import os -import tempfile -import threading -import unittest - -from .common import skip_python3 - -skip_python3() - -os.environ['BITMESSAGE_HOME'] = tempfile.gettempdir() - -from pybitmessage.helper_sql import ( - sqlQuery, sql_ready, sqlStoredProcedure) # noqa:E402 -from pybitmessage.class_sqlThread import sqlThread # noqa:E402 -from pybitmessage.addresses import encodeAddress # noqa:E402 - - -class TestSqlThread(unittest.TestCase): - """Test case for SQL thread""" - - @classmethod - def setUpClass(cls): - # Start SQL thread - sqlLookup = sqlThread() - sqlLookup.daemon = True - sqlLookup.start() - sql_ready.wait() - - @classmethod - def tearDownClass(cls): - sqlStoredProcedure('exit') - for thread in threading.enumerate(): - if thread.name == "SQL": - thread.join() - - def test_create_function(self): - """Check the result of enaddr function""" - encoded_str = encodeAddress(4, 1, "21122112211221122112") - - query = sqlQuery('SELECT enaddr(4, 1, "21122112211221122112")') - self.assertEqual( - query[0][-1], encoded_str, "test case fail for create_function") diff --git a/src/threads.py b/src/threads.py deleted file mode 100644 index ac8bf7a6..00000000 --- a/src/threads.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -PyBitmessage does various tasks in separate threads. Most of them inherit -from `.network.StoppableThread`. There are `addressGenerator` for -addresses generation, `objectProcessor` for processing the network objects -passed minimal validation, `singleCleaner` to periodically clean various -internal storages (like inventory and knownnodes) and do forced garbage -collection, `singleWorker` for doing PoW, `sqlThread` for querying sqlite -database. - -There are also other threads in the `.network` package. - -:func:`set_thread_name` is defined here for the threads that don't inherit from -:class:`.network.StoppableThread` -""" - -import threading - -from class_addressGenerator import addressGenerator -from class_objectProcessor import objectProcessor -from class_singleCleaner import singleCleaner -from class_singleWorker import singleWorker -from class_sqlThread import sqlThread - -try: - import prctl -except ImportError: - def set_thread_name(name): - """Set a name for the thread for python internal use.""" - threading.current_thread().name = name -else: - def set_thread_name(name): - """Set the thread name for external use (visible from the OS).""" - prctl.set_name(name) - - def _thread_name_hack(self): - set_thread_name(self.name) - threading.Thread.__bootstrap_original__(self) - # pylint: disable=protected-access - threading.Thread.__bootstrap_original__ = threading.Thread._Thread__bootstrap - threading.Thread._Thread__bootstrap = _thread_name_hack - - -printLock = threading.Lock() - -__all__ = [ - "addressGenerator", "objectProcessor", "singleCleaner", "singleWorker", - "sqlThread", "printLock" -] diff --git a/src/throttle.py b/src/throttle.py new file mode 100644 index 00000000..6e53f217 --- /dev/null +++ b/src/throttle.py @@ -0,0 +1,81 @@ +import math +import threading +import time + +from bmconfigparser import BMConfigParser +from singleton import Singleton +import state + +class Throttle(object): + minChunkSize = 4096 + maxChunkSize = 131072 + + def __init__(self, limit=0): + self.limit = limit + self.speed = 0 + self.chunkSize = Throttle.maxChunkSize + self.txTime = int(time.time()) + self.txLen = 0 + self.total = 0 + self.timer = threading.Event() + self.lock = threading.RLock() + self.resetChunkSize() + + def recalculate(self): + with self.lock: + now = int(time.time()) + if now > self.txTime: + self.speed = self.txLen / (now - self.txTime) + self.txLen -= self.limit * (now - self.txTime) + self.txTime = now + if self.txLen < 0 or self.limit == 0: + self.txLen = 0 + + def wait(self, dataLen): + with self.lock: + self.txLen += dataLen + self.total += dataLen + while state.shutdown == 0: + self.recalculate() + if self.limit == 0: + break + if self.txLen < self.limit: + break + self.timer.wait(0.2) + + def getSpeed(self): + self.recalculate() + return self.speed + + def resetChunkSize(self): + with self.lock: + # power of two smaller or equal to speed limit + try: + self.chunkSize = int(math.pow(2, int(math.log(self.limit,2)))) + except ValueError: + self.chunkSize = Throttle.maxChunkSize + # range check + if self.chunkSize < Throttle.minChunkSize: + self.chunkSize = Throttle.minChunkSize + elif self.chunkSize > Throttle.maxChunkSize: + self.chunkSize = Throttle.maxChunkSize + +@Singleton +class SendThrottle(Throttle): + def __init__(self): + Throttle.__init__(self, BMConfigParser().safeGetInt('bitmessagesettings', 'maxuploadrate')*1024) + + def resetLimit(self): + with self.lock: + self.limit = BMConfigParser().safeGetInt('bitmessagesettings', 'maxuploadrate')*1024 + Throttle.resetChunkSize(self) + +@Singleton +class ReceiveThrottle(Throttle): + def __init__(self): + Throttle.__init__(self, BMConfigParser().safeGetInt('bitmessagesettings', 'maxdownloadrate')*1024) + + def resetLimit(self): + with self.lock: + self.limit = BMConfigParser().safeGetInt('bitmessagesettings', 'maxdownloadrate')*1024 + Throttle.resetChunkSize(self) diff --git a/src/tr.py b/src/tr.py index eec82c37..cf7f16ac 100644 --- a/src/tr.py +++ b/src/tr.py @@ -1,59 +1,39 @@ -""" -Translating text -""" import os -try: - import state -except ImportError: - from . import state - +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: - """ - This is used so that the translateText function can be used - when we are in daemon mode and not using any QT functions. - """ - # pylint: disable=old-style-class,too-few-public-methods def __init__(self, context, text): self.context = context self.text = text - - def arg(self, _): - """Replace argument placeholders""" + def arg(self,argument): if '%' in self.text: - # This doesn't actually do anything with the arguments - # because we don't have a UI in which to display this information anyway. - return translateClass(self.context, self.text.replace('%', '', 1)) - return self.text + return translateClass(self.context, self.text.replace('%','',1)) # This doesn't actually do anything with the arguments because we don't have a UI in which to display this information anyway. + else: + return self.text - -def _translate(context, text, disambiguation=None, encoding=None, n=None): - # pylint: disable=unused-argument +def _translate(context, text, disambiguation = None, encoding = None, n = None): return translateText(context, text, n) - -def translateText(context, text, n=None): - """Translate text in context""" +def translateText(context, text, n = None): try: - enableGUI = state.enableGUI + is_daemon = shared.thisapp.daemon except AttributeError: # inside the plugin - enableGUI = True - if enableGUI: + is_daemon = False + if not is_daemon: try: from PyQt4 import QtCore, QtGui except Exception as err: - print('PyBitmessage requires PyQt unless you want to run it as a daemon' - ' and interact with it using the API.' - ' You can download PyQt from http://www.riverbankcomputing.com/software/pyqt/download' - ' or by searching Google for \'PyQt Download\'.' - ' If you want to run in daemon mode, see https://bitmessage.org/wiki/Daemon') - print('Error message:', err) - os._exit(0) # pylint: disable=protected-access + print 'PyBitmessage requires PyQt unless you want to run it as a daemon and interact with it using the API. You can download PyQt from http://www.riverbankcomputing.com/software/pyqt/download or by searching Google for \'PyQt Download\'. If you want to run in daemon mode, see https://bitmessage.org/wiki/Daemon' + print 'Error message:', err + os._exit(0) if n is None: return QtGui.QApplication.translate(context, text) - return QtGui.QApplication.translate(context, text, None, QtCore.QCoreApplication.CodecForTr, n) + else: + return QtGui.QApplication.translate(context, text, None, QtCore.QCoreApplication.CodecForTr, n) else: if '%' in text: - return translateClass(context, text.replace('%', '', 1)) - return text + return translateClass(context, text.replace('%','',1)) + else: + return text diff --git a/src/translations/bitmessage.pro b/src/translations/bitmessage.pro index b131d8e5..f5d6b946 100644 --- a/src/translations/bitmessage.pro +++ b/src/translations/bitmessage.pro @@ -1,19 +1,28 @@ SOURCES = ../addresses.py\ ../bitmessagemain.py\ ../class_addressGenerator.py\ + ../class_outgoingSynSender.py\ ../class_objectProcessor.py\ + ../class_receiveDataThread.py\ + ../class_sendDataThread.py\ ../class_singleCleaner.py\ + ../class_singleListener.py\ ../class_singleWorker.py\ ../class_sqlThread.py\ + ../helper_bitcoin.py\ + ../helper_bootstrap.py\ + ../helper_generic.py\ + ../helper_inbox.py\ ../helper_msgcoding.py\ - ../helper_search.py\ + ../helper_sent.py\ + ../helper_startup.py\ ../namecoin.py\ ../proofofwork.py\ + ../shared.py\ ../upnp.py\ ../bitmessageqt/__init__.py\ ../bitmessageqt/account.py\ - ../bitmessageqt/address_dialogs.py\ - ../bitmessageqt/addressvalidator.py\ + ../bitmessageqt/address_dialogs.py\ ../bitmessageqt/bitmessageui.py\ ../bitmessageqt/blacklist.py\ ../bitmessageqt/dialogs.py\ @@ -23,10 +32,9 @@ SOURCES = ../addresses.py\ ../bitmessageqt/messageview.py\ ../bitmessageqt/networkstatus.py\ ../bitmessageqt/newchandialog.py\ - ../bitmessageqt/settings.py\ - ../bitmessageqt/support.py\ - ../plugins/indicator_libmessaging.py\ - ../plugins/menu_qrcode.py + ../bitmessageqt/safehtmlparser.py\ + ../bitmessageqt/settings.py\ + ../plugins/qrcodeui.py FORMS = \ ../bitmessageqt/about.ui\ diff --git a/src/translations/bitmessage_eo.qm b/src/translations/bitmessage_eo.qm index 77c20edf..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 5707a390..beff4aca 100644 --- a/src/translations/bitmessage_eo.ts +++ b/src/translations/bitmessage_eo.ts @@ -60,27 +60,27 @@ @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 @@ -112,7 +112,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: @@ -152,54 +152,10 @@ Please type the desired email address (including @mailchuck.com) below: # specified. As this scheme uses deterministic public keys, you will receive # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - - - - - - # You can use this to configure your email gateway account -# Uncomment the setting you want to use -# Here are the options: -# -# pgp: server -# The email gateway will create and maintain PGP keys for you and sign, verify, -# encrypt and decrypt on your behalf. When you want to use PGP but are lazy, -# use this. Requires subscription. -# -# pgp: local -# The email gateway will not conduct PGP operations on your behalf. You can -# either not use PGP at all, or use it locally. -# -# attachments: yes -# Incoming attachments in the email will be uploaded to MEGA.nz, and you can -# download them from there by following the link. Requires a subscription. -# -# attachments: no -# Attachments will be ignored. -# -# archive: yes -# Your incoming emails will be archived on the server. Use this if you need -# help with debugging problems or you need a third party proof of emails. This -# however means that the operator of the service will be able to read your -# emails even after they have been delivered to you. -# -# archive: no -# Incoming emails will be deleted from the server as soon as they are relayed -# to you. -# -# masterpubkey_btc: BIP44 xpub key or electrum v1 public seed -# offset_btc: integer (defaults to 0) -# feeamount: number with up to 8 decimal places -# feecurrency: BTC, XBT, USD, EUR or GBP -# Use these if you want to charge people who send you emails. If this is on and -# an unknown person sends you an email, they will be requested to pay the fee -# specified. As this scheme uses deterministic public keys, you will receive -# the money directly. To turn it off again, set "feeamount" to 0. Requires -# subscription. # Tie ĉi vi povas agordi vian konton ĉe retpoŝta kluzo # Malkomenti agordojn kiujn vi volas uzi -# Jen agordoj: +# Jenaj agordoj: # # pgp: server # La retpoŝta kluzo kreos kaj prizorgos PGP-ŝlosilojn por vi por subskribi, @@ -239,122 +195,122 @@ Please type the desired email address (including @mailchuck.com) below: 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 blok-listo + Aldoni sendinton al via nigra listo - + Move to Trash Movi al rubujo - + Undelete Malforigi - + View HTML code as formatted text Montri HTML-n kiel aranĝitan tekston - + Save message as... Konservi mesaĝon kiel… - + Mark Unread Marki kiel nelegitan - + New Nova - + Enable Ŝalti - + Disable Malŝalti - + Set avatar... Agordi avataron… - + Copy address to clipboard Kopii adreson al tondejo - + Special address behavior... Speciala sinteno de adreso… - + Email gateway Retpoŝta kluzo - + Delete 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 forigi ĝin? - + Waiting for their encryption key. Will request it again soon. Atendado je ilia ĉifroŝlosilo. Baldaŭ petos ĝin denove. @@ -364,17 +320,17 @@ Please type the desired email address (including @mailchuck.com) below: - + 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 @@ -384,135 +340,135 @@ Please type the desired email address (including @mailchuck.com) below: - + 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 per redakti 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 per redakti 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 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.) + 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 per redakti 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. @@ -582,22 +538,22 @@ 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 @@ -605,19 +561,19 @@ Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dos 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. + 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. @@ -660,57 +616,57 @@ 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. + 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. - + New Message Nova mesaĝo @@ -725,59 +681,59 @@ Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dos - + Address is valid. Adreso estas ĝusta. - + The address you entered was invalid. Ignoring it. 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 ŝanĝo de numero de pordo efektivigu. + 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 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. - Rimarku, ke la templimon vi enmetis estas pli malgranda ol tempo dum kiu Bitmesaĝo atendas por resendi unuafoje, do viaj mesaĝoj estos senditaj neniam. + 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. @@ -790,44 +746,44 @@ Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dos - + Passphrase mismatch Pasfrazoj malsamas - + The passphrase you entered twice doesn't match. Try again. 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. + Eraro: La adreso kun kiu vi provas sendi estas malŝaltita. Vi devos ĝin ŝalti en la langeto 'Viaj identigoj' antaŭ uzi ĝin. @@ -835,42 +791,42 @@ Estas grava, ke vi faru sekurkopion de tiu dosiero. Ĉu vi volas malfermi la dos - + Entry added to the blacklist. Edit the label to your liking. - Aldonis elementon al la listo de blokitoj. Redaktu la etikedon laŭvole. + 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 la listo de blokitoj. Provu renomi la jaman se vi volas. + 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. Malforigis elementon. - + Save As... 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? @@ -879,7 +835,7 @@ Are you sure you want to delete the subscription? Ĉ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? @@ -888,32 +844,32 @@ Are you sure you want to delete the channel? Ĉ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… @@ -923,37 +879,37 @@ Are you sure you want to delete the channel? - + The address should start with ''BM-'' La adreso komencu kun “BM-” - + The address is not typed or copied correctly (the checksum failed). 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 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. Iuj datumoj koditaj en la adreso estas tro mallongaj. - + Some data encoded in the address is too long. Iuj datumoj koditaj en la adreso estas tro longaj. - + Some data encoded in the address is malformed. Iuj datumoj koditaj en la adreso estas misformitaj. @@ -963,12 +919,12 @@ Are you sure you want to delete the channel? - + Address is an old type. We cannot display its past broadcasts. 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. @@ -978,297 +934,297 @@ Are you sure you want to delete the channel? - + 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 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 - Anigi / krei kanalon + Aniĝi / Krei kanalon - + All accounts Ĉiuj kontoj - + Zoom level %1% Pligrandigo: %1 - + Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. Eraro: Vi ne povas aldoni duoble la saman adreson al via listo. Eble renomi la jaman se vi volas. - + Add new entry Aldoni novan elementon - + Display the %1 recent broadcast(s) from this address. - + 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 finiĝos… %1% - + Shutting down Pybitmessage... %1% Fermado de PyBitmessage… %1% - + Waiting for objects to be sent... %1% Atendado ĝis objektoj estos senditaj… %1% - + Saving settings... %1% Konservado de agordoj… %1% - + Shutting down core... %1% Fermado de kerno… %1% - + Stopping notifications... %1% Haltigado de sciigoj… %1% - + Shutdown imminent... %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% - + Sent Senditaj - + Generating one new address Kreado de unu nova adreso - + Done generating address. Doing work necessary to broadcast it... Adreso kreita. Kalkulado de laborpruvo, kiu endas por elsendi ĝin… - + Generating %1 new addresses. Kreado de %1 novaj adresoj. - + %1 is already in 'Your Identities'. Not adding it again. %1 jam estas en ‘Viaj Identigoj’. Ĝi ne estos aldonita ree. - + Done generating address Ĉiuj adresoj estas kreitaj @@ -1278,201 +1234,211 @@ Are you sure you want to delete the channel? - + Disk full Disko plenplena - + Alert: Your disk or data storage volume is full. Bitmessage will now exit. 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… - + 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 objektoHaltigis 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 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) - - Unknown namecoin interface type: %1 - Nekonata tipo de namecoin-fasado: %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. - - The connection to namecoin failed. - Malsukcesis konekti al namecoin. - - - + 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 @@ -1486,195 +1452,140 @@ Bonvenon al facila kaj sekura Bitmesaĝo * 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ĝi ĉe retpoŝta kluzo. Ĉu provi registriĝi? + 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. - + 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 - - Disconnecting - Malkonektado - - - - Connecting - Konektado - - - - Bitmessage will now drop all connections. Are you sure? - Bitmesaĝo ĉesos ĉiujn konektojn. Ĉu pluigi? - - - - Bitmessage will now start connecting to network. Are you sure? - Bitmesaĝo komencos konekti al la reto. Ĉu pluigi? - - - + 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… - + Waiting for finishing synchronisation... 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? - - Error occurred: could not load message from disk. - Eraro okazis: ne povis legi mesaĝon el la disko. - - - - Display the %n recent broadcast(s) from this address. - Montri %n lastan elsendon de tiu ĉi adreso.Montri %n lastajn elsendojn de tiu ĉi adreso. - - - + Go online Konekti - + Go offline Malkonekti - - - Clear - Forviŝi - - - - inbox - ricevujo - - - - new - novaj - - - - sent - senditaj - - - - trash - rubujo - 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 @@ -1682,14 +1593,14 @@ Bonvenon al facila kaj sekura Bitmesaĝo 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 @@ -1845,7 +1756,7 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj 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. @@ -1868,12 +1779,12 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj - + <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 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. @@ -1883,7 +1794,7 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj - + <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> @@ -1893,12 +1804,12 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj Use a Blacklist (Allow all incoming messages except those on the Blacklist) - Uzi liston de blokataj (permesi ĉiujn alvenajn mesaĝojn escepte tiujn en la listo) + Uzi nigran liston (permesas ĉiujn alvenajn mesaĝojn escepte tiujn en la nigra listo) Use a Whitelist (Block all incoming messages except those on the Whitelist) - Uzi liston de permesataj (bloki ĉiujn alvenajn mesaĝojn escepte tiujn en la listo) + Uzi blankan liston (blokas ĉiujn alvenajn mesaĝojn escepte tiujn en la blanka listo) @@ -1916,14 +1827,14 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj Adreso - + Blacklist - Blokataj kontaktoj + Nigra listo - + Whitelist - Permesataj kontaktoj + Blanka listo @@ -2000,7 +1911,7 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj 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). @@ -2053,27 +1964,27 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj - + 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 @@ -2088,32 +1999,32 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj Elŝuto: 0 kB/s - + Network Status - Reto + Reta stato - + byte(s) bitokobitokoj - + Object(s) to be synced: %n Objekto por samtempigi: %nObjektoj 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. @@ -2208,7 +2119,7 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj Create or join a chan - Krei aŭ anigi kanalon + Krei aŭ aniĝi kanalon @@ -2239,17 +2150,17 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj newchandialog - + Successfully created / joined chan %1 - Sukcese kreis / anigis al la kanalo %1 + Sukcese kreis / aniĝis al la kanalo %1 - + Chan creation / joining failed Kreado / aniĝado al kanalo malsukcesis - + Chan creation / joining cancelled Kreado / aniĝado al kanalo nuligita @@ -2257,21 +2168,29 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj 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 @@ -2328,218 +2247,218 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj settingsDialog - + Settings Agordoj - + Start Bitmessage on user login Startigi Bitmesaĝon dum ensaluto de uzanto - + Tray Taskopleto - + Start Bitmessage in the tray (don't show main window) - Startigi Bitmesaĝon en la taskopleto ne montrante tiun ĉi fenestron + Startigi Bitmesaĝon en la taskopleto (tray) ne montrante tiun fenestron - + Minimize to tray Plejetigi al taskopleto - + Close to tray Fermi al taskopleto - + Show notification when message received Montri sciigon kiam mesaĝo alvenas - + Run in Portable Mode Ekzekucii en Portebla Reĝimo - + 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 la Portebla Reĝimo, mesaĝoj kaj agordoj estas enmemorigitaj en la sama dosierujo kiel la programo mem anstataŭ en la dosierujo por datumoj de aplikaĵoj. Tio igas ĝin komforta por ekzekucii Bitmesaĝon el USB poŝmemorilo. + En Portebla Reĝimo, mesaĝoj kaj agordoj estas enmemorigitaj en la sama dosierujo kiel la programo mem anstataŭ en la dosierujo por datumoj de aplikaĵoj. Tio igas ĝin komforta ekzekucii Bitmesaĝon el USB poŝmemorilo. - + Willingly include unencrypted destination address when sending to a mobile device Volonte inkluzivi malĉifritan cel-adreson dum sendado al portebla aparato. - + Use Identicons Uzi ID-avatarojn - + Reply below Quote Respondi sub citaĵo - + Interface Language Fasada lingvo - + System Settings system Sistemaj agordoj - + User Interface Fasado - + Listening port - Aŭskultanta pordo + Aŭskultanta pordo (port) - + Listen for connections on port: Aŭskulti pri konektoj ĉe pordo: - + UPnP: UPnP: - + Bandwidth limit Rettrafika limo - + Maximum download rate (kB/s): [0: unlimited] Maksimuma rapido de elŝuto (kB/s): [0: senlima] - + Maximum upload rate (kB/s): [0: unlimited] Maksimuma rapido de alŝuto (kB/s): [0: senlima] - + Proxy server / Tor - Retperanta servilo / Tor + Retperanta (proxy) servilo / Tor - + Type: Speco: - + Server hostname: - Servil-nomo: + Servilo gastiga nomo (hostname): - + Port: - Pordo: + Pordo (port): - + Authentication Aŭtentigo - + Username: Uzantnomo: - + Pass: Pasvorto: - + Listen for incoming connections when using proxy Aŭskulti pri alvenaj konektoj kiam dum uzado de retperanto - + none neniu - + SOCKS4a SOCKS4a - + SOCKS5 SOCKS5 - + Network Settings - Agordoj de reto + Retaj agordoj - + Total difficulty: Tuta malfacilaĵo: - + 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, kiun la sendonto devos fari. Duobligo de tiu valoro, duobligas la kvanton de laboro. - + Small message difficulty: Et-mesaĝa malfacilaĵo: - + 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, 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 amikon al via 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. - + 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 'Et-mesaĝa malfacilaĵo' ĉefe efikas malfacilaĵon por sendi malgrandajn mesaĝojn. Duobligo de tiu valoro, preskaŭ duobligas malfacilaĵon por sendi malgrandajn mesaĝojn, sed preskaŭ ne efikas grandajn mesaĝojn. - + Demanded difficulty Postulata malfacilaĵo - + 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. Tie ĉi vi povas agordi maksimuman kvanton da laboro kiun vi faru por sendi mesaĝon al alian persono. Se vi agordos ilin al 0, ĉiuj valoroj estos akceptitaj. - + Maximum acceptable total difficulty: Maksimuma akceptata tuta malfacilaĵo: - + Maximum acceptable small message difficulty: Maksimuma akceptata malfacilaĵo por et-mesaĝoj: - + Max acceptable difficulty Maksimuma akcepta malfacilaĵo @@ -2549,87 +2468,87 @@ La “hazardnombra” adreso estas antaŭagordita, sed antaŭkalkuleblaj adresoj - + <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>Bitmesaĝo povas apliki alian Bitmono-bazitan programon - Namecoin - por fari adresojn hom-legeblajn. Ekzemple anstataŭ diri al via amiko longan Bitmesaĝan adreson, vi povas simple peti lin pri sendi mesaĝon al <span style=" font-style:italic;">id/kashnomo. </span></p><p>(Kreado de sia propra Bitmesaĝa adreso en Namecoin estas ankoraŭ ete malfacila).</p><p>Bitmesaĝo eblas uzi aŭ na namecoind rekte aŭ jaman aktivan aperon de nmcontrol.</p></body></html> + <html><head/><body><p>Bitmesaĝo povas apliki alian Bitmono-bazitan programon - Namecoin - por fari adresojn hom-legeblajn. Ekzemple anstataŭ diri al via amiko longan Bitmesaĝan adreson, vi povas simple peti lin pri sendi mesaĝon al <span style=" font-style:italic;">id/kashnomo. </span></p><p>(Kreado de sia propra Bitmesaĝa adreso en Namecoin-on estas ankoraŭ ete malfacila).</p><p>Bitmesaĝo eblas uzi aŭ na namecoind rekte aŭ jaman aktivan aperon de nmcontrol.</p></body></html> - + Host: Gastiga servilo: - + Password: Pasvorto: - + Test Testi - + Connect to: Konekti al: - + Namecoind Namecoind - + NMControl NMControl - + Namecoin integration Integrigo kun 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>Implicite se vi sendas mesaĝon al iu kaj li estos eksterrete por iomete da tempo, Bitmesaĝo provos resendi mesaĝon iam poste, kaj iam pli poste. La programo pluigos resendi mesaĝon ĝis sendonto konfirmos liveron. Tie ĉi vi povas ŝanĝi kiam Bitmesaĝo devos rezigni je sendado.</p><p>Lasu tiujn kampojn malplenaj por antaŭagordita sinteno.</p></body></html> - + Give up after Rezigni post - + and kaj - + days tagoj - + months. monatoj. - + Resends Expire Resenda fortempiĝo - + Hide connection notifications Ne montri sciigojn pri konekto - + Maximum outbound connections: [0: none] - Maksimume da eligaj konektoj: [0: senlima] + Maksimumo de eligaj konektoj: [0: senlima] - + Hardware GPU acceleration (OpenCL): Aparatara GPU-a plirapidigo (OpenCL): diff --git a/src/translations/bitmessage_fr.qm b/src/translations/bitmessage_fr.qm index 8cb08a3a..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 149fd1ef..3e64d657 100644 --- a/src/translations/bitmessage_fr.ts +++ b/src/translations/bitmessage_fr.ts @@ -2,17 +2,17 @@ AddAddressDialog - + Add new entry Ajouter une nouvelle entrée - + Label Étiquette - + Address Adresse @@ -20,99 +20,70 @@ EmailGatewayDialog - + Email gateway Passerelle de courriel - + Register on email gateway S’inscrire sur la passerelle de courriel - + Account status at email gateway Statut du compte sur la passerelle de courriel - + Change account settings at email gateway Changer les paramètres de compte sur la passerelle de courriel - + Unregister from email gateway Se désabonner de la passerelle de courriel - + Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. La passerelle de courriel vous permet de communiquer avec des utilisateurs de courriel. Actuellement, seulement la passerelle de courriel de Mailchuck (@mailchuck.com) est disponible. - + Desired email address (including @mailchuck.com): Adresse de courriel désirée (incluant @mailchuck.com) : - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - L’inscription a échoué : - - - - The requested email address is not available, please try a new one. - L'adresse électronique demandée n'est pas disponible, veuillez essayer une nouvelle. - - - - Sending email gateway registration request - Envoi de la demande d’inscription de la passerelle de courriel - - - - 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 - 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) : Email gateway registration - + Inscription à la passerelle de courriel 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: - + La passerelle de courriel vous permet de communiquer avec des utilisateurs de courriels. Actuellement, seule la passerelle de courriel de Mailchuck (@mailchuck.com) est disponible. +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: @@ -153,172 +124,171 @@ Please type the desired email address (including @mailchuck.com) below: # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - - - - - # You can use this to configure your email gateway account -# Uncomment the setting you want to use -# Here are the options: -# + # Vous pouvez utiliser ceci pour configurer votre compte de passerelle de +# messagerie. +# Décommentez les paramètres que vous souhaitez utiliser. +# Les options se trouvent ci-dessous : +# # pgp: server -# The email gateway will create and maintain PGP keys for you and sign, verify, -# encrypt and decrypt on your behalf. When you want to use PGP but are lazy, -# use this. Requires subscription. +# La passerelle de messagerie va créer et conserver pour vous les clefs PGP, +# et va signer, vérifier, chiffrer et déchiffrer en votre nom. Choisissez cela si +# vous voulez utilisez PGP mais que vous êtes paresseux. Exige une inscription. # # pgp: local -# The email gateway will not conduct PGP operations on your behalf. You can -# either not use PGP at all, or use it locally. +# La passerelle de messagerie ne va pas exécuter les commandes PGP en +# votre nom. Vous pouvez soit ne pas utiliser PGP du tout, soit l’utiliser +# localement. # # attachments: yes -# Incoming attachments in the email will be uploaded to MEGA.nz, and you can -# download them from there by following the link. Requires a subscription. +# Les pièces-jointes reçues dans le courriel seront téléversées sur MEGA.nz, +# d’où vous pourrez les télécharger en cliquant sur le lien. Exige une +# inscription. # # attachments: no -# Attachments will be ignored. -# +# Les pièces jointes seront ignorées. +# # archive: yes -# Your incoming emails will be archived on the server. Use this if you need -# help with debugging problems or you need a third party proof of emails. This -# however means that the operator of the service will be able to read your -# emails even after they have been delivered to you. +# Les courriels que vous recevrez seront archivés sur le serveur. Utilisez +# ceci si vous avez besoin d’aide pour des problèmes de déboguage ou +# si vous avez besoin d’une preuve par un tiers des courriels. Cela signifie +# cependant que le fournisseur du service pourra lire vos courriels même +# après leur réception. # # archive: no -# Incoming emails will be deleted from the server as soon as they are relayed -# to you. +# Les courriels reçus seront supprimés du serveur dès qu’ils vous auront été +# transmis. # -# masterpubkey_btc: BIP44 xpub key or electrum v1 public seed -# offset_btc: integer (defaults to 0) -# feeamount: number with up to 8 decimal places -# feecurrency: BTC, XBT, USD, EUR or GBP -# Use these if you want to charge people who send you emails. If this is on and -# an unknown person sends you an email, they will be requested to pay the fee -# specified. As this scheme uses deterministic public keys, you will receive -# the money directly. To turn it off again, set "feeamount" to 0. Requires -# subscription. - - +# masterpubkey_btc: clef xpub BIP44 ou graine publique electrum v1 +# offset_btc: entier (par défaut à 0) +# feeamount: nombre avec jusqu’à 8 décimales +# feecurrency: BTC, XBT, USD, EUR ou GBP +# Utilisez ceci si vous voulez faire payer ceux qui vous envoient des courriels. +# Si ceci est activé et qu’une personne inconnue vous envoie un courriel, il +# devra payer le tarif indiqué. Comme ce mécanisme emploie des clefs +# publiques déterministes, vous recevrez l’argent directement. Pour désactiver +# à nouveau ceci, réglez "feeamount" à 0. Exige une inscription. + 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. @@ -328,17 +298,17 @@ Please type the desired email address (including @mailchuck.com) below: - + 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 @@ -348,77 +318,77 @@ Please type the desired email address (including @mailchuck.com) below: - + 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 - + Send Envoyer - + Subscribe S’abonner - + Channel Canal - + Quit 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. @@ -426,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. @@ -543,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 @@ -567,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. @@ -622,57 +592,57 @@ 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". + 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. + 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 puis sélectionnez "Envoyer un message à ces adresses". + 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 @@ -682,157 +652,157 @@ Le destinataire doit l’obtenir avant ce temps. Si votre client Bitmessage ne r - + 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. + 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? @@ -841,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? @@ -850,282 +820,282 @@ 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). - + Bitmessage Bitmessage - + Identities Identités - + New Identity Nouvelle identité - + Search Chercher - + All Tous - + To Vers - + From De - + Subject Sujet - + Message Message - + Received Reçu - + Messages Messages - + Address book Carnet d’adresses - + Address Adresse - + Add Contact Ajouter un contact - + Fetch Namecoin ID Récupère l’ID Namecoin - + Subject: Sujet : - + From: De : - + To: Vers : - + Send ordinary Message Envoyer un message ordinaire - + Send Message to your Subscribers Envoyer un message à vos abonnés - + TTL: TTL : - + Subscriptions Abonnements - + Add new Subscription Ajouter un nouvel abonnement - + Chans Canaux - + Add Chan Ajouter un canal - + File Fichier - + Settings Paramètres - + Help Aide - + Import keys Importer les clés - + Manage keys Gérer les clés - + Ctrl+Q Ctrl+Q - + F1 F1 - + Contact support Contacter le support - + About À propos - + Regenerate deterministic addresses Regénérer les clés déterministes - + Delete all trashed messages Supprimer tous les messages dans la corbeille - + Join / Create chan Rejoindre / créer un canal - + All accounts Tous les comptes @@ -1135,102 +1105,102 @@ 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% - + %n hour(s) %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 @@ -1240,96 +1210,96 @@ 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 @@ -1344,97 +1314,107 @@ Difficulté requise du destinataire : %1 et %2 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. - - - The connection to namecoin failed. - La connexion à Namecoin a échouée. - 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 @@ -1446,198 +1426,123 @@ 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 - - Quiet Mode - Mode tranquille - - - + 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. - - Error: %1 - Erreur : %1 - - - + From %1 De %1 - - Disconnecting - Déconnexion - - - - Connecting - Connexion - - - - Bitmessage will now drop all connectins. Are you sure? - Bitmessage va maintenant laisser tomber toutes les connections. Êtes-vous sûr(e) ? - - - - Bitmessage will now start connecting to network. Are you sure? - - - - + 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 ? - - - Error occurred: could not load message from disk. - Une erreur a eu lieu : ne peut pas charger de message depuis le disque. - - - - Display the %n recent broadcast(s) from this address. - Afficher le messages de diffusion le plus récent originaire de cette adresse.Afficher les %n messages de diffusion les plus récents originaires de cette adresse. - - - - Go online - Passer en-ligne - - - - Go offline - Passer hors-ligne - - - - Clear - Vider - - - - inbox - entrant - - - - new - nouveau - - - - sent - envoyé - - - - trash - corbeille - MessageView - + Follow external link Suivre lien externe - + 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? Le lien "%1" s'ouvrira dans un navigateur. Cela pourrait être un risque de sécurité, cela pourrait vous désanonymiser ou télécharger des données malveillantes. Êtes-vous sûr(e) ? - + HTML detected, click here to display HTML détecté, cliquer ici pour l'afficher - + Click here to disable HTML Cliquer ici pour mettre hors de service le HTML @@ -1645,14 +1550,14 @@ Bienvenue dans le facile et sécurisé Bitmessage 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 @@ -1660,99 +1565,98 @@ Peut-être que vous devriez mettre à niveau Bitmessage. NewAddressDialog - + Create new Address Créer une nouvelle adresse - + 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: - Vous pouvez générer autant d’adresses que vous le souhaitez. En effet, nous vous encourageons à créer et à délaisser vos adresses. Vous pouvez générer des adresses en utilisant des nombres aléatoires ou en utilisant une phrase secrète. Si vous utilisez une phrase secrète, l’adresse sera une adresse "déterministe". -L’option "Nombre Aléatoire" est sélectionnée par défaut mais les adresses déterministes ont certains avantages et inconvénients : + Vous pouvez générer autant d’adresses que vous le souhaitez. En effet, nous vous encourageons à créer et à délaisser vos adresses. Vous pouvez générer des adresses en utilisant des nombres aléatoires ou en utilisant une phrase secrète. Si vous utilisez une phrase secrète, l’adresse sera une adresse "déterministe". L’option 'Nombre Aléatoire' est sélectionnée par défaut mais les adresses déterministes ont certains avantages et inconvénients : - + <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;">Avantages :<br/></span>Vous pouvez recréer vos adresses sur n’importe quel ordinateur. <br/>Vous n’avez pas à vous inquiéter à propos de la sauvegarde de votre fichier keys.dat tant que vous vous rappelez de votre phrase secrète. <br/><span style=" font-weight:600;">Inconvénients :<br/></span>Vous devez vous rappeler (ou noter) votre phrase secrète si vous souhaitez être capable de récréer vos clés si vous les perdez. <br/>Vous devez vous rappeler du numéro de version de l’adresse et du numéro de flux en plus de votre phrase secrète. <br/>Si vous choisissez une phrase secrète faible et que quelqu’un sur Internet parvient à la brute-forcer, il pourra lire vos messages et vous en envoyer.</p></body></html> - + Use a random number generator to make an address Utiliser un générateur de nombres aléatoires pour créer une adresse - + Use a passphrase to make addresses Utiliser une phrase secrète pour créer une adresse - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter Créer une adresse plus courte d’un ou deux caractères (nécessite plusieurs minutes de temps de calcul supplémentaires) - + Make deterministic addresses Créer une adresse déterministe - + Address version number: 4 Numéro de version de l’adresse : 4 - + In addition to your passphrase, you must remember these numbers: En plus de votre phrase secrète, vous devez vous rappeler ces numéros: - + Passphrase Phrase secrète - + Number of addresses to make based on your passphrase: - Nombre d’adresses basées sur votre phrase secrète à créer : + Nombre d’adresses à créer sur base de votre phrase secrète: - + Stream number: 1 Nombre de flux : 1 - + Retype passphrase Retapez la phrase secrète - + Randomly generate address Générer une adresse de manière aléatoire - + Label (not shown to anyone except you) Étiquette (seulement visible par vous) - + Use the most available stream Utiliser le flux le plus disponible - + (best if this is the first of many addresses you will create) (préférable si vous générez votre première adresse) - + Use the same stream as an existing address Utiliser le même flux qu’une adresse existante - + (saves you some bandwidth and processing power) (économise de la bande passante et de la puissance de calcul) @@ -1760,22 +1664,22 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les NewSubscriptionDialog - + Add new entry Ajouter une nouvelle entrée - + Label Étiquette - + Address Adresse - + Enter an address above. Entrez ci-dessus une adresse. @@ -1783,60 +1687,55 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les SpecialAddressBehaviorDialog - + Special Address Behavior Comportement spécial de l’adresse - + Behave as a normal address Se comporter comme une adresse normale - + Behave as a pseudo-mailing-list address Se comporter comme une adresse d’une pseudo liste de diffusion - + Mail received to a pseudo-mailing-list address will be automatically broadcast to subscribers (and thus will be public). Un mail reçu sur une adresse d’une pseudo liste de diffusion sera automatiquement diffusé aux abonnés (et sera donc public). - + Name of the pseudo-mailing-list: Nom de la pseudo liste de diffusion : - - - 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. - aboutDialog - + About À propos - - - PyBitmessage - - - version ? - + 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. @@ -1845,10 +1744,10 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les <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-2017 The Bitmessage Developers</p></body></html> - <html><head/><body><p>Copyright © 2012-2016 Jonathan Warren<br/>Copyright © 2013-2017 Les développeurs de Bitmessage</p></body></html> + + + <html><head/><body><p>Copyright &copy; 2012-2016 Jonathan Warren<br/>Copyright &copy; 2013-2016 The Bitmessage Developers</p></body></html> + <html><head/><body><p>Copyright &copy; 2012-2016 Jonathan Warren<br/>Copyright &copy; 2013-2016 Les développeurs de Bitmessage</p></body></html> @@ -1879,12 +1778,12 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les Adresse - + Blacklist Liste noire - + Whitelist Liste blanche @@ -1892,45 +1791,40 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les connectDialog - + Bitmessage Bitmessage - + Bitmessage won't connect to anyone until you let it. Bitmessage ne connectera à personne avant que vous ne le laissiez faire. - + Connect now Connexion maintenant - + Let me configure special network settings first Me laisser d’abord configurer des paramètres spéciaux de réseau - - - Work offline - Travailler hors-ligne - helpDialog - + Help Aide - + <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 étant un projet collaboratif, une aide peut être trouvée en ligne sur le Wiki de Bitmessage: @@ -1938,35 +1832,30 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les iconGlossaryDialog - + Icon Glossary Glossaire des icônes - + You have no connections with other peers. Vous n’avez aucune connexion avec d’autres pairs. - + 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. Vous avez au moins une connexion sortante avec un pair mais vous n’avez encore reçu aucune connexion entrante. Votre pare-feu ou routeur n’est probablement pas configuré pour transmettre les connexions TCP vers votre ordinateur. Bitmessage fonctionnera correctement, mais le réseau Bitmessage se portera mieux si vous autorisez les connexions entrantes. Cela vous permettra d’être un nœud mieux connecté. You are using TCP port ?. (This can be changed in the settings). - + Vous utilisez le port TCP ?. (Peut être changé dans les paramètres). - + You do have connections with other peers and your firewall is correctly configured. Vous avez des connexions avec d’autres pairs et votre pare-feu est configuré correctement. - - - 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). - networkstatus @@ -1976,37 +1865,37 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les 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° @@ -2016,114 +1905,114 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les - + 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 - + Network Status Statut du réseau - + byte(s) octetoctets - + Object(s) to be synced: %n Objet à synchroniser : %nObjets à 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 - Chiffrement de la connexion + - + List of streams negotiated between you and the peer - Liste de flux négociés entre vous et le pair + @@ -2180,8 +2069,8 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les - Chan passphrase/name: - Phrase passe ou nom : + Chan passhphrase/name: + Nom ou phrase mot de passe du canal : @@ -2202,7 +2091,7 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les newchandialog - + Successfully created / joined chan %1 Le canal %1 a été rejoint ou créé avec succès. @@ -2235,55 +2124,63 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les Module PoW C non disponible. Veuillez le construire. + + qrcodeDialog + + + QR-code + QR-code + + regenerateAddressesDialog - + Regenerate Existing Addresses Regénérer des adresses existantes - + Regenerate existing addresses - Adresses existantes régénérées + Regénérer des adresses existantes - + Passphrase Phrase secrète - + Number of addresses to make based on your passphrase: Nombre d’adresses basées sur votre phrase secrète à créer : - + Address version number: Numéro de version de l’adresse : - + Stream number: Numéro du flux : - + 1 1 - + Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter Créer une adresse plus courte d’un ou deux caractères (nécessite plusieurs minutes de temps de calcul supplémentaires) - + You must check (or not check) this box just like you did (or didn't) when you made your addresses the first time. Vous devez cocher (ou décocher) cette case comme vous l’aviez fait (ou non) lors de la création de vos adresses la première fois. - + 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. Si vous aviez généré des adresses déterministes mais les avez perdues à cause d’un accident (comme une panne de disque dur), vous pouvez les régénérer ici. Si vous aviez utilisé le générateur de nombres aléatoires pour créer vos adresses, ce formulaire ne vous sera d’aucune utilité. @@ -2291,218 +2188,218 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les 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 accomplir. Doubler cette valeur double la charge de travail. + 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é. + 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 @@ -2512,87 +2409,87 @@ L’option "Nombre Aléatoire" est sélectionnée par défaut mais les - + <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 77fa63d1..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 f11289f5..90e49dc2 100644 --- a/src/translations/bitmessage_ja.ts +++ b/src/translations/bitmessage_ja.ts @@ -2,17 +2,17 @@ AddAddressDialog - + Add new entry 新しい項目を追加 - + Label ラベル - + Address アドレス @@ -20,99 +20,70 @@ EmailGatewayDialog - + Email gateway メールゲートウェイ - + Register on email gateway メールゲートウェイで登録 - + Account status at email gateway メールゲートウェイのアカウント状態 - + Change account settings at email gateway メールゲートウェイでアカウントの設定を変更 - + Unregister from email gateway メールゲートウェイから登録抹消 - + Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. メールゲートウェイを使用すると、メールユーザーと通信できます。 現在、Mailchuck メールゲートウェイ (@mailchuck.com) のみが利用可能です。 - + Desired email address (including @mailchuck.com): 希望のメールアドレス (@mailchuck.com を含む): - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - 登録に失敗しました: - - - - The requested email address is not available, please try a new one. - リクエストしたメールアドレスは利用できません。新しいメールアドレスを試してください。 - - - - Sending email gateway registration request - メールゲートウェイの登録リクエストを送信しています - - - - Sending email gateway unregistration request - メールゲートウェイの登録抹消リクエストを送信しています - - - - Sending email gateway status request - メールゲートウェイの状態リクエストを送信しています - 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 を含む) を次のように記入してください: Email gateway registration - + メールゲートウェイの登録 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: - + メールゲートウェイを使用すると、メールユーザーと通信できます。 現在、Mailchuck メールゲートウェイ (@mailchuck.com) のみが利用可能です。 +希望のメールアドレス (@mailchuck.com を含む) を以下に入力してください: Mailchuck - + # You can use this to configure your email gateway account # Uncomment the setting you want to use # Here are the options: @@ -152,50 +123,6 @@ Please type the desired email address (including @mailchuck.com) below: # specified. As this scheme uses deterministic public keys, you will receive # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - - - - - - # You can use this to configure your email gateway account -# Uncomment the setting you want to use -# Here are the options: -# -# pgp: server -# The email gateway will create and maintain PGP keys for you and sign, verify, -# encrypt and decrypt on your behalf. When you want to use PGP but are lazy, -# use this. Requires subscription. -# -# pgp: local -# The email gateway will not conduct PGP operations on your behalf. You can -# either not use PGP at all, or use it locally. -# -# attachments: yes -# Incoming attachments in the email will be uploaded to MEGA.nz, and you can -# download them from there by following the link. Requires a subscription. -# -# attachments: no -# Attachments will be ignored. -# -# archive: yes -# Your incoming emails will be archived on the server. Use this if you need -# help with debugging problems or you need a third party proof of emails. This -# however means that the operator of the service will be able to read your -# emails even after they have been delivered to you. -# -# archive: no -# Incoming emails will be deleted from the server as soon as they are relayed -# to you. -# -# masterpubkey_btc: BIP44 xpub key or electrum v1 public seed -# offset_btc: integer (defaults to 0) -# feeamount: number with up to 8 decimal places -# feecurrency: BTC, XBT, USD, EUR or GBP -# Use these if you want to charge people who send you emails. If this is on and -# an unknown person sends you an email, they will be requested to pay the fee -# specified. As this scheme uses deterministic public keys, you will receive -# the money directly. To turn it off again, set "feeamount" to 0. Requires -# subscription. # これを使用して、メールゲートウェイアカウントを設定できます # 使用する設定のコメントを外してください @@ -224,14 +151,14 @@ Please type the desired email address (including @mailchuck.com) below: # 読むことができるということを意味します。 # # archive: no -# 受信メールは、あなたに転送されるとすぐにサーバーから削除されます。 +# 受信メールは、あなたに中継されるとすぐにサーバーから削除されます。 # # masterpubkey_btc: BIP44 xpub key または electrum v1 public seed # offset_btc: 整数 (デフォルトは 0) # feeamount: 小数点以下 8 桁までの数字 # feecurrency: BTC, XBT, USD, EUR または GBP # メールを送信した人に請求したい場合に、これらを使用します。 これがオンで、 -# 不明な人があなたにメールを送信した場合、指定された手数料を支払うように要求されます。 +# 未知の人があなたにメールを送信した場合、指定された料金を支払うように要求されます。 # この方式は決定的な公開鍵を使用するため、直接お金を受け取ることになります。 # もう一度オフにするには、「feeamount」を 0 に設定します。 # サブスクリプションが必要です。 @@ -241,122 +168,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. 暗号鍵を待っています。 すぐにもう一度リクエストします。 @@ -366,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 @@ -386,135 +313,131 @@ 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を表示 - + 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. - %1 -に保存されているkeys.datファイルを編集することで鍵を管理できます。 -このファイルをバックアップしておくことは重要です。 + %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を終了してください) + プログラムを同じディレクトリに保存されている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を終了してください) + %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 のどちらかにする必要があります。 @@ -584,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 @@ -610,19 +533,19 @@ 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 として登録を送信しています。送信を再試行する前に、登録が処理されるのをお待ちください。 + エラー: アカウントがメールゲートウェイに登録されていません。 今 %1 として登録を送信しています。送信を再試行する前に、登録が処理されるまでお待ちください。 @@ -665,57 +588,57 @@ 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を最新のバージョンへアップデートしてください。 + アドレス %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を最新のバージョンへアップデートしてください。 + アドレス %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はメッセージ送信の処理を行いますが、ネットワークに接続するまで送信はされません。 + 警告: 接続されていません。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 新規メッセージ @@ -725,157 +648,157 @@ It is important that you back up this file. Would you like to open the file now? - + 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. エラー: 購読に、同じアドレスを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? @@ -884,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? @@ -893,387 +816,387 @@ 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 (設定で変更できます)。 - + 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 Ctrrl+Q - + F1 F1 - + Contact support お問い合わせサポート - + About 概要 - + Regenerate deterministic addresses deterministicアドレスを再生成 - + Delete all trashed messages ゴミ箱のメッセージを全て削除する - + Join / Create chan チャンネルに参加 / 作成 - + All accounts すべてのアカウント - + Zoom level %1% ズーム レベル %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% + 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 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 アドレスの生成を完了しました @@ -1283,201 +1206,211 @@ 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 で送信しました - + 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 ポート%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) - - Unknown namecoin interface type: %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 を理解できませんでした。 - - The connection to namecoin failed. - namecoin への接続に失敗しました。 - - - + 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 @@ -1492,195 +1425,130 @@ Receiver's required difficulty: %1 and %2 - + 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 - エラー: %1 + - + From %1 送信元 %1 - - Disconnecting - 切断しています - - - - Connecting - 接続しています - - - - Bitmessage will now drop all connections. Are you sure? - Bitmessage はすべての接続を削除します。 よろしいですか? - - - - Bitmessage will now start connecting to network. Are you sure? - Bitmessage はネットワークへの接続を開始します。 よろしいですか? - - - + 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 のオブジェクトをダウンロードする必要があります。 今、終了すると、配送が遅れることがあります。 同期が完了するまで待ちますか? - + 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はネットワークに接続していません。 今、終了すると、配信が遅れる可能性があります。 接続して、同期が完了するまで待ちますか? + 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. - このアドレスから%nの最新の配信を表示します。 - - - - 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 ここをクリックするとHTMLを無効にします @@ -1688,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 不明なエンコード @@ -1703,98 +1571,98 @@ 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: 複数のアドレスを生成できます。アドレスを自由に生成、破棄することができます。アドレスは乱数かパスフレーズを使って生成できます。もしパスフレーズを使う場合、アドレスはdeterministicアドレスになります。デフォルトでは乱数による生成が選択されますが、deterministicアドレスにも長所と短所があります: - + <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 deterministicアドレスを作る - + 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) (帯域と処理能力を節約する) @@ -1802,22 +1670,22 @@ The 'Random Number' option is selected by default but deterministic ad NewSubscriptionDialog - + Add new entry 新しい項目を追加 - + Label ラベル - + Address アドレス - + Enter an address above. 上にアドレスを入力してください。 @@ -1825,60 +1693,55 @@ 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. - chanアドレスは仮想メーリングリストのアドレスには使用できません。 - aboutDialog - + About 概要 - - - PyBitmessage - - - version ? - + 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. このソフトウェアはベータ版です。 @@ -1887,10 +1750,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-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> + + + <html><head/><body><p>Copyright &copy; 2012-2016 Jonathan Warren<br/>Copyright &copy; 2013-2016 The Bitmessage Developers</p></body></html> + <html><head/><body><p>Copyright &copy; 2012-2016 Jonathan Warren<br/>Copyright &copy; 2013-2016 The Bitmessage 開発者</p></body></html> @@ -1921,12 +1784,12 @@ The 'Random Number' option is selected by default but deterministic ad アドレス - + Blacklist ブラックリスト - + Whitelist ホワイトリスト @@ -1934,45 +1797,40 @@ 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は協働プロジェクトです。ヘルプはBitmessage Wikiを参照してください: @@ -1980,35 +1838,30 @@ The 'Random Number' option is selected by default but deterministic ad iconGlossaryDialog - + Icon Glossary アイコン一覧 - + You have no connections with other peers. 他のpeerへ接続されていません。 - + 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. 発信接続のために1つ以上のピアへ接続を行っていますが、まだ着信接続を受け取っていません。ファイアーウォールかホームルーターが外部からこのコンピューターへのTCP接続を受け取れるように設定されていないかも知れません。Bitmessageは正常に動作しますが、外部からの接続を許可してより良く接続されたノードになることはBitmessageネットワークへの助けになります。 You are using TCP port ?. (This can be changed in the settings). - + 使用中のポート ? (設定で変更できます)。 - + You do have connections with other peers and your firewall is correctly configured. ファイアーウォールを適切に設定し、他のpeerへ接続してください。 - - - You are using TCP port %1. (This can be changed in the settings). - 使用中のポート %1 (設定で変更できます)。 - networkstatus @@ -2018,37 +1871,37 @@ 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 # ストリーム # @@ -2058,112 +1911,112 @@ The 'Random Number' option is selected by default but deterministic ad - + 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/秒 - + Network Status ネットワークの状態 - + 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 あなたとピアの間でネゴシエーションしたストリームのリスト @@ -2223,7 +2076,7 @@ The 'Random Number' option is selected by default but deterministic ad Chan passphrase/name: - チャンネルのパスフレーズ/名前: + @@ -2244,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 チャンネルの作成 / 参加をキャンセルしました @@ -2262,70 +2115,78 @@ 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. もし以前にdeterministicアドレスを作ったことがあり、何かしらのトラブル(ハードディスクの故障のような)でそれを紛失していた場合、ここで再生成することができます。もし乱数でアドレスを作っていたのであればこのフォームは再生成には使えません。 @@ -2333,218 +2194,218 @@ The 'Random Number' option is selected by default but deterministic ad settingsDialog - + Settings 設定 - + Start Bitmessage on user login ユーザのログイン時にBitmessageを起動 - + Tray トレイ - + Start Bitmessage in the tray (don't show main window) Bitmessageをトレイ内で起動する(メインウィンドウを表示しない) - + 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. ポータブルモード時、メッセージと設定ファイルは通常のアプリケーションデータのフォルダではなく同じディレクトリに保存されます。これによりBitmessageをUSBドライブから実行できます。 - + Willingly include unencrypted destination address when sending to a mobile device 携帯端末にメッセージを送る時は暗号化されていないアドレスを許可する - + Use Identicons 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です。この値を変更すると新しいアドレスではこのデフォルト値を引き上げることができます。その場合、新しいアドレスはメッセージの送信者により高い難易度を要求します。例外もあります: 友人や知り合いをアドレス帳に登録すると、Bitmessageは次にメッセージを送る際、自動的に要求される処理の難易度を最低限の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 許可する最大の難易度 @@ -2554,87 +2415,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はアドレスを読みやすくするため、NamecoinというBitcoinベースの別のプログラムを利用できます。例えば、あなたの友人に長いBitmessageアドレスを伝える代わりに、単純に<span style=" font-style:italic;">テスト</span>でメッセージを送るよう伝えることができます。</p><p>(Bitmessageアドレスを独自にNamecoinにするのはかなり難しいです)。</p><p>Bitmessageは直接namecoindを使うか、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>デフォルトでは、あなたが誰かにメッセージを送信して、相手が 2 日以上オフラインになっている場合、 Bitmessage はさらに 2 日後にメッセージを再送信します。 これは指数関数的後退で永遠に続きます。 受信者がそれらを確認するまで、メッセージは 5、10、20 日後に再送信されます。 ここで Bitmessage が一定の日数または月数後に諦める数を入力して、その動作を変更することができます。</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/translations/bitmessage_pl.qm b/src/translations/bitmessage_pl.qm index fb31e8d5..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 89c6162e..c97cb4bb 100644 --- a/src/translations/bitmessage_pl.ts +++ b/src/translations/bitmessage_pl.ts @@ -60,27 +60,27 @@ @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 @@ -112,7 +112,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: @@ -152,53 +152,9 @@ Please type the desired email address (including @mailchuck.com) below: # specified. As this scheme uses deterministic public keys, you will receive # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - - - - - - # You can use this to configure your email gateway account -# Uncomment the setting you want to use -# Here are the options: -# -# pgp: server -# The email gateway will create and maintain PGP keys for you and sign, verify, -# encrypt and decrypt on your behalf. When you want to use PGP but are lazy, -# use this. Requires subscription. -# -# pgp: local -# The email gateway will not conduct PGP operations on your behalf. You can -# either not use PGP at all, or use it locally. -# -# attachments: yes -# Incoming attachments in the email will be uploaded to MEGA.nz, and you can -# download them from there by following the link. Requires a subscription. -# -# attachments: no -# Attachments will be ignored. -# -# archive: yes -# Your incoming emails will be archived on the server. Use this if you need -# help with debugging problems or you need a third party proof of emails. This -# however means that the operator of the service will be able to read your -# emails even after they have been delivered to you. -# -# archive: no -# Incoming emails will be deleted from the server as soon as they are relayed -# to you. -# -# masterpubkey_btc: BIP44 xpub key or electrum v1 public seed -# offset_btc: integer (defaults to 0) -# feeamount: number with up to 8 decimal places -# feecurrency: BTC, XBT, USD, EUR or GBP -# Use these if you want to charge people who send you emails. If this is on and -# an unknown person sends you an email, they will be requested to pay the fee -# specified. As this scheme uses deterministic public keys, you will receive -# the money directly. To turn it off again, set "feeamount" to 0. Requires -# subscription. # Tutaj możesz skonfigurować ustawienia bramki poczty e-mail -# Odkomentuj (usuń znak „#”) opcje których chcesz użyć +# Odkomentuj (usuń znak '#') opcje których chcesz użyć # Ustawienia: # # pgp: server @@ -219,8 +175,8 @@ Please type the desired email address (including @mailchuck.com) below: # # archive: yes # Przychodzące wiadomości zostaną zarchiwizowane na serwerze. Użyj tej -# opcji przy diagnozowaniu problemów lub jeżeli potrzebujesz dowodu -# przesyłania wiadomości na zewnętrznym serwerze. Włączenie tej opcji +# opcji przy diagnozowaniu problemów, lub jeżeli potrzebujesz dowodu +# przesyłani wiadomości na zewnętrznym serwerze. Włączenie tej opcji # spowoduje, że operator usługi będzie mógł czytać Twoje listy nawet po # przesłaniu ich do Ciebie. # @@ -236,129 +192,129 @@ Please type the desired email address (including @mailchuck.com) below: # Tobie wiadomość. Jeżeli ta opcja jest włączona i nieznana osoba wyśle # Ci wiadomość, będzie poproszona o wniesienie opłaty. Ta funkcja używa # deterministycznych kluczy publicznych, dostaniesz pieniądze -# bezpośrednio. Aby ją ponownie wyłączyć, ustaw „feeamount” na 0. +# bezpośrednio. Aby ją ponownie wyłączyć, ustaw 'feeamount' na 0. # Wymaga subskrypcji. 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… - + Mark Unread Oznacz jako nieprzeczytane - + New Nowe - + Enable Aktywuj - + Disable Deaktywuj - + Set avatar... Ustaw awatar… - + Copy address to clipboard Kopiuj adres do schowka - + Special address behavior... Specjalne zachowanie adresu… - + Email gateway Przekaźnik e-mail - + Delete 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. @@ -368,17 +324,17 @@ Please type the desired email address (including @mailchuck.com) below: - + 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 @@ -388,77 +344,77 @@ Please type the desired email address (including @mailchuck.com) below: - + 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. @@ -467,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.) @@ -486,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. @@ -586,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 @@ -612,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. @@ -667,57 +623,57 @@ 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ść @@ -732,57 +688,57 @@ Zwykle 4-5 dniowy TTL jest odpowiedni. - + Address is valid. Adres jest prawidłowy. - + The address you entered was invalid. Ignoring it. 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. @@ -797,44 +753,44 @@ Zwykle 4-5 dniowy TTL jest odpowiedni. - + 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. + 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. @@ -842,42 +798,42 @@ Zwykle 4-5 dniowy TTL jest odpowiedni. - + 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. + Błąd: Adres znajduje się już na liście blokowanych. - + Moved items to trash. Przeniesiono wiadomości do kosza. - + Undeleted item. Przywrócono wiadomość. - + Save As... 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? @@ -886,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? @@ -895,32 +851,32 @@ 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… @@ -930,37 +886,37 @@ Czy na pewno chcesz usunąć ten kanał? - + The address should start with ''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. @@ -970,12 +926,12 @@ Czy na pewno chcesz usunąć ten kanał? - + Address is an old type. We cannot display its past broadcasts. 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 subskrypcji do wyświetlenia. @@ -985,297 +941,297 @@ Czy na pewno chcesz usunąć ten kanał? - + 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ł - + All accounts Wszystkie konta - + Zoom level %1% Poziom powiększenia %1% - + Error: You cannot add the same address to your list twice. Perhaps rename the existing one if you want. Błąd: Adres znajduje sie już na liście. - + Add new entry Dodaj nowy wpis - + Display the %1 recent broadcast(s) from this address. - + 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% - + Shutting down Pybitmessage... %1% Zamykanie PyBitmessage… %1% - + Waiting for objects to be sent... %1% Oczekiwanie na wysłanie obiektów… %1% - + Saving settings... %1% Zapisywanie ustawień… %1% - + Shutting down core... %1% Zamykanie rdzenia programu… %1% - + Stopping notifications... %1% Zatrzymywanie powiadomień… %1% - + Shutdown imminent... %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% - + Sent Wysłane - + Generating one new address Generowanie jednego nowego adresu - + Done generating address. Doing work necessary to broadcast it... Adresy wygenerowany. Wykonywanie dowodu pracy niezbędnego na jego rozesłanie… - + Generating %1 new addresses. Generowanie %1 nowych adresów. - + %1 is already in 'Your Identities'. Not adding it again. %1 jest już w 'Twoich tożsamościach'. Nie zostanie tu dodany. - + Done generating address Ukończono generowanie adresów @@ -1285,201 +1241,211 @@ Czy na pewno chcesz usunąć ten kanał? - + Disk full Dysk pełny - + Alert: Your disk or data storage volume is full. Bitmessage will now exit. 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… - + Broadcast sent on %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 obiektuZawieszony dowód pracy %n obiektówZawieszony dowód pracy %n obiektówZawieszony 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) - - Unknown namecoin interface type: %1 - Nieznany typ interfejsu namecoin: %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. - - The connection to namecoin failed. - Nie udało się połączyć z namecoin. - - - + 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 @@ -1493,195 +1459,140 @@ Witamy w przyjaznym i bezpiecznym Bitmessage * 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 - - Disconnecting - Rozłączanie - - - - Connecting - Łączenie - - - - Bitmessage will now drop all connections. Are you sure? - Bitmessage zatrzyma wszystkie połączenia? Czy kontynuować? - - - - Bitmessage will now start connecting to network. Are you sure? - Bitmessage rozpocznie łączenie z siecią. Czy kontynuować? - - - + 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… - + Waiting for finishing synchronisation... 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ć? - - Error occurred: could not load message from disk. - Wystąpił błąd: nie można załadować wiadomości z dysku. - - - - Display the %n recent broadcast(s) from this address. - Wyświetl %n ostatnią wiadomość przekazu z tego adresu.Wyświetl %n ostatnią wiadomość przekazu z tego adresu.Wyświetl %n ostatnich wiadomości przekazu z tego adresu.Wyświetl %n ostatnich wiadomości przekazu z tego adresu. - - - + Go online Połącz - + Go offline Rozłącz - - - Clear - Wymaż - - - - inbox - odebrane - - - - new - nowe - - - - sent - wysłane - - - - trash - kosz - 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 @@ -1689,14 +1600,14 @@ Witamy w przyjaznym i bezpiecznym Bitmessage MsgDecode - + The message has an unknown encoding. Perhaps you should upgrade Bitmessage. Wiadomość zawiera nierozpoznane kodowanie. Prawdopodobnie powinieneś zaktualizować Bitmessage. - + Unknown encoding Nierozpoznane kodowanie @@ -1852,7 +1763,7 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy 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. @@ -1875,12 +1786,12 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy - + <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. @@ -1890,7 +1801,7 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy - + <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> @@ -1923,12 +1834,12 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy Adres - + Blacklist Czarna lista - + Whitelist Biała lista @@ -2007,7 +1918,7 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy 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). @@ -2060,27 +1971,27 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy - + 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 @@ -2095,32 +2006,32 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy Pobieranie: 0 kB/s - + Network Status Stan sieci - + byte(s) bajtbajtówbajtówbajtów - + Object(s) to be synced: %n Jeden obiekt do zsynchronizowaniaObieków do zsynchronizowania: %nObieków do zsynchronizowania: %nObiekó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ść 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. @@ -2246,17 +2157,17 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy newchandialog - + Successfully created / joined chan %1 Pomyślnie utworzono / dołączono do kanału %1 - + Chan creation / joining failed Utworzenie / dołączenie do kanału nie powiodło się - + Chan creation / joining cancelled Utworzenie / dołączenie do kanału przerwane @@ -2264,21 +2175,29 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy 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 @@ -2335,218 +2254,218 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy settingsDialog - + Settings Ustawienia - + Start Bitmessage on user login Uruchom Bitmessage po zalogowaniu - + Tray Zasobnik systemowy - + Start Bitmessage in the tray (don't show main window) Uruchom Bitmessage w zasobniku (nie pokazuj głównego okna) - + Minimize to tray Minimalizuj do zasobnika - + Close to tray Zamknij do zasobnika - + Show notification when message received Wyświetl powiadomienia o przychodzących wiadomościach - + Run in Portable Mode Uruchom w trybie przenośnym - + 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. W trybie przenośnym, wiadomości i pliki konfiguracyjne są przechowywane w tym samym katalogu co program, zamiast w osobistym katalogu danych użytkownika. To sprawia, że wygodnie można uruchamiać Bitmessage z pamięci przenośnych. - + Willingly include unencrypted destination address when sending to a mobile device Chętnie umieść niezaszyfrowany adres docelowy podczas wysyłania na urządzenia przenośne. - + Use Identicons Użyj graficznych awatarów - + Reply below Quote Odpowiedź pod cytatem - + Interface Language Język interfejsu - + System Settings system Język systemu - + User Interface Interfejs - + Listening port Port nasłuchujący - + Listen for connections on port: Nasłuchuj połaczeń na porcie: - + UPnP: UPnP: - + Bandwidth limit Limity przepustowości - + Maximum download rate (kB/s): [0: unlimited] Maksymalna prędkość pobierania (w kB/s): [0: bez limitu] - + Maximum upload rate (kB/s): [0: unlimited] Maksymalna prędkość wysyłania (w kB/s): [0: bez limitu] - + Proxy server / Tor Serwer proxy / Tor - + Type: Typ: - + Server hostname: Adres serwera: - + Port: Port: - + Authentication Uwierzytelnienie - + Username: Użytkownik: - + Pass: Hasło: - + Listen for incoming connections when using proxy Nasłuchuj przychodzących połączeń podczas używania proxy - + none brak - + SOCKS4a SOCKS4a - + SOCKS5 SOCKS5 - + Network Settings Sieć - + Total difficulty: Całkowita trudność: - + The 'Total difficulty' affects the absolute amount of work the sender must complete. Doubling this value doubles the amount of work. 'Całkowita trudność' ma wpływ na całkowitą ilość pracy jaką nadawca musi wykonać. Podwojenie tej wartości, podwaja ilość pracy do wykonania. - + Small message difficulty: Trudność małej wiadomości: - + 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. Kiedy ktoś wysyła Ci wiadomość, jego komputer musi najpierw wykonać dowód pracy. Trudność tej pracy domyślnie wynosi 1. Możesz podwyższyć tę wartość dla nowo-utworzonych adresów podwyższając wartości tutaj. Każdy nowy adres będzie wymagał przez nadawców wyższej trudności. Jest jeden wyjątek: jeżeli dodasz kolegę do swojej książki adresowej, Bitmessage automatycznie powiadomi go kiedy następnym razem wyślesz do niego wiadomość, że musi tylko wykonać minimalną ilość pracy: trudność 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. 'Trudność małej wiadomości' głównie ma wpływ na trudność wysyłania małych wiadomości. Podwojenie tej wartości, prawie podwaja pracę potrzebną do wysłania małej wiadomości, ale w rzeczywistości nie ma wpływu na większe wiadomości. - + Demanded difficulty Wymagana trudność - + 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. Tutaj możesz ustawić maksymalną ilość pracy jaką zamierzasz wykonać aby wysłać wiadomość innej osobie. Ustawienie tych wartości na 0 oznacza, że każda wartość jest akceptowana. - + Maximum acceptable total difficulty: Maksymalna akceptowalna całkowita trudność: - + Maximum acceptable small message difficulty: Maksymalna akceptowalna trudność dla małych wiadomości: - + Max acceptable difficulty Maksymalna akceptowalna trudność @@ -2556,87 +2475,87 @@ Generowanie adresów „losowych” jest wybrane domyślnie, jednak deterministy - + <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 potrafi wykorzystać inny program oparty na Bitcoinie - Namecoin - aby sprawić adresy czytelnymi dla ludzi. Na przykład, zamiast podawać koledze swój długi adres Bitmessage, możesz po prostu powiedzieć mu aby wysłał wiadomość pod <span style=" font-style:italic;">id/ksywka</span>.</p><p>(Utworzenie swojego adresu Bitmessage w Namecoinie jest ciągle racze trudne).</p><p>Bitmessage może skorzystać albo bezpośrednio z namecoind, albo z działającego wątku nmcontrol.</p></body></html> - + Host: Host: - + Password: Hasło: - + Test Test - + Connect to: Połącz z: - + Namecoind Namecoind - + NMControl NMControl - + Namecoin integration Połączenie z 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>Domyślnie jeżeli wyślesz wiadomość do kogoś i ta osoba będzie poza siecią przez jakiś czas, Bitmessage spróbuje ponownie wysłać wiadomość trochę później, i potem ponownie. Program będzie próbował wysyłać wiadomość do czasu aż odbiorca potwierdzi odbiór. Tutaj możesz zmienić kiedy Bitmessage ma zaprzestać próby wysyłania.</p><p>Pozostaw te poza puste, aby ustawić domyślne zachowanie.</p></body></html> - + Give up after Poddaj się po - + and i - + days dniach - + months. miesiącach. - + Resends Expire Niedoręczone wiadomości - + Hide connection notifications Nie pokazuj powiadomień o połączeniu - + Maximum outbound connections: [0: none] Maksymalnych połączeń wychodzących: [0: brak] - + Hardware GPU acceleration (OpenCL): Przyspieszenie sprzętowe GPU (OpenCL): diff --git a/src/translations/bitmessage_ru.qm b/src/translations/bitmessage_ru.qm index 8c0269b9..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 4a80f62e..ffb2eac9 100644 --- a/src/translations/bitmessage_ru.ts +++ b/src/translations/bitmessage_ru.ts @@ -112,7 +112,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: @@ -153,172 +153,166 @@ Please type the desired email address (including @mailchuck.com) below: # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - - - - - # You can use this to configure your email gateway account -# Uncomment the setting you want to use -# Here are the options: -# + # Эти параметры можно использовать для настройки аккаунта email-шлюза +# Раскомментируйте (уберите символ #) те параметры, которые хотите использовать +# Параметры: +# # pgp: server -# The email gateway will create and maintain PGP keys for you and sign, verify, -# encrypt and decrypt on your behalf. When you want to use PGP but are lazy, -# use this. Requires subscription. +# Email-шлюз будет создавать и управлять PGP-ключами за вас, а также подписывать, проверять, +# шифровать и дешифровать от вашего имени. Используйте это, если вы желаете использовать PGP, но очень ленивы. +# Этот настройка требует подписки. # # pgp: local -# The email gateway will not conduct PGP operations on your behalf. You can -# either not use PGP at all, or use it locally. +# Шлюз электронной почты не будет проводить операции PGP от вашего имени. +# Вы можете не использовать PGP, или использовать его локально. # # attachments: yes -# Incoming attachments in the email will be uploaded to MEGA.nz, and you can -# download them from there by following the link. Requires a subscription. +# Вложения во входящий email-сообщениях будут загружены на MEGA.nz, +# вы сможете скачать из оттуда по ссылке. Требуется подписка. # # attachments: no -# Attachments will be ignored. -# +# Вложения будут проигнорированы. +# # archive: yes -# Your incoming emails will be archived on the server. Use this if you need -# help with debugging problems or you need a third party proof of emails. This -# however means that the operator of the service will be able to read your -# emails even after they have been delivered to you. +# Входящие email-сообщения будут заархивированы на сервере. Используйте эту настройку +# в целях отладки, или если вам нужно подтверждение email третьей стороной. +# Конечно, это означает, что оператор сервиса будет иметь возможность читать ваши email +# даже после доставки их вам. # # archive: no -# Incoming emails will be deleted from the server as soon as they are relayed -# to you. +# Входящие email-сообщения будут удалены с сервера после доставки их вам. # -# masterpubkey_btc: BIP44 xpub key or electrum v1 public seed -# offset_btc: integer (defaults to 0) -# feeamount: number with up to 8 decimal places -# feecurrency: BTC, XBT, USD, EUR or GBP -# Use these if you want to charge people who send you emails. If this is on and -# an unknown person sends you an email, they will be requested to pay the fee -# specified. As this scheme uses deterministic public keys, you will receive -# the money directly. To turn it off again, set "feeamount" to 0. Requires -# subscription. - - +# masterpubkey_btc: BIP44 xpub key или electrum v1 public seed +# offset_btc: целое число (по умолчанию 0) +# feeamount: число, содержащее до 8 десятичных цифр +# feecurrency: BTC, XBT, USD, EUR или GBP +# Используйте эту настроку, если хотите чтобы отправитель уплатил вознаграждение за отправку email вам. +# Если настройка включена, и неизвестный отправитель посылает вам email, +# то отправителю будет предложено уплатить указанное вознаграждение. +# Схема использует детерминистические открытые ключи, вы получите деньги напрямую. +# Чтобы выключить уплату вознаграждения, установите "feeamount" равным 0. +# Требует подписки. 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 Email-шлюз - + 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. Ожидаем ключ шифрования от Вашего собеседника. Запрос будет повторен через некоторое время. @@ -328,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 @@ -348,47 +342,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 @@ -398,12 +392,12 @@ Please type the desired email address (including @mailchuck.com) below: Отправить - + Subscribe Подписки - + Channel Канал @@ -413,13 +407,13 @@ 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. @@ -428,56 +422,59 @@ It is important that you back up this file. Создайте резервную копию этого файла перед тем как будете его редактировать. - + 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 перед тем, как вносить в него какие-либо изменения.) + Вы можете управлять Вашими ключами, редактируя файл 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, находящийся в + %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. @@ -547,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 @@ -573,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 байта). Пожалуйста, сократите сообщение перед отправкой. - + 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, пожалуйста, подождите пока процесс регистрации не завершится, прежде чем попытаться отправить сообщение заново. @@ -628,57 +625,57 @@ 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. + По поводу адреса %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. + По поводу адреса %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 выполнит работу, требуемую для отправки сообщения, но не отправит его до тех пор, пока вы не подключитесь. + Внимание: Вы не подключены к сети. 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 Новое сообщение @@ -693,57 +690,57 @@ It is important that you back up this file. Would you like to open the file now? - + 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, чтобы смена номера порта имела эффект. - + 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, чтобы закрыть уже существующие соединения. + 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 ждет перед первой попыткой переотправки сообщения, поэтому ваши сообщения никогда не будут переотправлены. @@ -778,24 +775,24 @@ It is important that you back up this file. Would you like to open the file now? Вы действительно должны ввести секретную фразу. - + 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. - Ошибка: адрес, с которого вы пытаетесь отправить, выключен. Вам нужно включить этот адрес во вкладке "Ваши адреса" перед использованием. + Ошибка: адрес, с которого Вы пытаетесь отправить, выключен. Вам нужно будет включить этот адрес во вкладке "Ваши адреса". @@ -803,42 +800,42 @@ It is important that you back up this file. Would you like to open the file now? - + 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? @@ -847,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? @@ -856,32 +853,32 @@ 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... Проверяем... @@ -1131,7 +1128,7 @@ Are you sure you want to delete the channel? Подключить или создать чан - + All accounts Все аккаунты @@ -1141,12 +1138,12 @@ 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 Добавить новую запись @@ -1156,42 +1153,42 @@ Are you sure you want to delete the channel? - + 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% @@ -1201,42 +1198,42 @@ Are you sure you want to delete the channel? %n час%n часа%n часов%n час(а/ов) - + %n day(s) %n день%n дня%n дней%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 Создание адресов завершено. @@ -1246,96 +1243,96 @@ 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 - + 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 @@ -1350,97 +1347,107 @@ Receiver's required difficulty: %1 and %2 Распределение портов 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 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. - - - The connection to namecoin failed. - Не удалось соединиться с namecoin. - 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 @@ -1454,119 +1461,119 @@ Receiver's required difficulty: %1 and %2 * участвуйте в обсуждениях в чанах - + 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. - Показать %1 прошлую рассылку с этого адреса.Показать %1 прошлых рассылки с этого адреса.Показать %1 прошлых рассылок с этого адреса.Показать %1 прошлых рассылок с этого адреса. + @@ -1584,24 +1591,24 @@ Receiver's required difficulty: %1 and %2 Очистить - + inbox входящие - + new новые - + sent отправленные - + trash - корзина + @@ -1630,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 Неизвестная кодировка @@ -1863,12 +1870,12 @@ The 'Random Number' option is selected by default but deterministic ad Адрес - + Blacklist Чёрный список - + Whitelist Белый список @@ -2219,6 +2226,14 @@ The 'Random Number' option is selected by default but deterministic ad Модуль C для PoW недоступен. Пожалуйста, соберите его. + + qrcodeDialog + + + QR-code + QR-код + + regenerateAddressesDialog @@ -2275,218 +2290,218 @@ The 'Random Number' option is selected by default but deterministic ad settingsDialog - + Settings Настройки - + Start Bitmessage on user login Запускать Bitmessage при входе в систему - + Tray Трей - + Start Bitmessage in the tray (don't show main window) Запускать Bitmessage в свернутом виде (не показывать главное окно) - + 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. В переносном режиме, все сообщения и конфигурационные файлы сохраняются в той же самой папке что и сама программа. Это делает более удобным использование Bitmessage с USB-флэшки. - + 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] Максимальная скорость загрузки (кБ/с): [0: не ограничено] - + Maximum upload rate (kB/s): [0: unlimited] Максимальная скорость отдачи (кБ/с): [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. Однако, есть одно исключение: если Вы специально добавите Вашего собеседника в адресную книгу, то Bitmessage автоматически уведомит его о том, что для него минимальная сложность будет составлять всегда всего лишь 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. Здесь Вы можете установить максимальную вычислительную работу, которую Вы согласны проделать, чтобы отправить сообщение другому пользователю. Ноль означает, что любое значение допустимо. - + Maximum acceptable total difficulty: Максимально допустимая общая сложность: - + Maximum acceptable small message difficulty: Максимально допустимая сложность для маленький сообщений: - + Max acceptable difficulty Макс допустимая сложность @@ -2496,87 +2511,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 умеет пользоваться программой Namecoin для того, чтобы сделать адреса более дружественными для пользователей. Например, вместо того, чтобы диктовать Вашему другу длинный и нудный адрес Bitmessage, Вы можете попросить его отправить сообщение на адрес вида <span style=" font-style:italic;">test. </span></p><p>(Перенести Ваш Bitmessage адрес в Namecoin по-прежнему пока довольно сложно).</p><p>Bitmessage может использовать либо прямо namecoind, либо уже запущенную программу 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>По умолчанию, когда вы отправляете сообщение кому-либо, и адресат находится оффлайн несколько дней, ваш Bitmessage перепосылает сообщение. Это будет продолжаться с увеличивающимся по экспоненте интервалом; сообщение будет переотправляться, например, через 5, 10, 20 дней, пока адресат их запрашивает. Здесь вы можете изменить это поведение, заставив Bitmessage прекращать переотправку по прошествии указанного количества дней или месяцев.</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 diff --git a/src/translations/bitmessage_zh_cn.qm b/src/translations/bitmessage_zh_cn.qm index 7cb18983..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 474f8c6c..58e16cb8 100644 --- a/src/translations/bitmessage_zh_cn.ts +++ b/src/translations/bitmessage_zh_cn.ts @@ -2,17 +2,17 @@ AddAddressDialog - + Add new entry 添加新条目 - + Label 标签 - + Address 地址 @@ -20,99 +20,69 @@ EmailGatewayDialog - + Email gateway 电子邮件网关 - + Register on email gateway 注册电子邮件网关 - + Account status at email gateway 电子邮件网关帐户状态 - + Change account settings at email gateway 更改电子邮件网关帐户设置 - + Unregister from email gateway 取消电子邮件网关注册 - + Email gateway allows you to communicate with email users. Currently, only the Mailchuck email gateway (@mailchuck.com) is available. 电子邮件网关允许您与电子邮件用户通信。目前,只有Mailchuck电子邮件网关(@mailchuck.com)可用。 - + Desired email address (including @mailchuck.com): 所需的电子邮件地址(包括 @mailchuck.com): - - - @mailchuck.com - @mailchuck.com - - - - Registration failed: - 注册失败: - - - - The requested email address is not available, please try a new one. - 请求的电子邮件地址不可用,请换一个新的试试。 - - - - Sending email gateway registration request - 发送电​​子邮件网关注册请求 - - - - Sending email gateway unregistration request - 发送电​​子邮件网关注销请求 - - - - Sending email gateway status request - 发送电​​子邮件网关状态请求 - 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)如下: Email gateway registration - + 电子邮件网关注册 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: - + 电子邮件网关允许您与电子邮件用户通信。目前,只有Mailchuck电子邮件网关(@mailchuck.com)可用。请键入所需的电子邮件地址(包括 @mailchuck.com)如下: Mailchuck - + # You can use this to configure your email gateway account # Uncomment the setting you want to use # Here are the options: @@ -153,61 +123,17 @@ Please type the desired email address (including @mailchuck.com) below: # the money directly. To turn it off again, set "feeamount" to 0. Requires # subscription. - - - - - # You can use this to configure your email gateway account -# Uncomment the setting you want to use -# Here are the options: -# -# pgp: server -# The email gateway will create and maintain PGP keys for you and sign, verify, -# encrypt and decrypt on your behalf. When you want to use PGP but are lazy, -# use this. Requires subscription. -# -# pgp: local -# The email gateway will not conduct PGP operations on your behalf. You can -# either not use PGP at all, or use it locally. -# -# attachments: yes -# Incoming attachments in the email will be uploaded to MEGA.nz, and you can -# download them from there by following the link. Requires a subscription. -# -# attachments: no -# Attachments will be ignored. -# -# archive: yes -# Your incoming emails will be archived on the server. Use this if you need -# help with debugging problems or you need a third party proof of emails. This -# however means that the operator of the service will be able to read your -# emails even after they have been delivered to you. -# -# archive: no -# Incoming emails will be deleted from the server as soon as they are relayed -# to you. -# -# masterpubkey_btc: BIP44 xpub key or electrum v1 public seed -# offset_btc: integer (defaults to 0) -# feeamount: number with up to 8 decimal places -# feecurrency: BTC, XBT, USD, EUR or GBP -# Use these if you want to charge people who send you emails. If this is on and -# an unknown person sends you an email, they will be requested to pay the fee -# specified. As this scheme uses deterministic public keys, you will receive -# the money directly. To turn it off again, set "feeamount" to 0. Requires -# subscription. - - #您可以用它来配置您的电子邮件网关帐户 + #您可以用它来配置你的电子邮件网关帐户 #取消您要使用的设定 #这里的选项: # # pgp: server #电子邮件网关将创建和维护PGP密钥,为您签名和验证, -#代表加密和解密。当您想使用PGP,但懒惰, +#代表加密和解密。当你想使用PGP,但懒惰, #用这个。需要订阅。 # # pgp: local -#电子邮件网关不会代您进行PGP操作。您可以 +#电子邮件网关不会代你进行PGP操作。您可以 #选择或者不使用PGP, 或在本地使用它。 # # attachement: yes @@ -221,7 +147,7 @@ Please type the desired email address (including @mailchuck.com) below: #您收到的邮件将在服务器上存档。如果您有需要请使用 #帮助调试问题,或者您需要第三方电子邮件的证明。这 #然而,意味着服务的操作运将能够读取您的 -#电子邮件即使电子邮件已经传送给您。 +#电子邮件即使电子邮件已经传送给你。 # # archive: no # 已传入的电子邮件将从服务器被删除只要他们已中继。 @@ -230,133 +156,132 @@ Please type the desired email address (including @mailchuck.com) below: #offset_btc:整数(默认为0) #feeamount:多达8位小数 #feecurrency号:BTC,XBT,美元,欧元或英镑 -#用这些,如果您想主管谁送您的电子邮件的人。如果这是在和 +#用这些,如果你想主管谁送你的电子邮件的人。如果这是在和 #一个不明身份的人向您发送一封电子邮件,他们将被要求支付规定的费用 -#。由于这个方案使用确定性的公共密钥,您会直接接收 +#。由于这个方案使用确定性的公共密钥,你会直接接收 #钱。要再次将其关闭,设置“feeamount”0 -#需要订阅。 - +#需要订阅。 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. 正在等待他们的加密密钥,我们会在稍后再次请求。 @@ -366,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 @@ -386,131 +311,131 @@ 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 显示比特信 - + 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 来编辑密钥。备份这个文件十分重要。 - + 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. @@ -580,41 +505,41 @@ 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 more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate. - TTL,或Time-To-Time是保留信息网络时间的长度. -收件人必须在此期间得到它. 如果您的Bitmessage客户沒有听到确认, 它会自动重新发送信息. Time-To-Live的时间越长, 您的电脑必须要做更多工作來发送信息. 四天或五天的 Time-To-Time, 通常是合适的. + 這TTL,或Time-To-Time是保留信息网络时间的长度. +收件人必须在此期间得到它. 如果您的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个字节),发送前请先缩短一些。 + 你正在尝试发送的信息已超过%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​​, 注册正在处理请稍候重试发送. @@ -659,57 +584,57 @@ 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 无法被比特信理解。也许您应该升级您的比特信到最新版本。 + 地址 %1 的地址版本号 %2 无法被比特信理解。也许你应该升级你的比特信到最新版本。 - + Stream number 节点流序号 - + Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version. - 地址 %1 的节点流序号 %2 无法被比特信所理解。也许您应该升级您的比特信到最新版本。 + 地址 %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 新消息 @@ -719,555 +644,555 @@ It is important that you back up this file. Would you like to open the file now? - + 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 ,也许您已经把它删掉了? + 比特信无法找到你的地址 %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? - 如果删除订阅, 您已经收到的信息将无法访问. 也许您可以考虑禁用订阅.禁用订阅将不会收到新信息, 但您仍然可以看到您已经收到的信息. + 如果删除订阅, 您已经收到的信息将无法访问. 也许你可以考虑禁用订阅.禁用订阅将不会收到新信息, 但您仍然可以看到你已经收到的信息. -您确定要删除订阅? +你确定要删除订阅? - + 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? - 如果您删除的频道, 您已经收到的信息将无法访问. 也许您可以考虑禁用频道. 禁用频道将不会收到新信息, 但您仍然可以看到您已经收到的信息. + 如果您删除的频道, 你已经收到的信息将无法访问. 也许你可以考虑禁用频道. 禁用频道将不会收到新信息, 但你仍然可以看到你已经收到的信息. -您确定要删除频道? +你确定要删除频道? - + 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 。(可以在设置中修改)。 - + 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 - 频道 + Chans - + Add Chan 添加 Chans - + 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 所有帐户 - + Zoom level %1% 缩放级别%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% - + %n hour(s) %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 完成生成地址 @@ -1277,201 +1202,211 @@ 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 + 问题: 目标是移动电话设备所请求的目的地包括在消息中, 但是这是在你的设置禁止. %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) - - Unknown namecoin interface type: %1 - 未知的 Namecoin 界面类型: %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。 - - The connection to namecoin failed. - 连接到 Namecoin 失败。 - - - + Your GPU(s) did not calculate correctly, disabling OpenCL. Please report to the developers. - 您的GPU不能够正确计算,正在关闭OpenGL。请报告给开发者。 + 你的GPU(s)不能够正确计算,关闭OpenGL。请报告给开发者。 - + Set notification sound... 设置通知提示音... - + Welcome to easy and secure Bitmessage * send messages to other people @@ -1482,198 +1417,133 @@ Receiver's required difficulty: %1 and %2 欢迎使用简便安全的比特信 *发送信息给其他人 *像推特那样发送广播信息 -*在频道里和其他人讨论 +*在频道(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 版本太高。要么您需要更新您的软件,要么对方需要降级 。 + 错误:收信地址%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 - - Disconnecting - 正在断开连接 - - - - Connecting - 已连接 - - - - Bitmessage will now drop all connections. Are you sure? - 比特信将要丢弃所有连接,您确定吗? - - - - Bitmessage will now start connecting to network. Are you sure? - 比特信将会开始连接至网络中,您确定吗? - - - + 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 件任务需要下载。如果你现在退出软件,可能会造成传输延时。是否等同步完成? - + 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. - 显示从此地址最近 %n 的广播。 - - - - 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”将在浏览器中打开。可能会有安全风险,可能会暴露您或下载恶意数据。确定吗? + 此链接“%1”将在浏览器中打开。可能会有安全风险,可能会暴露你或下载恶意数据。确定吗? - + HTML detected, click here to display 检测到HTML,单击此处来显示内容。 - + Click here to disable HTML 单击此处以禁止HTML。 @@ -1681,14 +1551,14 @@ Receiver's required difficulty: %1 and %2 MsgDecode - + The message has an unknown encoding. Perhaps you should upgrade Bitmessage. 这些消息使用了未知编码方式。 -您可能需要更新Bitmessage软件。 +你可能需要更新Bitmessage软件。 - + Unknown encoding 未知编码 @@ -1696,182 +1566,177 @@ 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/>您必须牢记密钥的同时也牢记地址版本号和the stream number . <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) - (节省您的带宽和处理能力) + (节省你的带宽和处理能力) NewSubscriptionDialog - + Add new entry 添加新条目 - + Label 标签 - + Address 地址 - + Enter an address above. - 请在上方键入地址。 + 输入上述地址. 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 - - - version ? - + 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阶段。 @@ -1880,10 +1745,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-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> + + + <html><head/><body><p>Copyright &copy; 2012-2016 Jonathan Warren<br/>Copyright &copy; 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> @@ -1914,12 +1779,12 @@ The 'Random Number' option is selected by default but deterministic ad 地址 - + Blacklist 黑名单 - + Whitelist 白名单 @@ -1927,45 +1792,40 @@ The 'Random Number' option is selected by default but deterministic ad connectDialog - + Bitmessage 比特信 - + Bitmessage won't connect to anyone until you let it. 除非您允许,比特信不会连接到任何人。 - + 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: 鉴于比特信是一个共同完成的项目,您可以在比特信的Wiki上了解如何帮助比特信: @@ -1973,35 +1833,30 @@ 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连接到您的电脑。比特信将正常运行,不过如果您允许入站连接的话将帮助比特信网络并成为一个通信状态更好的节点。 + 你有至少一个到其他节点的出站连接,但是尚未收到入站连接。您的防火墙或路由器可能尚未设置转发入站TCP连接到您的电脑。比特信将正常运行,不过如果您允许入站连接的话将帮助比特信网络并成为一个通信状态更好的节点。 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 @@ -2051,27 +1906,27 @@ The 'Random Number' option is selected by default but deterministic ad - + 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 @@ -2086,32 +1941,32 @@ The 'Random Number' option is selected by default but deterministic ad 下载: 0 kB /秒 - + Network Status 网络状态 - + 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公钥. @@ -2211,12 +2066,12 @@ The 'Random Number' option is selected by default but deterministic ad <html><head/><body><p>A chan exists when a group of people share the same decryption keys. The keys and bitmessage address used by a chan are generated from a human-friendly word or phrase (the chan name). To send a message to everyone in the chan, send a message to the chan address.</p><p>Chans are experimental and completely unmoderatable.</p><p>Enter a name for your chan. If you choose a sufficiently complex chan name (like a strong and unique passphrase) and none of your friends share it publicly, then the chan will be secure and private. However if you and someone else both create a chan with the same chan name, the same chan will be shared.</p></body></html> - <html><head/><body><p>当一群人共享同一样的加密钥匙时会创建一个频道。使用一个词组来命名密钥和Bitmessage地址。发送信息到频道地址就可以发送消息给每个成员。</p><p>频道功能为实验性功能,也不稳定。</p><p>为您的频道命名。如果您选择使用一个十分复杂的名字命令并且您的朋友不会公开它,那这个频道就是安全和私密的。然而如果您和其他人都创建了一个同样命名的频道,那么相同名字的频道将会被共享。</p></body></html> + <html><head/><body><p>当一群人共享同一样的加密钥匙时会创建一个频道。使用一个词组来命名密钥和bitmessage地址。发送信息到频道地址就可以发送消息给每个成员。</p><p>频道功能为实验性功能,也不稳定。</p><p>为你的频道命名。如果你选择使用一个十分复杂的名字命令并且你的朋友不会公开它,那这个频道就是安全和私密的。然而如果你和其他人都创建了一个同样命名的频道,那么相同名字的频道将会被共享。</p></body></html> Chan passphrase/name: - 通道密码/名称: + @@ -2237,17 +2092,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 频道创建或加入已取消 @@ -2255,70 +2110,78 @@ 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 + 二维码 + + 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. 如果您之前创建了静态地址,但是因为一些意外失去了它们(比如硬盘坏了),您可以在这里将他们再次生成。如果您使用随机数来生成的地址的话,那么这个表格对您没有帮助。 @@ -2326,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代表接受任何难度。 + 你可以在这里设置您所愿意接受的发送消息的最大难度。0代表接受任何难度。 - + Maximum acceptable total difficulty: 最大接受难度: - + Maximum acceptable small message difficulty: 最大接受的小消息难度: - + Max acceptable difficulty 最大可接受难度 @@ -2547,87 +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> + <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 aa04a556..ad72560c 100644 --- a/src/upnp.py +++ b/src/upnp.py @@ -1,30 +1,21 @@ -# pylint: disable=too-many-statements,too-many-branches,protected-access,no-self-use -""" -Complete UPnP port forwarding implementation in separate thread. -Reference: http://mattscodecave.com/posts/using-python-and-upnp-to-forward-a-port -""" - +# A simple upnp module to forward port for BitMessage +# Reference: http://mattscodecave.com/posts/using-python-and-upnp-to-forward-a-port import httplib -import re -import socket -import time -import urllib2 from random import randint -from urlparse import urlparse -from xml.dom.minidom import Document # nosec B408 -from defusedxml.minidom import parseString - +import socket +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 import state import tr -from bmconfigparser import config -from debug import logger -from network import BMConnectionPool, knownnodes, StoppableThread -from network.node import Peer - def createRequestXML(service, action, arguments=None): - """Router UPnP requests are XML formatted""" + from xml.dom.minidom import Document doc = Document() @@ -72,24 +63,22 @@ def createRequestXML(service, action, arguments=None): # our tree is ready, conver it to a string return doc.toxml() - class UPnPError(Exception): - """Handle a UPnP error""" - def __init__(self, message): - super(UPnPError, self).__init__() - logger.error(message) + self.message - -class Router: # pylint: disable=old-style-class - """Encapulate routing""" +class Router: name = "" path = "" address = None routerPath = None extPort = None - + def __init__(self, ssdpResponse, address): + import urllib2 + from xml.dom.minidom import parseString + from urlparse import urlparse + from debug import logger self.address = address @@ -103,9 +92,9 @@ class Router: # pylint: disable=old-style-class try: self.routerPath = urlparse(header['location']) if not self.routerPath or not hasattr(self.routerPath, "hostname"): - logger.error("UPnP: no hostname: %s", header['location']) + logger.error ("UPnP: no hostname: %s", header['location']) except KeyError: - logger.error("UPnP: missing location header") + logger.error ("UPnP: missing location header") # get the profile xml file and read it into a variable directory = urllib2.urlopen(header['location']).read() @@ -119,60 +108,45 @@ class Router: # pylint: disable=old-style-class for service in service_types: if service.childNodes[0].data.find('WANIPConnection') > 0 or \ - service.childNodes[0].data.find('WANPPPConnection') > 0: + service.childNodes[0].data.find('WANPPPConnection') > 0: self.path = service.parentNode.getElementsByTagName('controlURL')[0].childNodes[0].data - self.upnp_schema = re.sub(r'[^A-Za-z0-9:-]', '', service.childNodes[0].data.split(':')[-2]) - - def AddPortMapping( - self, - externalPort, - internalPort, - internalClient, - protocol, - description, - leaseDuration=0, - enabled=1, - ): # pylint: disable=too-many-arguments - """Add UPnP port mapping""" + self.upnp_schema = service.childNodes[0].data.split(':')[-2] + def AddPortMapping(self, externalPort, internalPort, internalClient, protocol, description, leaseDuration = 0, enabled = 1): + from debug import logger resp = self.soapRequest(self.upnp_schema + ':1', 'AddPortMapping', [ - ('NewRemoteHost', ''), - ('NewExternalPort', str(externalPort)), - ('NewProtocol', protocol), - ('NewInternalPort', str(internalPort)), - ('NewInternalClient', internalClient), - ('NewEnabled', str(enabled)), - ('NewPortMappingDescription', str(description)), - ('NewLeaseDuration', str(leaseDuration)) - ]) + ('NewRemoteHost', ''), + ('NewExternalPort', str(externalPort)), + ('NewProtocol', protocol), + ('NewInternalPort', str(internalPort)), + ('NewInternalClient', internalClient), + ('NewEnabled', str(enabled)), + ('NewPortMappingDescription', str(description)), + ('NewLeaseDuration', str(leaseDuration)) + ]) self.extPort = externalPort - logger.info("Successfully established UPnP mapping for %s:%i on external port %i", - internalClient, internalPort, externalPort) + logger.info("Successfully established UPnP mapping for %s:%i on external port %i", internalClient, internalPort, externalPort) return resp def DeletePortMapping(self, externalPort, protocol): - """Delete UPnP port mapping""" - + from debug import logger resp = self.soapRequest(self.upnp_schema + ':1', 'DeletePortMapping', [ - ('NewRemoteHost', ''), - ('NewExternalPort', str(externalPort)), - ('NewProtocol', protocol), - ]) + ('NewRemoteHost', ''), + ('NewExternalPort', str(externalPort)), + ('NewProtocol', protocol), + ]) logger.info("Removed UPnP mapping on external port %i", externalPort) return resp def GetExternalIPAddress(self): - """Get the external address""" - - resp = self.soapRequest( - self.upnp_schema + ':1', 'GetExternalIPAddress') - dom = parseString(resp.read()) - return dom.getElementsByTagName( - 'NewExternalIPAddress')[0].childNodes[0].data - + from xml.dom.minidom import parseString + resp = self.soapRequest(self.upnp_schema + ':1', 'GetExternalIPAddress') + dom = parseString(resp) + return dom.getElementsByTagName('NewExternalIPAddress')[0].childNodes[0].data + def soapRequest(self, service, action, arguments=None): - """Make a request to a router""" - + from xml.dom.minidom import parseString + from debug import logger conn = httplib.HTTPConnection(self.routerPath.hostname, self.routerPath.port) conn.request( 'POST', @@ -181,8 +155,8 @@ class Router: # pylint: disable=old-style-class { 'SOAPAction': '"urn:schemas-upnp-org:service:%s#%s"' % (service, action), 'Content-Type': 'text/xml' - } - ) + } + ) resp = conn.getresponse() conn.close() if resp.status == 500: @@ -190,26 +164,26 @@ class Router: # pylint: disable=old-style-class try: dom = parseString(respData) errinfo = dom.getElementsByTagName('errorDescription') - if errinfo: + if len(errinfo) > 0: logger.error("UPnP error: %s", respData) raise UPnPError(errinfo[0].childNodes[0].data) - except: # noqa:E722 - raise UPnPError("Unable to parse SOAP error: %s" % (respData)) + except: + raise UPnPError("Unable to parse SOAP error: %s" %(respData)) return resp - -class uPnPThread(StoppableThread): - """Start a thread to handle UPnP activity""" - +class uPnPThread(threading.Thread, StoppableThread): SSDP_ADDR = "239.255.255.250" GOOGLE_DNS = "8.8.8.8" SSDP_PORT = 1900 SSDP_MX = 2 SSDP_ST = "urn:schemas-upnp-org:device:InternetGatewayDevice:1" - def __init__(self): - super(uPnPThread, self).__init__(name="uPnPThread") - self.extPort = config.safeGetInt('bitmessagesettings', 'extport', default=None) + def __init__ (self): + threading.Thread.__init__(self, name="uPnPThread") + try: + self.extPort = BMConfigParser().getint('bitmessagesettings', 'extport') + except: + self.extPort = None self.localIP = self.getLocalIP() self.routers = [] self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -217,10 +191,11 @@ class uPnPThread(StoppableThread): self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) self.sock.settimeout(5) self.sendSleep = 60 + self.initStop() def run(self): - """Start the thread to manage UPnP activity""" - + from debug import logger + logger.debug("Starting UPnP thread") logger.debug("Local IP: %s", self.localIP) lastSent = 0 @@ -234,117 +209,104 @@ class uPnPThread(StoppableThread): if not bound: time.sleep(1) - # pylint: disable=attribute-defined-outside-init - self.localPort = config.getint('bitmessagesettings', 'port') - - while state.shutdown == 0 and config.safeGetBoolean('bitmessagesettings', 'upnp'): - if time.time() - lastSent > self.sendSleep and not self.routers: + 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: self.sendSearchRouter() - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: pass lastSent = time.time() try: - while state.shutdown == 0 and config.safeGetBoolean('bitmessagesettings', 'upnp'): - resp, (ip, _) = self.sock.recvfrom(1000) + while state.shutdown == 0 and BMConfigParser().safeGetBoolean('bitmessagesettings', 'upnp'): + resp,(ip,port) = self.sock.recvfrom(1000) if resp is None: continue newRouter = Router(resp, ip) for router in self.routers: - if router.routerPath == newRouter.routerPath: + if router.location == newRouter.location: break else: logger.debug("Found UPnP router at %s", ip) self.routers.append(newRouter) self.createPortMapping(newRouter) - try: - self_peer = Peer( - newRouter.GetExternalIPAddress(), - self.extPort - ) - except: # noqa:E722 - logger.debug('Failed to get external IP') - else: - with knownnodes.knownNodesLock: - knownnodes.addKnownNode( - 1, self_peer, is_self=True) - queues.UISignalQueue.put(('updateStatusBar', tr._translate( - "MainWindow", 'UPnP port mapping established on port %1' - ).arg(str(self.extPort)))) + queues.UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow",'UPnP port mapping established on port %1').arg(str(self.extPort)))) + # retry connections so that the submitted port is refreshed + with shared.alreadyAttemptedConnectionsListLock: + shared.alreadyAttemptedConnectionsList.clear() + shared.alreadyAttemptedConnectionsListResetTime = int( + time.time()) break - except socket.timeout: + except socket.timeout as e: pass - except: # noqa:E722 + except: logger.error("Failure running UPnP router search.", exc_info=True) for router in self.routers: if router.extPort is None: self.createPortMapping(router) try: self.sock.shutdown(socket.SHUT_RDWR) - except (IOError, OSError): # noqa:E722 + except: pass try: self.sock.close() - except (IOError, OSError): # noqa:E722 + except: pass deleted = False for router in self.routers: if router.extPort is not None: deleted = True self.deletePortMapping(router) + shared.extPort = None if deleted: - queues.UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow", 'UPnP port mapping removed'))) + queues.UISignalQueue.put(('updateStatusBar', tr._translate("MainWindow",'UPnP port mapping removed'))) logger.debug("UPnP thread done") def getLocalIP(self): - """Get the local IP of the node""" - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.connect((uPnPThread.GOOGLE_DNS, 1)) return s.getsockname()[0] def sendSearchRouter(self): - """Querying for UPnP services""" - + from debug import logger ssdpRequest = "M-SEARCH * HTTP/1.1\r\n" + \ - "HOST: %s:%d\r\n" % (uPnPThread.SSDP_ADDR, uPnPThread.SSDP_PORT) + \ - "MAN: \"ssdp:discover\"\r\n" + \ - "MX: %d\r\n" % (uPnPThread.SSDP_MX, ) + \ - "ST: %s\r\n" % (uPnPThread.SSDP_ST, ) + "\r\n" + "HOST: %s:%d\r\n" % (uPnPThread.SSDP_ADDR, uPnPThread.SSDP_PORT) + \ + "MAN: \"ssdp:discover\"\r\n" + \ + "MX: %d\r\n" % (uPnPThread.SSDP_MX, ) + \ + "ST: %s\r\n" % (uPnPThread.SSDP_ST, ) + "\r\n" try: logger.debug("Sending UPnP query") self.sock.sendto(ssdpRequest, (uPnPThread.SSDP_ADDR, uPnPThread.SSDP_PORT)) - except: # noqa:E722 + except: logger.exception("UPnP send query failed") def createPortMapping(self, router): - """Add a port mapping""" + from debug import logger for i in range(50): try: + routerIP, = unpack('>I', socket.inet_aton(router.address)) localIP = self.localIP if i == 0: - extPort = self.localPort # try same port first + extPort = self.localPort # try same port first elif i == 1 and self.extPort: - extPort = self.extPort # try external port from last time next + extPort = self.extPort # try external port from last time next else: - extPort = randint(32767, 65535) # nosec B311 - logger.debug( - "Attempt %i, requesting UPnP mapping for %s:%i on external port %i", - i, - localIP, - self.localPort, - extPort) + extPort = randint(32767, 65535) + logger.debug("Attempt %i, requesting UPnP mapping for %s:%i on external port %i", i, localIP, self.localPort, extPort) router.AddPortMapping(extPort, self.localPort, localIP, 'TCP', 'BitMessage') + shared.extPort = extPort self.extPort = extPort - config.set('bitmessagesettings', 'extport', str(extPort)) - config.save() + BMConfigParser().set('bitmessagesettings', 'extport', str(extPort)) + BMConfigParser().save() break except UPnPError: logger.debug("UPnP error: ", exc_info=True) def deletePortMapping(self, router): - """Delete a port mapping""" router.DeletePortMapping(router.extPort, 'TCP') + + + diff --git a/start.sh b/start.sh deleted file mode 100755 index 75ed7af3..00000000 --- a/start.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -python2 pybitmessage/bitmessagemain.py "$@" diff --git a/stdeb.cfg b/stdeb.cfg deleted file mode 100644 index 0d4cfbcb..00000000 --- a/stdeb.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[DEFAULT] -Package: pybitmessage -Section: net -Build-Depends: dh-python, libssl-dev, python-all-dev, python-setuptools, python-six -Depends: openssl, python-setuptools -Recommends: apparmor, python-msgpack, python-qt4, python-stem, tor -Suggests: python-pyopencl, python-jsonrpclib, python-defusedxml, python-qrcode -Suite: bionic -Setup-Env-Vars: DEB_BUILD_OPTIONS=nocheck diff --git a/tests-kivy.py b/tests-kivy.py deleted file mode 100644 index 9bc08880..00000000 --- a/tests-kivy.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -"""Custom tests runner script for tox and python3""" -import os -import random # noseq -import subprocess -import sys -import unittest - -from time import sleep - - -def unittest_discover(): - """Explicit test suite creation""" - loader = unittest.defaultTestLoader - loader.sortTestMethodsUsing = lambda a, b: random.randint(-1, 1) - return loader.discover('src.bitmessagekivy.tests') - - -if __name__ == "__main__": - in_docker = os.path.exists("/.dockerenv") - - if in_docker: - try: - os.mkdir("../out") - except FileExistsError: # noqa:F821 - pass - - ffmpeg = subprocess.Popen([ # pylint: disable=consider-using-with - "ffmpeg", "-y", "-nostdin", "-f", "x11grab", "-video_size", "720x1280", - "-v", "quiet", "-nostats", - "-draw_mouse", "0", "-i", os.environ['DISPLAY'], - "-codec:v", "libvpx-vp9", "-lossless", "1", "-r", "60", - "../out/test.webm" - ]) - sleep(2) # let ffmpeg start - result = unittest.TextTestRunner(verbosity=2).run(unittest_discover()) - sleep(1) - if in_docker: - ffmpeg.terminate() - try: - ffmpeg.wait(10) - except subprocess.TimeoutExpired: - ffmpeg.kill() - sys.exit(not result.wasSuccessful()) diff --git a/tests.py b/tests.py deleted file mode 100644 index 713b25ef..00000000 --- a/tests.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python -"""Custom tests runner script for tox and python3""" -import random # noseq -import sys -import unittest - - -def unittest_discover(): - """Explicit test suite creation""" - if sys.hexversion >= 0x3000000: - from pybitmessage import pathmagic - pathmagic.setup() - loader = unittest.defaultTestLoader - # randomize the order of tests in test cases - loader.sortTestMethodsUsing = lambda a, b: random.randint(-1, 1) - # pybitmessage symlink disappears on Windows! - testsuite = loader.discover('pybitmessage.tests') - testsuite.addTests([loader.discover('pybitmessage.pyelliptic')]) - - return testsuite - - -if __name__ == "__main__": - success = unittest.TextTestRunner(verbosity=2).run( - unittest_discover()).wasSuccessful() - try: - from pybitmessage.tests import common - except ImportError: - checkup = False - else: - checkup = common.checkup() - - if checkup and not success: - print(checkup) - - sys.exit(not success or checkup) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index fec3a54c..00000000 --- a/tox.ini +++ /dev/null @@ -1,67 +0,0 @@ -[tox] -requires = virtualenv<20.22.0 -envlist = reset,py{27,27-portable,35,36,38,39,310},stats -skip_missing_interpreters = true - -[testenv] -setenv = - BITMESSAGE_HOME = {envtmpdir} - HOME = {envtmpdir} - PYTHONWARNINGS = default -deps = -rrequirements.txt -commands = - python checkdeps.py - python src/bitmessagemain.py -t - coverage run -a -m tests - -[testenv:lint-basic] -skip_install = true -basepython = python3 -deps = - bandit - flake8 -commands = - bandit -r -s B101,B411,B413,B608 \ - -x checkdeps.*,bitmessagecurses,bitmessageqt,tests pybitmessage - flake8 pybitmessage --count --select=E9,F63,F7,F82 \ - --show-source --statistics - -[testenv:py27] -sitepackages = true - -[testenv:py27-doc] -deps = - .[docs] - -r docs/requirements.txt -commands = python setup.py build_sphinx - -[testenv:py27-portable] -skip_install = true -commands = python pybitmessage/bitmessagemain.py -t - -[testenv:py35] -skip_install = true - -[testenv:reset] -skip_install = true -deps = coverage -commands = coverage erase - -[testenv:stats] -skip_install = true -deps = coverage -commands = - coverage report - coverage xml - -[coverage:run] -source = src -omit = - tests.py - */tests/* - src/bitmessagekivy/* - src/version.py - src/fallback/umsgpack/* - -[coverage:report] -ignore_errors = true